diff --git a/.babelrc b/.babelrc
index b93bef72de1c4a34ae046c11096afb2ab922b82f..8cf07b73420940fe7804aa0f6f316e0a7c1a1c60 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,20 +1,20 @@
 {
-  "presets": [
-    ["latest", { "es2015": { "modules": false } }],
-    "stage-2"
-  ],
+  "presets": [["latest", { "es2015": { "modules": false } }], "stage-2"],
   "env": {
     "coverage": {
       "plugins": [
-        ["istanbul", {
-          "exclude": [
-            "spec/javascripts/**/*",
-            "app/assets/javascripts/locale/**/app.js"
-          ]
-        }],
-        ["transform-define", {
-          "process.env.BABEL_ENV": "coverage"
-        }]
+        [
+          "istanbul",
+          {
+            "exclude": ["spec/javascripts/**/*", "app/assets/javascripts/locale/**/app.js"]
+          }
+        ],
+        [
+          "transform-define",
+          {
+            "process.env.BABEL_ENV": "coverage"
+          }
+        ]
       ]
     }
   }
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 216ecf43beb222a7029e5abcf0250bcf529e3c6a..8699a903f2ab8fc539c432732041ed266650e58e 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -10,12 +10,6 @@ engines:
       - javascript
     exclude_paths:
       - "lib/api/v3/*"
-  eslint:
-    enabled: true
-    channel: "eslint-4"
-  rubocop:
-    enabled: true
-    channel: "gitlab-rubocop-0-52-1"
 ratings:
   paths:
   - Gemfile.lock
diff --git a/.eslintignore b/.eslintignore
index 1623b996213f9227fcd0b7db46b8d5911acc9603..33a8186fade907879669eacf34837b770e6c6e48 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,11 +1,12 @@
+/app/assets/javascripts/locale/**/app.js
+/config/
 /builds/
 /coverage/
 /coverage-javascript/
 /node_modules/
 /public/
+/scripts/
 /tmp/
 /vendor/
 karma.config.js
 webpack.config.js
-svg.config.js
-/app/assets/javascripts/locale/**/app.js
diff --git a/.eslintrc b/.eslintrc
index 8f9cdfb14ac06d94b5ca64f8de89409bd8f43e68..3f187db0c0739bb14ed9db04b25d1863ecbd256f 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,6 +1,5 @@
 {
   "env": {
-    "jquery": true,
     "browser": true,
     "es6": true
   },
@@ -36,15 +35,22 @@
     "import/no-commonjs": "error",
     "no-multiple-empty-lines": ["error", { "max": 1 }],
     "promise/catch-or-return": "error",
-    "no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
-    "vue/html-self-closing": ["error", {
-      "html": {
-        "void": "always",
-        "normal": "never",
-        "component": "always"
-      },
-      "svg": "always",
-      "math": "always"
-    }]
+    "no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
+    "no-mixed-operators": 0,
+    "space-before-function-paren": 0,
+    "curly": 0,
+    "arrow-parens": 0,
+    "vue/html-self-closing": [
+      "error",
+      {
+        "html": {
+          "void": "always",
+          "normal": "never",
+          "component": "always"
+        },
+        "svg": "always",
+        "math": "always"
+      }
+    ]
   }
 }
diff --git a/.flayignore b/.flayignore
index 87cb3507b0598e16a70ac89790c9e27d29e5a77d..3d69bb2c985b36cb0c59db7e69fcc5e7802e53f7 100644
--- a/.flayignore
+++ b/.flayignore
@@ -8,3 +8,4 @@ lib/gitlab/redis/*.rb
 lib/gitlab/gitaly_client/operation_service.rb
 lib/gitlab/background_migration/*
 app/models/project_services/kubernetes_service.rb
+lib/gitlab/workhorse.rb
diff --git a/.gitignore b/.gitignore
index fa39ae01ff0784640f34f7be3a26a2d2b0f166a2..e9ff0048c1c8bce46f5f2efec324a34476d1e59d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
 eslint-report.html
 /.gitlab_shell_secret
 .idea
+/.vscode/*
 /.rbenv-version
 .rbx/
 /.ruby-gemset
@@ -22,6 +23,9 @@ eslint-report.html
 /.yarn-cache
 /.byebug_history
 /Vagrantfile
+/app/assets/images/icons.json
+/app/assets/images/icons.svg
+/app/assets/images/illustrations/
 /app/assets/javascripts/locale/**/app.js
 /backups/*
 /config/aws.yml
@@ -46,6 +50,7 @@ eslint-report.html
 /db/data.yml
 /doc/code/*
 /dump.rdb
+/jsconfig.json
 /log/*.log*
 /node_modules/
 /nohup.out
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8a0c9802c15db5946c4aa4bb2d6e25181c84f2aa..f97feca9f7bcca224a1bea0df922613f707177a8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git-2.17-chrome-65.0-node-8.x-yarn-1.2-postgresql-9.6"
 
 .dedicated-runner: &dedicated-runner
   retry: 1
@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git
     - gitlab-org
 
 .default-cache: &default-cache
-  key: "ruby-2.3.6-with-yarn"
+  key: "ruby-2.3.7-with-yarn"
   paths:
     - vendor/ruby
     - .yarn-cache/
@@ -35,8 +35,14 @@ variables:
 
 before_script:
   - bundle --version
+  - date
   - source scripts/utils.sh
+  - date
   - source scripts/prepare_build.sh
+  - date
+
+after_script:
+  - date
 
 stages:
   - build
@@ -72,6 +78,19 @@ stages:
     - mysql:latest
     - redis:alpine
 
+.rails5-variables: &rails5-variables
+  script:
+    - export RAILS5=${RAILS5}
+    - export BUNDLE_GEMFILE=${BUNDLE_GEMFILE}
+
+.rails5: &rails5
+  allow_failure: true
+  only:
+    - /rails5/
+  variables:
+    BUNDLE_GEMFILE: "Gemfile.rails5"
+    RAILS5: "true"
+
 # Skip all jobs except the ones that begin with 'docs/'.
 # Used for commits including ONLY documentation changes.
 # https://docs.gitlab.com/ce/development/writing_documentation.html#testing
@@ -88,10 +107,31 @@ stages:
     - /(^docs[\/-].*|.*-docs$)/
     - /(^qa[\/-].*|.*-qa$)/
 
+# Jobs that only need to pull cache
+.dedicated-no-docs-pull-cache-job: &dedicated-no-docs-pull-cache-job
+  <<: *dedicated-runner
+  <<: *except-docs-and-qa
+  <<: *pull-cache
+  dependencies:
+    - setup-test-env
+  stage: test
+
+# Jobs that do not need a DB
+.dedicated-no-docs-no-db-pull-cache-job: &dedicated-no-docs-no-db-pull-cache-job
+  <<: *dedicated-no-docs-pull-cache-job
+  variables:
+    SETUP_DB: "false"
+
+.rake-exec: &rake-exec
+  <<: *dedicated-no-docs-no-db-pull-cache-job
+  script:
+    - bundle exec rake $CI_JOB_NAME
+
 .rspec-metadata: &rspec-metadata
   <<: *dedicated-runner
   <<: *except-docs-and-qa
   <<: *pull-cache
+  <<: *rails5-variables
   stage: test
   script:
     - JOB_NAME=( $CI_JOB_NAME )
@@ -122,14 +162,23 @@ stages:
   <<: *rspec-metadata
   <<: *use-pg
 
+.rspec-metadata-pg-rails5: &rspec-metadata-pg-rails5
+  <<: *rspec-metadata-pg
+  <<: *rails5
+
 .rspec-metadata-mysql: &rspec-metadata-mysql
   <<: *rspec-metadata
   <<: *use-mysql
 
+.rspec-metadata-mysql-rails5: &rspec-metadata-mysql-rails5
+  <<: *rspec-metadata-mysql
+  <<: *rails5
+
 .spinach-metadata: &spinach-metadata
   <<: *dedicated-runner
   <<: *except-docs-and-qa
   <<: *pull-cache
+  <<: *rails5-variables
   stage: test
   script:
     - JOB_NAME=( $CI_JOB_NAME )
@@ -153,10 +202,18 @@ stages:
   <<: *spinach-metadata
   <<: *use-pg
 
+.spinach-metadata-pg-rails5: &spinach-metadata-pg-rails5
+  <<: *spinach-metadata-pg
+  <<: *rails5
+
 .spinach-metadata-mysql: &spinach-metadata-mysql
   <<: *spinach-metadata
   <<: *use-mysql
 
+.spinach-metadata-mysql-rails5: &spinach-metadata-mysql-rails5
+  <<: *spinach-metadata-mysql
+  <<: *rails5
+
 .only-canonical-masters: &only-canonical-masters
   only:
     - master@gitlab-org/gitlab-ce
@@ -164,21 +221,23 @@ stages:
     - master@gitlab/gitlabhq
     - master@gitlab/gitlab-ee
 
-##
-# Trigger a package build in omnibus-gitlab repository
-#
-package-qa:
-  <<: *dedicated-runner
-  image: ruby:2.4-alpine
-  before_script: []
-  stage: build
-  cache: {}
-  when: manual
+.gitlab-setup: &gitlab-setup
+  <<: *dedicated-no-docs-pull-cache-job
+  <<: *use-pg
+  variables:
+    CREATE_DB_USER: "true"
   script:
-    - scripts/trigger-build-omnibus
-  only:
-    - //@gitlab-org/gitlab-ce
-    - //@gitlab-org/gitlab-ee
+    # Manually clone gitlab-test and only seed this project in
+    # db/fixtures/development/04_project.rb thanks to SIZE=1 below
+    - git clone https://gitlab.com/gitlab-org/gitlab-test.git
+       /home/git/repositories/gitlab-org/gitlab-test.git
+    - scripts/gitaly-test-spawn
+    - force=yes SIZE=1 FIXTURE_PATH="db/fixtures/development" bundle exec rake gitlab:setup
+  artifacts:
+    when: on_failure
+    expire_in: 1d
+    paths:
+      - log/development.log
 
 # Review docs base
 .review-docs: &review-docs
@@ -201,6 +260,57 @@ package-qa:
   only:
     - branches
 
+# DB migration, rollback, and seed jobs
+.db-migrate-reset: &db-migrate-reset
+  <<: *dedicated-no-docs-pull-cache-job
+  script:
+    - bundle exec rake db:migrate:reset
+
+.migration-paths: &migration-paths
+  <<: *dedicated-no-docs-pull-cache-job
+  variables:
+    CREATE_DB_USER: "true"
+  script:
+    - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
+    - git checkout -f FETCH_HEAD
+    - bundle install $BUNDLE_INSTALL_FLAGS
+    - date
+    - cp config/gitlab.yml.example config/gitlab.yml
+    - bundle exec rake db:drop db:create db:schema:load db:seed_fu
+    - date
+    - git checkout $CI_COMMIT_SHA
+    - bundle install $BUNDLE_INSTALL_FLAGS
+    - date
+    - . scripts/prepare_build.sh
+    - date
+    - bundle exec rake db:migrate
+
+##
+# Trigger a package build in omnibus-gitlab repository
+#
+package-and-qa:
+  <<: *dedicated-runner
+  image: ruby:2.4-alpine
+  before_script: []
+  stage: build
+  cache: {}
+  when: manual
+  variables:
+    GIT_STRATEGY: none
+  retry: 0
+  before_script:
+    # We need to download the script rather than clone the repo since the
+    # package-and-qa job will not be able to run when the branch gets
+    # deleted (when merging the MR).
+    - apk add --update openssl
+    - wget https://gitlab.com/$CI_PROJECT_PATH/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus
+    - chmod 755 trigger-build-omnibus
+  script:
+    - ./trigger-build-omnibus
+  only:
+    - //@gitlab-org/gitlab-ce
+    - //@gitlab-org/gitlab-ee
+
 # Trigger a docs build in gitlab-docs
 # Useful to preview the docs changes live
 review-docs-deploy:
@@ -254,10 +364,11 @@ update-tests-metadata:
       - rspec_flaky/
     policy: push
   script:
-    - retry gem install fog-aws mime-types
+    - retry gem install fog-aws mime-types activesupport
     - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
     - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
     - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
+    - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
     - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
     - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
     - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
@@ -265,7 +376,7 @@ update-tests-metadata:
 
 flaky-examples-check:
   <<: *dedicated-runner
-  image: ruby:2.3-alpine
+  image: ruby:2.4-alpine
   services: []
   before_script: []
   variables:
@@ -299,7 +410,9 @@ compile-assets:
     <<: *default-cache
   script:
     - node --version
+    - date
     - yarn install --frozen-lockfile --cache-folder .yarn-cache
+    - date
     - bundle exec rake gitlab:assets:compile
   artifacts:
     expire_in: 7d
@@ -387,30 +500,79 @@ spinach-pg 1 2: *spinach-metadata-pg
 spinach-mysql 0 2: *spinach-metadata-mysql
 spinach-mysql 1 2: *spinach-metadata-mysql
 
-# Static analysis jobs
-.ruby-static-analysis: &ruby-static-analysis
-  variables:
-    SIMPLECOV: "false"
-    SETUP_DB: "false"
-
-.rake-exec: &rake-exec
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
-  <<: *ruby-static-analysis
-  stage: test
-  script:
-    - bundle exec rake $CI_JOB_NAME
+rspec-pg-rails5 0 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 1 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 2 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 3 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 4 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 5 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 6 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 7 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 8 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 9 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 10 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 11 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 12 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 13 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 14 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 15 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 16 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 17 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 18 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 19 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 20 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 21 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 22 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 23 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 24 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 25 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 26 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 27 28: *rspec-metadata-pg-rails5
+
+rspec-mysql-rails5 0 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 1 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 2 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 3 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 4 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 5 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 6 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 7 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 8 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 9 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 10 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 11 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 12 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 13 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 14 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 15 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 16 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 17 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 18 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 19 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 20 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 21 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 22 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 23 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 24 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 25 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 26 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 27 28: *rspec-metadata-mysql-rails5
+
+spinach-pg-rails5 0 2: *spinach-metadata-pg-rails5
+spinach-pg-rails5 1 2: *spinach-metadata-pg-rails5
+
+spinach-mysql-rails5 0 2: *spinach-metadata-mysql-rails5
+spinach-mysql-rails5 1 2: *spinach-metadata-mysql-rails5
 
 static-analysis:
-  <<: *dedicated-runner
-  <<: *except-docs
-  <<: *ruby-static-analysis
-  stage: test
+  <<: *dedicated-no-docs-no-db-pull-cache-job
+  dependencies:
+    - compile-assets
+    - setup-test-env
   script:
     - scripts/static-analysis
   cache:
-    key: "ruby-2.3.6-with-yarn-and-rubocop"
+    key: "ruby-2.3.7-with-yarn-and-rubocop"
     paths:
       - vendor/ruby
       - .yarn-cache/
@@ -463,15 +625,6 @@ ee_compat_check:
     paths:
       - ee_compat_check/patches/*.patch
 
-# DB migration, rollback, and seed jobs
-.db-migrate-reset: &db-migrate-reset
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
-  stage: test
-  script:
-    - bundle exec rake db:migrate:reset
-
 db:migrate:reset-pg:
   <<: *db-migrate-reset
   <<: *use-pg
@@ -486,25 +639,6 @@ db:check-schema-pg:
   script:
     - source scripts/schema_changed.sh
 
-.migration-paths: &migration-paths
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
-  stage: test
-  variables:
-    SETUP_DB: "false"
-    CREATE_DB_USER: "true"
-  script:
-    - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
-    - git checkout -f FETCH_HEAD
-    - bundle install $BUNDLE_INSTALL_FLAGS
-    - cp config/gitlab.yml.example config/gitlab.yml
-    - bundle exec rake db:drop db:create db:schema:load db:seed_fu
-    - git checkout $CI_COMMIT_SHA
-    - bundle install $BUNDLE_INSTALL_FLAGS
-    - . scripts/prepare_build.sh
-    - bundle exec rake db:migrate
-
 migration:path-pg:
   <<: *migration-paths
   <<: *use-pg
@@ -514,12 +648,9 @@ migration:path-mysql:
   <<: *use-mysql
 
 .db-rollback: &db-rollback
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
-  stage: test
+  <<: *dedicated-no-docs-pull-cache-job
   script:
-    - bundle exec rake db:rollback STEP=119
+    - bundle exec rake db:migrate VERSION=20170523121229
     - bundle exec rake db:migrate
 
 db:rollback-pg:
@@ -530,27 +661,6 @@ db:rollback-mysql:
   <<: *db-rollback
   <<: *use-mysql
 
-.gitlab-setup: &gitlab-setup
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
-  stage: test
-  variables:
-    SIZE: "1"
-    SETUP_DB: "false"
-    CREATE_DB_USER: "true"
-    FIXTURE_PATH: db/fixtures/development
-  script:
-    - git clone https://gitlab.com/gitlab-org/gitlab-test.git
-       /home/git/repositories/gitlab-org/gitlab-test.git
-    - scripts/gitaly-test-spawn
-    - force=yes bundle exec rake gitlab:setup
-  artifacts:
-    when: on_failure
-    expire_in: 1d
-    paths:
-      - log/development.log
-
 gitlab:setup-pg:
   <<: *gitlab-setup
   <<: *use-pg
@@ -561,10 +671,7 @@ gitlab:setup-mysql:
 
 # Frontend-related jobs
 gitlab:assets:compile:
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
-  stage: test
+  <<: *dedicated-no-docs-no-db-pull-cache-job
   dependencies: []
   variables:
     NODE_ENV: "production"
@@ -574,7 +681,9 @@ gitlab:assets:compile:
     WEBPACK_REPORT: "true"
     NO_COMPRESSION: "true"
   script:
+    - date
     - yarn install --frozen-lockfile --production --cache-folder .yarn-cache
+    - date
     - bundle exec rake gitlab:assets:compile
   artifacts:
     name: webpack-report
@@ -583,17 +692,16 @@ gitlab:assets:compile:
       - webpack-report/
 
 karma:
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
+  <<: *dedicated-no-docs-pull-cache-job
   <<: *use-pg
-  stage: test
-  variables:
-    BABEL_ENV: "coverage"
-    CHROME_LOG_FILE: "chrome_debug.log"
+  dependencies:
+    - compile-assets
+    - setup-test-env
   script:
+    - export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
+    - date
     - scripts/gitaly-test-spawn
-    - bundle exec rake gettext:po_to_json
+    - date
     - bundle exec rake karma
   coverage: '/^Statements *: (\d+\.\d+%)/'
   artifacts:
@@ -601,48 +709,79 @@ karma:
     expire_in: 31d
     when: always
     paths:
-    - chrome_debug.log
-    - coverage-javascript/
+      - chrome_debug.log
+      - coverage-javascript/
 
 codequality:
-  <<: *except-docs
-  <<: *pull-cache
-  stage: test
-  image: docker:latest
+  <<: *dedicated-no-docs-no-db-pull-cache-job
+  image: docker:stable
+  allow_failure: true
+  # gitlab-org runners set `privileged: false` but we need to have it set to true
+  # since we're using Docker in Docker
+  tags: []
   before_script: []
   services:
-    - docker:dind
+    - docker:stable-dind
   variables:
     SETUP_DB: "false"
     DOCKER_DRIVER: overlay2
-    CODECLIMATE_FORMAT: json
   cache: {}
   dependencies: []
   script:
-    - ./scripts/codequality analyze -f json > raw_codeclimate.json || true
-    # The following line keeps only the fields used in the MR widget, reducing the JSON artifact size
-    - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,description,fingerprint,location})' > codeclimate.json
+    # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
+    - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+    - docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
   artifacts:
     paths: [codeclimate.json]
     expire_in: 1 week
 
 sast:
-  <<: *except-docs
-  image: registry.gitlab.com/gitlab-org/gl-sast:latest
+  <<: *dedicated-no-docs-no-db-pull-cache-job
+  image: docker:stable
   variables:
-    CONFIDENCE_LEVEL: 2
+    SAST_CONFIDENCE_LEVEL: 2
+    DOCKER_DRIVER: overlay2
+  allow_failure: true
+  tags: []
   before_script: []
+  cache: {}
+  dependencies: []
+  services:
+    - docker:stable-dind
   script:
-    - /app/bin/run .
+    - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+    - docker run
+        --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}"
+        --volume "$PWD:/code"
+        --volume /var/run/docker.sock:/var/run/docker.sock
+        "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
   artifacts:
     paths: [gl-sast-report.json]
 
-qa:internal:
-  <<: *dedicated-runner
-  <<: *except-docs
-  stage: test
+dependency_scanning:
+  <<: *dedicated-no-docs-no-db-pull-cache-job
+  image: docker:stable
   variables:
-    SETUP_DB: "false"
+    DOCKER_DRIVER: overlay2
+  allow_failure: true
+  tags: []
+  before_script: []
+  cache: {}
+  dependencies: []
+  services:
+    - docker:stable-dind
+  script:
+    - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+    - docker run
+        --env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}"
+        --volume "$PWD:/code"
+        --volume /var/run/docker.sock:/var/run/docker.sock
+        "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
+  artifacts:
+    paths: [gl-dependency-scanning-report.json]
+
+qa:internal:
+  <<: *dedicated-no-docs-no-db-pull-cache-job
   services: []
   script:
     - cd qa/
@@ -650,11 +789,7 @@ qa:internal:
     - bundle exec rspec
 
 qa:selectors:
-  <<: *dedicated-runner
-  <<: *except-docs
-  stage: test
-  variables:
-    SETUP_DB: "false"
+  <<: *dedicated-no-docs-no-db-pull-cache-job
   services: []
   script:
     - cd qa/
@@ -662,14 +797,14 @@ qa:selectors:
     - bundle exec bin/qa Test::Sanity::Selectors
 
 coverage:
+  # Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
+  # download artifacts from all the rspec jobs instead of from setup-test-env only
   <<: *dedicated-runner
   <<: *except-docs-and-qa
   <<: *pull-cache
-  stage: post-test
-  services: []
   variables:
     SETUP_DB: "false"
-    USE_BUNDLE_INSTALL: "true"
+  stage: post-test
   script:
     - bundle exec scripts/merge-simplecov
   coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
@@ -681,26 +816,25 @@ coverage:
     - coverage/assets/
 
 lint:javascript:report:
-  <<: *dedicated-runner
-  <<: *except-docs-and-qa
-  <<: *pull-cache
+  <<: *dedicated-no-docs-no-db-pull-cache-job
   stage: post-test
   dependencies:
     - compile-assets
     - setup-test-env
   before_script: []
   script:
+    - date
     - find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
+    - date
     - yarn run eslint-report || true # ignore exit code
   artifacts:
     name: eslint-report
     expire_in: 31d
     paths:
-    - eslint-report.html
+      - eslint-report.html
 
 pages:
-  <<: *dedicated-runner
-  <<: *pull-cache
+  <<: *dedicated-no-docs-no-db-pull-cache-job
   before_script: []
   stage: pages
   dependencies:
@@ -725,10 +859,7 @@ pages:
 # Insurance in case a gem needed by one of our releases gets yanked from
 # rubygems.org in the future.
 cache gems:
-  <<: *dedicated-runner
-  <<: *pull-cache
-  variables:
-    SETUP_DB: "false"
+  <<: *dedicated-no-docs-no-db-pull-cache-job
   script:
     - bundle package --all --all-platforms
   artifacts:
diff --git a/.gitlab/issue_templates/Security Developer Workflow.md b/.gitlab/issue_templates/Security Developer Workflow.md
new file mode 100644
index 0000000000000000000000000000000000000000..8dd447ed121e124b5149ac8fd47d6039b480cdb8
--- /dev/null
+++ b/.gitlab/issue_templates/Security Developer Workflow.md	
@@ -0,0 +1,70 @@
+<!--
+# Read me first!
+
+Create this issue under https://dev.gitlab.org/gitlab/gitlabhq
+
+Set the title to: `[Security] Description of the original issue`
+-->
+
+### Prior to the security release
+
+- [ ] Read the [security process for developers] if you are not familiar with it.
+- [ ] Link to the original issue adding it to the [links section](#links)
+- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org`
+- [ ] Create an MR targetting `org` `master`, prefixing your branch with `security-`
+- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]`   
+- [ ] Add a link to the MR to the [links section](#links)
+- [ ] Add a link to an EE MR if required
+- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**.
+- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping.
+
+#### Backports
+
+- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
+    - [ ] At this point, it might be easy to squash the commits from the MR into one
+    - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation]
+    - [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
+    - [ ] Create each MR targetting the security branch `security-X-Y`
+    - [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
+- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
+
+[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/process.md#secpick-script
+
+#### Documentation and final details
+
+- [ ] Check the topic on #security to see when the next release is going ot happen and add a link to the [links section](#links)
+- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
+- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
+- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
+- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
+
+### Summary
+#### Links
+
+| Description | Link |
+| -------- | -------- |
+| Original issue   | #TODO  |
+| Security release issue   | #TODO  |
+| `master` MR | !TODO   |
+| `master` MR (EE) | !TODO   |
+| `Backport X.Y` MR | !TODO   |
+| `Backport X.Y` MR | !TODO   |
+| `Backport X.Y` MR | !TODO   |
+| `Backport X.Y` MR (EE) | !TODO   |
+| `Backport X.Y` MR (EE) | !TODO   |
+| `Backport X.Y` MR (EE) | !TODO   |
+
+#### Details
+
+| Description | Details | Further details|
+| -------- | -------- | -------- |
+| Versions affected | X.Y  | |
+| Upgrade notes | | |
+| GitLab Settings updated | Yes/No| |
+| Migration required | Yes/No | |
+| Thanks | | |
+
+[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
+[RM list]:  https://about.gitlab.com/release-managers/
+
+/label ~security 
diff --git a/.gitlab/merge_request_templates/Database Changes.md b/.gitlab/merge_request_templates/Database Changes.md
index 8302b3b30c709a3f592800f469f11810eac80c38..2bb1f374e98984929404da4cdcd7bfae73edd59b 100644
--- a/.gitlab/merge_request_templates/Database Changes.md	
+++ b/.gitlab/merge_request_templates/Database Changes.md	
@@ -33,13 +33,16 @@ When removing columns, tables, indexes or other structures:
 
 ## General Checklist
 
-- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added, if necessary
-- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
+- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
+- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html)
 - [ ] API support added
 - [ ] Tests added for this feature/bug
 - Review
   - [ ] Has been reviewed by Backend
   - [ ] Has been reviewed by Database
-- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
-- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
+- [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
+- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
 - [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
+- [ ] Internationalization required/considered
+- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan
+- [ ] End-to-end tests pass (`package-and-qa` manual pipeline job)
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index 102eb7e79531a3a0e07bd53f95245fa00e2e4787..da38a703c3c2e8e87ec53d1588d474fb4efb6ab2 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -1,16 +1,29 @@
-See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html
+<!--See the general Documentation guidelines https://docs.gitlab.com/ce/development/writing_documentation.html -->
 
 ## What does this MR do?
 
-(briefly describe what this MR is about)
+<!-- Briefly describe what this MR is about -->
+
+## Related issues
+
+<!-- Mention the issue(s) this MR closes or is related to -->
+
+Closes 
 
 ## Moving docs to a new location?
 
-See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#changing-document-location
+Read the guidelines:
+https://docs.gitlab.com/ce/development/writing_documentation.html#changing-document-location
 
-- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location.
+- [ ] Make sure the old link is not removed and has its contents replaced with
+      a link to the new location.
 - [ ] Make sure internal links pointing to the document in question are not broken.
-- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory.
-- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/doc_styleguide.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread.
-- [ ] If working on CE, submit an MR to EE with the changes as well.
+- [ ] Search and replace any links referring to old docs in GitLab Rails app,
+      specifically under the `app/views/` and `ee/app/views` (for GitLab EE)  directories.
+- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments)
+      to the new document if there are any Disqus comments on the old document thread.
+- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
+      with the changes as well (https://docs.gitlab.com/ce/development/writing_documentation.html#cherry-picking-from-ce-to-ee).
 - [ ] Ping one of the technical writers for review.
+
+/label ~Documentation
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000000000000000000000000000000000..b674ccd50cf355b2773c6479ec9dfb1ed81cddb1
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+/app/assets/javascripts/locale/**/app.js
+/node_modules/
+/public/
+/vendor/
+/tmp/
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000000000000000000000000000000000000..3384551aea52e685c72c9e48a96b3d7ef15a7fe1
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,13 @@
+{
+  "printWidth": 100,
+  "singleQuote": true,
+  "trailingComma": "es5",
+  "overrides": [
+    {
+      "files": ["**/app/**/*", "**/spec/**/*"],
+      "options": {
+        "trailingComma": "all"
+      }
+    }
+  ]
+}
diff --git a/.rubocop.yml b/.rubocop.yml
index 293f61fb7255c58e068e94a8879d7ee40bdbf086..0582bfe8d70ce2fb8f824eb35f05ecb64508a710 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -31,6 +31,78 @@ Style/MutableConstant:
     - 'ee/db/post_migrate/**/*'
     - 'ee/db/geo/migrate/**/*'
 
+Naming/FileName:
+  ExpectMatchingDefinition: true
+  Exclude:
+    - 'spec/**/*'
+    - 'features/**/*'
+    - 'ee/spec/**/*'
+    - 'qa/spec/**/*'
+    - 'qa/qa/specs/**/*'
+    - 'qa/bin/*'
+    - 'config/**/*'
+    - 'lib/generators/**/*'
+    - 'ee/lib/generators/**/*'
+  IgnoreExecutableScripts: true
+  AllowedAcronyms:
+    - EE
+    - JSON
+    - LDAP
+    - IO
+    - HMAC
+    - QA
+    - ENV
+    - STL
+    - PDF
+    - SVG
+    - CTE
+    - DN
+    - RSA
+    - CI
+    - CD
+    - OAuth
+    # default ones:
+    - CLI
+    - DSL
+    - ACL
+    - API
+    - ASCII
+    - CPU
+    - CSS
+    - DNS
+    - EOF
+    - GUID
+    - HTML
+    - HTTP
+    - HTTPS
+    - ID
+    - IP
+    - JSON
+    - LHS
+    - QPS
+    - RAM
+    - RHS
+    - RPC
+    - SLA
+    - SMTP
+    - SQL
+    - SSH
+    - TCP
+    - TLS
+    - TTL
+    - UDP
+    - UI
+    - UID
+    - UUID
+    - URI
+    - URL
+    - UTF8
+    - VM
+    - XML
+    - XMPP
+    - XSRF
+    - XSS
+
 # Gitlab ###################################################################
 
 Gitlab/ModuleWithInstanceVariables:
@@ -46,6 +118,9 @@ Gitlab/ModuleWithInstanceVariables:
     - spec/support/**/*.rb
     - features/steps/**/*.rb
 
+Gitlab/HTTParty:
+  Enabled: true
+
 GitlabSecurity/PublicSend:
   Enabled: true
   Exclude:
diff --git a/.ruby-version b/.ruby-version
index e75da3e63d60425514e3bd5d5876289e4322c5ae..00355e29d11ac4a7ee62410face3b0adb50201ac 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.3.6
+2.3.7
diff --git a/.scss-lint.yml b/.scss-lint.yml
index dcd4cac780aa8bb49bc9edd23b0372e38e09579d..180d377d6f86a18d7f8d8e9c3ed559dd0d3e4abc 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -59,6 +59,8 @@ linters:
   # Reports when you define the same property twice in a single rule set.
   DuplicateProperty:
     enabled: true
+    ignore_consecutive:
+      - cursor
 
   # Separate rule, function, and mixin declarations with empty lines.
   EmptyLineBetweenBlocks:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8d399b2b98ac6c08091c61a0b54af9b2b637f3a..d56c86523f5b56642a76520716576c7f5058c02b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,305 @@
 documentation](doc/development/changelog.md) for instructions on adding your own
 entry.
 
+## 10.6.4 (2018-04-09)
+
+### Fixed (8 changes, 1 of them is from the community)
+
+- Correct copy text for the promote milestone and label modals. !17726
+- Avoid validation errors when running the Pages domain verification service. !17992
+- Fix autolinking URLs containing ampersands. !18045
+- Fix exceptions raised when migrating pipeline stages in the background. !18076
+- Work around Prometheus Helm chart name changes to fix integration. !18206 (joshlambert)
+- Don't show Jump to Discussion button on Issues.
+- Fix listing commit branch/tags that contain special characters.
+- Fix 404 in group boards when moving issue between lists.
+
+### Performance (1 change)
+
+- Free open file descriptors and libgit2 buffers in UpdatePagesService.
+
+
+## 10.6.3 (2018-04-03)
+
+### Security (2 changes)
+
+- Fix XSS on diff view stored on filenames.
+- Adds confidential notes channel for Slack/Mattermost.
+
+
+## 10.6.2 (2018-03-29)
+
+### Fixed (2 changes, 1 of them is from the community)
+
+- Don't capture trailing punctuation when autolinking. !17965
+- Cloning a repository over HTTPS with LDAP credentials causes a HTTP 401 Access denied. (Horatiu Eugen Vlad)
+
+
+## 10.6.1 (2018-03-27)
+
+### Security (1 change)
+
+- Bump rails-html-sanitizer to 1.0.4.
+
+### Fixed (2 changes)
+
+- Prevent auto-retry AccessDenied error from stopping transition to failed. !17862
+- Fix 500 error when trying to resolve non-ASCII conflicts in the editor. !17962
+
+### Performance (1 change)
+
+- Add indexes for user activity queries. !17890
+
+### Other (1 change)
+
+- Add documentation for runner IP address (#44232). !17837
+
+
+## 10.6.0 (2018-03-22)
+
+### Security (4 changes)
+
+- Fixed some SSRF vulnerabilities in services, hooks and integrations. !2337
+- Ensure that OTP backup codes are always invalidated.
+- Add verification for GitLab Pages custom domains.
+- Fix GitLab Auth0 integration signing in the wrong user.
+
+### Fixed (75 changes, 17 of them are from the community)
+
+- Ensure users cannot create environments with leading or trailing slashes (Fixes #39885). !15273
+- Fix new project path input overlapping. !16755 (George Tsiolis)
+- Respect description and visibility when creating project from template. !16820 (George Tsiolis)
+- Remove user notification settings for groups and projects when user leaves. !16906 (Jacopo Beschi @jacopo-beschi)
+- Fix Teleporting Emoji. !16963 (Jared Deckard <jared.deckard@gmail.com>)
+- Fix duplicate system notes when merging a merge request. !17035
+- Fix breadcrumb on labels page for groups. !17045 (Onuwa Nnachi Isaac)
+- Fix user avatar's vertical align on the issues and merge requests pages. !17072 (Laszlo Karpati)
+- Fix settings panels not expanding when fragment hash linked. !17074
+- Fix 404 when listing archived projects in a group where all projects have been archived. !17077 (Ashley Dumaine)
+- Allow to call PUT /projects/:id API with only ci_config_path specified. !17105 (Laszlo Karpati)
+- Fix long list of recipients on group request membership email. !17121 (Jacopo Beschi @jacopo-beschi)
+- Remove duplicated error message on duplicate variable validation. !17135
+- Keep "Import project" tab/form active when validation fails trying to import "Repo by URL". !17136
+- Fixed bug with unauthenticated requests through git ssh. !17149
+- Allows project rename after validation error. !17150
+- Fix "Remove source branch" button in Merge request widget during merge when pipeline succeeds state. !17192
+- Add missing pagination on the commit diff endpoint. !17203 (Maxime Roussin-B茅langer)
+- Fix get a single pages domain when project path contains a period. !17206 (Travis Miller)
+- remove avater underline. !17219 (Ken Ding)
+- Allows the usage of /milestone quick action for group milestones. !17239 (Jacopo Beschi @jacopo-beschi)
+- Encode branch name as binary before creating a RPC request to copy attributes. !17291
+- Restart Unicorn and Sidekiq when GRPC throws 14:Endpoint read failed. !17293
+- Do not persist Google Project verification flash errors after a page reload. !17299
+- Ensure group issues and merge requests pages show results from subgroups when there are no results from the current group. !17312
+- Prevent trace artifact migration to incur data loss. !17313
+- Fixes gpg popover layout. !17323
+- Return a 404 instead of 403 if the repository does not exist on disk. !17341
+- Fix Slack/Mattermost notifications not respecting `notify_only_default_branch` setting for pushes. !17345
+- Fix Group labels load failure when there are duplicate labels present. !17353
+- Allow Prometheus application to be installed from Cluster applications. !17372
+- Fixes Prometheus admin configuration page. !17377
+- Enable filtering MR list based on clicked label in MR sidebar. !17390
+- Fix code and wiki search results pages when non-ASCII text is displayed. !17413
+- Count comments on diffs and discussions as contributions for the contributions calendar. !17418 (Riccardo Padovani)
+- Add Assignees vue component missing data container. !17426 (George Tsiolis)
+- Update tooltip on pipeline cancel to Stop (#42946). !17444
+- Removing the two factor check when the user sets a new password. !17457
+- Fix quick actions for users who cannot update issues and merge requests. !17482
+- Stop loading spinner on error of milestone update on issue. !17507 (Takuya Noguchi)
+- Set margins around dropdown dividers to 4px. !17517
+- Fix pages flaky failure by reloading stale object. !17522
+- Remove extra breadcrumb on tags. !17562 (Takuya Noguchi)
+- Fix missing uploads after group transfer. !17658
+- Fix markdown table showing extra column. !17669
+- Ensure the API returns https links when https is configured. !17681
+- Sanitize extra blank spaces used when uploading a SSH key. !40552
+- Render htmlentities correctly for links not supported by Rinku.
+- Keep link when redacting unauthorized object links.
+- Handle empty state in Pipelines page.
+- Revert Project.public_or_visible_to_user changes and only apply to snippets.
+- Release libgit2 cache and open file descriptors after `git gc` run.
+- Fix project dashboard showing the wrong timestamps.
+- Fix "Can't modify frozen hash" error when project is destroyed.
+- Fix Error 500 when viewing a commit with a GPG signature in Geo.
+- Don't error out in system hook if user has `nil` datetime columns.
+- Remove double caching of Repository#empty?.
+- Don't delete todos or unassign issues and MRs when a user leaves a project.
+- Don't cache a nil repository root ref to prevent caching issues.
+- Escape HTML entities in commit messages.
+- Verify project import status again before marking as failed.
+- [GitHub Import] Create an empty wiki if wiki import failed.
+- Create empty wiki when import from GitLab and wiki is not there.
+- Make sure wiki exists when it's enabled.
+- Fix broken loading state for close issue button.
+- Fix code and wiki search results when filename is non-ASCII.
+- Fix file upload on project show page.
+- Fix squashing when a file is renamed.
+- Show loading button inline in refresh button in MR widget.
+- Fix close button on issues not working on mobile.
+- Adds tooltip in environment names to increase readability.
+- Fixed issue edit shortcut not opening edit form.
+- Fix 500 error being shown when diff has context marker with invalid encoding.
+- Render modified icon for moved file in changes dropdown.
+- Remember assignee when moving an issue.
+
+### Changed (16 changes, 9 of them are from the community)
+
+- Allow including custom attributes in API responses. !16526 (Markus Koller)
+- Apply new default and inline label design. !16956 (George Tsiolis)
+- Remove whitespace from the username/email sign in form field. !17020 (Peter lauck)
+- CI charts now include the current day. !17032 (Dakkaron)
+- Hide CI secret variable values after saving. !17044
+- Add new modal Vue component. !17108
+- Asciidoc now support inter-document cross references between files in repository. !17125 (Turo Soisenniemi)
+- Update issue closing pattern to allow variations in punctuation. !17198 (Vicky Chijwani)
+- Add a button to deploy a runner to a Kubernetes cluster in the settings page. !17278
+- Pages custom domain: allow update of key/certificate. !17376 (rfwatson)
+- Clear the Labels dropdown search filter after a selection is made. !17393 (Andrew Torres)
+- Hook data for pipelines includes detailed_status. !17607
+- Avoid showing unnecessary Trigger checkboxes for project Integrations with only one event. !17607
+- Display a link to external issue tracker when enabled.
+- Allow token authentication on go-get request.
+- Update SSH key link to include existing keys. (Brendan O'Leary)
+
+### Performance (24 changes, 5 of them are from the community)
+
+- Add catch-up background migration to migrate pipeline stages. !15741
+- Move BoardNewIssue vue component. !16947 (George Tsiolis)
+- Move IssuableTimeTracker vue component. !16948 (George Tsiolis)
+- Move RecentSearchesDropdownContent vue component. !16951 (George Tsiolis)
+- Move Assignees vue component. !16952 (George Tsiolis)
+- Improve performance of pipeline page by reducing DB queries. !17168
+- Store sha256 checksum to job artifacts. !17354
+- Move SidebarAssignees vue component. !17398 (George Tsiolis)
+- Improve database response time for user activity listing. !17454
+- Use persisted/memoized value for MRs shas instead of doing git lookups. !17555
+- Cache MergeRequests can_be_resolved_in_ui? git operations. !17589
+- Prevent the graphs page from generating unnecessary Gitaly requests. !37602
+- Use a user object in ApplicationHelper#avatar_icon where possible to avoid N+1 queries. !42800
+- Submit a single batch blob RPC to Gitaly per HTTP request when viewing diffs.
+- Avoid re-fetching merge-base SHA from Gitaly unnecessarily.
+- Don't use ProjectsFinder in TodosFinder.
+- Adding missing indexes on taggings table.
+- Add index on section_name_id on ci_build_trace_sections table.
+- Cache column_exists? for application settings.
+- Cache table_exists?('application_settings') to reduce repeated schema reloads.
+- Make --prune a configurable parameter in fetching a git remote.
+- Fix timeouts loading /admin/projects page.
+- Add partial indexes on todos to handle users with many todos.
+- Optimize search queries on the search page by setting a limit for matching records in project scope.
+
+### Added (30 changes, 9 of them are from the community)
+
+- Add CommonMark markdown engine (experimental). !14835 (blackst0ne)
+- API: Get references a commit is pushed to. !15026 (Robert Schilling)
+- Add overview of branches and a filter for active/stale branches. !15402 (Takuya Noguchi)
+- Add project export API. !15860 (Travis Miller)
+- expose more metrics in merge requests api. !16589 (haseebeqx)
+- #28481: Display time tracking totals on milestone page. !16753 (Riccardo Padovani)
+- Add a button on the project page to set up a Kubernetes cluster and enable Auto DevOps. !16900
+- Include cycle time in usage ping data. !16973
+- Add ability to use external plugins as an alternative to system hooks. !17003
+- Add search param to Branches API. !17005 (bunufi)
+- API endpoint for importing a project export. !17025
+- Display ingress IP address in the Kubernetes page. !17052
+- Implemented badge API endpoints. !17082
+- Allow installation of GitLab Runner with a single click. !17134
+- Allow commits endpoint to work over all commits of a repository. !17182
+- Display Runner IP Address. !17286
+- Add archive feature to trace. !17314
+- Allow maintainers to push to forks of their projects when a merge request is open. !17395
+- Foreground verification of uploads and LFS objects. !17402
+- Adds updated_at filter to issues and merge_requests API. !17417 (Jacopo Beschi @jacopo-beschi)
+- Port /wip quick action command to Merge Request creation (on description). !17463 (Adam Pahlevi)
+- Add a paragraph about security implications on Cluster's page. !17486
+- Add plugins list to the system hooks page. !17518
+- Enable privileged mode for GitLab Runner. !17528
+- Expose GITLAB_FEATURES as CI/CD variable (fixes #40994).
+- Upgrade GitLab Workhorse to 4.0.0.
+- Add discussions API for Issues and Snippets.
+- Add one group board to Libre.
+- Add support for filtering by source and target branch to merge requests API.
+
+### Other (18 changes, 7 of them are from the community)
+
+- Group MRs on issue page by project and namespace. !8494 (Jeff Stubler)
+- Make oauth provider login generic. !8809 (Horatiu Eugen Vlad)
+- Add email button to new issue by email. !10942 (Islam Wazery)
+- Update vue component naming guidelines. !17018 (George Tsiolis)
+- Added new design for promotion modals. !17197
+- Update to github-linguist 5.3.x. !17241 (Ken Ding)
+- update toml-rb to 1.0.0. !17259 (Ken Ding)
+- Keep track of projects a user interacted with. !17327
+- Moved o_auth/saml/ldap modules under gitlab/auth. !17359 (Horatiu Eugen Vlad)
+- Enables eslint in codeclimate job. !17392
+- Port Labels Select dropdown to Vue. !17411
+- Add NOT NULL constraint to projects.namespace_id. !17448
+- Ensure foreign keys on clusters applications. !17488
+- Started translation into Turkish, Indonesian and Filipino. !17526
+- Add documentation for displayed K8s Ingress IP address (#44330). !17836
+- Move Ruby endpoints to OPT_OUT.
+- Upgrade Workhorse to version 3.8.0 to support structured logging.
+- Use host URL to build JIRA remote link icon.
+
+
+## 10.5.7 (2018-04-03)
+
+### Security (2 changes)
+
+- Fix XSS on diff view stored on filenames.
+- Adds confidential notes channel for Slack/Mattermost.
+
+
+## 10.5.6 (2018-03-16)
+
+### Security (2 changes)
+
+- Fixed some SSRF vulnerabilities in services, hooks and integrations. !2337
+- Fix GitLab Auth0 integration signing in the wrong user.
+
+
+## 10.5.5 (2018-03-15)
+
+### Fixed (3 changes)
+
+- Fix missing uploads after group transfer. !17658
+- Fix code and wiki search results when filename is non-ASCII.
+- Remove double caching of Repository#empty?.
+
+### Performance (2 changes)
+
+- Adding missing indexes on taggings table.
+- Add index on section_name_id on ci_build_trace_sections table.
+
+
+## 10.5.4 (2018-03-08)
+
+### Fixed (11 changes)
+
+- Encode branch name as binary before creating a RPC request to copy attributes. !17291
+- Restart Unicorn and Sidekiq when GRPC throws 14:Endpoint read failed. !17293
+- Ensure group issues and merge requests pages show results from subgroups when there are no results from the current group. !17312
+- Prevent trace artifact migration to incur data loss. !17313
+- Return a 404 instead of 403 if the repository does not exist on disk. !17341
+- Allow Prometheus application to be installed from Cluster applications. !17372
+- Fixes Prometheus admin configuration page. !17377
+- Fix code and wiki search results pages when non-ASCII text is displayed. !17413
+- Fix pages flaky failure by reloading stale object. !17522
+- Fixed issue edit shortcut not opening edit form.
+- Revert Project.public_or_visible_to_user changes and only apply to snippets.
+
+### Performance (1 change)
+
+- Don't use ProjectsFinder in TodosFinder.
+
+
+## 10.5.3 (2018-03-01)
+
+### Security (1 change)
+
+- Ensure that OTP backup codes are always invalidated.
+
+
 ## 10.5.2 (2018-02-25)
 
 ### Fixed (7 changes)
@@ -219,6 +518,29 @@ entry.
 - Adds empty state illustration for pending job.
 
 
+## 10.4.7 (2018-04-03)
+
+### Security (2 changes)
+
+- Fix XSS on diff view stored on filenames.
+- Adds confidential notes channel for Slack/Mattermost.
+
+
+## 10.4.6 (2018-03-16)
+
+### Security (2 changes)
+
+- Fixed some SSRF vulnerabilities in services, hooks and integrations. !2337
+- Fix GitLab Auth0 integration signing in the wrong user.
+
+
+## 10.4.5 (2018-03-01)
+
+### Security (1 change)
+
+- Ensure that OTP backup codes are always invalidated.
+
+
 ## 10.4.4 (2018-02-16)
 
 ### Security (1 change)
@@ -443,6 +765,22 @@ entry.
 - Use a background migration for issues.closed_at.
 
 
+## 10.3.9 (2018-03-16)
+
+### Security (3 changes)
+
+- Fixed some SSRF vulnerabilities in services, hooks and integrations. !2337
+- Update nokogiri to 1.8.2. !16807
+- Fix GitLab Auth0 integration signing in the wrong user.
+
+
+## 10.3.8 (2018-03-01)
+
+### Security (1 change)
+
+- Ensure that OTP backup codes are always invalidated.
+
+
 ## 10.3.7 (2018-02-05)
 
 ### Security (4 changes)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b70d2da5bea0c349086cb98e9cc0776e520c36c3..02599907af71e23cb9c93c0e22513b9028361c1a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -26,7 +26,7 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
   - [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
   - [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
   - [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc)
-  - [Priority labels (~Deliverable and ~Stretch)](#priority-labels-deliverable-and-stretch)
+  - [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#priority-labels-deliverable-stretch-next-patch-release)
   - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
 - [Implement design & UI elements](#implement-design-ui-elements)
 - [Issue tracker](#issue-tracker)
@@ -98,8 +98,8 @@ coach is going to finish the merge request we assign the
 
 ## Helping others
 
-Please help other GitLab users when you can. The channels people will reach out
-on can be found on the [getting help page][getting-help].
+Please help other GitLab users when you can.
+The methods people will use to seek help can be found on the [getting help page][getting-help].
 
 Sign up for the mailing list, answer GitLab questions on StackOverflow or
 respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
@@ -126,7 +126,7 @@ Most issues will have labels for at least one of the following:
 - Type: ~"feature proposal", ~bug, ~customer, etc.
 - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
 - Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.
-- Priority: ~Deliverable, ~Stretch
+- Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release"
 
 All labels, their meaning and priority are defined on the
 [labels page][labels-page].
@@ -185,16 +185,64 @@ indicate if an issue needs backend work, frontend work, or both.
 Team labels are always capitalized so that they show up as the first label for
 any issue.
 
-### Priority labels (~Deliverable and ~Stretch)
+### Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")
 
-Priority labels help us clearly communicate expectations of the work for the
-release. There are two levels of priority labels:
+Milestone labels help us clearly communicate expectations of the work for the
+release. There are three levels of Milestone labels:
 
 - ~Deliverable: Issues that are expected to be delivered in the current
   milestone.
 - ~Stretch: Issues that are a stretch goal for delivering in the current
   milestone. If these issues are not done in the current release, they will
   strongly be considered for the next release.
+- ~"Next Patch Release": Issues to put in the next patch release. Work on these 
+  first, and add the "Pick Into X" label to the merge request, along with the
+  appropriate milestone.
+
+Each issue scheduled for the current milestone should be labeled ~Deliverable
+or ~"Stretch". Any open issue for a previous milestone should be labeled 
+~"Next Patch Release", or otherwise rescheduled to a different milestone.
+
+### Bug Priority labels (~P1, ~P2, ~P3 & etc.)
+
+Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be. If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
+This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
+
+| Label | Meaning         | Estimate time to fix                                             | Guidance |
+|-------|-----------------|------------------------------------------------------------------|----------|
+| ~P1   | Urgent Priority | The current release                                              |  |
+| ~P2   | High Priority   | The next release                                                 |  |
+| ~P3   | Medium Priority | Within the next 3 releases (approx one quarter)                  |  |
+| ~P4   | Low Priority    | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented  |
+
+#### Specific Priority guidance
+
+| Label | Availability / Performance                                   | 
+|-------|--------------------------------------------------------------|
+| ~P1   |                                                              | 
+| ~P2   | The issue is (almost) guaranteed to occur in the near future |  
+| ~P3   | The issue is likely to occur in the near future              |
+| ~P4   | The issue _may_ occur but it's not likely                    |
+
+### Bug Severity labels (~S1, ~S2, ~S3 & etc.)
+
+Severity labels help us clearly communicate the impact of a ~bug on users. 
+
+| Label | Meaning           | Impact of the defect                                  | Example |
+|-------|-------------------|-------------------------------------------------------|---------|
+| ~S1   | Blocker           | Outage, broken feature with no workaround             | Unable to create an issue. Data corruption/loss. Security breach. |
+| ~S2   | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. |
+| ~S3   | Major Severity    | Broken Feature, workaround acceptable                 | Can create merge requests only from the Merge Requests page, not through the Issue. |
+| ~S4   | Low Severity      | Functionality inconvenience or cosmetic issue         | Label colors are incorrect / not being displayed. |
+
+#### Specific Severity guidance
+
+| Label | Security Impact                                                   | 
+|-------|-------------------------------------------------------------------|
+| ~S1   | >50% customers impacted (possible company extinction level event) | 
+| ~S2   | Multiple customers impacted (but not apocalyptic)                 |  
+| ~S3   | A single customer impacted                                        |
+| ~S4   | No customer impact, or expected impact within 30 days             |
 
 ### Label for community contributors (~"Accepting Merge Requests")
 
@@ -675,3 +723,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
 
 [^1]: Please note that specs other than JavaScript specs are considered backend
       code.
+      
\ No newline at end of file
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 359ee08a7ce9c6f0008ef6080cf3b6745552feae..5f8cbfdb7d7481868f0a93fad342b92a61ac0d87 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.87.0
+0.95.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index a918a2aa18d5bec6a8bb93891a7a63c243111796..a3df0a6959e154733da89a5d6063742ce6d5b851 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.6.0
+0.8.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 090ea9dad19fcac569ba37f77519218863e4195a..a8a188756826dae72582b230aa315701fb096149 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-6.0.3
+7.1.2
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 40c341bdcdbe83bbbda981fa85368c0e1a63d0c7..ee74734aa2258df77aa09402d55798a1e2e55212 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-3.6.0
+4.1.0
diff --git a/Gemfile b/Gemfile
index d8cb5267d81776d3aa516d50b3bd62c0c6076352..71f27e0f6de52279bd5484c7ffdf7a0831de201e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,18 @@
+# --- Special code for migrating to Rails 5.0 ---
+def rails5?
+  %w[1 true].include?(ENV["RAILS5"])
+end
+
+gem_versions = {}
+gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0'      : '0.2'
+gem_versions['default_value_for']               = rails5? ? '~> 3.0.5' : '~> 3.0.0'
+gem_versions['rails']                           = rails5? ? '5.0.6'    : '4.2.10'
+gem_versions['rails-i18n']                      = rails5? ? '~> 5.1'   : '~> 4.0.9'
+# --- The end of special code for migrating to Rails 5.0 ---
+
 source 'https://rubygems.org'
 
-gem 'rails', '4.2.10'
+gem 'rails', gem_versions['rails']
 gem 'rails-deprecated_sanitizer', '~> 1.0.3'
 
 # Responders respond_to and respond_with
@@ -9,36 +21,37 @@ gem 'responders', '~> 2.0'
 gem 'sprockets', '~> 3.7.0'
 
 # Default values for AR models
-gem 'default_value_for', '~> 3.0.0'
+gem 'default_value_for', gem_versions['default_value_for']
 
 # Supported DBs
 gem 'mysql2', '~> 0.4.10', group: :mysql
 gem 'pg', '~> 0.18.2', group: :postgres
 
-gem 'rugged', '~> 0.26.0'
+gem 'rugged', '~> 0.27'
 gem 'grape-route-helpers', '~> 2.1.0'
 
 gem 'faraday', '~> 0.12'
 
 # Authentication libraries
 gem 'devise', '~> 4.2'
-gem 'doorkeeper', '~> 4.2.0'
-gem 'doorkeeper-openid_connect', '~> 1.2.0'
-gem 'omniauth', '~> 1.4.2'
-gem 'omniauth-auth0', '~> 1.4.1'
+gem 'doorkeeper', '~> 4.3'
+gem 'doorkeeper-openid_connect', '~> 1.3'
+gem 'omniauth', '~> 1.8'
+gem 'omniauth-auth0', '~> 2.0.0'
 gem 'omniauth-azure-oauth2', '~> 0.0.9'
 gem 'omniauth-cas3', '~> 1.1.4'
 gem 'omniauth-facebook', '~> 4.0.0'
 gem 'omniauth-github', '~> 1.1.1'
 gem 'omniauth-gitlab', '~> 1.0.2'
-gem 'omniauth-google-oauth2', '~> 0.5.2'
+gem 'omniauth-google-oauth2', '~> 0.5.3'
 gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
 gem 'omniauth-oauth2-generic', '~> 0.2.2'
-gem 'omniauth-saml', '~> 1.7.0'
+gem 'omniauth-saml', '~> 1.10'
 gem 'omniauth-shibboleth', '~> 1.2.0'
-gem 'omniauth-twitter', '~> 1.2.0'
+gem 'omniauth-twitter', '~> 1.4'
 gem 'omniauth_crowd', '~> 2.2.0'
 gem 'omniauth-authentiq', '~> 0.3.1'
+gem 'omniauth-jwt', '~> 0.0.2'
 gem 'rack-oauth2', '~> 1.2.1'
 gem 'jwt', '~> 1.5.6'
 
@@ -49,7 +62,7 @@ gem 'akismet', '~> 2.0'
 # Two-factor authentication
 gem 'devise-two-factor', '~> 3.0.0'
 gem 'rqrcode-rails3', '~> 0.1.7'
-gem 'attr_encrypted', '~> 3.0.0'
+gem 'attr_encrypted', '~> 3.1.0'
 gem 'u2f', '~> 0.2.1'
 
 # GitLab Pages
@@ -69,16 +82,9 @@ gem 'net-ldap'
 
 # Git Wiki
 # Required manually in config/initializers/gollum.rb to control load order
-# Before updating this gem, check if
-# https://github.com/gollum/gollum-lib/pull/292 has been merged.
-# If it has, then remove the monkey patch for update_page, rename_page and raw_data_in_committer
-# in config/initializers/gollum.rb
-gem 'gollum-lib', '~> 4.2', require: false
+gem 'gitlab-gollum-lib', '~> 4.2', require: false
 
-# Before updating this gem, check if
-# https://github.com/gollum/rugged_adapter/pull/28 has been merged.
-# If it has, then remove the monkey patch for tree_entry in config/initializers/gollum.rb
-gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
+gem 'gitlab-gollum-rugged_adapter', '~> 0.4.4', require: false
 
 # Language detection
 gem 'github-linguist', '~> 5.3.3', require: 'linguist'
@@ -104,16 +110,16 @@ gem 'carrierwave', '~> 1.2'
 gem 'dropzonejs-rails', '~> 0.7.1'
 
 # for backups
-gem 'fog-aws', '~> 1.4'
+gem 'fog-aws', '~> 2.0.1'
 gem 'fog-core', '~> 1.44'
-gem 'fog-google', '~> 0.5'
+gem 'fog-google', '~> 1.3.3'
 gem 'fog-local', '~> 0.3'
 gem 'fog-openstack', '~> 0.1'
 gem 'fog-rackspace', '~> 0.1.1'
 gem 'fog-aliyun', '~> 0.2.0'
 
 # for Google storage
-gem 'google-api-client', '~> 0.13.6'
+gem 'google-api-client', '~> 0.19.8'
 
 # for aws storage
 gem 'unf', '~> 0.1.4'
@@ -122,18 +128,19 @@ gem 'unf', '~> 0.1.4'
 gem 'seed-fu', '~> 2.3.7'
 
 # Markdown and HTML processing
-gem 'html-pipeline', '~> 1.11.0'
+gem 'html-pipeline', '~> 2.7.1'
 gem 'deckar01-task_list', '2.0.0'
 gem 'gitlab-markup', '~> 1.6.2'
 gem 'redcarpet', '~> 3.4'
+gem 'commonmarker', '~> 0.17'
 gem 'RedCloth', '~> 4.3.2'
 gem 'rdoc', '~> 4.2'
 gem 'org-ruby', '~> 0.9.12'
 gem 'creole', '~> 0.5.0'
 gem 'wikicloth', '0.8.1'
-gem 'asciidoctor', '~> 1.5.2'
-gem 'asciidoctor-plantuml', '0.0.7'
-gem 'rouge', '~> 2.0'
+gem 'asciidoctor', '~> 1.5.6'
+gem 'asciidoctor-plantuml', '0.0.8'
+gem 'rouge', '~> 3.1'
 gem 'truncato', '~> 0.7.9'
 gem 'bootstrap_form', '~> 2.7.0'
 gem 'nokogiri', '~> 1.8.2'
@@ -148,10 +155,10 @@ group :unicorn do
 end
 
 # State machine
-gem 'state_machines-activerecord', '~> 0.4.0'
+gem 'state_machines-activerecord', '~> 0.5.1'
 
 # Issue tags
-gem 'acts-as-taggable-on', '~> 4.0'
+gem 'acts-as-taggable-on', '~> 5.0'
 
 # Background jobs
 gem 'sidekiq', '~> 5.0'
@@ -207,7 +214,7 @@ gem 'asana', '~> 0.6.0'
 gem 'ruby-fogbugz', '~> 0.2.1'
 
 # Kubernetes integration
-gem 'kubeclient', '~> 2.2.0'
+gem 'kubeclient', '~> 3.0'
 
 # d3
 gem 'd3_rails', '~> 3.5.0'
@@ -217,10 +224,10 @@ gem 'sanitize', '~> 2.0'
 gem 'babosa', '~> 1.0.2'
 
 # Sanitizes SVG input
-gem 'loofah', '~> 2.0.3'
+gem 'loofah', '~> 2.2'
 
 # Working with license
-gem 'licensee', '~> 8.7.0'
+gem 'licensee', '~> 8.9'
 
 # Protect against bruteforcing
 gem 'rack-attack', '~> 4.4.1'
@@ -234,9 +241,6 @@ gem 'mousetrap-rails', '~> 1.4.6'
 # Detect and convert string character encoding
 gem 'charlock_holmes', '~> 0.7.5'
 
-# Faster JSON
-gem 'oj', '~> 2.17.4'
-
 # Faster blank
 gem 'fast_blank'
 
@@ -256,22 +260,21 @@ gem 'font-awesome-rails', '~> 4.7'
 gem 'gemojione', '~> 3.3'
 gem 'gon', '~> 6.1.0'
 gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails', '~> 4.3.1'
 gem 'request_store', '~> 1.3'
 gem 'select2-rails', '~> 3.5.9'
 gem 'virtus', '~> 1.0.1'
 gem 'base32', '~> 0.3.0'
 
 # Sentry integration
-gem 'sentry-raven', '~> 2.5.3'
+gem 'sentry-raven', '~> 2.7'
 
 gem 'premailer-rails', '~> 1.9.7'
 
 # I18n
 gem 'ruby_parser', '~> 3.8', require: false
-gem 'rails-i18n', '~> 4.0.9'
+gem 'rails-i18n', gem_versions['rails-i18n']
 gem 'gettext_i18n_rails', '~> 1.8.0'
-gem 'gettext_i18n_rails_js', '~> 1.2.0'
+gem 'gettext_i18n_rails_js', '~> 1.3'
 gem 'gettext', '~> 3.2.2', require: false, group: :development
 
 gem 'batch-loader', '~> 1.2.1'
@@ -279,7 +282,6 @@ gem 'batch-loader', '~> 1.2.1'
 # Perf bar
 gem 'peek', '~> 1.0.1'
 gem 'peek-gc', '~> 0.0.2'
-gem 'peek-host', '~> 1.0.0'
 gem 'peek-mysql2', '~> 1.1.0', group: :mysql
 gem 'peek-performance_bar', '~> 1.3.0'
 gem 'peek-pg', '~> 1.3.0', group: :postgres
@@ -299,8 +301,8 @@ group :metrics do
 end
 
 group :development do
-  gem 'foreman', '~> 0.78.0'
-  gem 'brakeman', '~> 3.6.0', require: false
+  gem 'foreman', '~> 0.84.0'
+  gem 'brakeman', '~> 4.2', require: false
 
   gem 'letter_opener_web', '~> 1.3.0'
   gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
@@ -359,13 +361,15 @@ group :development, :test do
   gem 'benchmark-ips', '~> 2.3.0', require: false
 
   gem 'license_finder', '~> 3.1', require: false
-  gem 'knapsack', '~> 1.11.0'
+  gem 'knapsack', '~> 1.16'
 
-  gem 'activerecord_sane_schema_dumper', '0.2'
+  gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
 
   gem 'stackprof', '~> 0.2.10', require: false
 
   gem 'simple_po_parser', '~> 1.1.2', require: false
+
+  gem 'timecop', '~> 0.8.0'
 end
 
 group :test do
@@ -373,21 +377,21 @@ group :test do
   gem 'email_spec', '~> 1.6.0'
   gem 'json-schema', '~> 2.8.0'
   gem 'webmock', '~> 2.3.2'
-  gem 'test_after_commit', '~> 1.1'
+  gem 'rails-controller-testing' if rails5? # Rails5 only gem.
+  gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0.
   gem 'sham_rack', '~> 1.3.6'
-  gem 'timecop', '~> 0.8.0'
   gem 'concurrent-ruby', '~> 1.0.5'
   gem 'test-prof', '~> 0.2.5'
 end
 
-gem 'octokit', '~> 4.6.2'
+gem 'octokit', '~> 4.8'
 
 gem 'mail_room', '~> 0.9.1'
 
 gem 'email_reply_trimmer', '~> 0.1'
 gem 'html2text'
 
-gem 'ruby-prof', '~> 0.16.2'
+gem 'ruby-prof', '~> 0.17.0'
 
 # OAuth
 gem 'oauth2', '~> 1.4'
@@ -400,7 +404,7 @@ gem 'vmstat', '~> 2.3.0'
 gem 'sys-filesystem', '~> 1.1.6'
 
 # SSH host key support
-gem 'net-ssh', '~> 4.1.0'
+gem 'net-ssh', '~> 4.2.0'
 gem 'sshkey', '~> 1.9.0'
 
 # Required for ED25519 SSH host key support
@@ -411,16 +415,18 @@ group :ed25519 do
 end
 
 # Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.87.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.97.0', require: 'gitaly'
+gem 'grpc', '~> 1.10.0'
+
 # Locked until https://github.com/google/protobuf/issues/4210 is closed
 gem 'google-protobuf', '= 3.5.1'
 
 gem 'toml-rb', '~> 1.0.0', require: false
 
 # Feature toggles
-gem 'flipper', '~> 0.11.0'
-gem 'flipper-active_record', '~> 0.11.0'
-gem 'flipper-active_support_cache_store', '~> 0.11.0'
+gem 'flipper', '~> 0.13.0'
+gem 'flipper-active_record', '~> 0.13.0'
+gem 'flipper-active_support_cache_store', '~> 0.13.0'
 
 # Structured logging
 gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index 6918f92aa840adc3e358bea49e70b535784fcb19..745732f353719d6d54baa3906ac3fdd30a8aa1d1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -40,13 +40,14 @@ GEM
       minitest (~> 5.1)
       thread_safe (~> 0.3, >= 0.3.4)
       tzinfo (~> 1.1)
-    acts-as-taggable-on (4.0.0)
-      activerecord (>= 4.0)
+    acts-as-taggable-on (5.0.0)
+      activerecord (>= 4.2.8)
     adamantium (0.2.0)
       ice_nine (~> 0.11.0)
       memoizable (~> 0.4.0)
     addressable (2.5.2)
       public_suffix (>= 2.0.2, < 4.0)
+    aes_key_wrap (1.0.1)
     akismet (2.0.0)
     allocations (1.0.5)
     arel (6.0.4)
@@ -55,17 +56,17 @@ GEM
       faraday_middleware (~> 0.9)
       faraday_middleware-multi_json (~> 0.0)
       oauth2 (~> 1.0)
-    asciidoctor (1.5.3)
-    asciidoctor-plantuml (0.0.7)
+    asciidoctor (1.5.6.2)
+    asciidoctor-plantuml (0.0.8)
       asciidoctor (~> 1.5)
     asset_sync (2.2.0)
       activemodel (>= 4.1.0)
       fog-core
       mime-types (>= 2.99)
       unf
-    ast (2.3.0)
+    ast (2.4.0)
     atomic (1.1.99)
-    attr_encrypted (3.0.3)
+    attr_encrypted (3.1.0)
       encryptor (~> 3.0.0)
     attr_required (1.0.0)
     autoprefixer-rails (6.2.3)
@@ -86,7 +87,7 @@ GEM
       coderay (>= 1.0.0)
       erubis (>= 2.6.6)
       rack (>= 0.9.0)
-    bindata (2.4.1)
+    bindata (2.4.3)
     binding_of_caller (0.7.2)
       debug_inspector (>= 0.0.1)
     blankslate (2.1.2.4)
@@ -94,7 +95,7 @@ GEM
       autoprefixer-rails (>= 5.2.1)
       sass (>= 3.3.4)
     bootstrap_form (2.7.0)
-    brakeman (3.6.1)
+    brakeman (4.2.1)
     browser (2.2.0)
     builder (3.2.3)
     bullet (5.5.1)
@@ -119,7 +120,7 @@ GEM
       activesupport (>= 4.0.0)
       mime-types (>= 1.16)
     cause (0.1)
-    charlock_holmes (0.7.5)
+    charlock_holmes (0.7.6)
     childprocess (0.7.0)
       ffi (~> 1.0, >= 1.0.11)
     chronic (0.10.2)
@@ -131,6 +132,8 @@ GEM
     coercible (1.0.0)
       descendants_tracker (~> 0.0.1)
     colorize (0.7.7)
+    commonmarker (0.17.8)
+      ruby-enum (~> 0.5)
     concord (0.1.5)
       adamantium (~> 0.2.0)
       equalizer (~> 0.0.9)
@@ -140,6 +143,7 @@ GEM
     connection_pool (2.2.1)
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
+    crass (1.0.3)
     creole (0.5.0)
     css_parser (1.5.0)
       addressable
@@ -172,12 +176,12 @@ GEM
     diff-lcs (1.3)
     diffy (3.1.0)
     docile (1.1.5)
-    domain_name (0.5.20161021)
+    domain_name (0.5.20170404)
       unf (>= 0.0.5, < 1.0.0)
-    doorkeeper (4.2.6)
+    doorkeeper (4.3.1)
       railties (>= 4.2)
-    doorkeeper-openid_connect (1.2.0)
-      doorkeeper (~> 4.0)
+    doorkeeper-openid_connect (1.3.0)
+      doorkeeper (~> 4.3)
       json-jwt (~> 1.6)
     dropzonejs-rails (0.7.2)
       rails (> 3.1)
@@ -192,7 +196,7 @@ GEM
     et-orbi (1.0.3)
       tzinfo
     eventmachine (1.0.8)
-    excon (0.57.1)
+    excon (0.60.0)
     execjs (2.6.0)
     expression_parser (0.9.0)
     factory_bot (4.8.2)
@@ -202,13 +206,13 @@ GEM
       railties (>= 3.0.0)
     faraday (0.12.2)
       multipart-post (>= 1.2, < 3)
-    faraday_middleware (0.11.0.1)
+    faraday_middleware (0.12.2)
       faraday (>= 0.7.4, < 1.0)
     faraday_middleware-multi_json (0.0.6)
       faraday_middleware
       multi_json
     fast_blank (1.0.0)
-    fast_gettext (1.4.0)
+    fast_gettext (1.6.0)
     ffaker (2.4.0)
     ffi (1.9.18)
     flay (2.10.0)
@@ -216,13 +220,13 @@ GEM
       path_expander (~> 1.0)
       ruby_parser (~> 3.0)
       sexp_processor (~> 4.0)
-    flipper (0.11.0)
-    flipper-active_record (0.11.0)
+    flipper (0.13.0)
+    flipper-active_record (0.13.0)
       activerecord (>= 3.2, < 6)
-      flipper (~> 0.11.0)
-    flipper-active_support_cache_store (0.11.0)
+      flipper (~> 0.13.0)
+    flipper-active_support_cache_store (0.13.0)
       activesupport (>= 3.2, < 6)
-      flipper (~> 0.11.0)
+      flipper (~> 0.13.0)
     flowdock (0.7.1)
       httparty (~> 0.7)
       multi_json
@@ -231,19 +235,20 @@ GEM
       fog-json (~> 1.0)
       ipaddress (~> 0.8)
       xml-simple (~> 1.1)
-    fog-aws (1.4.0)
+    fog-aws (2.0.1)
       fog-core (~> 1.38)
       fog-json (~> 1.0)
       fog-xml (~> 0.1)
       ipaddress (~> 0.8)
-    fog-core (1.44.3)
+    fog-core (1.45.0)
       builder
-      excon (~> 0.49)
+      excon (~> 0.58)
       formatador (~> 0.2)
-    fog-google (0.5.3)
+    fog-google (1.3.3)
       fog-core
       fog-json
       fog-xml
+      google-api-client (~> 0.19.1)
     fog-json (1.0.2)
       fog-core (~> 1.0)
       multi_json (~> 1.10)
@@ -263,7 +268,7 @@ GEM
       nokogiri (>= 1.5.11, < 2.0.0)
     font-awesome-rails (4.7.0.1)
       railties (>= 3.2, < 5.1)
-    foreman (0.78.0)
+    foreman (0.84.0)
       thor (~> 0.19.1)
     formatador (0.2.5)
     fuubar (2.2.0)
@@ -274,30 +279,41 @@ GEM
     gemojione (3.3.0)
       json
     get_process_mem (0.2.0)
-    gettext (3.2.2)
+    gettext (3.2.9)
       locale (>= 2.0.5)
       text (>= 1.3.0)
     gettext_i18n_rails (1.8.0)
       fast_gettext (>= 0.9.0)
-    gettext_i18n_rails_js (1.2.0)
+    gettext_i18n_rails_js (1.3.0)
       gettext (>= 3.0.2)
       gettext_i18n_rails (>= 0.7.1)
       po_to_json (>= 1.0.0)
       rails (>= 3.2.0)
     gherkin-ruby (0.3.2)
-    gitaly-proto (0.87.0)
+    gitaly-proto (0.97.0)
       google-protobuf (~> 3.1)
-      grpc (~> 1.0)
+      grpc (~> 1.10)
     github-linguist (5.3.3)
       charlock_holmes (~> 0.7.5)
       escape_utils (~> 1.1.0)
       mime-types (>= 1.19)
       rugged (>= 0.25.1)
-    github-markup (1.6.1)
+    github-markup (1.7.0)
     gitlab-flowdock-git-hook (1.0.1)
       flowdock (~> 0.7)
       gitlab-grit (>= 2.4.1)
       multi_json
+    gitlab-gollum-lib (4.2.7.2)
+      gemojione (~> 3.2)
+      github-markup (~> 1.6)
+      gollum-grit_adapter (~> 1.0)
+      nokogiri (>= 1.6.1, < 2.0)
+      rouge (~> 3.1)
+      sanitize (~> 2.1)
+      stringex (~> 2.6)
+    gitlab-gollum-rugged_adapter (0.4.4)
+      mime-types (>= 1.15)
+      rugged (~> 0.25)
     gitlab-grit (2.8.2)
       charlock_holmes (~> 0.6)
       diff-lcs (~> 1.1)
@@ -317,25 +333,14 @@ GEM
       activesupport (>= 4.2.0)
     gollum-grit_adapter (1.0.1)
       gitlab-grit (~> 2.7, >= 2.7.1)
-    gollum-lib (4.2.7)
-      gemojione (~> 3.2)
-      github-markup (~> 1.6)
-      gollum-grit_adapter (~> 1.0)
-      nokogiri (>= 1.6.1, < 2.0)
-      rouge (~> 2.1)
-      sanitize (~> 2.1)
-      stringex (~> 2.6)
-    gollum-rugged_adapter (0.4.4)
-      mime-types (>= 1.15)
-      rugged (~> 0.25)
     gon (6.1.0)
       actionpack (>= 3.0)
       json
       multi_json
       request_store (>= 1.0)
-    google-api-client (0.13.6)
+    google-api-client (0.19.8)
       addressable (~> 2.5, >= 2.5.1)
-      googleauth (~> 0.5)
+      googleauth (>= 0.5, < 0.7.0)
       httpclient (>= 2.8.1, < 3.0)
       mime-types (~> 3.0)
       representable (~> 3.0)
@@ -343,9 +348,9 @@ GEM
     google-protobuf (3.5.1)
     googleapis-common-protos-types (1.0.1)
       google-protobuf (~> 3.0)
-    googleauth (0.5.3)
+    googleauth (0.6.2)
       faraday (~> 0.12)
-      jwt (~> 1.4)
+      jwt (>= 1.4, < 3.0)
       logging (~> 2.0)
       memoist (~> 0.12)
       multi_json (~> 1.11)
@@ -353,7 +358,7 @@ GEM
       signet (~> 0.7)
     gpgme (2.0.13)
       mini_portile2 (~> 2.1)
-    grape (1.0.0)
+    grape (1.0.2)
       activesupport
       builder
       mustermann-grape (~> 1.0.0)
@@ -369,7 +374,7 @@ GEM
       rake
     grape_logging (1.7.0)
       grape
-    grpc (1.8.3)
+    grpc (1.10.0)
       google-protobuf (~> 3.1)
       googleapis-common-protos-types (~> 1.0.0)
       googleauth (>= 0.5.1, < 0.7)
@@ -386,7 +391,7 @@ GEM
       thor
       tilt
     hashdiff (0.3.4)
-    hashie (3.5.6)
+    hashie (3.5.7)
     hashie-forbidden_attributes (0.1.1)
       hashie (>= 3.0)
     health_check (2.6.0)
@@ -394,26 +399,26 @@ GEM
     hipchat (1.5.2)
       httparty
       mimemagic
-    html-pipeline (1.11.0)
+    html-pipeline (2.7.1)
       activesupport (>= 2)
-      nokogiri (~> 1.4)
+      nokogiri (>= 1.4)
     html2text (0.2.0)
       nokogiri (~> 1.6)
     htmlentities (4.3.4)
-    http (0.9.8)
+    http (2.2.2)
       addressable (~> 2.3)
       http-cookie (~> 1.0)
       http-form_data (~> 1.0.1)
       http_parser.rb (~> 0.6.0)
     http-cookie (1.0.3)
       domain_name (~> 0.5)
-    http-form_data (1.0.1)
+    http-form_data (1.0.3)
     http_parser.rb (0.6.0)
     httparty (0.13.7)
       json (~> 1.8)
       multi_xml (>= 0.5.2)
-    httpclient (2.8.2)
-    i18n (0.9.1)
+    httpclient (2.8.3)
+    i18n (0.9.5)
       concurrent-ruby (~> 1.0)
     ice_nine (0.11.2)
     influxdb (0.2.3)
@@ -425,15 +430,11 @@ GEM
       multipart-post
       oauth (~> 0.5, >= 0.5.0)
     jquery-atwho-rails (1.3.2)
-    jquery-rails (4.3.1)
-      rails-dom-testing (>= 1, < 3)
-      railties (>= 4.2.0)
-      thor (>= 0.14, < 2.0)
     json (1.8.6)
-    json-jwt (1.7.2)
+    json-jwt (1.9.2)
       activesupport
+      aes_key_wrap
       bindata
-      multi_json (>= 1.3)
       securecompare
       url_safe_base64
     json-schema (2.8.0)
@@ -452,13 +453,13 @@ GEM
       kaminari-core (= 1.0.1)
     kaminari-core (1.0.1)
     kgio (2.10.0)
-    knapsack (1.11.0)
+    knapsack (1.16.0)
       rake
       timecop (>= 0.1.0)
-    kubeclient (2.2.0)
-      http (= 0.9.8)
-      recursive-open-struct (= 1.0.0)
-      rest-client
+    kubeclient (3.0.0)
+      http (~> 2.2.2)
+      recursive-open-struct (~> 1.0.4)
+      rest-client (~> 2.0)
     launchy (2.4.3)
       addressable (~> 2.3)
     letter_opener (1.4.1)
@@ -475,7 +476,7 @@ GEM
       toml (= 0.1.2)
       with_env (> 1.0)
       xml-simple
-    licensee (8.7.0)
+    licensee (8.9.2)
       rugged (~> 0.24)
     little-plugger (1.1.4)
     locale (2.1.2)
@@ -486,7 +487,8 @@ GEM
       actionpack (>= 4, < 5.2)
       activesupport (>= 4, < 5.2)
       railties (>= 4, < 5.2)
-    loofah (2.0.3)
+    loofah (2.2.2)
+      crass (~> 1.0.2)
       nokogiri (>= 1.5.9)
     mail (2.7.0)
       mini_mime (>= 0.1.1)
@@ -499,38 +501,37 @@ GEM
       mime-types-data (~> 3.2015)
     mime-types-data (3.2016.0521)
     mimemagic (0.3.0)
-    mini_mime (0.1.4)
+    mini_mime (1.0.0)
     mini_portile2 (2.3.0)
     minitest (5.7.0)
     mousetrap-rails (1.4.6)
-    multi_json (1.12.2)
+    multi_json (1.13.1)
     multi_xml (0.6.0)
     multipart-post (2.0.0)
-    mustermann (1.0.0)
+    mustermann (1.0.2)
     mustermann-grape (1.0.0)
       mustermann (~> 1.0.0)
     mysql2 (0.4.10)
     net-ldap (0.16.0)
-    net-ssh (4.1.0)
+    net-ssh (4.2.0)
     netrc (0.11.0)
     nokogiri (1.8.2)
       mini_portile2 (~> 2.3.0)
     numerizer (0.1.1)
-    oauth (0.5.1)
+    oauth (0.5.4)
     oauth2 (1.4.0)
       faraday (>= 0.8, < 0.13)
       jwt (~> 1.0)
       multi_json (~> 1.3)
       multi_xml (~> 0.5)
       rack (>= 1.2, < 3)
-    octokit (4.6.2)
+    octokit (4.8.0)
       sawyer (~> 0.8.0, >= 0.5.3)
-    oj (2.17.5)
-    omniauth (1.4.2)
-      hashie (>= 1.2, < 4)
-      rack (>= 1.0, < 3)
-    omniauth-auth0 (1.4.1)
-      omniauth-oauth2 (~> 1.1)
+    omniauth (1.8.1)
+      hashie (>= 3.4.6, < 3.6.0)
+      rack (>= 1.6.2, < 3)
+    omniauth-auth0 (2.0.0)
+      omniauth-oauth2 (~> 1.4)
     omniauth-authentiq (0.3.1)
       omniauth-oauth2 (~> 1.3, >= 1.3.1)
     omniauth-azure-oauth2 (0.0.9)
@@ -549,11 +550,13 @@ GEM
     omniauth-gitlab (1.0.2)
       omniauth (~> 1.0)
       omniauth-oauth2 (~> 1.0)
-    omniauth-google-oauth2 (0.5.2)
-      jwt (~> 1.5)
-      multi_json (~> 1.3)
+    omniauth-google-oauth2 (0.5.3)
+      jwt (>= 1.5)
       omniauth (>= 1.1.1)
-      omniauth-oauth2 (>= 1.3.1)
+      omniauth-oauth2 (>= 1.5)
+    omniauth-jwt (0.0.2)
+      jwt
+      omniauth (~> 1.1)
     omniauth-kerberos (0.3.0)
       omniauth-multipassword
       timfel-krb5-auth (~> 0.8)
@@ -562,19 +565,19 @@ GEM
     omniauth-oauth (1.1.0)
       oauth
       omniauth (~> 1.0)
-    omniauth-oauth2 (1.4.0)
-      oauth2 (~> 1.0)
+    omniauth-oauth2 (1.5.0)
+      oauth2 (~> 1.1)
       omniauth (~> 1.2)
     omniauth-oauth2-generic (0.2.2)
       omniauth-oauth2 (~> 1.0)
-    omniauth-saml (1.7.0)
-      omniauth (~> 1.3)
-      ruby-saml (~> 1.4)
+    omniauth-saml (1.10.0)
+      omniauth (~> 1.3, >= 1.3.2)
+      ruby-saml (~> 1.7)
     omniauth-shibboleth (1.2.1)
       omniauth (>= 1.0.0)
-    omniauth-twitter (1.2.1)
-      json (~> 1.3)
+    omniauth-twitter (1.4.0)
       omniauth-oauth (~> 1.1)
+      rack
     omniauth_crowd (2.2.3)
       activesupport
       nokogiri (>= 1.4.4)
@@ -584,8 +587,8 @@ GEM
     orm_adapter (0.5.0)
     os (0.9.6)
     parallel (1.12.1)
-    parser (2.4.0.2)
-      ast (~> 2.3)
+    parser (2.5.1.0)
+      ast (~> 2.4.0)
     parslet (1.5.0)
       blankslate (~> 2.0)
     path_expander (1.0.2)
@@ -595,13 +598,11 @@ GEM
       railties (>= 4.0.0)
     peek-gc (0.0.2)
       peek
-    peek-host (1.0.0)
-      peek
     peek-mysql2 (1.1.0)
       atomic (>= 1.0.0)
       mysql2
       peek
-    peek-performance_bar (1.3.0)
+    peek-performance_bar (1.3.1)
       peek (>= 0.1.0)
     peek-pg (1.3.0)
       concurrent-ruby
@@ -646,9 +647,9 @@ GEM
       pry (~> 0.10)
     pry-rails (0.3.5)
       pry (>= 0.9.10)
-    public_suffix (3.0.0)
+    public_suffix (3.0.2)
     pyu-ruby-sasl (0.0.3.3)
-    rack (1.6.8)
+    rack (1.6.9)
     rack-accept (0.4.5)
       rack (>= 0.4)
     rack-attack (4.4.1)
@@ -660,7 +661,7 @@ GEM
       httpclient (>= 2.4)
       multi_json (>= 1.3.6)
       rack (>= 1.1)
-    rack-protection (1.5.3)
+    rack-protection (2.0.1)
       rack
     rack-proxy (0.6.0)
       rack
@@ -679,12 +680,12 @@ GEM
       sprockets-rails
     rails-deprecated_sanitizer (1.0.3)
       activesupport (>= 4.2.0.alpha)
-    rails-dom-testing (1.0.8)
-      activesupport (>= 4.2.0.beta, < 5.0)
+    rails-dom-testing (1.0.9)
+      activesupport (>= 4.2.0, < 5.0)
       nokogiri (~> 1.6)
       rails-deprecated_sanitizer (>= 1.0.1)
-    rails-html-sanitizer (1.0.3)
-      loofah (~> 2.0)
+    rails-html-sanitizer (1.0.4)
+      loofah (~> 2.2, >= 2.2.2)
     rails-i18n (4.0.9)
       i18n (~> 0.7)
       railties (~> 4.0)
@@ -711,7 +712,7 @@ GEM
     re2 (1.1.1)
     recaptcha (3.0.0)
       json
-    recursive-open-struct (1.0.0)
+    recursive-open-struct (1.0.5)
     redcarpet (3.4.0)
     redis (3.3.5)
     redis-actionpack (5.0.2)
@@ -739,14 +740,14 @@ GEM
     request_store (1.3.1)
     responders (2.3.0)
       railties (>= 4.2.0, < 5.1)
-    rest-client (2.0.0)
+    rest-client (2.0.2)
       http-cookie (>= 1.0.2, < 2.0)
       mime-types (>= 1.16, < 4.0)
       netrc (~> 0.8)
     retriable (3.1.1)
     rinku (2.0.0)
     rotp (2.1.2)
-    rouge (2.2.1)
+    rouge (3.1.1)
     rqrcode (0.7.0)
       chunky_png
     rqrcode-rails3 (0.1.7)
@@ -797,11 +798,13 @@ GEM
       rubocop (>= 0.51)
     rubocop-rspec (1.22.1)
       rubocop (>= 0.52.1)
+    ruby-enum (0.7.2)
+      i18n
     ruby-fogbugz (0.2.1)
       crack (~> 0.4)
-    ruby-prof (0.16.2)
+    ruby-prof (0.17.0)
     ruby-progressbar (1.9.0)
-    ruby-saml (1.4.1)
+    ruby-saml (1.7.2)
       nokogiri (>= 1.5.10)
     ruby_parser (3.9.0)
       sexp_processor (~> 4.1)
@@ -810,7 +813,7 @@ GEM
     rubyzip (1.2.1)
     rufus-scheduler (3.4.0)
       et-orbi (~> 1.0)
-    rugged (0.26.0)
+    rugged (0.27.0)
     safe_yaml (1.0.4)
     sanitize (2.1.0)
       nokogiri (>= 1.4.4)
@@ -840,7 +843,7 @@ GEM
     selenium-webdriver (3.5.0)
       childprocess (~> 0.5)
       rubyzip (~> 1.0)
-    sentry-raven (2.5.3)
+    sentry-raven (2.7.2)
       faraday (>= 0.7.6, < 1.0)
     settingslogic (2.0.9)
     sexp_processor (4.9.0)
@@ -858,10 +861,10 @@ GEM
       sidekiq (>= 4.2.1)
     sidekiq-limit_fetch (3.4.0)
       sidekiq (>= 4)
-    signet (0.7.3)
+    signet (0.8.1)
       addressable (~> 2.3)
       faraday (~> 0.9)
-      jwt (~> 1.5)
+      jwt (>= 1.5, < 3.0)
       multi_json (~> 1.10)
     simple_po_parser (1.1.2)
     simplecov (0.14.1)
@@ -897,14 +900,14 @@ GEM
     sqlite3 (1.3.13)
     sshkey (1.9.0)
     stackprof (0.2.10)
-    state_machines (0.4.0)
-    state_machines-activemodel (0.4.0)
-      activemodel (>= 4.1, < 5.1)
-      state_machines (>= 0.4.0)
-    state_machines-activerecord (0.4.0)
-      activerecord (>= 4.1, < 5.1)
-      state_machines-activemodel (>= 0.3.0)
-    stringex (2.7.1)
+    state_machines (0.5.0)
+    state_machines-activemodel (0.5.1)
+      activemodel (>= 4.1, < 6.0)
+      state_machines (>= 0.5.0)
+    state_machines-activerecord (0.5.1)
+      activerecord (>= 4.1, < 6.0)
+      state_machines-activemodel (>= 0.5.0)
+    stringex (2.8.4)
     sys-filesystem (1.1.6)
       ffi
     sysexits (1.2.0)
@@ -929,7 +932,7 @@ GEM
     truncato (0.7.10)
       htmlentities (~> 4.3.1)
       nokogiri (~> 1.8.0, >= 1.7.0)
-    tzinfo (1.2.4)
+    tzinfo (1.2.5)
       thread_safe (~> 0.1)
     u2f (0.2.1)
     uber (0.1.0)
@@ -938,7 +941,7 @@ GEM
       json (>= 1.8.0)
     unf (0.1.4)
       unf_ext
-    unf_ext (0.0.7.4)
+    unf_ext (0.0.7.5)
     unicode-display_width (1.3.0)
     unicorn (5.1.0)
       kgio (~> 2.6)
@@ -947,13 +950,13 @@ GEM
       get_process_mem (~> 0)
       unicorn (>= 4, < 6)
     uniform_notifier (1.10.0)
-    unparser (0.2.6)
+    unparser (0.2.7)
       abstract_type (~> 0.0.7)
       adamantium (~> 0.2.0)
       concord (~> 0.1.5)
       diff-lcs (~> 1.3)
       equalizer (~> 0.0.9)
-      parser (>= 2.3.1.2, < 2.5)
+      parser (>= 2.3.1.2, < 2.6)
       procto (~> 0.0.2)
     url_safe_base64 (0.2.2)
     validates_hostname (1.0.6)
@@ -990,15 +993,15 @@ DEPENDENCIES
   RedCloth (~> 4.3.2)
   ace-rails-ap (~> 4.1.0)
   activerecord_sane_schema_dumper (= 0.2)
-  acts-as-taggable-on (~> 4.0)
+  acts-as-taggable-on (~> 5.0)
   addressable (~> 2.5.2)
   akismet (~> 2.0)
   allocations (~> 1.0)
   asana (~> 0.6.0)
-  asciidoctor (~> 1.5.2)
-  asciidoctor-plantuml (= 0.0.7)
+  asciidoctor (~> 1.5.6)
+  asciidoctor-plantuml (= 0.0.8)
   asset_sync (~> 2.2.0)
-  attr_encrypted (~> 3.0.0)
+  attr_encrypted (~> 3.1.0)
   awesome_print (~> 1.2.0)
   babosa (~> 1.0.2)
   base32 (~> 0.3.0)
@@ -1009,7 +1012,7 @@ DEPENDENCIES
   binding_of_caller (~> 0.7.2)
   bootstrap-sass (~> 3.3.0)
   bootstrap_form (~> 2.7.0)
-  brakeman (~> 3.6.0)
+  brakeman (~> 4.2)
   browser (~> 2.2)
   bullet (~> 5.5.0)
   bundler-audit (~> 0.5.0)
@@ -1019,6 +1022,7 @@ DEPENDENCIES
   charlock_holmes (~> 0.7.5)
   chronic (~> 0.10.2)
   chronic_duration (~> 0.10.6)
+  commonmarker (~> 0.17)
   concurrent-ruby (~> 1.0.5)
   connection_pool (~> 2.0)
   creole (~> 0.5.0)
@@ -1029,8 +1033,8 @@ DEPENDENCIES
   devise (~> 4.2)
   devise-two-factor (~> 3.0.0)
   diffy (~> 3.1.0)
-  doorkeeper (~> 4.2.0)
-  doorkeeper-openid_connect (~> 1.2.0)
+  doorkeeper (~> 4.3)
+  doorkeeper-openid_connect (~> 1.3)
   dropzonejs-rails (~> 0.7.1)
   email_reply_trimmer (~> 0.1)
   email_spec (~> 1.6.0)
@@ -1039,92 +1043,91 @@ DEPENDENCIES
   fast_blank
   ffaker (~> 2.4)
   flay (~> 2.10.0)
-  flipper (~> 0.11.0)
-  flipper-active_record (~> 0.11.0)
-  flipper-active_support_cache_store (~> 0.11.0)
+  flipper (~> 0.13.0)
+  flipper-active_record (~> 0.13.0)
+  flipper-active_support_cache_store (~> 0.13.0)
   fog-aliyun (~> 0.2.0)
-  fog-aws (~> 1.4)
+  fog-aws (~> 2.0.1)
   fog-core (~> 1.44)
-  fog-google (~> 0.5)
+  fog-google (~> 1.3.3)
   fog-local (~> 0.3)
   fog-openstack (~> 0.1)
   fog-rackspace (~> 0.1.1)
   font-awesome-rails (~> 4.7)
-  foreman (~> 0.78.0)
+  foreman (~> 0.84.0)
   fuubar (~> 2.2.0)
   gemnasium-gitlab-service (~> 0.2)
   gemojione (~> 3.3)
   gettext (~> 3.2.2)
   gettext_i18n_rails (~> 1.8.0)
-  gettext_i18n_rails_js (~> 1.2.0)
-  gitaly-proto (~> 0.87.0)
+  gettext_i18n_rails_js (~> 1.3)
+  gitaly-proto (~> 0.97.0)
   github-linguist (~> 5.3.3)
   gitlab-flowdock-git-hook (~> 1.0.1)
+  gitlab-gollum-lib (~> 4.2)
+  gitlab-gollum-rugged_adapter (~> 0.4.4)
   gitlab-markup (~> 1.6.2)
   gitlab-styles (~> 2.3)
   gitlab_omniauth-ldap (~> 2.0.4)
-  gollum-lib (~> 4.2)
-  gollum-rugged_adapter (~> 0.4.4)
   gon (~> 6.1.0)
-  google-api-client (~> 0.13.6)
+  google-api-client (~> 0.19.8)
   google-protobuf (= 3.5.1)
   gpgme
   grape (~> 1.0)
   grape-entity (~> 0.6.0)
   grape-route-helpers (~> 2.1.0)
   grape_logging (~> 1.7)
+  grpc (~> 1.10.0)
   haml_lint (~> 0.26.0)
   hamlit (~> 2.6.1)
   hashie-forbidden_attributes
   health_check (~> 2.6.0)
   hipchat (~> 1.5.0)
-  html-pipeline (~> 1.11.0)
+  html-pipeline (~> 2.7.1)
   html2text
   httparty (~> 0.13.3)
   influxdb (~> 0.2)
   jira-ruby (~> 1.4)
   jquery-atwho-rails (~> 1.3.2)
-  jquery-rails (~> 4.3.1)
   json-schema (~> 2.8.0)
   jwt (~> 1.5.6)
   kaminari (~> 1.0)
-  knapsack (~> 1.11.0)
-  kubeclient (~> 2.2.0)
+  knapsack (~> 1.16)
+  kubeclient (~> 3.0)
   letter_opener_web (~> 1.3.0)
   license_finder (~> 3.1)
-  licensee (~> 8.7.0)
+  licensee (~> 8.9)
   lograge (~> 0.5)
-  loofah (~> 2.0.3)
+  loofah (~> 2.2)
   mail_room (~> 0.9.1)
   method_source (~> 0.8)
   minitest (~> 5.7.0)
   mousetrap-rails (~> 1.4.6)
   mysql2 (~> 0.4.10)
   net-ldap
-  net-ssh (~> 4.1.0)
+  net-ssh (~> 4.2.0)
   nokogiri (~> 1.8.2)
   oauth2 (~> 1.4)
-  octokit (~> 4.6.2)
-  oj (~> 2.17.4)
-  omniauth (~> 1.4.2)
-  omniauth-auth0 (~> 1.4.1)
+  octokit (~> 4.8)
+  omniauth (~> 1.8)
+  omniauth-auth0 (~> 2.0.0)
   omniauth-authentiq (~> 0.3.1)
   omniauth-azure-oauth2 (~> 0.0.9)
   omniauth-cas3 (~> 1.1.4)
   omniauth-facebook (~> 4.0.0)
   omniauth-github (~> 1.1.1)
   omniauth-gitlab (~> 1.0.2)
-  omniauth-google-oauth2 (~> 0.5.2)
+  omniauth-google-oauth2 (~> 0.5.3)
+  omniauth-jwt (~> 0.0.2)
   omniauth-kerberos (~> 0.3.0)
   omniauth-oauth2-generic (~> 0.2.2)
-  omniauth-saml (~> 1.7.0)
+  omniauth-saml (~> 1.10)
   omniauth-shibboleth (~> 1.2.0)
-  omniauth-twitter (~> 1.2.0)
+  omniauth-twitter (~> 1.4)
   omniauth_crowd (~> 2.2.0)
   org-ruby (~> 0.9.12)
   peek (~> 1.0.1)
   peek-gc (~> 0.0.2)
-  peek-host (~> 1.0.0)
   peek-mysql2 (~> 1.1.0)
   peek-performance_bar (~> 1.3.0)
   peek-pg (~> 1.3.0)
@@ -1157,7 +1160,7 @@ DEPENDENCIES
   redis-rails (~> 5.0.2)
   request_store (~> 1.3)
   responders (~> 2.0)
-  rouge (~> 2.0)
+  rouge (~> 3.1)
   rqrcode-rails3 (~> 0.1.7)
   rspec-parameterized
   rspec-rails (~> 3.6.0)
@@ -1167,17 +1170,17 @@ DEPENDENCIES
   rubocop (~> 0.52.1)
   rubocop-rspec (~> 1.22.1)
   ruby-fogbugz (~> 0.2.1)
-  ruby-prof (~> 0.16.2)
+  ruby-prof (~> 0.17.0)
   ruby_parser (~> 3.8)
   rufus-scheduler (~> 3.4)
-  rugged (~> 0.26.0)
+  rugged (~> 0.27)
   sanitize (~> 2.0)
   sass-rails (~> 5.0.6)
   scss_lint (~> 0.56.0)
   seed-fu (~> 2.3.7)
   select2-rails (~> 3.5.9)
   selenium-webdriver (~> 3.5)
-  sentry-raven (~> 2.5.3)
+  sentry-raven (~> 2.7)
   settingslogic (~> 2.0.9)
   sham_rack (~> 1.3.6)
   shoulda-matchers (~> 3.1.2)
@@ -1195,7 +1198,7 @@ DEPENDENCIES
   sprockets (~> 3.7.0)
   sshkey (~> 1.9.0)
   stackprof (~> 0.2.10)
-  state_machines-activerecord (~> 0.4.0)
+  state_machines-activerecord (~> 0.5.1)
   sys-filesystem (~> 1.1.6)
   test-prof (~> 0.2.5)
   test_after_commit (~> 1.1)
diff --git a/Gemfile.rails5 b/Gemfile.rails5
new file mode 100644
index 0000000000000000000000000000000000000000..2b526b19ba0dfaf3e038e8db6e84335f71ad3177
--- /dev/null
+++ b/Gemfile.rails5
@@ -0,0 +1,7 @@
+# BUNDLE_GEMFILE=Gemfile.rails5 bundle install
+
+ENV["RAILS5"] = "true"
+
+gemfile = File.expand_path("../Gemfile", __FILE__)
+
+eval(File.read(gemfile), nil, gemfile)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
new file mode 100644
index 0000000000000000000000000000000000000000..a0330cbdd025e8f68fba560de4b4be560ff2926e
--- /dev/null
+++ b/Gemfile.rails5.lock
@@ -0,0 +1,1228 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    RedCloth (4.3.2)
+    abstract_type (0.0.7)
+    ace-rails-ap (4.1.4)
+    actioncable (5.0.6)
+      actionpack (= 5.0.6)
+      nio4r (>= 1.2, < 3.0)
+      websocket-driver (~> 0.6.1)
+    actionmailer (5.0.6)
+      actionpack (= 5.0.6)
+      actionview (= 5.0.6)
+      activejob (= 5.0.6)
+      mail (~> 2.5, >= 2.5.4)
+      rails-dom-testing (~> 2.0)
+    actionpack (5.0.6)
+      actionview (= 5.0.6)
+      activesupport (= 5.0.6)
+      rack (~> 2.0)
+      rack-test (~> 0.6.3)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    actionview (5.0.6)
+      activesupport (= 5.0.6)
+      builder (~> 3.1)
+      erubis (~> 2.7.0)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.0, >= 1.0.3)
+    activejob (5.0.6)
+      activesupport (= 5.0.6)
+      globalid (>= 0.3.6)
+    activemodel (5.0.6)
+      activesupport (= 5.0.6)
+    activerecord (5.0.6)
+      activemodel (= 5.0.6)
+      activesupport (= 5.0.6)
+      arel (~> 7.0)
+    activerecord_sane_schema_dumper (1.0)
+      rails (>= 5, < 6)
+    activesupport (5.0.6)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      i18n (~> 0.7)
+      minitest (~> 5.1)
+      tzinfo (~> 1.1)
+    acts-as-taggable-on (5.0.0)
+      activerecord (>= 4.2.8)
+    adamantium (0.2.0)
+      ice_nine (~> 0.11.0)
+      memoizable (~> 0.4.0)
+    addressable (2.5.2)
+      public_suffix (>= 2.0.2, < 4.0)
+    aes_key_wrap (1.0.1)
+    akismet (2.0.0)
+    allocations (1.0.5)
+    arel (7.1.4)
+    asana (0.6.3)
+      faraday (~> 0.9)
+      faraday_middleware (~> 0.9)
+      faraday_middleware-multi_json (~> 0.0)
+      oauth2 (~> 1.0)
+    asciidoctor (1.5.6.1)
+    asciidoctor-plantuml (0.0.8)
+      asciidoctor (~> 1.5)
+    asset_sync (2.2.0)
+      activemodel (>= 4.1.0)
+      fog-core
+      mime-types (>= 2.99)
+      unf
+    ast (2.4.0)
+    atomic (1.1.100)
+    attr_encrypted (3.1.0)
+      encryptor (~> 3.0.0)
+    attr_required (1.0.1)
+    autoprefixer-rails (8.1.0.1)
+      execjs
+    awesome_print (1.2.0)
+    axiom-types (0.1.1)
+      descendants_tracker (~> 0.0.4)
+      ice_nine (~> 0.11.0)
+      thread_safe (~> 0.3, >= 0.3.1)
+    babosa (1.0.2)
+    base32 (0.3.2)
+    batch-loader (1.2.1)
+    bcrypt (3.1.11)
+    bcrypt_pbkdf (1.0.0)
+    benchmark-ips (2.3.0)
+    better_errors (2.1.1)
+      coderay (>= 1.0.0)
+      erubis (>= 2.6.6)
+      rack (>= 0.9.0)
+    bindata (2.4.3)
+    binding_of_caller (0.7.3)
+      debug_inspector (>= 0.0.1)
+    blankslate (2.1.2.4)
+    bootstrap-sass (3.3.7)
+      autoprefixer-rails (>= 5.2.1)
+      sass (>= 3.3.4)
+    bootstrap_form (2.7.0)
+    brakeman (4.2.1)
+    browser (2.5.3)
+    builder (3.2.3)
+    bullet (5.5.1)
+      activesupport (>= 3.0.0)
+      uniform_notifier (~> 1.10.0)
+    bundler-audit (0.5.0)
+      bundler (~> 1.2)
+      thor (~> 0.18)
+    byebug (9.0.6)
+    capybara (2.18.0)
+      addressable
+      mini_mime (>= 0.1.3)
+      nokogiri (>= 1.3.3)
+      rack (>= 1.0.0)
+      rack-test (>= 0.5.4)
+      xpath (>= 2.0, < 4.0)
+    capybara-screenshot (1.0.18)
+      capybara (>= 1.0, < 3)
+      launchy
+    carrierwave (1.2.2)
+      activemodel (>= 4.0.0)
+      activesupport (>= 4.0.0)
+      mime-types (>= 1.16)
+    charlock_holmes (0.7.5)
+    childprocess (0.9.0)
+      ffi (~> 1.0, >= 1.0.11)
+    chronic (0.10.2)
+    chronic_duration (0.10.6)
+      numerizer (~> 0.1.1)
+    chunky_png (1.3.10)
+    citrus (3.0.2)
+    coderay (1.1.2)
+    coercible (1.0.0)
+      descendants_tracker (~> 0.0.1)
+    colorize (0.8.1)
+    commonmarker (0.17.9)
+      ruby-enum (~> 0.5)
+    concord (0.1.5)
+      adamantium (~> 0.2.0)
+      equalizer (~> 0.0.9)
+    concurrent-ruby (1.0.5)
+    concurrent-ruby-ext (1.0.5)
+      concurrent-ruby (= 1.0.5)
+    connection_pool (2.2.1)
+    crack (0.4.3)
+      safe_yaml (~> 1.0.0)
+    crass (1.0.3)
+    creole (0.5.0)
+    css_parser (1.6.0)
+      addressable
+    d3_rails (3.5.17)
+      railties (>= 3.1.0)
+    daemons (1.2.6)
+    database_cleaner (1.5.3)
+    debug_inspector (0.0.3)
+    debugger-ruby_core_source (1.3.8)
+    deckar01-task_list (2.0.0)
+      html-pipeline
+    declarative (0.0.10)
+    declarative-option (0.1.0)
+    default_value_for (3.0.5)
+      activerecord (>= 3.2.0, < 5.2)
+    descendants_tracker (0.0.4)
+      thread_safe (~> 0.3, >= 0.3.1)
+    devise (4.4.1)
+      bcrypt (~> 3.0)
+      orm_adapter (~> 0.1)
+      railties (>= 4.1.0, < 5.2)
+      responders
+      warden (~> 1.2.3)
+    devise-two-factor (3.0.2)
+      activesupport (< 5.2)
+      attr_encrypted (>= 1.3, < 4, != 2)
+      devise (~> 4.0)
+      railties (< 5.2)
+      rotp (~> 2.0)
+    diff-lcs (1.3)
+    diffy (3.1.0)
+    docile (1.1.5)
+    domain_name (0.5.20170404)
+      unf (>= 0.0.5, < 1.0.0)
+    doorkeeper (4.3.1)
+      railties (>= 4.2)
+    doorkeeper-openid_connect (1.3.0)
+      doorkeeper (~> 4.3)
+      json-jwt (~> 1.6)
+    dropzonejs-rails (0.7.4)
+      rails (> 3.1)
+    email_reply_trimmer (0.1.10)
+    email_spec (1.6.0)
+      launchy (~> 2.1)
+      mail (~> 2.2)
+    encryptor (3.0.0)
+    equalizer (0.0.11)
+    erubis (2.7.0)
+    escape_utils (1.1.1)
+    et-orbi (1.0.9)
+      tzinfo
+    eventmachine (1.2.5)
+    excon (0.60.0)
+    execjs (2.7.0)
+    expression_parser (0.9.0)
+    factory_bot (4.8.2)
+      activesupport (>= 3.0.0)
+    factory_bot_rails (4.8.2)
+      factory_bot (~> 4.8.2)
+      railties (>= 3.0.0)
+    faraday (0.12.2)
+      multipart-post (>= 1.2, < 3)
+    faraday_middleware (0.12.2)
+      faraday (>= 0.7.4, < 1.0)
+    faraday_middleware-multi_json (0.0.6)
+      faraday_middleware
+      multi_json
+    fast_blank (1.0.0)
+    fast_gettext (1.6.0)
+    ffaker (2.8.1)
+    ffi (1.9.23)
+    flay (2.10.0)
+      erubis (~> 2.7.0)
+      path_expander (~> 1.0)
+      ruby_parser (~> 3.0)
+      sexp_processor (~> 4.0)
+    flipper (0.13.0)
+    flipper-active_record (0.13.0)
+      activerecord (>= 3.2, < 6)
+      flipper (~> 0.13.0)
+    flipper-active_support_cache_store (0.13.0)
+      activesupport (>= 3.2, < 6)
+      flipper (~> 0.13.0)
+    flowdock (0.7.1)
+      httparty (~> 0.7)
+      multi_json
+    fog-aliyun (0.2.0)
+      fog-core (~> 1.27)
+      fog-json (~> 1.0)
+      ipaddress (~> 0.8)
+      xml-simple (~> 1.1)
+    fog-aws (2.0.1)
+      fog-core (~> 1.38)
+      fog-json (~> 1.0)
+      fog-xml (~> 0.1)
+      ipaddress (~> 0.8)
+    fog-core (1.45.0)
+      builder
+      excon (~> 0.58)
+      formatador (~> 0.2)
+    fog-google (1.3.3)
+      fog-core
+      fog-json
+      fog-xml
+      google-api-client (~> 0.19.1)
+    fog-json (1.0.2)
+      fog-core (~> 1.0)
+      multi_json (~> 1.10)
+    fog-local (0.5.0)
+      fog-core (>= 1.27, < 3.0)
+    fog-openstack (0.1.24)
+      fog-core (~> 1.40)
+      fog-json (>= 1.0)
+      ipaddress (>= 0.8)
+    fog-rackspace (0.1.5)
+      fog-core (>= 1.35)
+      fog-json (>= 1.0)
+      fog-xml (>= 0.1)
+      ipaddress (>= 0.8)
+    fog-xml (0.1.3)
+      fog-core
+      nokogiri (>= 1.5.11, < 2.0.0)
+    font-awesome-rails (4.7.0.3)
+      railties (>= 3.2, < 5.2)
+    foreman (0.84.0)
+      thor (~> 0.19.1)
+    formatador (0.2.5)
+    fuubar (2.2.0)
+      rspec-core (~> 3.0)
+      ruby-progressbar (~> 1.4)
+    gemnasium-gitlab-service (0.2.6)
+      rugged (~> 0.21)
+    gemojione (3.3.0)
+      json
+    get_process_mem (0.2.1)
+    gettext (3.2.9)
+      locale (>= 2.0.5)
+      text (>= 1.3.0)
+    gettext_i18n_rails (1.8.0)
+      fast_gettext (>= 0.9.0)
+    gettext_i18n_rails_js (1.3.0)
+      gettext (>= 3.0.2)
+      gettext_i18n_rails (>= 0.7.1)
+      po_to_json (>= 1.0.0)
+      rails (>= 3.2.0)
+    gherkin-ruby (0.3.2)
+    gitaly-proto (0.97.0)
+      google-protobuf (~> 3.1)
+      grpc (~> 1.10)
+    github-linguist (5.3.3)
+      charlock_holmes (~> 0.7.5)
+      escape_utils (~> 1.1.0)
+      mime-types (>= 1.19)
+      rugged (>= 0.25.1)
+    github-markup (1.7.0)
+    gitlab-flowdock-git-hook (1.0.1)
+      flowdock (~> 0.7)
+      gitlab-grit (>= 2.4.1)
+      multi_json
+    gitlab-gollum-lib (4.2.7.1)
+      gemojione (~> 3.2)
+      github-markup (~> 1.6)
+      gollum-grit_adapter (~> 1.0)
+      nokogiri (>= 1.6.1, < 2.0)
+      rouge (~> 2.1)
+      sanitize (~> 2.1)
+      stringex (~> 2.6)
+    gitlab-gollum-rugged_adapter (0.4.4)
+      mime-types (>= 1.15)
+      rugged (~> 0.25)
+    gitlab-grit (2.8.2)
+      charlock_holmes (~> 0.6)
+      diff-lcs (~> 1.1)
+      mime-types (>= 1.16)
+      posix-spawn (~> 0.3)
+    gitlab-markup (1.6.3)
+    gitlab-styles (2.3.2)
+      rubocop (~> 0.51)
+      rubocop-gitlab-security (~> 0.1.0)
+      rubocop-rspec (~> 1.19)
+    gitlab_omniauth-ldap (2.0.4)
+      net-ldap (~> 0.16)
+      omniauth (~> 1.3)
+      pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
+      rubyntlm (~> 0.5)
+    globalid (0.4.1)
+      activesupport (>= 4.2.0)
+    gollum-grit_adapter (1.0.1)
+      gitlab-grit (~> 2.7, >= 2.7.1)
+    gon (6.1.0)
+      actionpack (>= 3.0)
+      json
+      multi_json
+      request_store (>= 1.0)
+    google-api-client (0.19.8)
+      addressable (~> 2.5, >= 2.5.1)
+      googleauth (>= 0.5, < 0.7.0)
+      httpclient (>= 2.8.1, < 3.0)
+      mime-types (~> 3.0)
+      representable (~> 3.0)
+      retriable (>= 2.0, < 4.0)
+    google-protobuf (3.5.1)
+    googleapis-common-protos-types (1.0.1)
+      google-protobuf (~> 3.0)
+    googleauth (0.6.2)
+      faraday (~> 0.12)
+      jwt (>= 1.4, < 3.0)
+      logging (~> 2.0)
+      memoist (~> 0.12)
+      multi_json (~> 1.11)
+      os (~> 0.9)
+      signet (~> 0.7)
+    gpgme (2.0.16)
+      mini_portile2 (~> 2.3)
+    grape (1.0.2)
+      activesupport
+      builder
+      mustermann-grape (~> 1.0.0)
+      rack (>= 1.3.0)
+      rack-accept
+      virtus (>= 1.0.0)
+    grape-entity (0.6.1)
+      activesupport (>= 5.0.0)
+      multi_json (>= 1.3.2)
+    grape-route-helpers (2.1.0)
+      activesupport
+      grape (>= 0.16.0)
+      rake
+    grape_logging (1.7.0)
+      grape
+    grpc (1.10.0)
+      google-protobuf (~> 3.1)
+      googleapis-common-protos-types (~> 1.0.0)
+      googleauth (>= 0.5.1, < 0.7)
+    haml (4.0.7)
+      tilt
+    haml_lint (0.26.0)
+      haml (>= 4.0, < 5.1)
+      rainbow
+      rake (>= 10, < 13)
+      rubocop (>= 0.49.0)
+      sysexits (~> 1.1)
+    hamlit (2.6.2)
+      temple (~> 0.7.6)
+      thor
+      tilt
+    hashdiff (0.3.7)
+    hashie (3.5.7)
+    hashie-forbidden_attributes (0.1.1)
+      hashie (>= 3.0)
+    health_check (2.6.0)
+      rails (>= 4.0)
+    hipchat (1.5.4)
+      httparty
+      mimemagic
+    html-pipeline (2.7.1)
+      activesupport (>= 2)
+      nokogiri (>= 1.4)
+    html2text (0.2.1)
+      nokogiri (~> 1.6)
+    htmlentities (4.3.4)
+    http (2.2.2)
+      addressable (~> 2.3)
+      http-cookie (~> 1.0)
+      http-form_data (~> 1.0.1)
+      http_parser.rb (~> 0.6.0)
+    http-cookie (1.0.3)
+      domain_name (~> 0.5)
+    http-form_data (1.0.3)
+    http_parser.rb (0.6.0)
+    httparty (0.13.7)
+      json (~> 1.8)
+      multi_xml (>= 0.5.2)
+    httpclient (2.8.3)
+    i18n (0.9.5)
+      concurrent-ruby (~> 1.0)
+    ice_nine (0.11.2)
+    influxdb (0.5.3)
+    ipaddress (0.8.3)
+    jira-ruby (1.5.0)
+      activesupport
+      multipart-post
+      oauth (~> 0.5, >= 0.5.0)
+    jquery-atwho-rails (1.3.2)
+    json (1.8.6)
+    json-jwt (1.9.2)
+      activesupport
+      aes_key_wrap
+      bindata
+      securecompare
+      url_safe_base64
+    json-schema (2.8.0)
+      addressable (>= 2.4)
+    jwt (1.5.6)
+    kaminari (1.1.1)
+      activesupport (>= 4.1.0)
+      kaminari-actionview (= 1.1.1)
+      kaminari-activerecord (= 1.1.1)
+      kaminari-core (= 1.1.1)
+    kaminari-actionview (1.1.1)
+      actionview
+      kaminari-core (= 1.1.1)
+    kaminari-activerecord (1.1.1)
+      activerecord
+      kaminari-core (= 1.1.1)
+    kaminari-core (1.1.1)
+    kgio (2.11.2)
+    knapsack (1.16.0)
+      rake
+    kubeclient (3.0.0)
+      http (~> 2.2.2)
+      recursive-open-struct (~> 1.0.4)
+      rest-client (~> 2.0)
+    launchy (2.4.3)
+      addressable (~> 2.3)
+    letter_opener (1.6.0)
+      launchy (~> 2.2)
+    letter_opener_web (1.3.3)
+      actionmailer (>= 3.2)
+      letter_opener (~> 1.0)
+      railties (>= 3.2)
+    license_finder (3.1.1)
+      bundler
+      httparty
+      rubyzip
+      thor
+      toml (= 0.1.2)
+      with_env (> 1.0)
+      xml-simple
+    licensee (8.9.2)
+      rugged (~> 0.24)
+    little-plugger (1.1.4)
+    locale (2.1.2)
+    logging (2.2.2)
+      little-plugger (~> 1.1)
+      multi_json (~> 1.10)
+    lograge (0.9.0)
+      actionpack (>= 4)
+      activesupport (>= 4)
+      railties (>= 4)
+      request_store (~> 1.0)
+    loofah (2.2.2)
+      crass (~> 1.0.2)
+      nokogiri (>= 1.5.9)
+    mail (2.7.0)
+      mini_mime (>= 0.1.1)
+    mail_room (0.9.1)
+    memoist (0.16.0)
+    memoizable (0.4.2)
+      thread_safe (~> 0.3, >= 0.3.1)
+    method_source (0.9.0)
+    mime-types (3.1)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2016.0521)
+    mimemagic (0.3.2)
+    mini_mime (1.0.0)
+    mini_portile2 (2.3.0)
+    minitest (5.7.0)
+    mousetrap-rails (1.4.6)
+    multi_json (1.13.1)
+    multi_xml (0.6.0)
+    multipart-post (2.0.0)
+    mustermann (1.0.2)
+    mustermann-grape (1.0.0)
+      mustermann (~> 1.0.0)
+    mysql2 (0.4.10)
+    net-ldap (0.16.1)
+    net-ssh (4.2.0)
+    netrc (0.11.0)
+    nio4r (2.2.0)
+    nokogiri (1.8.2)
+      mini_portile2 (~> 2.3.0)
+    numerizer (0.1.1)
+    oauth (0.5.4)
+    oauth2 (1.4.0)
+      faraday (>= 0.8, < 0.13)
+      jwt (~> 1.0)
+      multi_json (~> 1.3)
+      multi_xml (~> 0.5)
+      rack (>= 1.2, < 3)
+    octokit (4.8.0)
+      sawyer (~> 0.8.0, >= 0.5.3)
+    omniauth (1.8.1)
+      hashie (>= 3.4.6, < 3.6.0)
+      rack (>= 1.6.2, < 3)
+    omniauth-auth0 (2.0.0)
+      omniauth-oauth2 (~> 1.4)
+    omniauth-authentiq (0.3.1)
+      omniauth-oauth2 (~> 1.3, >= 1.3.1)
+    omniauth-azure-oauth2 (0.0.9)
+      jwt (~> 1.0)
+      omniauth (~> 1.0)
+      omniauth-oauth2 (~> 1.4)
+    omniauth-cas3 (1.1.4)
+      addressable (~> 2.3)
+      nokogiri (~> 1.7, >= 1.7.1)
+      omniauth (~> 1.2)
+    omniauth-facebook (4.0.0)
+      omniauth-oauth2 (~> 1.2)
+    omniauth-github (1.1.2)
+      omniauth (~> 1.0)
+      omniauth-oauth2 (~> 1.1)
+    omniauth-gitlab (1.0.3)
+      omniauth (~> 1.0)
+      omniauth-oauth2 (~> 1.0)
+    omniauth-google-oauth2 (0.5.3)
+      jwt (>= 1.5)
+      omniauth (>= 1.1.1)
+      omniauth-oauth2 (>= 1.5)
+    omniauth-jwt (0.0.2)
+      jwt
+      omniauth (~> 1.1)
+    omniauth-kerberos (0.3.0)
+      omniauth-multipassword
+      timfel-krb5-auth (~> 0.8)
+    omniauth-multipassword (0.4.2)
+      omniauth (~> 1.0)
+    omniauth-oauth (1.1.0)
+      oauth
+      omniauth (~> 1.0)
+    omniauth-oauth2 (1.5.0)
+      oauth2 (~> 1.1)
+      omniauth (~> 1.2)
+    omniauth-oauth2-generic (0.2.4)
+      omniauth-oauth2 (~> 1.0)
+    omniauth-saml (1.10.0)
+      omniauth (~> 1.3, >= 1.3.2)
+      ruby-saml (~> 1.7)
+    omniauth-shibboleth (1.2.1)
+      omniauth (>= 1.0.0)
+    omniauth-twitter (1.4.0)
+      omniauth-oauth (~> 1.1)
+      rack
+    omniauth_crowd (2.2.3)
+      activesupport
+      nokogiri (>= 1.4.4)
+      omniauth (~> 1.0)
+    org-ruby (0.9.12)
+      rubypants (~> 0.2)
+    orm_adapter (0.5.0)
+    os (0.9.6)
+    parallel (1.12.1)
+    parser (2.5.0.5)
+      ast (~> 2.4.0)
+    parslet (1.5.0)
+      blankslate (~> 2.0)
+    path_expander (1.0.2)
+    peek (1.0.1)
+      concurrent-ruby (>= 0.9.0)
+      concurrent-ruby-ext (>= 0.9.0)
+      railties (>= 4.0.0)
+    peek-gc (0.0.2)
+      peek
+    peek-mysql2 (1.1.0)
+      atomic (>= 1.0.0)
+      mysql2
+      peek
+    peek-performance_bar (1.3.1)
+      peek (>= 0.1.0)
+    peek-pg (1.3.0)
+      concurrent-ruby
+      concurrent-ruby-ext
+      peek
+      pg
+    peek-rblineprof (0.2.0)
+      peek
+      rblineprof
+    peek-redis (1.2.0)
+      atomic (>= 1.0.0)
+      peek
+      redis
+    peek-sidekiq (1.0.3)
+      atomic (>= 1.0.0)
+      peek
+      sidekiq
+    pg (0.18.4)
+    po_to_json (1.0.1)
+      json (>= 1.6.0)
+    posix-spawn (0.3.13)
+    powerpack (0.1.1)
+    premailer (1.11.1)
+      addressable
+      css_parser (>= 1.6.0)
+      htmlentities (>= 4.0.0)
+    premailer-rails (1.9.7)
+      actionmailer (>= 3, < 6)
+      premailer (~> 1.7, >= 1.7.9)
+    proc_to_ast (0.1.0)
+      coderay
+      parser
+      unparser
+    procto (0.0.3)
+    prometheus-client-mmap (0.9.1)
+    pry (0.11.3)
+      coderay (~> 1.1.0)
+      method_source (~> 0.9.0)
+    pry-byebug (3.4.3)
+      byebug (>= 9.0, < 9.1)
+      pry (~> 0.10)
+    pry-rails (0.3.6)
+      pry (>= 0.10.4)
+    public_suffix (3.0.2)
+    pyu-ruby-sasl (0.0.3.3)
+    rack (2.0.4)
+    rack-accept (0.4.5)
+      rack (>= 0.4)
+    rack-attack (4.4.1)
+      rack
+    rack-cors (1.0.2)
+    rack-oauth2 (1.2.3)
+      activesupport (>= 2.3)
+      attr_required (>= 0.0.5)
+      httpclient (>= 2.4)
+      multi_json (>= 1.3.6)
+      rack (>= 1.1)
+    rack-protection (2.0.1)
+      rack
+    rack-proxy (0.6.4)
+      rack
+    rack-test (0.6.3)
+      rack (>= 1.0)
+    rails (5.0.6)
+      actioncable (= 5.0.6)
+      actionmailer (= 5.0.6)
+      actionpack (= 5.0.6)
+      actionview (= 5.0.6)
+      activejob (= 5.0.6)
+      activemodel (= 5.0.6)
+      activerecord (= 5.0.6)
+      activesupport (= 5.0.6)
+      bundler (>= 1.3.0)
+      railties (= 5.0.6)
+      sprockets-rails (>= 2.0.0)
+    rails-controller-testing (1.0.2)
+      actionpack (~> 5.x, >= 5.0.1)
+      actionview (~> 5.x, >= 5.0.1)
+      activesupport (~> 5.x)
+    rails-deprecated_sanitizer (1.0.3)
+      activesupport (>= 4.2.0.alpha)
+    rails-dom-testing (2.0.3)
+      activesupport (>= 4.2.0)
+      nokogiri (>= 1.6)
+    rails-html-sanitizer (1.0.3)
+      loofah (~> 2.0)
+    rails-i18n (5.1.1)
+      i18n (>= 0.7, < 2)
+      railties (>= 5.0, < 6)
+    railties (5.0.6)
+      actionpack (= 5.0.6)
+      activesupport (= 5.0.6)
+      method_source
+      rake (>= 0.8.7)
+      thor (>= 0.18.1, < 2.0)
+    rainbow (2.2.2)
+      rake
+    raindrops (0.19.0)
+    rake (12.3.0)
+    rb-fsevent (0.10.3)
+    rb-inotify (0.9.10)
+      ffi (>= 0.5.0, < 2)
+    rblineprof (0.3.7)
+      debugger-ruby_core_source (~> 1.3)
+    rbnacl (4.0.2)
+      ffi
+    rbnacl-libsodium (1.0.16)
+      rbnacl (>= 3.0.1)
+    rdoc (4.3.0)
+    re2 (1.1.1)
+    recaptcha (3.4.0)
+      json
+    recursive-open-struct (1.0.5)
+    redcarpet (3.4.0)
+    redis (3.3.5)
+    redis-actionpack (5.0.2)
+      actionpack (>= 4.0, < 6)
+      redis-rack (>= 1, < 3)
+      redis-store (>= 1.1.0, < 2)
+    redis-activesupport (5.0.4)
+      activesupport (>= 3, < 6)
+      redis-store (>= 1.3, < 2)
+    redis-namespace (1.5.3)
+      redis (~> 3.0, >= 3.0.4)
+    redis-rack (2.0.4)
+      rack (>= 1.5, < 3)
+      redis-store (>= 1.2, < 2)
+    redis-rails (5.0.2)
+      redis-actionpack (>= 5.0, < 6)
+      redis-activesupport (>= 5.0, < 6)
+      redis-store (>= 1.2, < 2)
+    redis-store (1.4.1)
+      redis (>= 2.2, < 5)
+    representable (3.0.4)
+      declarative (< 0.1.0)
+      declarative-option (< 0.2.0)
+      uber (< 0.2.0)
+    request_store (1.4.0)
+      rack (>= 1.4)
+    responders (2.4.0)
+      actionpack (>= 4.2.0, < 5.3)
+      railties (>= 4.2.0, < 5.3)
+    rest-client (2.0.2)
+      http-cookie (>= 1.0.2, < 2.0)
+      mime-types (>= 1.16, < 4.0)
+      netrc (~> 0.8)
+    retriable (3.1.1)
+    rinku (2.0.4)
+    rotp (2.1.2)
+    rouge (2.2.1)
+    rqrcode (0.10.1)
+      chunky_png (~> 1.0)
+    rqrcode-rails3 (0.1.7)
+      rqrcode (>= 0.4.2)
+    rspec (3.6.0)
+      rspec-core (~> 3.6.0)
+      rspec-expectations (~> 3.6.0)
+      rspec-mocks (~> 3.6.0)
+    rspec-core (3.6.0)
+      rspec-support (~> 3.6.0)
+    rspec-expectations (3.6.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.6.0)
+    rspec-mocks (3.6.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.6.0)
+    rspec-parameterized (0.4.0)
+      binding_of_caller
+      parser
+      proc_to_ast
+      rspec (>= 2.13, < 4)
+      unparser
+    rspec-rails (3.6.1)
+      actionpack (>= 3.0)
+      activesupport (>= 3.0)
+      railties (>= 3.0)
+      rspec-core (~> 3.6.0)
+      rspec-expectations (~> 3.6.0)
+      rspec-mocks (~> 3.6.0)
+      rspec-support (~> 3.6.0)
+    rspec-retry (0.4.6)
+      rspec-core
+    rspec-set (0.1.3)
+    rspec-support (3.6.0)
+    rspec_profiling (0.0.5)
+      activerecord
+      pg
+      rails
+      sqlite3
+    rubocop (0.52.1)
+      parallel (~> 1.10)
+      parser (>= 2.4.0.2, < 3.0)
+      powerpack (~> 0.1)
+      rainbow (>= 2.2.2, < 4.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (~> 1.0, >= 1.0.1)
+    rubocop-gitlab-security (0.1.1)
+      rubocop (>= 0.51)
+    rubocop-rspec (1.22.2)
+      rubocop (>= 0.52.1)
+    ruby-enum (0.7.2)
+      i18n
+    ruby-fogbugz (0.2.1)
+      crack (~> 0.4)
+    ruby-prof (0.17.0)
+    ruby-progressbar (1.9.0)
+    ruby-saml (1.7.2)
+      nokogiri (>= 1.5.10)
+    ruby_parser (3.11.0)
+      sexp_processor (~> 4.9)
+    rubyntlm (0.6.2)
+    rubypants (0.7.0)
+    rubyzip (1.2.1)
+    rufus-scheduler (3.4.2)
+      et-orbi (~> 1.0)
+    rugged (0.27.0)
+    safe_yaml (1.0.4)
+    sanitize (2.1.0)
+      nokogiri (>= 1.4.4)
+    sass (3.5.5)
+      sass-listen (~> 4.0.0)
+    sass-listen (4.0.0)
+      rb-fsevent (~> 0.9, >= 0.9.4)
+      rb-inotify (~> 0.9, >= 0.9.7)
+    sass-rails (5.0.7)
+      railties (>= 4.0.0, < 6)
+      sass (~> 3.1)
+      sprockets (>= 2.8, < 4.0)
+      sprockets-rails (>= 2.0, < 4.0)
+      tilt (>= 1.1, < 3)
+    sawyer (0.8.1)
+      addressable (>= 2.3.5, < 2.6)
+      faraday (~> 0.8, < 1.0)
+    scss_lint (0.56.0)
+      rake (>= 0.9, < 13)
+      sass (~> 3.5.3)
+    securecompare (1.0.0)
+    seed-fu (2.3.7)
+      activerecord (>= 3.1)
+      activesupport (>= 3.1)
+    select2-rails (3.5.10)
+      thor (~> 0.14)
+    selenium-webdriver (3.11.0)
+      childprocess (~> 0.5)
+      rubyzip (~> 1.2)
+    sentry-raven (2.7.2)
+      faraday (>= 0.7.6, < 1.0)
+    settingslogic (2.0.9)
+    sexp_processor (4.10.1)
+    sham_rack (1.3.6)
+      rack
+    shoulda-matchers (3.1.2)
+      activesupport (>= 4.0.0)
+    sidekiq (5.1.1)
+      concurrent-ruby (~> 1.0)
+      connection_pool (~> 2.2, >= 2.2.0)
+      rack-protection (>= 1.5.0)
+      redis (>= 3.3.5, < 5)
+    sidekiq-cron (0.6.3)
+      rufus-scheduler (>= 3.3.0)
+      sidekiq (>= 4.2.1)
+    sidekiq-limit_fetch (3.4.0)
+      sidekiq (>= 4)
+    signet (0.8.1)
+      addressable (~> 2.3)
+      faraday (~> 0.9)
+      jwt (>= 1.5, < 3.0)
+      multi_json (~> 1.10)
+    simple_po_parser (1.1.3)
+    simplecov (0.14.1)
+      docile (~> 1.1.0)
+      json (>= 1.8, < 3)
+      simplecov-html (~> 0.10.0)
+    simplecov-html (0.10.2)
+    slack-notifier (1.5.1)
+    spinach (0.8.10)
+      colorize
+      gherkin-ruby (>= 0.3.2)
+      json
+    spinach-rails (0.2.1)
+      capybara (>= 2.0.0)
+      railties (>= 3)
+      spinach (>= 0.4)
+    spinach-rerun-reporter (0.0.2)
+      spinach (~> 0.8)
+    spring (2.0.2)
+      activesupport (>= 4.2)
+    spring-commands-rspec (1.0.4)
+      spring (>= 0.9.1)
+    spring-commands-spinach (1.1.0)
+      spring (>= 0.9.1)
+    sprockets (3.7.1)
+      concurrent-ruby (~> 1.0)
+      rack (> 1, < 3)
+    sprockets-rails (3.2.1)
+      actionpack (>= 4.0)
+      activesupport (>= 4.0)
+      sprockets (>= 3.0.0)
+    sqlite3 (1.3.13)
+    sshkey (1.9.0)
+    stackprof (0.2.11)
+    state_machines (0.5.0)
+    state_machines-activemodel (0.5.1)
+      activemodel (>= 4.1, < 6.0)
+      state_machines (>= 0.5.0)
+    state_machines-activerecord (0.5.1)
+      activerecord (>= 4.1, < 6.0)
+      state_machines-activemodel (>= 0.5.0)
+    stringex (2.8.4)
+    sys-filesystem (1.1.9)
+      ffi
+    sysexits (1.2.0)
+    temple (0.7.7)
+    test-prof (0.2.5)
+    text (1.3.1)
+    thin (1.7.2)
+      daemons (~> 1.0, >= 1.0.9)
+      eventmachine (~> 1.0, >= 1.0.4)
+      rack (>= 1, < 3)
+    thor (0.19.4)
+    thread_safe (0.3.6)
+    tilt (2.0.8)
+    timecop (0.8.1)
+    timfel-krb5-auth (0.8.3)
+    toml (0.1.2)
+      parslet (~> 1.5.0)
+    toml-rb (1.0.0)
+      citrus (~> 3.0, > 3.0)
+    truncato (0.7.10)
+      htmlentities (~> 4.3.1)
+      nokogiri (~> 1.8.0, >= 1.7.0)
+    tzinfo (1.2.5)
+      thread_safe (~> 0.1)
+    u2f (0.2.1)
+    uber (0.1.0)
+    uglifier (2.7.2)
+      execjs (>= 0.3.0)
+      json (>= 1.8.0)
+    unf (0.1.4)
+      unf_ext
+    unf_ext (0.0.7.5)
+    unicode-display_width (1.3.0)
+    unicorn (5.1.0)
+      kgio (~> 2.6)
+      raindrops (~> 0.7)
+    unicorn-worker-killer (0.4.4)
+      get_process_mem (~> 0)
+      unicorn (>= 4, < 6)
+    uniform_notifier (1.10.0)
+    unparser (0.2.7)
+      abstract_type (~> 0.0.7)
+      adamantium (~> 0.2.0)
+      concord (~> 0.1.5)
+      diff-lcs (~> 1.3)
+      equalizer (~> 0.0.9)
+      parser (>= 2.3.1.2, < 2.6)
+      procto (~> 0.0.2)
+    url_safe_base64 (0.2.2)
+    validates_hostname (1.0.8)
+      activerecord (>= 3.0)
+      activesupport (>= 3.0)
+    version_sorter (2.1.0)
+    virtus (1.0.5)
+      axiom-types (~> 0.1)
+      coercible (~> 1.0)
+      descendants_tracker (~> 0.0, >= 0.0.3)
+      equalizer (~> 0.0, >= 0.0.9)
+    vmstat (2.3.0)
+    warden (1.2.7)
+      rack (>= 1.0)
+    webmock (2.3.2)
+      addressable (>= 2.3.6)
+      crack (>= 0.3.2)
+      hashdiff
+    webpack-rails (0.9.11)
+      railties (>= 3.2.0)
+    websocket-driver (0.6.5)
+      websocket-extensions (>= 0.1.0)
+    websocket-extensions (0.1.3)
+    wikicloth (0.8.1)
+      builder
+      expression_parser
+      rinku
+    with_env (1.1.0)
+    xml-simple (1.1.5)
+    xpath (3.0.0)
+      nokogiri (~> 1.8)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  RedCloth (~> 4.3.2)
+  ace-rails-ap (~> 4.1.0)
+  activerecord_sane_schema_dumper (= 1.0)
+  acts-as-taggable-on (~> 5.0)
+  addressable (~> 2.5.2)
+  akismet (~> 2.0)
+  allocations (~> 1.0)
+  asana (~> 0.6.0)
+  asciidoctor (~> 1.5.6)
+  asciidoctor-plantuml (= 0.0.8)
+  asset_sync (~> 2.2.0)
+  attr_encrypted (~> 3.1.0)
+  awesome_print (~> 1.2.0)
+  babosa (~> 1.0.2)
+  base32 (~> 0.3.0)
+  batch-loader (~> 1.2.1)
+  bcrypt_pbkdf (~> 1.0)
+  benchmark-ips (~> 2.3.0)
+  better_errors (~> 2.1.0)
+  binding_of_caller (~> 0.7.2)
+  bootstrap-sass (~> 3.3.0)
+  bootstrap_form (~> 2.7.0)
+  brakeman (~> 4.2)
+  browser (~> 2.2)
+  bullet (~> 5.5.0)
+  bundler-audit (~> 0.5.0)
+  capybara (~> 2.15)
+  capybara-screenshot (~> 1.0.0)
+  carrierwave (~> 1.2)
+  charlock_holmes (~> 0.7.5)
+  chronic (~> 0.10.2)
+  chronic_duration (~> 0.10.6)
+  commonmarker (~> 0.17)
+  concurrent-ruby (~> 1.0.5)
+  connection_pool (~> 2.0)
+  creole (~> 0.5.0)
+  d3_rails (~> 3.5.0)
+  database_cleaner (~> 1.5.0)
+  deckar01-task_list (= 2.0.0)
+  default_value_for (~> 3.0.5)
+  devise (~> 4.2)
+  devise-two-factor (~> 3.0.0)
+  diffy (~> 3.1.0)
+  doorkeeper (~> 4.3)
+  doorkeeper-openid_connect (~> 1.3)
+  dropzonejs-rails (~> 0.7.1)
+  email_reply_trimmer (~> 0.1)
+  email_spec (~> 1.6.0)
+  factory_bot_rails (~> 4.8.2)
+  faraday (~> 0.12)
+  fast_blank
+  ffaker (~> 2.4)
+  flay (~> 2.10.0)
+  flipper (~> 0.13.0)
+  flipper-active_record (~> 0.13.0)
+  flipper-active_support_cache_store (~> 0.13.0)
+  fog-aliyun (~> 0.2.0)
+  fog-aws (~> 2.0.1)
+  fog-core (~> 1.44)
+  fog-google (~> 1.3.3)
+  fog-local (~> 0.3)
+  fog-openstack (~> 0.1)
+  fog-rackspace (~> 0.1.1)
+  font-awesome-rails (~> 4.7)
+  foreman (~> 0.84.0)
+  fuubar (~> 2.2.0)
+  gemnasium-gitlab-service (~> 0.2)
+  gemojione (~> 3.3)
+  gettext (~> 3.2.2)
+  gettext_i18n_rails (~> 1.8.0)
+  gettext_i18n_rails_js (~> 1.3)
+  gitaly-proto (~> 0.97.0)
+  github-linguist (~> 5.3.3)
+  gitlab-flowdock-git-hook (~> 1.0.1)
+  gitlab-gollum-lib (~> 4.2)
+  gitlab-gollum-rugged_adapter (~> 0.4.4)
+  gitlab-markup (~> 1.6.2)
+  gitlab-styles (~> 2.3)
+  gitlab_omniauth-ldap (~> 2.0.4)
+  gon (~> 6.1.0)
+  google-api-client (~> 0.19.8)
+  google-protobuf (= 3.5.1)
+  gpgme
+  grape (~> 1.0)
+  grape-entity (~> 0.6.0)
+  grape-route-helpers (~> 2.1.0)
+  grape_logging (~> 1.7)
+  grpc (~> 1.10.0)
+  haml_lint (~> 0.26.0)
+  hamlit (~> 2.6.1)
+  hashie-forbidden_attributes
+  health_check (~> 2.6.0)
+  hipchat (~> 1.5.0)
+  html-pipeline (~> 2.7.1)
+  html2text
+  httparty (~> 0.13.3)
+  influxdb (~> 0.2)
+  jira-ruby (~> 1.4)
+  jquery-atwho-rails (~> 1.3.2)
+  json-schema (~> 2.8.0)
+  jwt (~> 1.5.6)
+  kaminari (~> 1.0)
+  knapsack (~> 1.16)
+  kubeclient (~> 3.0)
+  letter_opener_web (~> 1.3.0)
+  license_finder (~> 3.1)
+  licensee (~> 8.9)
+  lograge (~> 0.5)
+  loofah (~> 2.2)
+  mail_room (~> 0.9.1)
+  method_source (~> 0.8)
+  minitest (~> 5.7.0)
+  mousetrap-rails (~> 1.4.6)
+  mysql2 (~> 0.4.10)
+  net-ldap
+  net-ssh (~> 4.2.0)
+  nokogiri (~> 1.8.2)
+  oauth2 (~> 1.4)
+  octokit (~> 4.8)
+  omniauth (~> 1.8)
+  omniauth-auth0 (~> 2.0.0)
+  omniauth-authentiq (~> 0.3.1)
+  omniauth-azure-oauth2 (~> 0.0.9)
+  omniauth-cas3 (~> 1.1.4)
+  omniauth-facebook (~> 4.0.0)
+  omniauth-github (~> 1.1.1)
+  omniauth-gitlab (~> 1.0.2)
+  omniauth-google-oauth2 (~> 0.5.3)
+  omniauth-jwt (~> 0.0.2)
+  omniauth-kerberos (~> 0.3.0)
+  omniauth-oauth2-generic (~> 0.2.2)
+  omniauth-saml (~> 1.10)
+  omniauth-shibboleth (~> 1.2.0)
+  omniauth-twitter (~> 1.4)
+  omniauth_crowd (~> 2.2.0)
+  org-ruby (~> 0.9.12)
+  peek (~> 1.0.1)
+  peek-gc (~> 0.0.2)
+  peek-mysql2 (~> 1.1.0)
+  peek-performance_bar (~> 1.3.0)
+  peek-pg (~> 1.3.0)
+  peek-rblineprof (~> 0.2.0)
+  peek-redis (~> 1.2.0)
+  peek-sidekiq (~> 1.0.3)
+  pg (~> 0.18.2)
+  premailer-rails (~> 1.9.7)
+  prometheus-client-mmap (~> 0.9.1)
+  pry-byebug (~> 3.4.1)
+  pry-rails (~> 0.3.4)
+  rack-attack (~> 4.4.1)
+  rack-cors (~> 1.0.0)
+  rack-oauth2 (~> 1.2.1)
+  rack-proxy (~> 0.6.0)
+  rails (= 5.0.6)
+  rails-controller-testing
+  rails-deprecated_sanitizer (~> 1.0.3)
+  rails-i18n (~> 5.1)
+  rainbow (~> 2.2)
+  raindrops (~> 0.18)
+  rblineprof (~> 0.3.6)
+  rbnacl (~> 4.0)
+  rbnacl-libsodium
+  rdoc (~> 4.2)
+  re2 (~> 1.1.1)
+  recaptcha (~> 3.0)
+  redcarpet (~> 3.4)
+  redis (~> 3.2)
+  redis-namespace (~> 1.5.2)
+  redis-rails (~> 5.0.2)
+  request_store (~> 1.3)
+  responders (~> 2.0)
+  rouge (~> 2.0)
+  rqrcode-rails3 (~> 0.1.7)
+  rspec-parameterized
+  rspec-rails (~> 3.6.0)
+  rspec-retry (~> 0.4.5)
+  rspec-set (~> 0.1.3)
+  rspec_profiling (~> 0.0.5)
+  rubocop (~> 0.52.1)
+  rubocop-rspec (~> 1.22.1)
+  ruby-fogbugz (~> 0.2.1)
+  ruby-prof (~> 0.17.0)
+  ruby_parser (~> 3.8)
+  rufus-scheduler (~> 3.4)
+  rugged (~> 0.27)
+  sanitize (~> 2.0)
+  sass-rails (~> 5.0.6)
+  scss_lint (~> 0.56.0)
+  seed-fu (~> 2.3.7)
+  select2-rails (~> 3.5.9)
+  selenium-webdriver (~> 3.5)
+  sentry-raven (~> 2.7)
+  settingslogic (~> 2.0.9)
+  sham_rack (~> 1.3.6)
+  shoulda-matchers (~> 3.1.2)
+  sidekiq (~> 5.0)
+  sidekiq-cron (~> 0.6.0)
+  sidekiq-limit_fetch (~> 3.4)
+  simple_po_parser (~> 1.1.2)
+  simplecov (~> 0.14.0)
+  slack-notifier (~> 1.5.1)
+  spinach-rails (~> 0.2.1)
+  spinach-rerun-reporter (~> 0.0.2)
+  spring (~> 2.0.0)
+  spring-commands-rspec (~> 1.0.4)
+  spring-commands-spinach (~> 1.1.0)
+  sprockets (~> 3.7.0)
+  sshkey (~> 1.9.0)
+  stackprof (~> 0.2.10)
+  state_machines-activerecord (~> 0.5.1)
+  sys-filesystem (~> 1.1.6)
+  test-prof (~> 0.2.5)
+  thin (~> 1.7.0)
+  timecop (~> 0.8.0)
+  toml-rb (~> 1.0.0)
+  truncato (~> 0.7.9)
+  u2f (~> 0.2.1)
+  uglifier (~> 2.7.2)
+  unf (~> 0.1.4)
+  unicorn (~> 5.1.0)
+  unicorn-worker-killer (~> 0.4.4)
+  validates_hostname (~> 1.0.6)
+  version_sorter (~> 2.1.0)
+  virtus (~> 1.0.1)
+  vmstat (~> 2.3.0)
+  webmock (~> 2.3.2)
+  webpack-rails (~> 0.9.10)
+  wikicloth (= 0.8.1)
+
+BUNDLED WITH
+   1.16.1
diff --git a/LICENSE b/LICENSE
index 15c423e14167628ee746a39b7e047cb458b29b8e..a76372fad2cf52132f679d5ef0f8c7a5a5768f41 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,25 +1,7 @@
-Copyright (c) 2011-2017 GitLab B.V.
+Copyright GitLab B.V.
 
-With regard to the GitLab Software:
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-For all third party components incorporated into the GitLab Software, those 
-components are licensed under the original license provided by the owner of the 
-applicable component.
\ No newline at end of file
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/PROCESS.md b/PROCESS.md
index c24210341e0be44b9f6c5b65573c2c308eb65be7..f206506f7c5c8a3fb6ec4e0517eeee344cdcd68f 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -53,7 +53,7 @@ Below we describe the contributing process to GitLab for two reasons:
 Several people from the [GitLab team][team] are helping community members to get
 their contributions accepted by meeting our [Definition of done][done].
 
-What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
+What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/.
 
 ## Assigning issues
 
@@ -71,7 +71,7 @@ star, smile, etc.). Some good tips about code reviews can be found in our
 
 ## Feature freeze on the 7th for the release on the 22nd
 
-After 7th at 23:59 (Pacific Standard Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
+After 7th at 23:59 (Pacific Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
 Merge requests may still be merged into master during this period,
 but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch.
 
@@ -202,6 +202,9 @@ you can ask for an exception to be made.
 Go to [Release tasks issue tracker](https://gitlab.com/gitlab-org/release/tasks/issues/new) and create an issue
 using the `Exception-request` issue template.
 
+**Do not** set the relevant `Pick into X.Y` label (see above) before request an
+exception; this should be done after the exception is approved.
+
 You can find who is who on the [team page](https://about.gitlab.com/team/).
 
 Whether an exception is made is determined by weighing the benefit and urgency of the change
diff --git a/Procfile b/Procfile
index cad738d4292169d2eed99e2e598c56f7125bb7d3..1776fd97942ffe9fc704d98d4823077c62f332b3 100644
--- a/Procfile
+++ b/Procfile
@@ -4,4 +4,3 @@
 #
 web: RAILS_ENV=development bin/web start_foreground
 worker: RAILS_ENV=development bin/background_jobs start_foreground
-# mail_room: bundle exec mail_room -q -c config/mail_room.yml
diff --git a/README.md b/README.md
index 9ead6d51c5df950d9a076d924fd27670d3fa3886..9c1aad653077e659e13e7b72a7d453365aa39030 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,12 @@ You can access a new installation with the login **`root`** and password **`5ive
 
 GitLab is an open source project and we are very happy to accept community contributions. Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
 
+## Licensing
+
+GitLab Community Edition (CE) is available freely under the MIT Expat license.
+
+All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component.
+
 ## Install a development environment
 
 To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
diff --git a/VERSION b/VERSION
index f860e97e4cf5eaf39768d26c5c34bc783ed77ced..7a86eda5728f6f79464925cdc456f688bbf9950a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-10.6.0-pre
+10.7.0-pre
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_canceled.ico b/app/assets/images/ci_favicons/canary/favicon_status_canceled.ico
new file mode 100644
index 0000000000000000000000000000000000000000..48b1095370d446df1f3107565b1c5e72c0128c7d
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_canceled.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_created.ico b/app/assets/images/ci_favicons/canary/favicon_status_created.ico
new file mode 100644
index 0000000000000000000000000000000000000000..623c728faf6981b0b21af20b6b351ddd277fffb7
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_created.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_failed.ico b/app/assets/images/ci_favicons/canary/favicon_status_failed.ico
new file mode 100644
index 0000000000000000000000000000000000000000..3073fe5a761ba1825bfbab4337a0eacf36b848c9
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_failed.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_manual.ico b/app/assets/images/ci_favicons/canary/favicon_status_manual.ico
new file mode 100644
index 0000000000000000000000000000000000000000..6c713d7b67557ea33f2b8cfccd51f3343c29fdbb
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_manual.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_not_found.ico b/app/assets/images/ci_favicons/canary/favicon_status_not_found.ico
new file mode 100644
index 0000000000000000000000000000000000000000..dbf855fdafd3b9465e3df615223739b2c03c1abf
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_not_found.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_pending.ico b/app/assets/images/ci_favicons/canary/favicon_status_pending.ico
new file mode 100644
index 0000000000000000000000000000000000000000..ccd00606aeb39546a35b0fc902765d0a085f394d
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_pending.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_running.ico b/app/assets/images/ci_favicons/canary/favicon_status_running.ico
new file mode 100644
index 0000000000000000000000000000000000000000..968e7c4c2d4a95852ef4215b88ea9a2c030e8c28
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_running.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_skipped.ico b/app/assets/images/ci_favicons/canary/favicon_status_skipped.ico
new file mode 100644
index 0000000000000000000000000000000000000000..7e3be35cc3a5f869c574290f58ca1bbc6f4095ef
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_skipped.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_success.ico b/app/assets/images/ci_favicons/canary/favicon_status_success.ico
new file mode 100644
index 0000000000000000000000000000000000000000..a1fb6e91d653f60418ab8d1c1925b7f99aa69c2f
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_success.ico differ
diff --git a/app/assets/images/ci_favicons/canary/favicon_status_warning.ico b/app/assets/images/ci_favicons/canary/favicon_status_warning.ico
new file mode 100644
index 0000000000000000000000000000000000000000..5d931619fb29fc424d6506565e160709e12e1e8a
Binary files /dev/null and b/app/assets/images/ci_favicons/canary/favicon_status_warning.ico differ
diff --git a/app/assets/images/ext_snippet_icons/ext_snippet_icons.png b/app/assets/images/ext_snippet_icons/ext_snippet_icons.png
new file mode 100644
index 0000000000000000000000000000000000000000..20380adc4e52eed8376e2e6371e646b0f76b8c23
Binary files /dev/null and b/app/assets/images/ext_snippet_icons/ext_snippet_icons.png differ
diff --git a/app/assets/images/ext_snippet_icons/logo.png b/app/assets/images/ext_snippet_icons/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..794c9cc2dbc9e3416c10455a10baabf94ae14fe0
Binary files /dev/null and b/app/assets/images/ext_snippet_icons/logo.png differ
diff --git a/app/assets/images/favicon-yellow.ico b/app/assets/images/favicon-yellow.ico
new file mode 100644
index 0000000000000000000000000000000000000000..b650f277fb66d6193e27e1f2bfe59f751159270b
Binary files /dev/null and b/app/assets/images/favicon-yellow.ico differ
diff --git a/app/assets/images/icons.json b/app/assets/images/icons.json
deleted file mode 100644
index 19843d24e22abeb7df5d6a2c0b8ba201f8fe7da8..0000000000000000000000000000000000000000
--- a/app/assets/images/icons.json
+++ /dev/null
@@ -1 +0,0 @@
-{"iconCount":191,"spriteSize":86607,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","soft-unwrap","soft-wrap","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}
\ No newline at end of file
diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg
deleted file mode 100644
index 6aec54d05431e50a9a44025ce6e8269bc05b68a8..0000000000000000000000000000000000000000
--- a/app/assets/images/icons.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 16 16" id="abuse" xmlns="http://www.w3.org/2000/svg"><path d="M11.408.328l4.029 3.222A1.5 1.5 0 0 1 16 4.72v6.555a1.5 1.5 0 0 1-.563 1.171l-4.026 3.224a1.5 1.5 0 0 1-.937.329H5.529a1.5 1.5 0 0 1-.937-.328L.563 12.45A1.5 1.5 0 0 1 0 11.28V4.724a1.5 1.5 0 0 1 .563-1.171L4.589.329A1.5 1.5 0 0 1 5.526 0h4.945c.34 0 .67.116.937.328zM10.296 2H5.702L2 4.964v6.074L5.704 14h4.594L14 11.036V4.962L10.296 2zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="account" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.195 9.965l-.568-.875a.25.25 0 0 1 .015-.294l.405-.5a.25.25 0 0 1 .283-.075l.938.36c.257-.183.543-.325.851-.42l.322-.988A.25.25 0 0 1 11.679 7h.642a.25.25 0 0 1 .238.173l.322.988c.308.095.594.237.851.42l.938-.36a.25.25 0 0 1 .283.076l.405.5a.25.25 0 0 1 .015.293l-.568.875c.113.297.18.616.193.95l.898.54a.25.25 0 0 1 .115.27l-.144.626a.25.25 0 0 1-.222.193l-1.115.098a3.015 3.015 0 0 1-.512.608l.165 1.18a.25.25 0 0 1-.138.259l-.577.281a.25.25 0 0 1-.29-.05l-.874-.905a3.035 3.035 0 0 1-.608 0l-.875.904a.25.25 0 0 1-.289.051l-.577-.281a.25.25 0 0 1-.138-.26l.165-1.18a3.015 3.015 0 0 1-.512-.607l-1.115-.098a.25.25 0 0 1-.222-.193l-.144-.626a.25.25 0 0 1 .115-.27l.898-.54c.013-.334.08-.653.193-.95zM6.789 8.023A12.845 12.845 0 0 0 6 8c-5.036 0-6 2.74-6 4.48C0 14.22.076 15 6 15c.553 0 1.055-.006 1.51-.02A5.977 5.977 0 0 1 6 11c0-1.083.287-2.1.79-2.977zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM12 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="admin" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.162 2.5a3.5 3.5 0 0 1-3.163 5.479L6.08 14.766a1.5 1.5 0 0 1-2.598-1.5L7.4 6.479A3.5 3.5 0 0 1 10.564 1L8.9 3.88l2.599 1.5 1.663-2.88zm-8.63 11.949a.5.5 0 1 0 .5-.866.5.5 0 0 0-.5.866z"/></symbol><symbol viewBox="0 0 16 16" id="angle-double-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.414 7.95l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 1 0 1.414-1.415L10.414 7.95zm-7 0l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 0 0 1.414-1.415L3.414 7.95z"/></symbol><symbol viewBox="0 0 16 16" id="angle-double-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.536 7.95L1.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 1 1-1.414-1.415L5.536 7.95zm7 0L8.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.414-1.415l4.243-4.242z"/></symbol><symbol viewBox="0 0 16 16" id="angle-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 10.243l-4.95-4.95a1 1 0 0 0-1.414 1.414l5.657 5.657a.997.997 0 0 0 1.414 0l5.657-5.657a1 1 0 0 0-1.414-1.414L8 10.243z"/></symbol><symbol viewBox="0 0 16 16" id="angle-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.757 8l4.95-4.95a1 1 0 1 0-1.414-1.414L3.636 7.293a.997.997 0 0 0 0 1.414l5.657 5.657a1 1 0 0 0 1.414-1.414L5.757 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.243 8l-4.95-4.95a1 1 0 0 1 1.414-1.414l5.657 5.657a.997.997 0 0 1 0 1.414l-5.657 5.657a1 1 0 0 1-1.414-1.414L10.243 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 6.757l-4.95 4.95a1 1 0 1 1-1.414-1.414l5.657-5.657a.997.997 0 0 1 1.414 0l5.657 5.657a1 1 0 0 1-1.414 1.414L8 6.757z"/></symbol><symbol viewBox="0 0 16 16" id="appearance" xmlns="http://www.w3.org/2000/svg"><path d="M11.161 12.456l.232.121c.1.053.175.094.249.137.53.318.844.75.857 1.402.012 1.397-1.116 1.756-3.12 1.858a23.85 23.85 0 0 1-1.38.026A8 8 0 0 1 0 8a8 8 0 0 1 8-8c4.417 0 7.998 3.582 7.998 7.977.06 2.621-1.312 3.586-4.48 3.648-.602.008-1.068.043-1.4.104.228.192.598.47 1.043.727zm-3.287-.943c-.019-1.495 1.228-1.856 3.611-1.888C13.67 9.582 14.028 9.33 13.998 8A6 6 0 1 0 8 14c.603 0 .91-.004 1.277-.023a9.7 9.7 0 0 0 .478-.035c-1.172-.738-1.868-1.47-1.88-2.43zM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-2-3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM4 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="applications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 1v2h2V1H7zm0 5h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm0 1v2h2V7h-2zM1 12h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm0 1v2h2v-2H1zm6-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm6 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="approval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.536 10.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 1 1 9.12 9.243l1.415 1.414zM7.632 8.109A2 2 0 0 0 7 11.364l2.121 2.121a1.996 1.996 0 0 0 2.807.021C11.686 14.554 10.627 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.6 0 1.142.038 1.632.109zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="arrow-down" xmlns="http://www.w3.org/2000/svg"><path d="M10.472 7.282a.862.862 0 0 1 1.26-.006c.357.364.357.958 0 1.285L8.627 11.73A.886.886 0 0 1 8 12a.849.849 0 0 1-.627-.27L4.275 8.561a.904.904 0 0 1-.013-1.285.861.861 0 0 1 1.26-.007l2.486 2.527z"/></symbol><symbol viewBox="0 0 16 16" id="arrow-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 6H2a2 2 0 1 0 0 4h7v2.586a1 1 0 0 0 1.707.707l4.586-4.586a1 1 0 0 0 0-1.414l-4.586-4.586A1 1 0 0 0 9 3.414V6z"/></symbol><symbol viewBox="0 0 16 16" id="assignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 5V4a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V7h-1a1 1 0 0 1 0-2h1zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="bold" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4 12.5v-9A1.5 1.5 0 0 1 5.5 2h2.104c2.182 0 3.879.681 3.879 2.982 0 1.067-.517 2.227-1.374 2.595v.073C11.176 7.963 12 8.865 12 10.466 12 12.914 10.19 14 7.911 14H5.5A1.5 1.5 0 0 1 4 12.5zm2.376-5.696H7.49c1.164 0 1.665-.552 1.665-1.417 0-.94-.534-1.289-1.649-1.289h-1.13v2.706zm0 5.098h1.341c1.293 0 1.956-.515 1.956-1.62 0-1.049-.647-1.472-1.956-1.472H6.376v3.092z"/></symbol><symbol viewBox="0 0 16 16" id="book" xmlns="http://www.w3.org/2000/svg"><path d="M7 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2v4.191a.5.5 0 0 1-.724.447l-1.052-.526a.5.5 0 0 0-.448 0l-1.052.526A.5.5 0 0 1 7 6.191V2zM5 0h6a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="bookmark" xmlns="http://www.w3.org/2000/svg"><path d="M6.746 10.505a2 2 0 0 1 2.508 0L11 11.911V3H5v8.91l1.746-1.405zM5 1h6a2 2 0 0 1 2 2v10.999a1 1 0 0 1-1.627.779L8 12.064l-3.373 2.714A1 1 0 0 1 3 13.998V3a2 2 0 0 1 2-2z"/></symbol><symbol viewBox="0 0 16 16" id="branch" xmlns="http://www.w3.org/2000/svg"><path d="M6 11.978v.29a2 2 0 1 1-2 0V3.732a2 2 0 1 1 2 0v3.849c.592-.491 1.31-.854 2.15-1.081 1.308-.353 1.875-.882 1.893-1.743a2 2 0 1 1 2.002-.051C12.053 6.54 10.857 7.84 8.67 8.43 7.056 8.867 6.195 9.98 6 11.978zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm6 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 15a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="bullhorn" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.143 10H7V4H3a3 3 0 1 0 0 6h.143l.734 5.141a1 1 0 0 0 .99.859h1.556a.5.5 0 0 0 .495-.57L6.143 10zM8 4c1.034.02 2.039-.274 3.014-.883.727-.455 1.836-1.334 3.328-2.637A1 1 0 0 1 16 1.233v10.764a1 1 0 0 1-1.595.803c-1.658-1.227-2.788-1.992-3.392-2.294-.781-.39-1.785-.559-3.013-.506V4z"/></symbol><symbol viewBox="0 0 16 16" id="calendar" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 2h2a2 2 0 0 1 2 2H0a2 2 0 0 1 2-2h2V1a1 1 0 1 1 2 0v1h4V1a1 1 0 1 1 2 0v1zM0 4h16v9a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4zm2 2.5V13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6.5a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5zM5 8h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="cancel" xmlns="http://www.w3.org/2000/svg"><path d="M3.11 4.523a6 6 0 0 0 8.367 8.367L3.109 4.524zM4.522 3.11l8.368 8.368A6 6 0 0 0 4.524 3.11zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 16 16" id="chart" xmlns="http://www.w3.org/2000/svg"><path d="M15 14a1 1 0 0 1 0 2H2a2 2 0 0 1-2-2V1a1 1 0 1 1 2 0v13h13zM3.142 8.735l2.502-2.561a.5.5 0 0 1 .714-.003L8 7.833l3.592-4.553a.5.5 0 0 1 .796.015l2.516 3.454a.5.5 0 0 1 .096.295V12.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V9.085a.5.5 0 0 1 .142-.35z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.078 8.2l3.535-3.536a2 2 0 0 1 2.828 2.828l-4.949 4.95c-.39.39-.902.586-1.414.586a1.994 1.994 0 0 1-1.414-.586l-4.95-4.95a2 2 0 1 1 2.828-2.828l3.536 3.535z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.977 7.998l3.535-3.535a2 2 0 1 0-2.828-2.828l-4.95 4.949c-.39.39-.586.902-.586 1.414 0 .512.196 1.024.586 1.414l4.95 4.95a2 2 0 1 0 2.828-2.828L7.977 7.998z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.22 7.998L4.683 4.463a2 2 0 0 1 2.828-2.828l4.95 4.949c.39.39.586.902.586 1.414a1.99 1.99 0 0 1-.586 1.414l-4.95 4.95a2 2 0 0 1-2.828-2.828l3.535-3.536z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.778 8.957l3.535 3.535a2 2 0 1 0 2.828-2.828l-4.949-4.95a1.994 1.994 0 0 0-1.414-.586c-.512 0-1.024.196-1.414.586l-4.95 4.95a2 2 0 1 0 2.828 2.828l3.536-3.535z"/></symbol><symbol viewBox="0 0 16 16" id="clock" xmlns="http://www.w3.org/2000/svg"><path d="M9 7h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V5a1 1 0 1 1 2 0v2zm-1 9A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.414 8l4.95-4.95a1 1 0 0 0-1.414-1.414L8 6.586l-4.95-4.95A1 1 0 0 0 1.636 3.05L6.586 8l-4.95 4.95a1 1 0 1 0 1.414 1.414L8 9.414l4.95 4.95a1 1 0 1 0 1.414-1.414L9.414 8z"/></symbol><symbol viewBox="0 0 16 16" id="code" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M15.871 8.243a.997.997 0 0 0-.293-.707L12.75 4.707a1 1 0 0 0-1.414 1.414l2.12 2.122-2.12 2.121a1 1 0 0 0 1.414 1.414l2.828-2.828a.997.997 0 0 0 .293-.707zm-13.243 0L4.75 6.12a1 1 0 1 0-1.414-1.414L.507 7.536a.997.997 0 0 0 0 1.414l2.829 2.828a1 1 0 1 0 1.414-1.414L2.628 8.243zm6.407-4.107a1 1 0 0 1 .707 1.225L8.19 11.157a1 1 0 1 1-1.931-.518L7.81 4.843a1 1 0 0 1 1.224-.707z"/></symbol><symbol viewBox="0 0 9 13" id="collapse"><path d="M.084.25C.01.18-.015.12.008.071.031.024.093 0 .194 0h8.521c.1 0 .162.024.185.072.023.048-.002.107-.075.177l-4.11 3.935a.372.372 0 0 1-.11.072h-.301a.508.508 0 0 1-.11-.072L.084.249zM.377 6.88a.364.364 0 0 1-.26-.105.334.334 0 0 1-.11-.25v-.709c0-.096.036-.179.11-.249a.364.364 0 0 1 .26-.105h8.15c.101 0 .188.035.261.105.074.07.11.153.11.25v.709c0 .096-.036.179-.11.249a.364.364 0 0 1-.26.105H.377zM.084 12.132c-.074.07-.099.129-.076.177.023.048.085.072.186.072h8.521c.1 0 .162-.024.185-.072.023-.048-.002-.107-.075-.177l-4.11-3.935a.372.372 0 0 0-.11-.072h-.301a.508.508 0 0 0-.11.072l-4.11 3.935z"/></symbol><symbol viewBox="0 0 16 16" id="comment" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comment-dots" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586zM5 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="comment-next" xmlns="http://www.w3.org/2000/svg"><path d="M8 5V4a.5.5 0 0 1 .8-.4l2.667 2a.5.5 0 0 1 0 .8L8.8 8.4A.5.5 0 0 1 8 8V7H6a1 1 0 1 1 0-2h2zM1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comments" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.75 10L0 13V3a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H3.75zM13 5h1a2 2 0 0 1 2 2v8l-2.667-2H8a2 2 0 0 1-2-2h4a3 3 0 0 0 3-3V5z"/></symbol><symbol viewBox="0 0 16 16" id="commit" xmlns="http://www.w3.org/2000/svg"><path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3.876-1.008a4.002 4.002 0 0 1-7.752 0A1.01 1.01 0 0 1 4 9H1a1 1 0 1 1 0-2h3c.042 0 .083.003.124.008a4.002 4.002 0 0 1 7.752 0A1.01 1.01 0 0 1 12 7h3a1 1 0 0 1 0 2h-3a1.01 1.01 0 0 1-.124-.008z"/></symbol><symbol viewBox="0 0 16 16" id="credit-card" xmlns="http://www.w3.org/2000/svg"><path d="M14 5a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1h12zm0 3H2v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm6.5 8h3a.5.5 0 1 1 0 1h-3a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="cut" xmlns="http://www.w3.org/2000/svg"><rect width="16" height="2" y="7" fill-rule="evenodd" rx="1"/></symbol><symbol viewBox="0 0 16 16" id="dashboard" xmlns="http://www.w3.org/2000/svg"><path d="M7.709 10.021l.696-2.6a.5.5 0 0 1 .966.26l-.657 2.45A2 2 0 0 1 10 12H6a2 2 0 0 1 1.709-1.979zM0 8.9a8 8 0 0 1 15.998 0H16v3.6a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5V8.9zM14 9A6 6 0 1 0 2 9v3.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V9zM3.5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm9 0a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-7-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm5 0a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1z"/></symbol><symbol viewBox="0 0 16 16" id="disk" xmlns="http://www.w3.org/2000/svg"><path d="M16 11.764V3a3 3 0 0 0-3-3H3a3 3 0 0 0-3 3v8.764A2.989 2.989 0 0 1 2 11V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v8c.768 0 1.47.289 2 .764zM2 12h12a2 2 0 1 1 0 4H2a2 2 0 1 1 0-4zm10 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="doc_code" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zm1.036 7.607a.498.498 0 0 1-.147.354l-1.414 1.414a.5.5 0 0 1-.707-.707l1.06-1.06-1.06-1.061a.5.5 0 0 1 .707-.707l1.414 1.414a.498.498 0 0 1 .147.353zm-4.822 0l1.06 1.061a.5.5 0 0 1-.706.707l-1.414-1.414a.498.498 0 0 1 0-.707l1.414-1.414a.5.5 0 1 1 .707.707l-1.06 1.06zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="doc_image" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM7.333 9.667l1.313-1.313a.5.5 0 0 1 .708 0L12 11H4l2.188-1.75a.5.5 0 0 1 .624 0l.521.417zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 8a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM4 11h8v.7a.3.3 0 0 1-.3.3H4.3a.3.3 0 0 1-.3-.3V11z"/></symbol><symbol viewBox="0 0 16 16" id="doc_text" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 11h5a.5.5 0 1 1 0 1h-5a.5.5 0 1 1 0-1zm0-2h5a.5.5 0 1 1 0 1h-5a.5.5 0 0 1 0-1zm0-2h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z"/></symbol><symbol viewBox="0 0 105 26" id="double-headed-arrow" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.018 11.089L15.138.614c1.23-.911 3.086-.795 4.147.26.461.46.715 1.045.715 1.651v20.95C20 24.869 18.684 26 17.06 26a3.238 3.238 0 0 1-1.921-.614L1.019 14.911C-.212 14-.347 12.405.714 11.35c.094-.094.195-.18.303-.261zm102.964 0c.108.08.21.167.303.26 1.061 1.056.925 2.65-.303 3.562l-14.12 10.475A3.238 3.238 0 0 1 87.94 26C86.316 26 85 24.87 85 23.475V2.525c0-.606.254-1.192.715-1.65 1.061-1.056 2.917-1.172 4.146-.26l14.12 10.474zM35 17a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/></symbol><symbol viewBox="0 0 16 16" id="download" xmlns="http://www.w3.org/2000/svg"><path d="M9 12h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0l-2-2.667A.5.5 0 0 1 6 12h1V8a1 1 0 1 1 2 0v4zM4 9a1 1 0 1 1 0 2 4 4 0 0 1-1.971-7.481 4 4 0 0 1 6.633-2.505 3.999 3.999 0 0 1 3.82 2.014A4 4 0 0 1 12 11a1 1 0 0 1 0-2 2 2 0 1 0 0-4h-1a2 2 0 0 0-3.112-1.662A2 2 0 1 0 4.268 5H4a2 2 0 1 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M14 10h-3a1 1 0 0 1-1-1V6H8.527A.527.527 0 0 0 8 6.527V13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-3zm-4-7H8.527c-.18 0-.355.013-.527.04V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2v2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3zM8.527 4h2.323a.5.5 0 0 1 .35.143l4.65 4.551a.5.5 0 0 1 .15.357V13a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6.527A2.527 2.527 0 0 1 8.527 4z"/></symbol><symbol viewBox="0 0 16 16" id="earth" xmlns="http://www.w3.org/2000/svg"><path d="M8.7 2.04l-.082.177c.283.223.422.413.417.571-.008.237-.311.057-.444.274-.133.218.038.542-.112.637-.15.096-.398-.386-.479-.46-.054-.049-.166-.257-.336-.625l-.216-.225a.844.844 0 0 0-.418-.035c-.177.038-.075.1-.035.132.04.032.32.037.452.2.132.164.03.224-.05.298-.054.05-.157.062-.31.035H5.952l-.402.398.03.325.229.455.324-.463c.008-.206.058-.342.15-.41.14-.1.342-.15.534-.085.191.066-.057.218.011.271.068.053.204-.098.313-.02.11.08.07.155.104.322.036.167.254.114.398.328.144.215.19.29.147.483-.043.195-.168.26-.305.232-.138-.028-.107-.246-.275-.348-.168-.102-.266-.114-.386-.054-.12.06-.016.129.023.235.04.106.274.321.224.43-.05.107-.108.116-.42 0-.21-.077-.414-.007-.615.212l-.76.722c-.153.715-.3 1.13-.44 1.243-.211.17-.177-.483-.483-.656-.306-.174-.494-.047-.8-.07-.307-.023-.42.65-.38.873a.434.434 0 0 0 .221.321c.236-.141.39-.184.465-.128.11.084-.144.267-.074.425.07.158.314.069.386.283.073.213.084.48-.05.706-.135.227-.275.178-.4.053-.127-.126-.033-.375-.255-.704-.223-.329-.381-.337-.63-.787-.158-.287-.35-.743-.575-1.366a6 6 0 0 0 3.21 7.198l.001-.075c0-.577-.004-.944-.012-1.102-.011-.236-.95-.945-1.104-1.2-.154-.256-.34-.595-.355-.746-.016-.151.185-.232.344-.325.16-.093-.11-.367.028-.626.137-.258.395-.438.496-.356.101.081.058.228.267.333.209.104.077-.213.456-.178.38.035.143.201.252.216.11.016.113-.127.299-.143.186-.015.282.445.471.622.19.178.452.008.611.043.159.034.267.09.402.255.136.166-.03.352.073.557.103.205 1.07.22 1.433.255.364.034.371.011.371.324s-.166.314-.453.507c-.286.193-.166.462-.38.762-.212.3-.316.062-.622.14-.306.077-.413.382-.452.568-.039.186-.386.094-.877.232-.29.082-.429.144-.569.204a6.002 6.002 0 0 0 7.682-4.3c-.094-.384-.18-.63-.258-.74-.213-.297-.36.21-.924.49-.564.278-.57-.288-.81-.49-.16-.133-.212-.44-.158-.92-.005-.478.02-.828.077-1.049.057-.221.126-.543.207-.965.351-.373.606-.572.764-.595.237-.034.336.374.658.3a.315.315 0 0 0 .035-.01 5.993 5.993 0 0 0-.475-.824l-.309-.043a.646.646 0 0 0-.332-.117c-.205-.02-.025.128-.089.24-.064.112-.235.724-.437.685-.201-.039-.204-.374-.17-.668.036-.294-.077-.35-.2-.412-.124-.062-.325-.213-.556-.295-.232-.082-.123-.175-.093-.274.03-.1.208-.015.193-.058-.014-.044-.313-.135-.266-.167.03-.02.2-.02.506.003l.216-.012.293-.163a.58.58 0 0 0-.376-.22c-.233-.036-.513-.034-.73-.142-.205-.103-.458-.36-.643-.638A5.965 5.965 0 0 0 8.7 2.04zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 1600 1600" id="ellipsis_v" xmlns="http://www.w3.org/2000/svg"><path d="M1088 1248v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V224q0-40 28-68t68-28h192q40 0 68 28t28 68z"/></symbol><symbol viewBox="0 0 18 18" id="emoji_slightly_smiling_face" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445 2.91 2.91 0 0 0 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smile" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smiley" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z"/></symbol><symbol viewBox="0 0 16 16" id="epic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14.985 8.044l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637A2 2 0 0 0 1.618 9h11.661a2 2 0 0 0 1.706-.956zm0 3l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637a2 2 0 0 0 .576.084h11.661a2 2 0 0 0 1.706-.956zM3.618 2h10.995a1 1 0 0 1 .948 1.316l-1.333 4a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l2-4A1 1 0 0 1 3.618 2zm-.382 4h9.322l.667-2H4.236l-1 2z"/></symbol><symbol viewBox="0 0 16 16" id="external-link" xmlns="http://www.w3.org/2000/svg"><path d="M13.121 4.177l-4.95 4.95a1 1 0 1 1-1.414-1.414l4.95-4.95-1.386-1.386a.5.5 0 0 1 .299-.85l4.709-.524a.5.5 0 0 1 .552.552l-.523 4.71a.5.5 0 0 1-.851.297l-1.386-1.385zM12 8.884a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3v-8a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-4z"/></symbol><symbol viewBox="0 0 16 16" id="eye" xmlns="http://www.w3.org/2000/svg"><path d="M8 14C4.816 14 2.253 12.284.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2s5.747 1.716 7.607 5.019a2 2 0 0 1 0 1.962C13.747 12.284 11.184 14 8 14zm0-2c2.41 0 4.338-1.29 5.864-4C12.338 5.29 10.411 4 8 4 5.59 4 3.662 5.29 2.136 8 3.662 10.71 5.589 12 8 12zm0-1a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm1-3a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="eye-slash" xmlns="http://www.w3.org/2000/svg"><path d="M13.618 2.62L1.62 14.619a1 1 0 0 1-.985-1.668l1.525-1.526C1.516 10.742.926 9.927.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2c1.074 0 2.076.195 3.006.58l.944-.944a1 1 0 0 1 1.668.985zM8.068 11a3 3 0 0 0 2.931-2.932l-2.931 2.931zm-3.02-2.462a3 3 0 0 1 3.49-3.49l.884-.884A6.044 6.044 0 0 0 8 4C5.59 4 3.662 5.29 2.136 8c.445.79.924 1.46 1.439 2.011l1.473-1.473zm.421 5.06l1.658-1.658c.283.04.575.06.873.06 2.41 0 4.338-1.29 5.864-4a11.023 11.023 0 0 0-1.133-1.664l1.418-1.418a12.799 12.799 0 0 1 1.458 2.1 2 2 0 0 1 0 1.963C13.747 12.284 11.184 14 8 14a7.883 7.883 0 0 1-2.53-.402z"/></symbol><symbol viewBox="0 0 16 16" id="file-addition" xmlns="http://www.w3.org/2000/svg"><path d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3z"/></symbol><symbol viewBox="0 0 16 16" id="file-deletion" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm2 6h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="file-modified" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm5 4a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"/></symbol><symbol viewBox="0 0 16 16" id="filter" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 6v9l-3.724-1.862A.5.5 0 0 1 6 12.691V6L1.854 1.854A.5.5 0 0 1 2.207 1h11.586a.5.5 0 0 1 .353.854L10 6z"/></symbol><symbol viewBox="0 0 16 16" id="folder" xmlns="http://www.w3.org/2000/svg"><path d="M13 3a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.81a3 3 0 0 1 2.827 1.995L13 3z"/></symbol><symbol viewBox="0 0 16 16" id="folder-o" xmlns="http://www.w3.org/2000/svg"><path d="M13 5l-4.365-.005a2 2 0 0 1-1.882-1.33A1 1 0 0 0 5.81 3H2v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zm0-2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.81a3 3 0 0 1 2.827 1.995L13 3z"/></symbol><symbol viewBox="0 0 16 16" id="folder-open" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14.59 5.464a2.998 2.998 0 0 1 1.096 3.845l-1.666 3.436A4 4 0 0 1 10.46 15H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.558a2 2 0 0 1 1.898 1.368l.21.632h4.973a2 2 0 0 1 2 2 2 2 0 0 1-.027.329l-.023.135zM5.285 7a1 1 0 0 0-.9.564l-1.939 4a1 1 0 0 0 .9 1.436h7.074a2 2 0 0 0 1.8-1.128l1.665-3.436a1 1 0 0 0-.9-1.436h-7.7z"/></symbol><symbol viewBox="0 0 16 16" id="fork" xmlns="http://www.w3.org/2000/svg"><path d="M9 12.268a2 2 0 1 1-2 0V8.874A4.002 4.002 0 0 1 4 5V3.732a2 2 0 1 1 2 0V5a2 2 0 1 0 4 0V3.732a2 2 0 1 1 2 0V5a4.002 4.002 0 0 1-3 3.874v3.394zM11 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="geo-nodes" xmlns="http://www.w3.org/2000/svg"><path d="M9.7 13.1l-.2.2c-.7.8-2 .9-2.8.1-.1 0-.1-.1-.1-.1l-.2-.2c-2 .2-3.4.7-3.4 1.4 0 .8 2.2 1.5 5 1.5s5-.7 5-1.5c0-.7-1.4-1.2-3.3-1.4M7.3 12.7c.4.4 1 .3 1.4-.1C11.6 9.5 13 7 13 5.3 13 2.4 10.8 0 8 0S3 2.4 3 5.3C3 7 4.4 9.5 7.3 12.7M8 2c1.6 0 3 1.4 3 3.3 0 1-1 2.8-3 5.2-2-2.4-3-4.2-3-5.2C5 3.4 6.4 2 8 2"/><circle cx="8" cy="5" r="1"/></symbol><symbol viewBox="0 0 16 16" id="git-merge" xmlns="http://www.w3.org/2000/svg"><path d="M11 12.268V5a1 1 0 0 0-1-1v1a.5.5 0 0 1-.8.4l-2.667-2a.5.5 0 0 1 0-.8L9.2.6a.5.5 0 0 1 .8.4v1a3 3 0 0 1 3 3v7.268a2 2 0 1 1-2 0zm-6 0a2 2 0 1 1-2 0V4.732a2 2 0 1 1 2 0v7.536zM4 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm8 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="group" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.048 11.997C-.377 11.975.013 11.782.013 10.56.013 9.235.653 8 4 8c.444 0 .84.022 1.194.062.164.435.426.82.76 1.132-1.786.389-2.721 1.353-2.906 2.803zm2.94-7.222a2.993 2.993 0 0 0-.976 1.95 2 2 0 1 1 .975-1.95zm6.964 7.222c-.185-1.45-1.12-2.414-2.906-2.803.334-.311.596-.697.76-1.132C11.16 8.022 11.556 8 12 8c3.346 0 3.987 1.235 3.987 2.56 0 1.222.39 1.415-3.035 1.437zm-1.964-5.272a2.993 2.993 0 0 0-.976-1.95 2 2 0 1 1 .976 1.95zM8 9a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 5c-2.177 0-3.987-.115-3.987-1.44S4.653 10 8 10c3.346 0 3.987 1.235 3.987 2.56S10.177 14 8 14z"/></symbol><symbol viewBox="0 0 16 16" id="history" xmlns="http://www.w3.org/2000/svg"><path d="M2.868 3.24a7 7 0 1 1-.043 9.475 1 1 0 0 1 1.478-1.348 5 5 0 1 0 .124-6.865l.796.645a.5.5 0 0 1-.193.873l-3.232.814a.5.5 0 0 1-.622-.504L1.3 3a.5.5 0 0 1 .814-.37l.754.61zM9 8h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V6a1 1 0 1 1 2 0v2z"/></symbol><symbol viewBox="0 0 16 16" id="home" xmlns="http://www.w3.org/2000/svg"><path d="M9 13h3v-3H4v3h3v-1a1 1 0 0 1 2 0v1zm5-3v3.659c0 .729-.657 1.341-1.5 1.341h-9c-.843 0-1.5-.612-1.5-1.341V10h-.88C.502 10 0 9.486 0 8.853c0-.307.12-.601.333-.816l6.405-6.463a1.56 1.56 0 0 1 2.374-.052L15.66 8.03c.444.441.455 1.167.024 1.622a1.108 1.108 0 0 1-.804.348H14zM7.95 3.273l-4.595 4.64h9.264l-4.67-4.64z"/></symbol><symbol viewBox="0 0 16 16" id="hook" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1h4zm0 1H6v1a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4zM7 8a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h2a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3v4a2 2 0 1 0 4 0h-.44a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H15a4 4 0 0 1-7 2.646A4 4 0 0 1 1 12H.56a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H3a2 2 0 1 0 4 0V8z"/></symbol><symbol viewBox="0 0 16 16" id="hourglass" xmlns="http://www.w3.org/2000/svg"><path d="M10.331 4.889A2.988 2.988 0 0 0 11 3V2H5v1c0 .362.064.709.182 1.03l5.15.859zM3 14v-1c0-1.78.93-3.342 2.33-4.228.447-.327.67-.582.67-.764 0-.19-.242-.46-.725-.815A4.996 4.996 0 0 1 3 3V2H2a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2h-1v1a4.997 4.997 0 0 1-2.39 4.266c-.407.3-.61.545-.61.734 0 .19.203.434.61.734A4.997 4.997 0 0 1 13 13v1h1a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2h1zm8 0v-1a3 3 0 0 0-6 0v1h6z"/></symbol><symbol viewBox="0 0 38 38" id="image-comment-dark" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#1F78D1"/><path fill="#FFF" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></symbol><symbol viewBox="0 0 38 38" id="image-comment-light" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></symbol><symbol viewBox="0 0 16 16" id="import" xmlns="http://www.w3.org/2000/svg"><path d="M9 8h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0L5.6 8.8A.5.5 0 0 1 6 8h1V1a1 1 0 1 1 2 0v7zM0 8a1 1 0 1 1 2 0 6 6 0 1 0 12 0 1 1 0 0 1 2 0A8 8 0 1 1 0 8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-block" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.803 8a5.97 5.97 0 0 0-.462 1H4.5a.5.5 0 0 1 0-1h1.303zM4.5 5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm7.5.083a6.04 6.04 0 0 0-2 0V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.083a5.96 5.96 0 0 0 .72 2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v2.083zm1.121 3.796zM11 16a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm-1.293-2.292a3 3 0 0 0 4.001-4.001l-4.001 4zm-1.415-1.415l4.001-4a3 3 0 0 0-4.001 4.001z"/></symbol><symbol viewBox="0 0 16 16" id="issue-child" xmlns="http://www.w3.org/2000/svg"><path d="M11 8H5v1h1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2V7a.997.997 0 0 1 1-1h3V4H4.5a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9v2h3a.997.997 0 0 1 1 1v2h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm-9 3v2h3v-2H2zm9 0v2h3v-2h-3z"/></symbol><symbol viewBox="0 0 16 16" id="issue-close" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M10.874 2H12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-2c-.918 0-1.74-.413-2.29-1.063a3.987 3.987 0 0 0 1.988-.984A1 1 0 0 0 10 14h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-1V3c0-.345-.044-.68-.126-1zM4 0h3a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-external" xmlns="http://www.w3.org/2000/svg"><path d="M11 4a5.99 5.99 0 0 0-2 .341V3a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h2.528a6.003 6.003 0 0 0 2.705 1.736A2.99 2.99 0 0 1 8 16H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3v1zM8.212 8.97l-.568-.876A.25.25 0 0 1 7.66 7.8l.404-.5a.25.25 0 0 1 .284-.076l.938.36c.256-.182.543-.325.85-.42l.323-.988a.25.25 0 0 1 .237-.173h.643a.25.25 0 0 1 .238.173l.321.989c.308.094.595.237.852.418l.937-.359a.25.25 0 0 1 .284.076l.404.5a.25.25 0 0 1 .016.293l-.568.875c.113.297.18.616.192.95l.9.54a.25.25 0 0 1 .114.27l-.145.627a.25.25 0 0 1-.221.192l-1.115.098a3.015 3.015 0 0 1-.512.608l.165 1.18a.25.25 0 0 1-.138.259l-.577.282a.25.25 0 0 1-.29-.051l-.874-.905a3.035 3.035 0 0 1-.608 0l-.875.905a.25.25 0 0 1-.29.05l-.577-.281a.25.25 0 0 1-.138-.26L9 12.254a3.015 3.015 0 0 1-.512-.607l-1.114-.098a.25.25 0 0 1-.222-.192l-.145-.627a.25.25 0 0 1 .115-.27l.899-.54c.012-.334.08-.653.192-.95zm2.806 2.034a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="issue-new" xmlns="http://www.w3.org/2000/svg"><path d="M10 2V1a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V4H9a1 1 0 1 1 0-2h1zm0 6a1 1 0 0 1 2 0v5a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h1a1 1 0 1 1 0 2H5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm0-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open-m" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-parent" xmlns="http://www.w3.org/2000/svg"><path d="M11 11H5v1h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H3v-2a.997.997 0 0 1 1-1h3V7H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H9v2h3a.997.997 0 0 1 1 1v2h2.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H11v-1zM6 3v2h4V3H6z"/></symbol><symbol viewBox="0 0 16 16" id="issues" xmlns="http://www.w3.org/2000/svg"><path d="M10.458 15.012l.311.055a3 3 0 0 0 3.476-2.433l1.389-7.879A3 3 0 0 0 13.2 1.28L11.23.933a3.002 3.002 0 0 0-.824-.031c.364.59.58 1.28.593 2.02l1.854.328a1 1 0 0 1 .811 1.158l-1.389 7.879a1 1 0 0 1-1.158.81l-.118-.02a3.98 3.98 0 0 1-.541 1.935zM3 0h4a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="italic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.5 12l2-8H6a1 1 0 1 1 0-2h6a1 1 0 0 1 0 2h-1.5l-2 8H10a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2h1.5z"/></symbol><symbol viewBox="0 0 16 16" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7.575 6.689a4.002 4.002 0 0 1 6.274-4.86 4 4 0 0 1-4.86 6.274l-2.21 2.21.706.708a1 1 0 1 1-1.414 1.414l-.707-.707-.707.707.707.707a1 1 0 1 1-1.414 1.414l-.707-.707a1 1 0 0 1-1.414-1.414l5.746-5.746zm2.032-.618a2 2 0 1 0 2.828-2.828A2 2 0 0 0 9.607 6.07z"/></symbol><symbol viewBox="0 0 16 16" id="key-2" xmlns="http://www.w3.org/2000/svg"><path d="M5.172 14.157l-.344.344-2.485.133a.462.462 0 0 1-.497-.503l.14-2.24a.599.599 0 0 1 .177-.382l5.155-5.155a4 4 0 1 1 2.828 2.828l-1.439 1.44-1.06-.354-.708.707.354 1.06-.707.708-1.06-.354-.708.707.354 1.06zm6.01-8.839a1 1 0 1 0 1.414-1.414 1 1 0 0 0-1.414 1.414z"/></symbol><symbol viewBox="0 0 16 16" id="label" xmlns="http://www.w3.org/2000/svg"><path d="M11.782 14.718a3 3 0 0 1-4.242 0L1.652 8.829a2 2 0 0 1-.565-1.702l.54-3.703a2 2 0 0 1 1.69-1.69l3.703-.54a2 2 0 0 1 1.703.564l5.888 5.888a3 3 0 0 1 0 4.243l-2.829 2.829zm1.415-5.657L7.309 3.173l-3.703.54-.54 3.702 5.888 5.888a1 1 0 0 0 1.414 0l2.829-2.828a1 1 0 0 0 0-1.414zM5.732 5.525A1 1 0 1 1 7.146 6.94a1 1 0 0 1-1.414-1.414z"/></symbol><symbol viewBox="0 0 16 16" id="labels" xmlns="http://www.w3.org/2000/svg"><path d="M9.424 2.254l2.08-.905a1 1 0 0 1 1.206.326l3.013 4.12a1 1 0 0 1 .16.849l-1.947 7.264a3 3 0 0 1-3.675 2.122l-.5-.135a3.999 3.999 0 0 0 1.082-1.782 1 1 0 0 0 1.16-.722l1.823-6.802-2.258-3.087-.687.299a2 2 0 0 0-.628-.88l-.829-.667zM.377 3.7L4.4.498a1 1 0 0 1 1.25.003L9.627 3.7a1 1 0 0 1 .373.78V13a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4.482A1 1 0 0 1 .377 3.7zM2 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4.958L5.02 2.561 2 4.964V13zm3-6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="leave" xmlns="http://www.w3.org/2000/svg"><path d="M11 7V5.883a.5.5 0 0 1 .757-.429l3.528 2.117a.5.5 0 0 1 0 .858l-3.528 2.117a.5.5 0 0 1-.757-.43V9H7a1 1 0 1 1 0-2h4zm-2 6.256a1 1 0 0 1 2 0A2.744 2.744 0 0 1 8.256 16H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5.19A2.81 2.81 0 0 1 11 2.81a1 1 0 0 1-2 0A.81.81 0 0 0 8.19 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h5.256c.41 0 .744-.333.744-.744z"/></symbol><symbol viewBox="0 0 16 16" id="level-up" xmlns="http://www.w3.org/2000/svg"><path fill="#2E2E2E" fill-rule="evenodd" d="M7 6h3.489a.5.5 0 0 0 .373-.832L6.374.117a.5.5 0 0 0-.748 0l-4.488 5.05A.5.5 0 0 0 1.51 6H5v7a3 3 0 0 0 3 3h6a1 1 0 0 0 0-2H8a1 1 0 0 1-1-1V6z"/></symbol><symbol viewBox="0 0 16 16" id="license" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.56 8.9l2.66 4.606a.3.3 0 0 1-.243.45l-1.678.094a.1.1 0 0 0-.078.044l-.953 1.432a.3.3 0 0 1-.51-.016L9.097 10.9a5.994 5.994 0 0 0 3.464-2zm-5.23 2.063L4.707 15.51a.3.3 0 0 1-.51.016l-.953-1.432a.1.1 0 0 0-.078-.044l-1.678-.094a.3.3 0 0 1-.243-.45l2.48-4.297a5.983 5.983 0 0 0 3.607 1.754zM8 10A5 5 0 1 1 8 0a5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="link" xmlns="http://www.w3.org/2000/svg"><path d="M6.986 3.35l2.12-2.122a4 4 0 0 1 5.657 5.657l-2.828 2.829a4 4 0 0 1-5.657 0 1 1 0 0 1 1.414-1.415 2 2 0 0 0 2.829 0l2.828-2.828a2 2 0 1 0-2.828-2.828l-1.001 1a5.018 5.018 0 0 0-2.534-.294zm2.12 9.192l-2.12 2.121a4 4 0 1 1-5.658-5.656l2.829-2.829a4 4 0 0 1 5.657 0 1 1 0 1 1-1.415 1.414 2 2 0 0 0-2.828 0l-2.828 2.829a2 2 0 1 0 2.828 2.828l1.001-1.001a5.018 5.018 0 0 0 2.534.294z"/></symbol><symbol viewBox="0 0 16 16" id="list-bulleted" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-7h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 5h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm-4 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-2h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="list-numbered" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 2h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 0 1 0-2zM1.156 5v-.828h.816V2.204h-.72v-.636c.432-.084.708-.192.996-.372h.756v2.976h.684V5H1.156zm-.18 5v-.588c.9-.828 1.596-1.464 1.596-1.98 0-.342-.192-.504-.468-.504-.252 0-.444.18-.624.36l-.552-.552c.396-.42.756-.612 1.32-.612.768 0 1.308.492 1.308 1.248 0 .612-.576 1.284-1.092 1.812.192-.024.468-.048.636-.048h.636V10H.976zm1.26 5.072c-.618 0-1.068-.204-1.356-.54l.468-.648c.234.216.51.36.78.36.336 0 .552-.12.552-.36 0-.288-.15-.456-.948-.456v-.72c.636 0 .828-.168.828-.432 0-.228-.138-.348-.396-.348-.252 0-.432.108-.672.312l-.516-.624c.372-.312.768-.492 1.236-.492.84 0 1.38.384 1.38 1.074 0 .366-.204.642-.612.822v.024c.432.132.732.432.732.912 0 .72-.684 1.116-1.476 1.116z"/></symbol><symbol viewBox="0 0 16 16" id="location" xmlns="http://www.w3.org/2000/svg"><path d="M8.755 15.144a1 1 0 0 1-1.51 0C3.748 11.114 2 8.065 2 6a6 6 0 1 1 12 0c0 2.065-1.748 5.113-5.245 9.144zM12 6a4 4 0 1 0-8 0c0 1.314 1.312 3.71 4 6.944C10.688 9.71 12 7.314 12 6zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="location-dot" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.314 13.087C4.382 13.295 3 13.85 3 14.5c0 .828 2.239 1.5 5 1.5s5-.672 5-1.5c0-.65-1.382-1.205-3.314-1.413l-.202.225a2 2 0 0 1-2.968 0l-.202-.225zm2.428-.445a1 1 0 0 1-1.484 0C4.419 9.5 3 7.037 3 5.252 3 2.353 5.239 0 8 0s5 2.352 5 5.253c0 1.784-1.42 4.247-4.258 7.389zM11 5.252C11 3.436 9.634 2 8 2S5 3.435 5 5.253c0 1.027.974 2.824 3 5.203 2.026-2.38 3-4.176 3-5.203zM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M10 5V4h2v1a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3V4h2v1h4zM4 7a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1H4zm0-3a4 4 0 1 1 8 0h-2a2 2 0 1 0-4 0H4z"/></symbol><symbol viewBox="0 0 16 16" id="lock-open" xmlns="http://www.w3.org/2000/svg"><path d="M4.044 4a4 4 0 0 1 6.99-2.658 1 1 0 1 1-1.495 1.33A2 2 0 0 0 6.044 4a.998.998 0 0 1-.07.367v.701H12a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3v-5a3 3 0 0 1 2.974-3V4h.07zM4 7.07a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="log" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-5h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm0 3h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-2h3a1 1 0 0 1 0 2H8a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="mail" xmlns="http://www.w3.org/2000/svg"><path d="M14 5.6L9.338 9.796a2 2 0 0 1-2.676 0L2 5.6V11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5.6zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm.212 2L8 8.31 12.788 4H3.212z"/></symbol><symbol viewBox="0 0 16 16" id="menu" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.143 2h13.714C15.488 2 16 2.448 16 3s-.512 1-1.143 1H1.143C.512 4 0 3.552 0 3s.512-1 1.143-1zm0 5h13.714C15.488 7 16 7.448 16 8s-.512 1-1.143 1H1.143C.512 9 0 8.552 0 8s.512-1 1.143-1zm0 5h13.714c.631 0 1.143.448 1.143 1s-.512 1-1.143 1H1.143C.512 14 0 13.552 0 13s.512-1 1.143-1z"/></symbol><symbol viewBox="0 0 16 16" id="merge-request-close" xmlns="http://www.w3.org/2000/svg"><path d="M9.414 8l1.414 1.414a1 1 0 1 1-1.414 1.414L8 9.414l-1.414 1.414a1 1 0 1 1-1.414-1.414L6.586 8 5.172 6.586a1 1 0 1 1 1.414-1.414L8 6.586l1.414-1.414a1 1 0 1 1 1.414 1.414L9.414 8zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="messages" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-.98-1.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM4.464 2.464L5.88 3.88a3 3 0 0 0 0 4.242L4.464 9.536a5 5 0 0 1 0-7.072zm7.072 7.072L10.12 8.12a3 3 0 0 0 0-4.242l1.415-1.415a5 5 0 0 1 0 7.072zM2.343.343l1.414 1.414a6 6 0 0 0 0 8.486l-1.414 1.414a8 8 0 0 1 0-11.314zm11.314 11.314l-1.414-1.414a6 6 0 0 0 0-8.486L13.657.343a8 8 0 0 1 0 11.314z"/></symbol><symbol viewBox="0 0 16 16" id="mobile-issue-close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.657 10.728L2.12 7.192A1 1 0 1 0 .707 8.607l4.243 4.242a.997.997 0 0 0 1.414 0l8.485-8.485a1 1 0 1 0-1.414-1.414l-7.778 7.778z"/></symbol><symbol viewBox="0 0 16 16" id="monitor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 13v1h3a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2h3v-1H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-3zM3 2a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm5.723 6.416l-2.66-1.773-1.71 1.71a.5.5 0 1 1-.707-.707l2-2a.5.5 0 0 1 .631-.062l2.66 1.773 2.71-2.71a.5.5 0 0 1 .707.707l-3 3a.5.5 0 0 1-.631.062z"/></symbol><symbol viewBox="0 0 16 16" id="more" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="notifications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 14H2.435a2 2 0 0 1-1.761-2.947c.962-1.788 1.521-3.065 1.68-3.832.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024c3.755.528 4.375 4.27 4.761 6.043.188.86.742 2.188 1.661 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0zm5.805-6.468c-.325-1.492-.37-1.674-.61-2.288C10.6 3.716 9.742 3 8.07 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.208 1.012-.827 2.424-1.877 4.375H13.64c-.993-1.937-1.6-3.396-1.835-4.468z"/></symbol><symbol viewBox="0 0 16 16" id="notifications-off" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.26 5.089c.243.757.382 1.478.5 2.017.187.86.74 2.188 1.66 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0H4.35l2-2h7.29c-.993-1.937-1.6-3.396-1.835-4.468-.07-.326-.129-.59-.178-.81l1.634-1.633zM10.943 1.75l-1.48 1.48C9.07 3.076 8.612 3 8.069 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.065.317-.17.673-.317 1.073L.45 12.242a1.99 1.99 0 0 1 .224-1.19c.962-1.787 1.521-3.064 1.68-3.831.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024 4.867 4.867 0 0 1 1.944.688zm2.932-.105a1 1 0 0 1 0 1.415L2.561 14.374a1 1 0 1 1-1.415-1.414L12.46 1.646a1 1 0 0 1 1.414 0z"/></symbol><symbol viewBox="0 0 16 16" id="overview" xmlns="http://www.w3.org/2000/svg"><path d="M2 0h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2h-3zM2 9h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3h-3z"/></symbol><symbol viewBox="0 0 16 16" id="pencil" xmlns="http://www.w3.org/2000/svg"><path d="M13.02 1.293l1.414 1.414a1 1 0 0 1 0 1.414L4.119 14.436a1 1 0 0 1-.704.293l-2.407.008L1 12.316a1 1 0 0 1 .293-.71L11.605 1.292a1 1 0 0 1 1.414 0zm-1.416 1.415l-.707.707L12.31 4.83l.707-.707-1.414-1.415zM3.411 13.73l1.123-1.122H3.12v-1.415L2 12.312l.005 1.422 1.406-.005z"/></symbol><symbol viewBox="0 0 16 16" id="pencil-square" xmlns="http://www.w3.org/2000/svg"><path d="M12 9a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V9zm.778-7.179l1.414 1.415-6.476 6.476a1 1 0 0 1-.498.27l-1.51.325.323-1.512a1 1 0 0 1 .27-.497l6.477-6.477zM15.607.407a1 1 0 0 1 0 1.414l-.708.707-1.414-1.414.707-.707a1 1 0 0 1 1.415 0z"/></symbol><symbol viewBox="0 0 16 16" id="pipeline" xmlns="http://www.w3.org/2000/svg"><path d="M8.969 7.25a2 2 0 1 1-1.938 0A1.002 1.002 0 0 1 7 7V5.083a.2.2 0 0 1 .06-.142l.877-.87a.1.1 0 0 1 .141 0l.864.87A.2.2 0 0 1 9 5.083V7c0 .086-.01.17-.031.25zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm4.5-4a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="play" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.765 15.835c-.545.321-1.258.159-1.593-.363A1.075 1.075 0 0 1 1 14.89V1.11C1 .496 1.518 0 2.158 0c.214 0 .424.057.607.165l11.684 6.89c.544.321.714 1.005.38 1.526a1.135 1.135 0 0 1-.38.364l-11.684 6.89z"/></symbol><symbol viewBox="0 0 16 16" id="plus" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7H2a1 1 0 1 0 0 2h5v5a1 1 0 0 0 2 0V9h5a1 1 0 0 0 0-2H9V2a1 1 0 1 0-2 0v5z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 7V4a1 1 0 1 0-2 0v3H4a1 1 0 1 0 0 2h3v3a1 1 0 0 0 2 0V9h3a1 1 0 0 0 0-2H9zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square-o" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="podcast" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862a1 1 0 0 1-.785 1.177A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-1-1 1 1 0 0 1 .02-.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 7.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM4.464 2.464A1 1 0 0 1 5.88 3.88a3 3 0 0 0 0 4.242 1 1 0 0 1-1.415 1.415 5 5 0 0 1 0-7.072zm7.072 7.072A1 1 0 0 1 10.12 8.12a3 3 0 0 0 0-4.242 1 1 0 0 1 1.415-1.415 5 5 0 0 1 0 7.072zM2.343.343a1 1 0 1 1 1.414 1.414 6 6 0 0 0 0 8.486 1 1 0 1 1-1.414 1.414 8 8 0 0 1 0-11.314zm11.314 11.314a1 1 0 1 1-1.414-1.414 6 6 0 0 0 0-8.486A1 1 0 0 1 13.657.343a8 8 0 0 1 0 11.314z"/></symbol><symbol viewBox="0 0 16 16" id="preferences" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 12h10a1 1 0 0 1 0 2H5a1 1 0 0 1-2 0v-2a1 1 0 0 1 2 0zm-3 0H1a1 1 0 0 0 0 2h1v-2zm11-5h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0zm-3 0H1a1 1 0 1 0 0 2h9V7zM6 2h9a1 1 0 0 1 0 2H6a1 1 0 1 1-2 0V2a1 1 0 1 1 2 0zM3 2H1a1 1 0 1 0 0 2h2V2z"/></symbol><symbol viewBox="0 0 16 16" id="profile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-4.274-3.404C4.412 9.709 5.694 9 8 9c2.313 0 3.595.7 4.28 1.586A4.997 4.997 0 0 1 8 13a4.997 4.997 0 0 1-4.274-2.404zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="project" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol><symbol viewBox="0 0 16 16" id="push-rules" xmlns="http://www.w3.org/2000/svg"><path d="M6.268 9a2 2 0 0 1 3.464 0H11a1 1 0 0 1 0 2H9.732a2 2 0 0 1-3.464 0H5a1 1 0 0 1 0-2h1.268zM7 2H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-1v3.515a.3.3 0 0 1-.434.268l-1.432-.716a.3.3 0 0 0-.268 0l-1.432.716A.3.3 0 0 1 7 5.515V2zM4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm4 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="question" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm-1.46-5.602h2.233a3.97 3.97 0 0 1 .051-.558c.029-.17.073-.326.133-.469.06-.143.14-.28.242-.41.102-.13.228-.263.38-.399.26-.24.504-.467.733-.683a5.03 5.03 0 0 0 .598-.668c.17-.23.302-.477.399-.742a2.66 2.66 0 0 0 .144-.907c0-.505-.083-.95-.25-1.335a2.55 2.55 0 0 0-.723-.97 3.2 3.2 0 0 0-1.152-.589 5.441 5.441 0 0 0-1.531-.2c-.516 0-.998.063-1.445.188a3.19 3.19 0 0 0-1.168.59c-.331.268-.594.61-.79 1.027-.195.417-.295.917-.3 1.5h2.64c.006-.224.04-.416.102-.578.062-.161.142-.293.238-.394a.921.921 0 0 1 .332-.227 1.04 1.04 0 0 1 .39-.074c.34 0 .593.095.763.285.169.19.254.488.254.895 0 .328-.106.63-.317.906-.21.276-.499.565-.863.867-.214.182-.39.374-.531.574-.141.2-.253.42-.336.657a3.656 3.656 0 0 0-.176.777 7.89 7.89 0 0 0-.05.937zm-.321 2.375c0 .188.035.362.105.524.07.161.17.3.301.418.13.117.284.21.46.277.178.068.376.102.595.102.218 0 .416-.034.593-.102.178-.068.331-.16.461-.277a1.2 1.2 0 0 0 .301-.418c.07-.162.106-.336.106-.524a1.3 1.3 0 0 0-.106-.523 1.2 1.2 0 0 0-.3-.418 1.461 1.461 0 0 0-.462-.277 1.651 1.651 0 0 0-.593-.102c-.22 0-.417.034-.594.102a1.46 1.46 0 0 0-.461.277 1.2 1.2 0 0 0-.3.418 1.284 1.284 0 0 0-.106.523z"/></symbol><symbol viewBox="0 0 16 16" id="question-o" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-.778-4.151c0-.301.014-.575.044-.82a3.2 3.2 0 0 1 .154-.68c.073-.208.17-.4.294-.575.123-.176.278-.343.465-.503a4.81 4.81 0 0 0 .755-.758c.185-.242.277-.506.277-.793 0-.356-.074-.617-.222-.783-.148-.166-.37-.25-.667-.25a.92.92 0 0 0-.342.065.806.806 0 0 0-.29.199 1.04 1.04 0 0 0-.209.345 1.5 1.5 0 0 0-.088.506H5.082c.005-.51.092-.948.263-1.313.171-.364.401-.664.69-.899.29-.234.63-.406 1.023-.516a4.66 4.66 0 0 1 1.264-.164c.497 0 .944.058 1.34.174.397.117.733.289 1.008.517.276.227.487.51.633.847.146.337.218.727.218 1.17 0 .295-.042.56-.126.792a2.52 2.52 0 0 1-.349.65 4.4 4.4 0 0 1-.523.584c-.2.19-.414.389-.642.598a2.73 2.73 0 0 0-.332.349c-.089.114-.16.233-.212.359a1.868 1.868 0 0 0-.116.41 3.39 3.39 0 0 0-.044.489H7.222zm-.28 2.078c0-.164.03-.317.092-.458a1.05 1.05 0 0 1 .263-.366c.114-.103.248-.183.403-.243a1.45 1.45 0 0 1 .52-.089c.191 0 .364.03.52.09.154.059.289.14.403.242.114.103.201.224.263.366.061.141.092.294.092.458 0 .164-.03.316-.092.458a1.05 1.05 0 0 1-.263.365 1.278 1.278 0 0 1-.404.243 1.43 1.43 0 0 1-.52.089c-.19 0-.364-.03-.519-.089-.155-.06-.29-.14-.403-.243a1.05 1.05 0 0 1-.263-.365 1.135 1.135 0 0 1-.093-.458z"/></symbol><symbol viewBox="0 0 16 16" id="quote" xmlns="http://www.w3.org/2000/svg"><path d="M15 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9h-2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1zM7 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9H3a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="redo" xmlns="http://www.w3.org/2000/svg"><path d="M4.625 4.423A4.897 4.897 0 0 1 8.079 3c2.73 0 4.944 2.239 4.944 5s-2.214 5-4.944 5c-1.41 0-2.723-.6-3.655-1.633a.98.98 0 0 0-1.397-.066 1.008 1.008 0 0 0-.064 1.413A6.87 6.87 0 0 0 8.079 15C11.9 15 15 11.866 15 8s-3.099-7-6.921-7A6.866 6.866 0 0 0 3.08 3.158L1.833 2.137a.49.49 0 0 0-.695.074.504.504 0 0 0-.11.311L1 7.26a.497.497 0 0 0 .6.492l4.576-1.013a.5.5 0 0 0 .206-.877L4.625 4.423z"/></symbol><symbol viewBox="0 0 16 16" id="remove" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2v10a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V3zm3-2a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1H5zM4 3v10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3H4zm2.5 2a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm3 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 16 16" id="repeat" xmlns="http://www.w3.org/2000/svg"><path d="M11.375 4.423A4.897 4.897 0 0 0 7.921 3c-2.73 0-4.944 2.239-4.944 5s2.214 5 4.944 5c1.41 0 2.723-.6 3.655-1.633a.98.98 0 0 1 1.397-.066c.403.373.432 1.005.064 1.413A6.87 6.87 0 0 1 7.921 15C4.1 15 1 11.866 1 8s3.099-7 6.921-7c1.915 0 3.706.792 4.999 2.158l1.247-1.021a.49.49 0 0 1 .695.074c.07.088.11.198.11.311L15 7.26a.497.497 0 0 1-.6.492L9.824 6.739a.5.5 0 0 1-.206-.877l1.757-1.439z"/></symbol><symbol viewBox="0 0 16 16" id="retry" xmlns="http://www.w3.org/2000/svg"><path d="M4.114 6.958a4 4 0 0 0 5.283 4.775 1 1 0 1 1 .712 1.87A6 6 0 0 1 2.182 6.44l-.741-.2a.5.5 0 0 1-.12-.915l2.195-1.268a.5.5 0 0 1 .683.183l1.268 2.196a.5.5 0 0 1-.563.733l-.79-.212zm7.777 2.084a4 4 0 0 0-5.284-4.775 1 1 0 0 1-.712-1.87 6 6 0 0 1 7.927 7.162l.742.2a.5.5 0 0 1 .12.915l-2.196 1.268a.5.5 0 0 1-.683-.183l-1.267-2.196a.5.5 0 0 1 .562-.733l.79.212z"/></symbol><symbol viewBox="0 0 16 16" id="scale" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.99 9a.792.792 0 0 0-.078-.231L13 7l-.912 1.769a.791.791 0 0 0-.077.231h1.978zm-10 0a.792.792 0 0 0-.078-.231L3 7l-.912 1.769A.791.791 0 0 0 2.011 9h1.978zM2 0h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm3 14h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zM8 4a1 1 0 0 1 1 1v9H7V5a1 1 0 0 1 1-1zm-4.53-.714l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 3 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158L2.53 3.286a.53.53 0 0 1 .94 0zm10 0l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 13 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158l2.266-4.735a.53.53 0 0 1 .94 0z"/></symbol><symbol viewBox="0 0 16 16" id="screen-full" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14 14v-2a1 1 0 0 1 2 0v3a.997.997 0 0 1-1 1h-3a1 1 0 0 1 0-2h2zM2 14v-2a1 1 0 0 0-2 0v3a1 1 0 0 0 1 1h3a1 1 0 0 0 0-2H2zM15.707.293A.997.997 0 0 1 16 1v3a1 1 0 0 1-2 0V2h-2a1 1 0 0 1 0-2h3c.276 0 .526.112.707.293zM2 2v2a1 1 0 1 1-2 0V1a.997.997 0 0 1 1-1h3a1 1 0 1 1 0 2H2zm4 4h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="screen-normal" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 3V1a1 1 0 1 1 2 0v3a.997.997 0 0 1-1 1H1a1 1 0 1 1 0-2h2zm10 0h2a1 1 0 0 1 0 2h-3a.997.997 0 0 1-1-1V1a1 1 0 0 1 2 0v2zM3 13H1a1 1 0 0 1 0-2h3a.997.997 0 0 1 1 1v3a1 1 0 0 1-2 0v-2zm10 0v2a1 1 0 0 1-2 0v-3a.997.997 0 0 1 1-1h3a1 1 0 0 1 0 2h-2zM6.5 7h3a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 12 16" id="scroll_down" xmlns="http://www.w3.org/2000/svg"><path class="fbfirst-triangle" d="M1.048 14.155a.508.508 0 0 0-.32.105c-.091.07-.136.154-.136.25v.71c0 .095.045.178.135.249.09.07.197.105.321.105h10.043a.51.51 0 0 0 .321-.105c.09-.07.136-.154.136-.25v-.71c0-.095-.045-.178-.136-.249a.508.508 0 0 0-.32-.105"/><path class="fbsecond-triangle" d="M.687 8.027c-.09-.087-.122-.16-.093-.22.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 12.91a.458.458 0 0 1-.136.089h-.37a.626.626 0 0 1-.136-.09"/><path class="fbthird-triangle" d="M.687 1.027C.597.94.565.867.594.807c.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 5.91a.458.458 0 0 1-.136.09h-.37a.626.626 0 0 1-.136-.09"/></symbol><symbol viewBox="0 0 12 16" id="scroll_up" xmlns="http://www.w3.org/2000/svg"><path d="M1.048 1.845a.508.508 0 0 1-.32-.105c-.091-.07-.136-.154-.136-.25V.78c0-.095.045-.178.135-.249a.508.508 0 0 1 .321-.105h10.043a.51.51 0 0 1 .321.105c.09.07.136.154.136.25v.71c0 .095-.045.178-.136.249a.508.508 0 0 1-.32.105M.687 7.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 3.09A.458.458 0 0 0 6.257 3h-.37a.626.626 0 0 0-.136.09M.687 14.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 10.09a.458.458 0 0 0-.136-.09h-.37a.626.626 0 0 0-.136.09"/></symbol><symbol viewBox="0 0 16 16" id="search" xmlns="http://www.w3.org/2000/svg"><path d="M8.853 8.854a3.5 3.5 0 1 0-4.95-4.95 3.5 3.5 0 0 0 4.95 4.95zm.207 2.328a5.5 5.5 0 1 1 2.121-2.121l3.329 3.328a1.5 1.5 0 0 1-2.121 2.121L9.06 11.182z"/></symbol><symbol viewBox="0 0 16 16" id="settings" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.415 5.803L1.317 4.084A.5.5 0 0 1 1.35 3.5l.805-.994a.5.5 0 0 1 .564-.153l1.878.704a5.975 5.975 0 0 1 1.65-.797L6.885.342A.5.5 0 0 1 7.36 0h1.28a.5.5 0 0 1 .474.342l.639 1.918a5.97 5.97 0 0 1 1.65.797l1.877-.704a.5.5 0 0 1 .565.153l.805.994a.5.5 0 0 1 .032.584l-1.097 1.719c.217.551.354 1.143.399 1.76l1.731 1.058a.5.5 0 0 1 .227.54l-.288 1.246a.5.5 0 0 1-.44.385l-2.008.19a6.026 6.026 0 0 1-1.142 1.431l.265 1.995a.5.5 0 0 1-.277.516l-1.15.56a.5.5 0 0 1-.576-.1l-1.424-1.452a6.047 6.047 0 0 1-1.804 0l-1.425 1.453a.5.5 0 0 1-.576.1l-1.15-.561a.5.5 0 0 1-.276-.516l.265-1.995a6.026 6.026 0 0 1-1.143-1.43l-2.008-.191a.5.5 0 0 1-.44-.385L.058 9.16a.5.5 0 0 1 .226-.539l1.732-1.058a5.968 5.968 0 0 1 .399-1.76zM8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="shield" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8c1.657 0 3 1.373 3 3.067v7.346c0 1.065-.54 2.053-1.426 2.611l-4 2.52a2.944 2.944 0 0 1-3.148 0l-4-2.52A3.083 3.083 0 0 1 1 10.414V3.066C1 1.373 2.343 0 4 0zm0 2.045c-.552 0-1 .457-1 1.022v7.346c0 .355.18.685.475.87l4 2.52a.981.981 0 0 0 1.05 0l4-2.52c.295-.185.475-.515.475-.87V3.067c0-.565-.448-1.022-1-1.022H4zm0 1.533c0-.282.224-.511.5-.511h4V12.1a.52.52 0 0 1-.069.258.494.494 0 0 1-.684.183l-3.5-2.098a.513.513 0 0 1-.247-.44V3.577z"/></symbol><symbol viewBox="0 0 16 16" id="slight-frown" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-2.163-3.275a2.499 2.499 0 0 1 4.343.03.5.5 0 0 1-.871.49 1.5 1.5 0 0 0-2.607-.018.5.5 0 1 1-.865-.502zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="slight-smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-5.163 2.254a.5.5 0 1 1 .865-.502 1.499 1.499 0 0 0 2.607-.018.5.5 0 1 1 .871.49 2.499 2.499 0 0 1-4.343.03z"/></symbol><symbol viewBox="0 0 16 16" id="smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM6.18 6.27a.5.5 0 0 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zm6 0a.5.5 0 1 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zM5 9a3 3 0 0 0 6 0H5z"/></symbol><symbol viewBox="0 0 16 16" id="smiley" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM5 9h6a3 3 0 0 1-6 0z"/></symbol><symbol viewBox="0 0 16 16" id="snippet" xmlns="http://www.w3.org/2000/svg"><path d="M10.67 9.31a3.001 3.001 0 0 1 2.062 5.546 3 3 0 0 1-3.771-4.559 1.007 1.007 0 0 1-.095-.137l-4.5-7.794a1 1 0 0 1 1.732-1l4.5 7.794c.028.05.052.1.071.15zm-3.283.35l-.289.5c-.028.05-.06.095-.095.137a3.001 3.001 0 0 1-3.77 4.56A3 3 0 0 1 5.294 9.31c.02-.051.043-.102.071-.15l.866-1.5 1.155 2zm2.31-4l-1.156-2 1.325-2.294a1 1 0 0 1 1.732 1L9.696 5.66zm-5.465 7.464a1 1 0 1 0 1-1.732 1 1 0 0 0-1 1.732zm7.5 0a1 1 0 1 0-1-1.732 1 1 0 0 0 1 1.732z"/></symbol><symbol viewBox="0 0 16 16" id="soft-unwrap" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.5 11v-.598a.5.5 0 0 1 .765-.424l2.557 1.598a.5.5 0 0 1 0 .848l-2.557 1.598a.5.5 0 0 1-.765-.424V13H2a1 1 0 0 1 0-2h4.5zM2 3h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm10 4h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="soft-wrap" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.5 13v.598a.5.5 0 0 1-.765.424l-2.557-1.598a.5.5 0 0 1 0-.848l2.557-1.598a.5.5 0 0 1 .765.424V11H12a1 1 0 0 0 0-2H2a1 1 0 1 1 0-2h10a3 3 0 0 1 0 6h-1.5zM2 3h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 8h3a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="spam" xmlns="http://www.w3.org/2000/svg"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 14 14" id="spinner" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="7" cy="7" r="6" stroke="#000" stroke-opacity=".1" stroke-width="2"/><path fill="#000" fill-opacity=".1" fill-rule="nonzero" d="M7 0a7 7 0 0 1 7 7h-2a5 5 0 0 0-5-5V0z"/></g></symbol><symbol viewBox="0 0 16 16" id="staged" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3h4a1 1 0 1 1 0 2H2a1 1 0 1 1 0-2zm9 6a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM2 7h4a1 1 0 1 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="star" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.609 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 16 16" id="star-o" xmlns="http://www.w3.org/2000/svg"><path d="M10.975 10.99a3 3 0 0 1 .655-2.083l1.54-1.916-2.219-.576a3 3 0 0 1-1.825-1.37L8 3.15 6.874 5.044a3 3 0 0 1-1.825 1.371l-2.218.576 1.54 1.916a3 3 0 0 1 .654 2.083l-.165 2.4 1.965-.836a3 3 0 0 1 2.348 0l1.965.836-.164-2.399zM7.61 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 14 14" id="status_canceled" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M5.2 3.8l4.9 4.9c.2.2.2.5 0 .7l-.7.7c-.2.2-.5.2-.7 0L3.8 5.2c-.2-.2-.2-.5 0-.7l.7-.7c.2-.2.5-.2.7 0"/></g></symbol><symbol viewBox="0 0 22 22" id="status_canceled_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M8.171 5.971l7.7 7.7a.76.76 0 0 1 0 1.1l-1.1 1.1a.76.76 0 0 1-1.1 0l-7.7-7.7a.76.76 0 0 1 0-1.1l1.1-1.1a.76.76 0 0 1 1.1 0"/></symbol><symbol viewBox="0 0 16 16" id="status_closed" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.83a1 1 0 0 1 1.414 1.416l-3.535 3.535a1 1 0 0 1-1.415.001l-2.12-2.12a1 1 0 1 1 1.413-1.415zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 14 14" id="status_created" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><circle cx="7" cy="7" r="3.25"/></g></symbol><symbol viewBox="0 0 22 22" id="status_created_borderless" xmlns="http://www.w3.org/2000/svg"><circle cx="11" cy="11" r="5.107"/></symbol><symbol viewBox="0 0 14 14" id="status_failed" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 5.969L5.599 4.568a.29.29 0 0 0-.413.004l-.614.614a.294.294 0 0 0-.004.413L5.968 7l-1.4 1.401a.29.29 0 0 0 .004.413l.614.614c.113.114.3.117.413.004L7 8.032l1.401 1.4a.29.29 0 0 0 .413-.004l.614-.614a.294.294 0 0 0 .004-.413L8.032 7l1.4-1.401a.29.29 0 0 0-.004-.413l-.614-.614a.294.294 0 0 0-.413-.004L7 5.968z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_failed_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 9.38L8.798 7.178a.455.455 0 0 0-.65.006l-.964.965a.462.462 0 0 0-.006.65L9.38 11l-2.202 2.202a.455.455 0 0 0 .006.65l.965.964a.462.462 0 0 0 .65.006L11 12.62l2.202 2.202a.455.455 0 0 0 .65-.006l.964-.965a.462.462 0 0 0 .006-.65L12.62 11l2.202-2.202a.455.455 0 0 0-.006-.65l-.965-.964a.462.462 0 0 0-.65-.006L11 9.38z"/></symbol><symbol viewBox="0 0 14 14" id="status_manual" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M10.5 7.63V6.37l-.787-.13c-.044-.175-.132-.349-.263-.61l.481-.652-.918-.913-.657.478a2.346 2.346 0 0 0-.612-.26L7.656 3.5H6.388l-.132.783c-.219.043-.394.13-.612.26l-.657-.478-.918.913.437.652c-.131.218-.175.392-.262.61l-.744.086v1.261l.787.13c.044.218.132.392.263.61l-.438.651.92.913.655-.434c.175.086.394.173.613.26l.131.783h1.313l.131-.783c.219-.043.394-.13.613-.26l.656.478.918-.913-.48-.652c.13-.218.218-.435.262-.61l.656-.13zM7 8.283a1.285 1.285 0 0 1-1.313-1.305c0-.739.57-1.304 1.313-1.304.744 0 1.313.565 1.313 1.304 0 .74-.57 1.305-1.313 1.305z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_manual_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M16.5 11.99v-1.98l-1.238-.206c-.068-.273-.206-.546-.412-.956l.756-1.025-1.444-1.435-1.03.752a3.686 3.686 0 0 0-.963-.41L12.03 5.5h-1.994l-.206 1.23c-.343.068-.618.205-.962.41l-1.031-.752-1.444 1.435.687 1.025c-.206.341-.275.615-.412.956L5.5 9.941v1.981l1.237.205c.07.342.207.615.413.957l-.688 1.025 1.444 1.434 1.032-.683c.274.137.618.274.962.41l.206 1.23h2.063l.206-1.23c.344-.068.619-.205.963-.41l1.03.752 1.444-1.435-.756-1.025c.207-.341.344-.683.413-.956l1.031-.205zM11 13.017c-1.169 0-2.063-.889-2.063-2.05 0-1.162.894-2.05 2.063-2.05s2.063.888 2.063 2.05c0 1.161-.894 2.05-2.063 2.05z"/></symbol><symbol viewBox="0 0 14 14" id="status_notfound" xmlns="http://www.w3.org/2000/svg"><path d="M7 14A7 7 0 1 1 7 0a7 7 0 0 1 0 14z"/><path d="M7 13A6 6 0 1 0 7 1a6 6 0 0 0 0 12z" fill="#FFF"/><path d="M8.16 7.184c.519-.37.904-.857 1.07-1.477.384-1.427-.619-2.897-2.246-2.897-.732 0-1.327.26-1.766.692a2.163 2.163 0 0 0-.509.743.75.75 0 0 0 1.4.54.78.78 0 0 1 .16-.213c.168-.165.39-.262.715-.262.597 0 .936.496.798 1.007-.067.249-.235.462-.492.644-.231.165-.47.264-.601.3a.75.75 0 0 0-.556.724v1.421a.75.75 0 0 0 1.5 0v-.909a3.74 3.74 0 0 0 .526-.313z"/><ellipse cx="6.889" cy="10.634" rx="1" ry="1"/></symbol><symbol viewBox="0 0 22 22" id="status_notfound_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M12.822 11.29c.816-.581 1.421-1.348 1.683-2.322.603-2.243-.973-4.553-3.53-4.553-1.15 0-2.085.41-2.775 1.089-.42.413-.672.835-.8 1.167a1.179 1.179 0 0 0 2.2.847c.016-.043.1-.184.252-.334.264-.259.613-.412 1.123-.412.938 0 1.47.78 1.254 1.584-.105.39-.37.726-.773 1.012a3.25 3.25 0 0 1-.945.47 1.179 1.179 0 0 0-.874 1.138v2.234a1.179 1.179 0 1 0 2.358 0v-1.43a5.9 5.9 0 0 0 .827-.492z"/><ellipse cx="10.825" cy="16.711" rx="1.275" ry="1.322"/></symbol><symbol viewBox="0 0 14 14" id="status_open" xmlns="http://www.w3.org/2000/svg"><path d="M0 7c0-3.866 3.142-7 7-7 3.866 0 7 3.142 7 7 0 3.866-3.142 7-7 7-3.866 0-7-3.142-7-7z"/><path d="M1 7c0 3.309 2.69 6 6 6 3.309 0 6-2.69 6-6 0-3.309-2.69-6-6-6-3.309 0-6 2.69-6 6z" fill="#FFF"/><path d="M7 9.219a2.218 2.218 0 1 0 0-4.436A2.218 2.218 0 0 0 7 9.22zm0 1.12a3.338 3.338 0 1 1 0-6.676 3.338 3.338 0 0 1 0 6.676z"/></symbol><symbol viewBox="0 0 14 14" id="status_pending" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M4.7 5.3c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H5c-.2 0-.3-.1-.3-.3V5.3m3 0c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H8c-.2 0-.3-.1-.3-.3V5.3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_pending_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M7.386 8.329c0-.315.157-.472.471-.472h1.414c.315 0 .472.157.472.472v5.342c0 .315-.157.472-.472.472H7.857c-.314 0-.471-.157-.471-.472V8.33m4.714 0c0-.315.157-.472.471-.472h1.415c.314 0 .471.157.471.472v5.342c0 .315-.157.472-.471.472H12.57c-.314 0-.471-.157-.471-.472V8.33"/></symbol><symbol viewBox="0 0 14 14" id="status_running" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 3c2.2 0 4 1.8 4 4s-1.8 4-4 4c-1.3 0-2.5-.7-3.3-1.7L7 7V3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_running_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 4.714c3.457 0 6.286 2.829 6.286 6.286 0 3.457-2.829 6.286-6.286 6.286-2.043 0-3.929-1.1-5.186-2.672L11 11V4.714"/></symbol><symbol viewBox="0 0 14 14" id="status_skipped" xmlns="http://www.w3.org/2000/svg"><path d="M7 14A7 7 0 1 1 7 0a7 7 0 0 1 0 14z"/><path d="M7 13A6 6 0 1 0 7 1a6 6 0 0 0 0 12z" fill="#FFF"/><path d="M6.415 7.04L4.579 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L5.341 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L6.415 7.04zm2.54 0L7.119 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L7.881 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L8.955 7.04z"/></symbol><symbol viewBox="0 0 22 22" id="status_skipped_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M14.072 11.063l-2.82 2.82a.46.46 0 0 0-.001.652l.495.495a.457.457 0 0 0 .653-.001l3.7-3.7a.46.46 0 0 0 .001-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.479-3.479a.464.464 0 0 0-.654.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/><path d="M10.08 11.063l-2.819 2.82a.46.46 0 0 0-.002.652l.496.495a.457.457 0 0 0 .652-.001l3.7-3.7a.46.46 0 0 0 .002-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.48-3.479a.464.464 0 0 0-.653.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/></symbol><symbol viewBox="0 0 14 14" id="status_success" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_success_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.866 12.095l-1.95-1.95a.462.462 0 0 0-.647.01l-.964.964a.46.46 0 0 0-.01.646l3.013 3.014a.787.787 0 0 0 1.106.008l.425-.425 4.854-4.853a.462.462 0 0 0 .002-.659l-.964-.964a.468.468 0 0 0-.658.002l-4.207 4.207z"/></symbol><symbol viewBox="0 0 14 14" id="status_success_solid" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7zm6.278.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 14 14" id="status_warning" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6 3.5c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v4c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-4m0 6c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v1c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-1"/></g></symbol><symbol viewBox="0 0 22 22" id="status_warning_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.429 5.5c0-.471.314-.786.785-.786h1.572c.471 0 .785.315.785.786v6.286c0 .471-.314.785-.785.785h-1.572c-.471 0-.785-.314-.785-.785V5.5m0 9.429c0-.472.314-.786.785-.786h1.572c.471 0 .785.314.785.786V16.5c0 .471-.314.786-.785.786h-1.572c-.471 0-.785-.315-.785-.786v-1.571"/></symbol><symbol viewBox="0 0 16 16" id="stop" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z"/></symbol><symbol viewBox="0 0 16 16" id="task-done" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="template" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm.8 2h2.4a.8.8 0 0 1 .8.8v1.4a.8.8 0 0 1-.8.8H3.8a.8.8 0 0 1-.8-.8V4.8a.8.8 0 0 1 .8-.8zm4.7 0h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm0 2h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm-5 3h9a.5.5 0 1 1 0 1h-9a.5.5 0 0 1 0-1zm0 2h9a.5.5 0 1 1 0 1h-9a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="terminal" xmlns="http://www.w3.org/2000/svg"><path d="M7 8a.997.997 0 0 1-.293.707l-1.414 1.414a1 1 0 1 1-1.414-1.414L4.586 8l-.707-.707a1 1 0 1 1 1.414-1.414l1.414 1.414A.997.997 0 0 1 7 8zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4zm5 7h2a1 1 0 0 1 0 2H9a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="thumb-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 11h5.282a2 2 0 0 0 1.963-2.38l-.563-2.905a3 3 0 0 0-.243-.732l-1.103-2.286A3 3 0 0 0 10.964 1H7a3 3 0 0 0-3 3v6.3a2 2 0 0 0 .436 1.247l3.11 3.9a.632.632 0 0 0 .941.053l.137-.137a1 1 0 0 0 .28-.87L8.329 11zM1 10h2V3H1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="thumb-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.103 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.487.5l.137.137a1 1 0 0 1 .28.87L8.329 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="thumbtack" xmlns="http://www.w3.org/2000/svg"><path d="M7.125 9h-2.19a.5.5 0 0 1-.417-.777L6 6V2L5.362.724A.5.5 0 0 1 5.809 0h4.382a.5.5 0 0 1 .447.724L10 2v4l1.482 2.223a.5.5 0 0 1-.416.777H8.875L8 16l-.875-7z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 16 16" id="timer" xmlns="http://www.w3.org/2000/svg"><path d="M12.022 3.27l.77-.77a1 1 0 0 1 1.415 1.414l-.728.729a7 7 0 1 1-1.456-1.372zM8 14A5 5 0 1 0 8 4a5 5 0 0 0 0 10zm0-9a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zM6 0h4a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-add" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 4V2a1 1 0 0 1 2 0v2h2a1 1 0 0 1 0 2h-2v2a1 1 0 0 1-2 0V6H8a1 1 0 1 1 0-2h2zm2 7a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-done" xmlns="http://www.w3.org/2000/svg"><path d="M8.243 7.485l4.95-4.95a1 1 0 1 1 1.414 1.415L8.95 9.607a.997.997 0 0 1-1.414 0L4.707 6.778a1 1 0 0 1 1.414-1.414l2.122 2.121zM12 11a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="token" xmlns="http://www.w3.org/2000/svg"><path d="M3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H3zm1 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="unapproval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.95 8.536l1.06-1.061a1 1 0 0 1 1.415 1.414l-1.061 1.06 1.06 1.061a1 1 0 0 1-1.414 1.415l-1.06-1.061-1.06 1.06a1 1 0 1 1-1.415-1.414l1.06-1.06-1.06-1.06a1 1 0 0 1 1.414-1.415l1.06 1.06zm-3.768-.33c.006.503.201 1.006.586 1.39l.353.354-.353.353a2 2 0 1 0 2.828 2.829l.354-.354.047.048C11.964 14.363 11.527 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.834 0 1.557.074 2.182.205zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="unassignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11 5h4a1 1 0 0 1 0 2h-4a1 1 0 0 1 0-2zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="unlink" xmlns="http://www.w3.org/2000/svg"><path d="M11.295 8.845l-.659-1.664a1.78 1.78 0 0 0 .04-.04l1.415-1.414c.586-.586.654-1.468.152-1.97s-1.384-.434-1.97.152L8.859 5.323a1.781 1.781 0 0 0-.04.04l-1.664-.658c.141-.208.305-.408.491-.594l1.415-1.414c1.366-1.367 3.424-1.525 4.596-.354 1.171 1.172 1.013 3.23-.354 4.596L11.89 8.354c-.186.186-.386.35-.594.491zm-2.45 2.45a4.075 4.075 0 0 1-.491.594l-1.415 1.414c-1.366 1.367-3.424 1.525-4.596.354-1.171-1.172-1.013-3.23.354-4.596L4.11 7.646c.186-.186.386-.35.594-.491l.659 1.664a1.781 1.781 0 0 0-.04.04l-1.415 1.414c-.586.586-.654 1.468-.152 1.97s1.384.434 1.97-.152l1.414-1.414a1.78 1.78 0 0 0 .04-.04l1.664.658zm3.812-2.088h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-.05a.5.5 0 0 1 .5-.5zm-.384 2.116l1.415 1.414a.5.5 0 0 1 0 .708l-.037.036a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 0-.707l.036-.037a.5.5 0 0 1 .707 0zm-2.823 1.09a.5.5 0 0 1 .5-.5h.052a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9.95a.5.5 0 0 1-.5-.5v-2zm-2.748-9.16a.5.5 0 0 1-.5.5h-.05a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h.05a.5.5 0 0 1 .5.5v2zm-2.116.383a.5.5 0 0 1 0 .707l-.036.036a.5.5 0 0 1-.707 0L2.428 2.965a.5.5 0 0 1 0-.707l.037-.036a.5.5 0 0 1 .707 0l1.414 1.414zm-1.09 2.823h-2a.5.5 0 0 1-.5-.5v-.051a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5z"/></symbol><symbol viewBox="0 0 16 16" id="unstaged" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="user" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 8c-6.888 0-6.976-.78-6.976-2.52S2.144 8 8 8s6.976 2.692 6.976 4.48c0 1.788-.088 2.52-6.976 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="users" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.521 8.01C15.103 8.19 16 10.755 16 12.48c0 1.533-.056 2.29-3.808 2.475.609-.54.808-1.331.808-2.475 0-1.911-.804-3.503-2.479-4.47zm-1.67-1.228A3.987 3.987 0 0 0 9.976 4a3.987 3.987 0 0 0-1.125-2.782 3 3 0 1 1 0 5.563zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="volume-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 5h1v6H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zm2 0l4.445-2.964A1 1 0 0 1 9 2.87v10.26a1 1 0 0 1-1.555.833L3 11V5zm10.283 7.89a.5.5 0 0 1-.66-.752A5.485 5.485 0 0 0 14.5 8c0-1.601-.687-3.09-1.865-4.128a.5.5 0 0 1 .661-.75A6.484 6.484 0 0 1 15.5 8a6.485 6.485 0 0 1-2.217 4.89zm-2.002-2.236a.5.5 0 1 1-.652-.758c.55-.472.871-1.157.871-1.896 0-.732-.315-1.411-.856-1.883a.5.5 0 0 1 .658-.753A3.492 3.492 0 0 1 12.5 8c0 1.033-.45 1.994-1.219 2.654z"/></symbol><symbol viewBox="0 0 16 16" id="warning" xmlns="http://www.w3.org/2000/svg"><path d="M15.572 10.506c.867 1.42.375 3.247-1.098 4.082a3.184 3.184 0 0 1-1.57.412h-9.81C1.387 15 0 13.665 0 12.018a2.9 2.9 0 0 1 .427-1.512L5.332 2.47C6.2 1.05 8.096.577 9.57 1.412c.453.257.831.622 1.098 1.059l4.905 8.035zM8.89 3.479a1.014 1.014 0 0 0-.366-.353 1.053 1.053 0 0 0-1.412.353l-4.905 8.035a.967.967 0 0 0-.143.504c0 .549.462.994 1.032.994h9.81c.184 0 .364-.048.523-.137a.974.974 0 0 0 .366-1.361L8.889 3.479zM8 5a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="work" xmlns="http://www.w3.org/2000/svg"><path d="M12 3h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h1V2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1zM6 2v1h4V2H6zM3 5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H3zm1.5 1a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm7 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/cluster_popover.svg b/app/assets/images/illustrations/cluster_popover.svg
deleted file mode 100644
index 202231373f1bfae35e2dc62972e79a7ec11a76fb..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/cluster_popover.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="142" height="104" viewBox="0 0 142 104"><g fill="none" fill-rule="evenodd"><g transform="translate(112 4)"><path fill="#FFF" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#FC6D26" rx="5"/></g><g transform="translate(5 74)"><rect width="30" height="30" fill="#FFF" rx="8"/><path fill="#E1DBF1" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#6B4FBB" rx="5"/></g><path fill="#FFF" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#FC6D26" rx="4"/><g transform="translate(112 77)"><rect width="24" height="24" fill="#FFF" rx="6"/><path fill="#E1DBF1" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#6B4FBB" rx="4"/></g><g transform="translate(46 29)"><rect width="46" height="46" y="2" fill="#E1DBF1" rx="10"/><rect width="46" height="46" fill="#E1DBF1" rx="10"/><path fill="#C3B8E3" fill-rule="nonzero" d="M10 4a6 6 0 0 0-6 6v26a6 6 0 0 0 6 6h26a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h26c5.523 0 10 4.477 10 10v26c0 5.523-4.477 10-10 10H10C4.477 46 0 41.523 0 36V10C0 4.477 4.477 0 10 0z"/><rect width="14" height="14" x="16" y="16" fill="#6B4FBB" rx="2"/></g><path fill="#E1DBF1" fill-rule="nonzero" d="M98.413 35.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#C3B8E3" d="M104.78 29.32a2 2 0 0 1-2.826-2.829l2.122-2.12a2 2 0 0 1 2.827 2.83l-2.122 2.12z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M42.413 89.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#E1DBF1" d="M48.78 83.32a2 2 0 1 1-2.826-2.829l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.122 2.12z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M27.713 26.531a2 2 0 1 1 2.574-3.062l2.296 1.93a2 2 0 1 1-2.573 3.062l-2.297-1.93z"/><path fill="#C3B8E3" d="M34.604 32.321a2 2 0 1 1 2.573-3.062l2.297 1.93A2 2 0 0 1 36.9 34.25l-2.297-1.93z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M93.74 74.553a2 2 0 0 1 2.52-3.106l2.33 1.891a2 2 0 1 1-2.521 3.106l-2.33-1.891z"/><path fill="#E1DBF1" d="M100.727 80.225a2 2 0 1 1 2.521-3.105l2.33 1.89a2 2 0 1 1-2.522 3.106l-2.33-1.89z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/clusters_empty.svg b/app/assets/images/illustrations/clusters_empty.svg
deleted file mode 100644
index 39627a1c31438124b29ba6ff76d3fe4931ace14d..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/clusters_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="128" viewBox="0 0 142 128" width="142" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M94 62h20v4H94z" fill="#f0edf8"/><path d="M84.828 84l17.678 17.678-2.828 2.828L82 86.828z" fill="#fee1d3"/><path d="M42.828 24l17.678 17.678-2.828 2.828L40 26.828zM40 101.678L57.678 84l2.828 2.828-17.678 17.678z" fill="#f0edf8"/><path d="M82 41.678L99.678 24l2.828 2.828-17.678 17.678zM28 62h20v4H28zM3 52h24v24H3z" fill="#fee1d3"/><path d="M31 3h24v24H31z" fill="#f0edf8"/><path d="M87 3h24v24H87z" fill="#fef0e8"/><path d="M115 52h24v24h-24z" fill="#f0edf8"/><path d="M87 101h24v24H87z" fill="#fee1d3"/><path d="M31 101h24v24H31z" fill="#f0edf8"/><path d="M49 42h44v44H49z" fill="#c3b8e3"/><g fill-rule="nonzero"><path d="M5 53a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V54a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H5a5 5 0 0 1-5-5V54a5 5 0 0 1 5-5z" fill="#fdc4a8"/><path d="M56 43a6 6 0 0 0-6 6v30a6 6 0 0 0 6 6h30a6 6 0 0 0 6-6V49a6 6 0 0 0-6-6zm0-4h30c5.523 0 10 4.477 10 10v30c0 5.523-4.477 10-10 10H56c-5.523 0-10-4.477-10-10V49c0-5.523 4.477-10 10-10z" fill="#6b4fbb"/><path d="M89 4a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H89a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z" fill="#fee1d3"/><path d="M89 102a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-20a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H89a5 5 0 0 1-5-5v-20a5 5 0 0 1 5-5z" fill="#fdc4a8"/><path d="M117 53a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V54a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5h-20a5 5 0 0 1-5-5V54a5 5 0 0 1 5-5zM33 102a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-20a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H33a5 5 0 0 1-5-5v-20a5 5 0 0 1 5-5zM33 4a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H33a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z" fill="#e1dbf1"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/convdev_no_data.svg b/app/assets/images/illustrations/convdev/convdev_no_data.svg
deleted file mode 100644
index b90eddcccfaa4b7741c32972024155cd3ecce415..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/convdev_no_data.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="360" height="220" viewBox="0 0 360 220"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".02" d="M125 44V24.003C125 18.48 129.483 14 135.005 14h89.99C230.52 14 235 18.477 235 24.003V43h84.992C326.624 43 332 48.372 332 55.002v144.996c0 6.63-5.38 12.002-12.008 12.002h-85.984c-6.632 0-12.008-5.372-12.008-12.002V183h-78v17.002c0 6.626-5.38 11.998-12.008 11.998H46.008C39.376 212 34 206.624 34 200.002V55.998C34 49.372 39.38 44 46.008 44H125z"/><g transform="translate(214 36)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><path fill="#F0EDF8" fill-rule="nonzero" d="M57 111c-11.598 0-21-9.402-21-21s9.402-21 21-21 21 9.402 21 21-9.402 21-21 21zm0-4c9.39 0 17-7.61 17-17s-7.61-17-17-17-17 7.61-17 17 7.61 17 17 17z"/><path fill="#6B4FBB" d="M58 88v-6.997c0-1.11-.895-2.003-2-2.003-1.112 0-2 .897-2 2.003v8.994a1.999 1.999 0 0 0 2.503 1.94c.162.04.33.063.506.063h7.98a2 2 0 0 0 .001-4H58z"/><rect width="8" height="4" x="8" y="14" fill="#EEE" rx="2"/><path fill="#EEE" d="M21 16c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 21 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 34 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 47 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 60 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 73 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 86 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 99 16z"/></g><g transform="translate(118 7)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><g fill-rule="nonzero"><path fill="#F0EDF8" d="M57 112c-12.15 0-22-9.85-22-22s9.85-22 22-22 22 9.85 22 22-9.85 22-22 22zm0-6c8.837 0 16-7.163 16-16s-7.163-16-16-16-16 7.163-16 16 7.163 16 16 16z"/><path fill="#6B4FBB" d="M41.692 105.8A21.93 21.93 0 0 0 57 112c12.15 0 22-9.85 22-22s-9.85-22-22-22v6c8.837 0 16 7.163 16 16s-7.163 16-16 16a15.935 15.935 0 0 1-11.133-4.508l-4.175 4.31z"/></g><path fill="#EEE" d="M8 16c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2H9.998A1.995 1.995 0 0 1 8 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 21 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 34 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 47 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 60 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 73 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 86 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 99 16z"/></g><g transform="translate(26 36)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006v147.988A8 8 0 0 0 12.005 168h89.99a8.007 8.007 0 0 0 8.005-8.006V12.006A8 8 0 0 0 101.995 4h-89.99A8.007 8.007 0 0 0 4 12.006zm-4 0C0 5.376 5.377 0 12.005 0h89.99C108.628 0 114 5.37 114 12.006v147.988c0 6.63-5.377 12.006-12.005 12.006h-89.99C5.372 172 0 166.63 0 159.994V12.006z"/><g transform="translate(21 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(69 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(38 42)"><rect width="22" height="4" x="8" fill="#FEE1D3" rx="2"/><rect width="38" height="4" y="12" fill="#FB722E" rx="2"/></g><path fill="#EEE" d="M4 14h106v4H4z"/><path fill="#333" d="M35.724 138h9.696v-2.856h-2.856V122.76h-2.592c-1.08.648-2.136 1.08-3.792 1.392v2.184h2.856v8.808h-3.312V138zm17.736.288c-2.952 0-5.76-2.208-5.76-7.56 0-5.688 2.952-8.256 6.168-8.256 2.016 0 3.48.84 4.44 1.824l-1.848 2.112c-.528-.576-1.488-1.08-2.376-1.08-1.68 0-3.024 1.2-3.144 4.752.792-1.008 2.112-1.608 3.048-1.608 2.616 0 4.536 1.488 4.536 4.704 0 3.168-2.304 5.112-5.064 5.112zm-.072-2.64c1.056 0 1.92-.744 1.92-2.472 0-1.608-.84-2.208-1.992-2.208-.792 0-1.68.432-2.304 1.512.312 2.4 1.32 3.168 2.376 3.168zM63.9 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/convdev_no_index.svg b/app/assets/images/illustrations/convdev/convdev_no_index.svg
deleted file mode 100644
index 4aaf505e0b83844180afdb4b9ba4647cf814ce8f..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/convdev_no_index.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="360" height="200" viewBox="0 0 360 200"><g fill="none" fill-rule="evenodd" transform="translate(3 11)"><rect width="110" height="168" x="6" y="8" fill="#000" fill-opacity=".02" rx="10"/><g transform="translate(0 2)"><rect width="110" height="168" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M2 10.006v147.988A8 8 0 0 0 10.005 166h89.99a8.007 8.007 0 0 0 8.005-8.006V10.006A8 8 0 0 0 99.995 2h-89.99A8.007 8.007 0 0 0 2 10.006zm-4 0C-2 3.376 3.377-2 10.005-2h89.99C106.628-2 112 3.37 112 10.006v147.988c0 6.63-5.377 12.006-12.005 12.006h-89.99C3.372 170-2 164.63-2 157.994V10.006z"/><g transform="translate(19 80)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(67 80)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(36 40)"><rect width="22" height="4" x="8" fill="#FEE1D3" rx="2"/><rect width="38" height="4" y="12" fill="#FB722E" rx="2"/></g><path fill="#EEE" d="M2 12h106v4H2z"/><path fill="#333" d="M38.048 127.792c.792 0 1.68-.432 2.28-1.512-.312-2.4-1.296-3.168-2.376-3.168-1.032 0-1.92.744-1.92 2.472 0 1.608.864 2.208 2.016 2.208zm-.552 8.496c-2.016 0-3.504-.864-4.464-1.824l1.872-2.112c.504.576 1.464 1.08 2.352 1.08 1.704 0 3.024-1.2 3.144-4.752-.792 1.008-2.112 1.608-3.048 1.608-2.592 0-4.536-1.488-4.536-4.704 0-3.168 2.304-5.112 5.064-5.112 2.952 0 5.784 2.208 5.784 7.56 0 5.688-2.976 8.256-6.168 8.256zm13.488 0c-3.048 0-5.304-1.704-5.304-4.176 0-1.848 1.152-2.976 2.592-3.744v-.096c-1.176-.888-2.04-1.992-2.04-3.6 0-2.592 2.04-4.2 4.872-4.2 2.784 0 4.632 1.656 4.632 4.176 0 1.464-.936 2.64-1.992 3.336v.096c1.464.792 2.64 1.968 2.64 3.984 0 2.4-2.16 4.224-5.4 4.224zm.96-9.168c.6-.696.936-1.44.936-2.232 0-1.176-.696-1.968-1.848-1.968-.936 0-1.704.576-1.704 1.752 0 1.248 1.056 1.848 2.616 2.448zm-.888 6.72c1.176 0 2.04-.624 2.04-1.896 0-1.344-1.296-1.848-3.216-2.664-.672.624-1.176 1.488-1.176 2.424 0 1.344 1.08 2.136 2.352 2.136zm10.8-3.84c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g><g transform="translate(122)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><g transform="translate(21 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><g transform="translate(69 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><path fill="#FEE1D3" d="M44 44a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 44zM34 56a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 34 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 74 56z"/><rect width="8" height="4" x="8" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="21" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="34" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="47" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="60" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="73" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="86" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="99" y="14" fill="#EEE" rx="2"/><path fill="#EEE" d="M46.716 138.288c-3.264 0-5.448-2.784-5.448-7.968s2.184-7.848 5.448-7.848c3.264 0 5.448 2.664 5.448 7.848 0 5.184-2.184 7.968-5.448 7.968zm0-2.736c1.2 0 2.112-1.08 2.112-5.232 0-4.176-.912-5.112-2.112-5.112-1.176 0-2.112.936-2.112 5.112 0 4.152.936 5.232 2.112 5.232zM57.564 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g><g transform="translate(243)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><path fill="#FEE1D3" d="M44 44a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 44zM34 56a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 34 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 74 56z"/><g transform="translate(21 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><g transform="translate(69 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><rect width="8" height="4" x="8" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="21" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="34" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="47" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="60" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="73" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="86" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="99" y="14" fill="#EEE" rx="2"/><path fill="#EEE" d="M46.716 138.288c-3.264 0-5.448-2.784-5.448-7.968s2.184-7.848 5.448-7.848c3.264 0 5.448 2.664 5.448 7.848 0 5.184-2.184 7.968-5.448 7.968zm0-2.736c1.2 0 2.112-1.08 2.112-5.232 0-4.176-.912-5.112-2.112-5.112-1.176 0-2.112.936-2.112 5.112 0 4.152.936 5.232 2.112 5.232zM57.564 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/convdev_overview.svg b/app/assets/images/illustrations/convdev/convdev_overview.svg
deleted file mode 100644
index a06d70812ca8b42b4116d32cbe06626c2f44370f..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/convdev_overview.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="208" height="127" viewBox="0 0 208 127" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="58" height="98" y="17" rx="6"/><rect id="b" width="58" height="98" x="3.5" y="17" rx="6"/><rect id="c" width="58" height="98.394" rx="6"/></defs><g fill="none" fill-rule="evenodd" transform="translate(1)"><path fill="#000" fill-opacity=".06" fill-rule="nonzero" d="M16 11.06c0-1.39.56-2.69 1.534-3.635.398-.386.41-1.025.027-1.426a.993.993 0 0 0-1.413-.028A7.075 7.075 0 0 0 14 11.062c0 .556.448 1.007 1 1.007s1-.452 1-1.01zm6.432-5.043h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0H185a4.95 4.95 0 0 1 3.254 1.215.995.995 0 0 0 1.41-.108c.36-.423.312-1.06-.107-1.422A6.944 6.944 0 0 0 185 4h-.568c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zM190 11.932v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.008zm0 10.89v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.01v-4.838c0-.557-.448-1.01-1-1.01s-1 .453-1 1.01zm0 10.89v4.84c0 .555.448 1.007 1 1.007s1-.453 1-1.01v-4.84c0-.556-.448-1.007-1-1.007s-1 .45-1 1.008zm0 10.888v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008V44.6c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008zm0 10.89v4.84c0 .556.448 1.007 1 1.007s1-.45 1-1.008v-4.84c0-.557-.448-1.01-1-1.01s-1 .453-1 1.01zm0 10.89v4.838c0 .557.448 1.01 1 1.01s1-.453 1-1.01v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.01zm0 10.888v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.008v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008zm0 10.89v4.84c0 .556.448 1.007 1 1.007s1-.45 1-1.008v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.007zm0 10.888v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.008zm-.24 21.446a5.06 5.06 0 0 1-2.572 2.985 1.01 1.01 0 0 0-.46 1.348c.24.5.84.708 1.336.464a7.06 7.06 0 0 0 3.598-4.178c.17-.53-.12-1.098-.644-1.27a1 1 0 0 0-1.26.65zm-8.063 3.49h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.577-.116a5.009 5.009 0 0 1-3.19-2.3.994.994 0 0 0-1.373-.333c-.472.29-.62.91-.332 1.386.99 1.632 2.6 2.8 4.465 3.215a1 1 0 0 0 1.192-.768 1.005 1.005 0 0 0-.762-1.2zM16 105.292v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.01v4.838c0 .557.448 1.01 1 1.01s1-.453 1-1.01zm0-10.89v-4.84c0-.555-.448-1.007-1-1.007s-1 .452-1 1.008v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.007zm0-10.888v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008zm0-10.89v-4.84c0-.556-.448-1.007-1-1.007s-1 .45-1 1.008v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.008zm0-10.89v-4.838c0-.557-.448-1.01-1-1.01s-1 .453-1 1.01v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.01zm0-11.888v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.008v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008zm0-9.89v-4.84c0-.556-.448-1.007-1-1.007s-1 .45-1 1.007v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008zm0-10.888v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.008zm0-10.89v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.01v4.838c0 .557.448 1.01 1 1.01s1-.453 1-1.01z"/><g transform="translate(74)"><rect width="58" height="98" y="20" fill="#000" fill-opacity=".02" rx="6"/><use fill="#FFF" xlink:href="#a"/><rect width="56" height="96" x="1" y="18" stroke="#EEE" stroke-width="2" rx="6"/><g transform="translate(16 45.185)"><path fill="#333" d="M.59 33.815h5.655V32.15H4.58v-7.225H3.066c-.63.378-1.246.63-2.212.812v1.274H2.52v5.14H.59v1.665zm10.093.168c-1.778 0-3.094-.994-3.094-2.436 0-1.078.67-1.736 1.51-2.184v-.056c-.685-.518-1.19-1.162-1.19-2.1 0-1.512 1.19-2.45 2.843-2.45 1.624 0 2.702.966 2.702 2.436 0 .854-.546 1.54-1.162 1.946v.055c.854.462 1.54 1.148 1.54 2.324 0 1.4-1.26 2.463-3.15 2.463zm.56-5.348c.35-.406.546-.84.546-1.302 0-.686-.407-1.148-1.08-1.148-.545 0-.993.336-.993 1.022 0 .728.616 1.078 1.526 1.428zm-.518 3.92c.686 0 1.19-.364 1.19-1.106 0-.785-.756-1.08-1.876-1.555-.393.364-.687.868-.687 1.414 0 .783.63 1.245 1.372 1.245zm6.3-2.24c-1.316 0-2.268-1.078-2.268-2.912 0-1.82.952-2.884 2.268-2.884 1.316 0 2.282 1.063 2.282 2.883 0 1.834-.966 2.912-2.282 2.912zm0-1.148c.462 0 .84-.462.84-1.764s-.378-1.736-.84-1.736c-.462 0-.84.434-.84 1.736s.378 1.764.84 1.764zm.308 4.816l4.928-9.464h1.19l-4.927 9.463h-1.19zm6.426 0c-1.317 0-2.27-1.078-2.27-2.912 0-1.82.953-2.883 2.27-2.883 1.315 0 2.28 1.064 2.28 2.884 0 1.835-.965 2.913-2.28 2.913zm0-1.148c.46 0 .84-.462.84-1.764 0-1.3-.38-1.735-.84-1.735-.463 0-.84.434-.84 1.736 0 1.303.377 1.765.84 1.765z"/><rect width="13" height="2" x="6" y=".815" fill="#FB722E" rx="1"/><path fill="#F0EDF8" d="M3 47.815c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1zm0 6c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1z"/><rect width="20" height="2" x="3" y="6.815" fill="#FEE1D3" rx="1"/></g><g transform="translate(10.81)"><circle cx="18.19" cy="18" r="18" fill="#FFF"/><path fill="#F0EDF8" fill-rule="nonzero" d="M18.19 34c8.837 0 16-7.163 16-16s-7.163-16-16-16-16 7.163-16 16 7.163 16 16 16zm0 2c-9.94 0-18-8.06-18-18s8.06-18 18-18 18 8.06 18 18-8.06 18-18 18z"/><g transform="translate(10 11)"><path fill="#C3B8E3" fill-rule="nonzero" d="M2.19 13.32L5.397 11h7.783a.998.998 0 0 0 1.01-1V3c0-.55-.45-1-1.01-1H3.2a.998.998 0 0 0-1.01 1v10.32zM6.045 13l-3.422 2.476C1.28 16.45.19 15.892.19 14.23V3c0-1.657 1.337-3 3.01-3h9.98a3.004 3.004 0 0 1 3.01 3v7c0 1.657-1.337 3-3.01 3H6.045z"/><rect width="4" height="2" x="5.19" y="4" fill="#6B4FBB" rx="1"/><rect width="6" height="2" x="5.19" y="7" fill="#6B4FBB" rx="1"/></g></g></g><g transform="translate(144.5)"><rect width="58" height="98" x=".5" y="20" fill="#000" fill-opacity=".02" rx="6"/><use fill="#FFF" xlink:href="#b"/><rect width="56" height="96" x="4.5" y="18" stroke="#EEE" stroke-width="2" rx="6"/><g transform="translate(19 46.185)"><path fill="#333" d="M4.01 33.746c1.793 0 3.305-.938 3.305-2.59 0-1.148-.742-1.876-1.764-2.17v-.056c.953-.406 1.485-1.05 1.485-1.974 0-1.554-1.232-2.436-3.066-2.436-1.093 0-1.99.434-2.8 1.134l1.035 1.26c.56-.49 1.036-.784 1.666-.784.7 0 1.093.364 1.093.98 0 .714-.504 1.19-2.1 1.19v1.456c1.932 0 2.394.49 2.394 1.274 0 .672-.574 1.05-1.442 1.05-.756 0-1.414-.378-1.946-.896l-.953 1.302c.644.756 1.652 1.26 3.094 1.26zm4.51-.168h6.257v-1.736h-1.792c-.42 0-1.036.056-1.484.112 1.443-1.512 2.843-3.108 2.843-4.606 0-1.708-1.19-2.828-2.94-2.828-1.274 0-2.1.476-2.982 1.414l1.12 1.106c.45-.476.94-.91 1.583-.91.77 0 1.26.476 1.26 1.344 0 1.26-1.596 2.786-3.864 4.928v1.176zm9.505-3.5c-1.316 0-2.268-1.078-2.268-2.912 0-1.82.952-2.884 2.268-2.884 1.316 0 2.282 1.064 2.282 2.884 0 1.834-.966 2.912-2.282 2.912zm0-1.148c.462 0 .84-.462.84-1.764s-.378-1.736-.84-1.736c-.462 0-.84.434-.84 1.736s.378 1.764.84 1.764zm.308 4.816l4.928-9.464h1.19l-4.927 9.464h-1.19zm6.426 0c-1.317 0-2.27-1.078-2.27-2.912 0-1.82.953-2.884 2.27-2.884 1.315 0 2.28 1.064 2.28 2.884 0 1.834-.965 2.912-2.28 2.912zm0-1.148c.46 0 .84-.462.84-1.764s-.38-1.736-.84-1.736c-.463 0-.84.434-.84 1.736s.377 1.764.84 1.764z"/><rect width="13" height="2.008" x="7.5" fill="#FB722E" rx="1.004"/><path fill="#F0EDF8" d="M3.5 47.19c0-.556.455-1.005 1.006-1.005h17.988c.556 0 1.006.445 1.006 1.004 0 .553-.455 1.003-1.006 1.003H4.506A1.003 1.003 0 0 1 3.5 47.188zm0 6.023c0-.555.455-1.004 1.006-1.004h17.988c.556 0 1.006.444 1.006 1.003 0 .554-.455 1.004-1.006 1.004H4.506A1.003 1.003 0 0 1 3.5 53.212z"/><rect width="20" height="2.008" x="4" y="6.024" fill="#FEE1D3" rx="1.004"/></g><g transform="translate(14.413)"><circle cx="18.087" cy="18" r="18" fill="#FFF"/><path fill="#F0EDF8" fill-rule="nonzero" d="M18.087 34c8.836 0 16-7.163 16-16s-7.164-16-16-16c-8.837 0-16 7.163-16 16s7.163 16 16 16zm0 2c-9.942 0-18-8.06-18-18s8.058-18 18-18c9.94 0 18 8.06 18 18s-8.06 18-18 18z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M18.087 24a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0 2c-4.42 0-8-3.582-8-8s3.58-8 8-8a8 8 0 0 1 0 16z"/><path fill="#6B4FBB" d="M19.087 17v-2c0-.556-.448-1-1-1-.557 0-1 .448-1 1v3a.997.997 0 0 0 .998 1h3c.557 0 1-.448 1-1 0-.556-.447-1-1-1h-2z"/></g></g><rect width="58" height="98" x="3" y="20" fill="#000" fill-opacity=".02" rx="6"/><g transform="translate(0 16.754)"><use fill="#FFF" xlink:href="#c"/><rect width="56" height="96.394" x="1" y="1" stroke="#EEE" stroke-width="2" rx="6"/><g transform="translate(16 29.618)"><path fill="#333" d="M3.137 27.84c.462 0 .98-.253 1.33-.883-.182-1.4-.756-1.848-1.386-1.848-.6 0-1.12.433-1.12 1.44 0 .94.505 1.29 1.177 1.29zm-.322 4.955A3.626 3.626 0 0 1 .21 31.73l1.093-1.23c.294.335.854.63 1.372.63.994 0 1.764-.7 1.834-2.773-.463.588-1.233.938-1.78.938-1.51 0-2.645-.868-2.645-2.744 0-1.847 1.344-2.98 2.954-2.98 1.72 0 3.373 1.287 3.373 4.41 0 3.317-1.736 4.815-3.598 4.815zm8.12 0c-1.722 0-3.36-1.288-3.36-4.41 0-3.318 1.722-4.816 3.598-4.816 1.176 0 2.03.49 2.59 1.063l-1.078 1.232c-.308-.336-.868-.63-1.386-.63-.98 0-1.765.7-1.835 2.772.462-.588 1.232-.938 1.778-.938 1.526 0 2.646.867 2.646 2.743 0 1.848-1.345 2.982-2.955 2.982zm-.042-1.54c.616 0 1.12-.434 1.12-1.442 0-.938-.49-1.288-1.162-1.288-.46 0-.98.252-1.343.882.182 1.4.77 1.848 1.386 1.848zm6.132-2.128c-1.316 0-2.268-1.078-2.268-2.912 0-1.82.952-2.884 2.268-2.884 1.316 0 2.282 1.065 2.282 2.885 0 1.834-.966 2.912-2.282 2.912zm0-1.148c.462 0 .84-.463.84-1.765 0-1.302-.378-1.736-.84-1.736-.462 0-.84.433-.84 1.735s.378 1.764.84 1.764zm.308 4.815l4.928-9.464h1.19l-4.927 9.465h-1.19zm6.426 0c-1.317 0-2.27-1.078-2.27-2.912 0-1.82.953-2.884 2.27-2.884 1.315 0 2.28 1.063 2.28 2.883 0 1.834-.965 2.912-2.28 2.912zm0-1.148c.46 0 .84-.462.84-1.764s-.38-1.736-.84-1.736c-.463 0-.84.434-.84 1.736s.377 1.764.84 1.764z"/><rect width="13" height="2.008" x="6.5" y=".314" fill="#FEE1D3" rx="1.004"/><path fill="#F0EDF8" d="M3 46.627c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1zm0 6c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1z"/><rect width="20" height="2" x="3" y="5.627" fill="#FB722E" rx="1"/></g></g><g transform="translate(10.41)"><circle cx="18.589" cy="18" r="18" fill="#FFF"/><path fill="#F0EDF8" fill-rule="nonzero" d="M18.59 34c8.836 0 16-7.163 16-16s-7.164-16-16-16c-8.837 0-16 7.163-16 16s7.163 16 16 16zm0 2c-9.942 0-18-8.06-18-18s8.058-18 18-18c9.94 0 18 8.06 18 18s-8.06 18-18 18z"/><path fill="#C3B8E3" d="M17.05 19.262h3.367l.248-2.808H17.3l-.25 2.808zm-.177 2.008l-.144 1.627c-.06.662-.646 1.2-1.3 1.2h.25c-.658 0-1.144-.534-1.085-1.2l.144-1.627H13.59a1.001 1.001 0 0 1-1.003-1.004c0-.555.455-1.004 1.002-1.004h1.325l.248-2.808h-1.15a1 1 0 0 1-1.004-1.004 1.01 1.01 0 0 1 1.004-1.004h1.33l.106-1.2c.058-.66.644-1.198 1.298-1.198h-.25c.66 0 1.145.533 1.086 1.2l-.106 1.198h3.365l.107-1.2c.058-.66.644-1.198 1.298-1.198h-.25c.66 0 1.145.533 1.086 1.2l-.106 1.198h1.03c.554 0 1.003.446 1.003 1.004 0 .555-.455 1.004-1 1.004H22.8l-.25 2.808h1.037a1 1 0 0 1 1.002 1.004c0 .554-.456 1.004-1.003 1.004h-1.214l-.144 1.627c-.06.662-.646 1.2-1.3 1.2h.25c-.658 0-1.144-.534-1.085-1.2l.144-1.627H16.87z"/><path fill="#6B4FBB" d="M17.05 19.262l-.177 2.008H14.74l.177-2.008h2.134zm-1.707-4.816h2.135l-.178 2.008h-2.135l.178-2.008zm5.5 0h2.135l-.178 2.008h-2.135l.178-2.008zm1.708 4.816l-.177 2.008H20.24l.177-2.008h2.134z"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_1.svg b/app/assets/images/illustrations/convdev/i2p_step_1.svg
deleted file mode 100644
index 67467b1513df4f99633a521482833a914c56cd52..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_1.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M45.688 18.854c-4.869-1.989-10.488-1.975-15.29-.001a20.014 20.014 0 0 0-6.493 4.268 19.798 19.798 0 0 0-4.346 6.381 19.135 19.135 0 0 0-1.525 7.537c0 2.066.33 4.118.983 6.104a20.142 20.142 0 0 0 1.83 3.937 5.983 5.983 0 0 0-2.086 4.538c0 3.309 2.691 6 6 6s6-2.691 6-6-2.691-6-6-6c-.779 0-1.522.154-2.205.425a18.13 18.13 0 0 1-1.642-3.533 17.467 17.467 0 0 1-.881-5.472c0-2.351.459-4.623 1.391-6.814a17.721 17.721 0 0 1 3.88-5.675 18.057 18.057 0 0 1 5.85-3.845c4.329-1.778 9.392-1.79 13.78.002a18.077 18.077 0 0 1 5.843 3.84c3.39 3.34 5.257 7.776 5.257 12.493a17.463 17.463 0 0 1-.878 5.481 17.451 17.451 0 0 1-2.569 4.923c-2.134 2.866-3.818 4.698-5.174 6.173-2.424 2.643-3.98 4.599-4.383 8.384H32.215a1 1 0 1 0 0 2h11.739a1 1 0 0 0 .999-.947c.19-3.645 1.345-5.263 3.934-8.09 1.385-1.506 3.107-3.381 5.304-6.331a19.422 19.422 0 0 0 2.864-5.489c.651-1.98.98-4.04.979-6.109 0-5.256-2.078-10.198-5.856-13.92a20.079 20.079 0 0 0-6.49-4.265M28.761 51.612c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4M40 74h-4a1 1 0 1 0 0 2h4a1 1 0 1 0 0-2M42 70h-8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M38 10a1 1 0 0 0 1-1V1a1 1 0 1 0-2 0v8a1 1 0 0 0 1 1M20.828 15.828a.999.999 0 0 0 .707-1.707l-5.656-5.656a.999.999 0 1 0-1.414 1.414l5.656 5.656a.997.997 0 0 0 .707.293M10 33H2a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M60.12 8.465l-5.656 5.656a.999.999 0 1 0 1.414 1.414l5.656-5.656a.999.999 0 1 0-1.414-1.414M74 33h-8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M43 66H33a1 1 0 1 0 0 2h10a1 1 0 1 0 0-2"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_10.svg b/app/assets/images/illustrations/convdev/i2p_step_10.svg
deleted file mode 100644
index 588ecd81414b94d690de5ea4a1d590e2cc6b77c5..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_10.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M5 43a1 1 0 1 0 2 0v-4h4a1 1 0 1 0 0-2H7v-4a1 1 0 1 0-2 0v4H1a1 1 0 1 0 0 2h4v4M75 37h-4v-4a1 1 0 1 0-2 0v4h-4a1 1 0 1 0 0 2h4v4a1 1 0 1 0 2 0v-4h4a1 1 0 1 0 0-2M21 38a1 1 0 0 0 .47.848l8 5a.999.999 0 0 0 1.061-1.696L23.887 38l6.644-4.152a1 1 0 1 0-1.061-1.695l-8 5A.998.998 0 0 0 21 38M55 38a1 1 0 0 0-.47-.848l-8-5a.999.999 0 1 0-1.061 1.695L52.113 38l-6.644 4.152a1 1 0 1 0 1.061 1.696l8-5A1 1 0 0 0 55 38M41.803 26.05a1 1 0 0 0-1.256.65l-7 22a1.001 1.001 0 0 0 .953 1.303 1 1 0 0 0 .953-.697l7-22a1.001 1.001 0 0 0-.65-1.256M62 7c3.859 0 7 3.141 7 7v11a1 1 0 1 0 2 0V14c0-4.963-4.04-9-9-9H45.91c-.479-2.833-2.943-5-5.91-5-3.309 0-6 2.691-6 6s2.691 6 6 6c2.967 0 5.431-2.167 5.91-5H62m-22 3c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4M6 26a1 1 0 0 0 1-1V14c0-3.859 3.141-7 7-7h11.09l-3.293 3.293a.999.999 0 1 0 1.414 1.414l5-5a.999.999 0 0 0 0-1.414l-5-5a.999.999 0 1 0-1.414 1.414L25.09 5H14c-4.963 0-9 4.04-9 9v11a1 1 0 0 0 1 1M36 64c-2.967 0-5.431 2.167-5.91 5H14c-3.859 0-7-3.141-7-7V51a1 1 0 1 0-2 0v11c0 4.963 4.04 9 9 9h16.09c.478 2.833 2.942 5 5.91 5 3.309 0 6-2.691 6-6s-2.691-6-6-6m0 10c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4M70 50a1 1 0 0 0-1 1v11c0 3.859-3.141 7-7 7H50.91l3.293-3.293a.999.999 0 1 0-1.414-1.414l-5 5a.999.999 0 0 0 0 1.414l5 5a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414L50.91 71H62c4.963 0 9-4.04 9-9V51a1 1 0 0 0-1-1"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_2.svg b/app/assets/images/illustrations/convdev/i2p_step_2.svg
deleted file mode 100644
index 4280024c23ca1da3cfc0149bab8596d41aa1a40b..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_2.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M42.26 40.44a.989.989 0 0 0 1.109-.877l2.625-22.444a.997.997 0 0 0-.993-1.117h-14a1 1 0 0 0-.994 1.108l3.454 31.575a6.981 6.981 0 0 0-2.46 5.317c0 3.859 3.141 7 7 7s7-3.141 7-7-3.141-7-7-7c-.94 0-1.835.189-2.655.527l-3.23-29.527h11.761L41.383 39.33a1 1 0 0 0 .877 1.11m.741 13.562c0 2.757-2.243 5-5 5s-5-2.243-5-5 2.243-5 5-5 5 2.243 5 5"/><path d="M73.236 23.749a1 1 0 0 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_3.svg b/app/assets/images/illustrations/convdev/i2p_step_3.svg
deleted file mode 100644
index 7690f91b4207fa169e0f46ae531af5fd801e8ff2..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_3.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M12 8c0-3.309-2.691-6-6-6S0 4.691 0 8c0 2.967 2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909s2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909s2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909 0 3.309 2.691 6 6 6s6-2.691 6-6c0-2.967-2.167-5.431-5-5.91v-8.18c2.833-.478 5-2.942 5-5.91s-2.167-5.431-5-5.91v-8.18c2.833-.478 5-2.942 5-5.91s-2.167-5.431-5-5.91v-8.18c2.833-.479 5-2.943 5-5.91M2 8c0-2.206 1.794-4 4-4s4 1.794 4 4-1.794 4-4 4-4-1.794-4-4m8 60c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4m0-20c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4m0-20c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4M21 6h54a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M21 12h35a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 24H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M21 32h34a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 44H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M21 52h34a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 64H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M55 70H21a1 1 0 1 0 0 2h34a1 1 0 1 0 0-2"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_4.svg b/app/assets/images/illustrations/convdev/i2p_step_4.svg
deleted file mode 100644
index ba21b9e2c3a9b2aa9ac2e11f2a2a24e20fd0f45f..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_4.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M67.7 10h-6.751C60.442 4.402 55.728 0 50 0c-6.06 0-11 4.935-11 11s4.935 11 11 11c5.728 0 10.442-4.402 10.949-10H67.7c1.269 0 2.3.987 2.3 2.2v57.6c0 1.213-1.031 2.2-2.3 2.2H8.3C7.031 74 6 73.013 6 71.8V14.2C6 12.987 7.031 12 8.3 12h15.15a1 1 0 1 0 0-2H8.3C5.929 10 4 11.884 4 14.2v57.6C4 74.116 5.929 76 8.3 76h59.4c2.371 0 4.3-1.884 4.3-4.2V14.2c0-2.316-1.929-4.2-4.3-4.2M50 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9"/><path d="M21.293 29.29a.999.999 0 0 0 0 1.414l12.975 12.975-12.975 12.974a.999.999 0 1 0 1.414 1.414l13.682-13.682a.999.999 0 0 0 0-1.414L22.707 29.29a.999.999 0 0 0-1.414 0M54 59a1 1 0 1 0 0-2H42a1 1 0 1 0 0 2h12"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_5.svg b/app/assets/images/illustrations/convdev/i2p_step_5.svg
deleted file mode 100644
index 3c8f8422a979a14b056d112c31cd3d38277ad14a..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_5.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M48.949 37C48.442 31.402 43.728 27 38 27s-10.442 4.402-10.949 10h-13.05a1 1 0 1 0 0 2h13.05c.507 5.598 5.221 10 10.949 10s10.442-4.402 10.949-10h12.24a1 1 0 1 0 0-2h-12.24M38 47c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9"/><path d="M73.236 23.749a1 1 0 0 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_6.svg b/app/assets/images/illustrations/convdev/i2p_step_6.svg
deleted file mode 100644
index 933860798ada9d23a838bd22a958ef9c5d800dda..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_6.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M14.267 7.32l-4.896 5.277-1.702-1.533a.999.999 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36M31 9h44a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2M31 15h24a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2"/><path d="M11 0C4.93 0 0 4.935 0 11s4.935 11 11 11 11-4.935 11-11S17.065 0 11 0m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M14.267 34.32l-4.896 5.277-1.702-1.533a1 1 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36M75 34H31a1 1 0 1 0 0 2h44a1 1 0 1 0 0-2M31 42h24a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2"/><path d="M11 27C4.93 27 0 31.935 0 38s4.935 11 11 11 11-4.935 11-11-4.935-11-11-11m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M14.267 61.32l-4.896 5.277-1.702-1.533a1 1 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36"/><path d="M11 54C4.93 54 0 58.935 0 65s4.935 11 11 11 11-4.935 11-11-4.935-11-11-11m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M75 61H31a1 1 0 1 0 0 2h44a1 1 0 1 0 0-2M55 67H31a1 1 0 1 0 0 2h24a1 1 0 1 0 0-2"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_7.svg b/app/assets/images/illustrations/convdev/i2p_step_7.svg
deleted file mode 100644
index d97c8f7c2d44211495866a8c437a6c1980519bf5..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_7.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M73.236 23.749a1 1 0 1 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/><path d="M27.19 32.17a.997.997 0 0 0-1.366-.364L13.17 39.132a1 1 0 0 0 0 1.73l12.654 7.326a1 1 0 0 0 1.002-1.73l-11.159-6.461 11.159-6.461a.998.998 0 0 0 .364-1.366M48.808 47.827a1 1 0 0 0 1.366.364l12.654-7.326a1 1 0 0 0 0-1.73l-12.654-7.326a1 1 0 0 0-1.002 1.73L60.331 40l-11.159 6.461a.998.998 0 0 0-.364 1.366M42.71 23.06L31.398 56.29a1 1 0 0 0 1.892.645l11.312-33.23a1 1 0 0 0-1.892-.645"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_8.svg b/app/assets/images/illustrations/convdev/i2p_step_8.svg
deleted file mode 100644
index 919bbeff319e829182bff428a4d4c157f182378e..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_8.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M62.44 54.765l-9.912-11.09c.315-3.881.481-7.241.508-10.271-.029-13.871-3.789-23.05-13.413-32.746-.855-.859-2.411-.828-3.294.059-7.594 7.65-11.139 13.934-12.575 22.3a6.94 6.94 0 0 0-4.699 2.039c-1.321 1.321-2.05 3.079-2.05 4.949s.729 3.628 2.051 4.949c1.321 1.322 3.079 2.051 4.949 2.051s3.628-.729 4.949-2.051a6.951 6.951 0 0 0 2.051-4.949 6.955 6.955 0 0 0-2.051-4.949c-.9-.9-2-1.517-3.205-1.824 1.373-7.859 4.764-13.818 11.999-21.11.128-.13.356-.158.456-.059 9.207 9.274 12.805 18.06 12.832 31.33-.026 3.079-.202 6.527-.536 10.54a.997.997 0 0 0 .25.749l10.166 11.379c.062.076.109.23.093.32l-4.547 17.407c-.004.015-.009.036-.079.106a.403.403 0 0 1-.2.106l-3.577.002c-.144-.009-.265-.077-.309-.153l-5.425-10.328a1.002 1.002 0 0 0-.886-.535H30.024c-.371 0-.713.206-.886.535l-5.407 10.303-.069.072a.366.366 0 0 1-.199.105l-3.588.001c-.179-.009-.304-.123-.33-.227l-4.531-17.338a.525.525 0 0 1 .049-.34L25.26 44.682a1 1 0 0 0-1.492-1.332L13.539 54.803c-.448.554-.63 1.312-.474 2.084l4.544 17.396c.253.963 1.146 1.669 2.218 1.719h3.636c.581 0 1.187-.261 1.615-.693.114-.114.286-.286.406-.528l5.144-9.793h14.754l5.16 9.822c.396.697 1.124 1.143 2.01 1.192l3.712-.003a2.396 2.396 0 0 0 1.544-.694c.313-.316.504-.646.598-1.022l4.557-17.451a2.502 2.502 0 0 0-.518-2.066M29.01 30.001c0 1.335-.521 2.591-1.465 3.535s-2.2 1.465-3.535 1.465-2.591-.521-3.535-1.465-1.465-2.2-1.465-3.535.521-2.591 1.465-3.535 2.2-1.465 3.535-1.465 2.591.521 3.535 1.465 1.465 2.2 1.465 3.535"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_9.svg b/app/assets/images/illustrations/convdev/i2p_step_9.svg
deleted file mode 100644
index 2d1b10d430d238530310836fcd6de35936089d98..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_9.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M68 67c-1.725 0-3.36.541-4.723 1.545A12.998 12.998 0 0 0 52 62c-2.734 0-5.359.853-7.555 2.43L42.159 49h1.228l3.829 7.645c.339.598.962.979 1.724 1.022l2.812-.003a2.07 2.07 0 0 0 1.316-.595c.264-.266.433-.559.514-.882l3.433-13.145a2.138 2.138 0 0 0-.449-1.763l-7.385-8.268c.231-2.875.354-5.376.374-7.641C49.532 14.863 46.684 7.908 39.393.564c-.737-.742-2.072-.715-2.829.044-5.617 5.659-8.309 10.336-9.446 16.463a5.95 5.95 0 0 0-3.36 1.686C22.624 19.891 22 21.397 22 23s.624 3.109 1.758 4.242C24.891 28.376 26.397 29 28 29s3.109-.624 4.242-1.758C33.376 26.109 34 24.603 34 23s-.624-3.109-1.758-4.242a5.952 5.952 0 0 0-3.098-1.648c1.095-5.538 3.637-9.855 8.83-15.14 6.874 6.924 9.561 13.485 9.581 23.392-.021 2.316-.151 4.903-.402 7.91a.999.999 0 0 0 .25.749l7.663 8.572-3.391 13.07-2.695.036-4.081-8.15a1.001 1.001 0 0 0-.895-.553h-12.01c-.379 0-.725.214-.895.553l-4.04 8.114-2.707.015-3.427-13.07 7.671-8.588a1 1 0 0 0-1.492-1.332l-7.7 8.623c-.383.47-.54 1.116-.406 1.787l3.419 13.08c.216.829.98 1.438 1.907 1.48h2.735c.508 0 1.016-.218 1.391-.595.091-.09.242-.241.358-.475l3.804-7.597h1.228l-2.286 15.43a12.914 12.914 0 0 0-7.555-2.43c-4.685 0-8.979 2.53-11.277 6.545a7.943 7.943 0 0 0-4.723-1.545c-4.411 0-8 3.589-8 8a1 1 0 0 0 1 1h74a1 1 0 0 0 1-1c0-4.411-3.589-8-8-8m-36-44a3.973 3.973 0 0 1-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0-.756-.756-1.172-1.76-1.172-2.828s.416-2.072 1.172-2.828 1.76-1.172 2.828-1.172 2.072.416 2.828 1.172 1.172 1.76 1.172 2.828m-29.917 51a6.01 6.01 0 0 1 5.917-5c1.638 0 3.17.652 4.313 1.836a.998.998 0 0 0 1.634-.289 11.011 11.011 0 0 1 10.05-6.547c2.836 0 5.532 1.085 7.593 3.055a1.001 1.001 0 0 0 1.681-.576l2.588-17.479h4.275l2.589 17.479a.999.999 0 1 0 1.681.576 10.945 10.945 0 0 1 7.593-3.055c4.343 0 8.288 2.57 10.05 6.547a.998.998 0 0 0 1.634.289 5.948 5.948 0 0 1 4.313-1.836 6.01 6.01 0 0 1 5.917 5H2.076"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/epics.svg b/app/assets/images/illustrations/epics.svg
deleted file mode 100644
index 1a37e6bba5fa8d7978e0b1ca3210529154fa0d68..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/epics.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="300" viewBox="0 0 430 300"><g fill="none" fill-rule="evenodd"><g transform="translate(75 53)"><rect width="284" height="208" y="5" fill="#F9F9F9" rx="10"/><rect width="284" height="208" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M10 4a6 6 0 0 0-6 6v188a6 6 0 0 0 6 6h264a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h264c5.523 0 10 4.477 10 10v188c0 5.523-4.477 10-10 10H10c-5.523 0-10-4.477-10-10V10C0 4.477 4.477 0 10 0z"/><path fill="#EEE" fill-rule="nonzero" d="M25.168 153.995c3.837-.215 7.173.028 10.119.691a3 3 0 1 0 1.318-5.853c-3.509-.79-7.4-1.074-11.773-.828a3 3 0 1 0 .336 5.99zm19.043 4.66c2.401 1.704 4.388 3.61 7.569 7.083a3 3 0 0 0 4.424-4.054c-3.448-3.763-5.686-5.911-8.522-7.923a3 3 0 1 0-3.471 4.894zm15.575 15.173c3.181 2.675 6.52 4.665 10.397 6.039a3 3 0 0 0 2.004-5.655c-3.162-1.121-5.884-2.743-8.54-4.976a3 3 0 1 0-3.861 4.592zm22.133 8.148c1.02.037 2.067.045 3.143.023a72.664 72.664 0 0 0 8.346-.638 3 3 0 1 0-.812-5.945c-2.442.334-4.996.53-7.658.585a48.55 48.55 0 0 1-2.796-.021 3 3 0 0 0-.223 5.996zm22.778-3.286c3.9-1.37 7.427-3.15 10.54-5.305a3 3 0 0 0-3.415-4.933c-2.665 1.845-5.712 3.382-9.114 4.578a3 3 0 0 0 1.989 5.66zm19.156-13.62a33.752 33.752 0 0 0 5.276-10.817 3 3 0 1 0-5.773-1.633 27.753 27.753 0 0 1-4.341 8.9 3 3 0 1 0 4.838 3.55zm6.577-22.657c-.187-3.817-.926-7.71-2.204-11.596a3 3 0 0 0-5.7 1.874c1.113 3.384 1.75 6.745 1.91 10.016a3 3 0 1 0 5.994-.294zm-7.097-22.26c-1.897-3.2-4.152-6.325-6.748-9.344a3 3 0 0 0-4.55 3.913c2.372 2.756 4.421 5.597 6.136 8.49a3 3 0 0 0 5.162-3.06zm-11.546-17.793c-.938-3.025-1.402-6.42-1.365-9.976a3 3 0 0 0-6-.063c-.043 4.163.506 8.177 1.634 11.816a3 3 0 1 0 5.731-1.777zm.053-20.107c.905-3.341 2.22-6.538 3.904-9.448a3 3 0 0 0-5.194-3.004c-1.948 3.368-3.463 7.048-4.501 10.884a3 3 0 1 0 5.791 1.568zm10.134-17.305c2.475-2.28 5.265-4.09 8.335-5.374a3 3 0 1 0-2.314-5.536c-3.725 1.558-7.105 3.75-10.086 6.497a3 3 0 1 0 4.065 4.413zm18.177-7.586c3.202-.18 6.599.092 10.18.843a3 3 0 0 0 1.23-5.872c-4.086-.857-8.009-1.172-11.747-.962a3 3 0 1 0 .337 5.99zm20.047 3.95c3.068 1.268 6.232 2.842 9.487 4.728a3 3 0 0 0 3.009-5.191c-3.48-2.017-6.883-3.71-10.204-5.083a3 3 0 1 0-2.292 5.545zm19.578 9.955c3.711 1.586 7.376 2.77 10.997 3.565a3 3 0 0 0 1.286-5.86c-3.248-.713-6.555-1.782-9.925-3.222a3 3 0 1 0-2.358 5.517zm22.591 4.789c3.94-.04 7.808-.553 11.61-1.513a3 3 0 1 0-1.468-5.817 43.358 43.358 0 0 1-10.203 1.33 3 3 0 0 0 .061 6zm22.52-5.558c3.335-1.637 6.607-3.613 9.845-5.916a3 3 0 1 0-3.477-4.89c-2.984 2.122-5.98 3.931-9.011 5.42a3 3 0 1 0 2.643 5.386zm18.678-13.054a3 3 0 0 1-4.02-4.454 130.547 130.547 0 0 0 5.31-5.088 3 3 0 1 1 4.265 4.22 136.507 136.507 0 0 1-5.555 5.322zm-48.722 25.641a3 3 0 1 1 4.314-4.17c3.056 3.16 5.075 6.744 6.172 10.754a3 3 0 0 1-5.787 1.584c-.834-3.047-2.35-5.739-4.699-8.168zm5.347 18.049a3 3 0 1 1 5.978.52c-.282 3.232-.805 6.273-1.832 11.206a3 3 0 0 1-5.874-1.222c.981-4.717 1.473-7.572 1.728-10.504zm-3.777 21.555a3 3 0 0 1 5.953.747c-.5 3.988-.397 7.09.399 9.67a3 3 0 1 1-5.733 1.769c-1.087-3.52-1.217-7.426-.62-12.186zm7.393 22.444a3 3 0 0 1 4.461-4.013c2.703 3.005 5.224 5.296 7.594 6.947a3 3 0 0 1-3.429 4.924c-2.775-1.932-5.632-4.53-8.626-7.858zm20.352 12.28a3 3 0 1 1 .334-5.99c2.77.154 5.453-.554 9.224-2.254a3 3 0 0 1 2.466 5.47c-4.57 2.06-8.103 2.993-12.024 2.775zm21.784-7.058a3 3 0 0 1-1.815-5.719c4.227-1.342 8.24-1.61 12.496-.572a3 3 0 0 1-1.421 5.83c-3.116-.76-6.025-.566-9.26.46zM106.53 56.038a3 3 0 1 1-3.45 4.909c-1.074-.755-6.723-6.044-8.083-7.204a68.019 68.019 0 0 0-.332-.281 3 3 0 1 1 3.865-4.59l.362.306c1.643 1.402 6.971 6.391 7.638 6.86zM88.536 42.422a3 3 0 0 1-2.285 5.548c-3.14-1.293-5.78-1.34-8.105-.05a3 3 0 0 1-2.91-5.247c4.087-2.266 8.597-2.187 13.3-.25zM66.698 48.73a3 3 0 0 1 2.029 5.647c-4.432 1.592-8.786.835-13.166-1.88a3 3 0 1 1 3.16-5.1c2.93 1.816 5.425 2.25 7.977 1.333zm-15.636-8.038a3 3 0 0 1-4.352 4.13c-.911-.96-1.85-1.98-3.061-3.32-.295-.325-2.437-2.703-3.07-3.4-.47-.518-.9-.988-1.313-1.436a3 3 0 0 1 4.41-4.068c.425.46.866.942 1.346 1.47.642.709 2.79 3.092 3.076 3.41a180.865 180.865 0 0 0 2.964 3.214z"/><path fill="#E1DBF1" d="M254.66 72.196l2-3.464a2 2 0 1 0-3.464-2l-2 3.464-3.464-2a2 2 0 0 0-2 3.464l3.464 2-2 3.464a2 2 0 0 0 3.464 2l2-3.464 3.464 2a2 2 0 1 0 2-3.464l-3.464-2zm-151.904 78.732l2.829-2.828a2 2 0 0 0-2.829-2.829l-2.828 2.829-2.828-2.829a2 2 0 0 0-2.829 2.829l2.829 2.828-2.829 2.829a2 2 0 1 0 2.829 2.828l2.828-2.828 2.828 2.828a2 2 0 1 0 2.829-2.828l-2.829-2.829z"/><path fill="#6B4FBB" d="M210.66 173.66l3.464-2a2 2 0 1 0-2-3.464l-3.464 2-2-3.464a2 2 0 0 0-3.464 2l2 3.464-3.464 2a2 2 0 1 0 2 3.464l3.464-2 2 3.464a2 2 0 1 0 3.464-2l-2-3.464z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M27 181a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm0-4a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M138 85a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-4a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M200 57a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-4a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/><path fill="#FC6D26" fill-rule="nonzero" d="M222.647 121.647v5h5v-5h-5zm0-4h5a4 4 0 0 1 4 4v5a4 4 0 0 1-4 4h-5a4 4 0 0 1-4-4v-5a4 4 0 0 1 4-4z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M103.647 28.647v5h5v-5h-5zm0-4h5a4 4 0 0 1 4 4v5a4 4 0 0 1-4 4h-5a4 4 0 0 1-4-4v-5a4 4 0 0 1 4-4z"/><path fill="#FC6D26" fill-rule="nonzero" d="M85 103.488L81.841 108h6.318L85 103.488zm6.436 2.218A4 4 0 0 1 88.159 112H81.84a4 4 0 0 1-3.277-6.294l3.16-4.512a4 4 0 0 1 6.553 0l3.159 4.512z"/></g><path fill="#F9F9F9" d="M334.376 99.43A48.805 48.805 0 0 0 366 111c27.062 0 49-21.938 49-49s-21.938-49-49-49-49 21.938-49 49c0 9.454 2.677 18.283 7.315 25.77l-3.05 11.306a2.5 2.5 0 0 0 3.064 3.065l10.047-2.71z"/><path fill="#FFF" d="M339.376 94.43A48.805 48.805 0 0 0 371 106c27.062 0 49-21.938 49-49S398.062 8 371 8s-49 21.938-49 49c0 9.454 2.677 18.283 7.315 25.77l-3.05 11.306a2.5 2.5 0 0 0 3.064 3.065l10.047-2.71z"/><path fill="#EEE" fill-rule="nonzero" d="M329.85 99.072a4.5 4.5 0 0 1-5.516-5.517l2.827-10.48C322.501 75.258 320 66.31 320 57c0-28.167 22.833-51 51-51s51 22.833 51 51-22.833 51-51 51c-11.859 0-23.096-4.064-32.102-11.37l-9.048 2.442zm10.817-6.169C349.091 100.027 359.737 104 371 104c25.957 0 47-21.043 47-47s-21.043-47-47-47-47 21.043-47 47c0 8.859 2.453 17.351 7.016 24.716l.456.737-3.277 12.144c.072.527.347.685.613.613l11.059-2.984.8.677z"/><g transform="translate(354 34)"><path fill="#E1DBF1" fill-rule="nonzero" d="M13 4a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-8zm0-4h8a5 5 0 0 1 5 5v1a5 5 0 0 1-5 5h-8a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M5 11a1 1 0 0 0 0 2h24a1 1 0 0 0 0-2H5zm0-4h24a5 5 0 0 1 0 10H5A5 5 0 0 1 5 7z"/><rect width="12" height="4" x="11" y="31" fill="#C3B8E3" rx="2"/><rect width="12" height="4" x="11" y="19" fill="#C3B8E3" rx="2"/><rect width="12" height="4" x="11" y="37" fill="#E1DBF1" rx="2"/><rect width="12" height="4" x="11" y="43" fill="#C3B8E3" rx="2"/><rect width="12" height="4" x="11" y="25" fill="#E1DBF1" rx="2"/></g><path fill="#F9F9F9" d="M344.238 225.072A38.83 38.83 0 0 1 368 217c21.54 0 39 17.46 39 39s-17.46 39-39 39-39-17.46-39-39a38.84 38.84 0 0 1 4.001-17.227l-3.737-13.85a2.5 2.5 0 0 1 3.065-3.064l11.91 3.213z"/><path fill="#FFF" d="M348.238 221.072A38.83 38.83 0 0 1 372 213c21.54 0 39 17.46 39 39s-17.46 39-39 39-39-17.46-39-39a38.84 38.84 0 0 1 4.001-17.227l-3.737-13.85a2.5 2.5 0 0 1 3.065-3.064l11.91 3.213z"/><path fill="#EEE" fill-rule="nonzero" d="M336.85 215.928a4.5 4.5 0 0 0-5.516 5.517l3.543 13.13A40.848 40.848 0 0 0 331 252c0 22.644 18.356 41 41 41s41-18.356 41-41-18.356-41-41-41a40.82 40.82 0 0 0-24.182 7.887l-10.968-2.96zm12.608 6.73A36.824 36.824 0 0 1 372 215c20.435 0 37 16.565 37 37s-16.565 37-37 37-37-16.565-37-37c0-5.747 1.31-11.304 3.795-16.343l.334-.677-3.934-14.577a.5.5 0 0 1 .613-.613l12.865 3.471.785-.604z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M356.097 255.962a7 7 0 0 0 8.81 10.88l1.093-.885v1.454a7 7 0 1 0 14 0v-1.454l1.092.885a7 7 0 1 0 8.81-10.88l-1.185-.96 1.455-.337a7 7 0 1 0-3.15-13.64l-1.4.323.623-1.278a7 7 0 0 0-12.583-6.137l-.662 1.356-.662-1.356a7 7 0 0 0-12.583 6.137l.623 1.278-1.4-.324a7 7 0 1 0-3.15 13.641l1.455.336-1.186.96zm5.464-.913a11.914 11.914 0 0 1-.444-1.95l-.19-1.362-4.2-.97a3 3 0 0 1 1.35-5.845l4.178.964.768-1.145c.373-.557.793-1.082 1.254-1.57l.95-1.006-1.877-3.849a3 3 0 0 1 5.393-2.63l1.892 3.879 1.363-.113a12.188 12.188 0 0 1 2.004 0l1.363.113 1.892-3.879a3 3 0 0 1 5.393 2.63l-1.877 3.849.95 1.006c.461.488.88 1.013 1.254 1.57l.768 1.145 4.178-.964a3 3 0 1 1 1.35 5.846l-4.2.97-.19 1.36a11.914 11.914 0 0 1-.444 1.95l-.413 1.302 3.36 2.72a3 3 0 1 1-3.776 4.663l-3.32-2.688-1.196.706a11.94 11.94 0 0 1-1.808.873l-1.286.492v4.295a3 3 0 1 1-6 0v-4.295l-1.286-.492a11.94 11.94 0 0 1-1.808-.873l-1.196-.706-3.32 2.688a3 3 0 1 1-3.776-4.663l3.36-2.72-.413-1.301z"/><path fill="#FC6D26" fill-rule="nonzero" d="M373 245.411a6 6 0 1 0 0 12 6 6 0 0 0 0-12zm0 4a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/><g><path fill="#F9F9F9" d="M94.624 162.43A48.805 48.805 0 0 1 63 174c-27.062 0-49-21.938-49-49s21.938-49 49-49 49 21.938 49 49c0 9.454-2.677 18.283-7.315 25.77l3.05 11.306a2.5 2.5 0 0 1-3.064 3.065l-10.047-2.71z"/><path fill="#FFF" stroke="#EEE" stroke-width="4" d="M89.624 157.43A48.805 48.805 0 0 1 58 169c-27.062 0-49-21.938-49-49s21.938-49 49-49 49 21.938 49 49c0 9.454-2.677 18.283-7.315 25.77l3.05 11.306a2.5 2.5 0 0 1-3.064 3.065l-10.047-2.71z"/><path fill="#EEE" fill-rule="nonzero" d="M99.15 162.072a4.5 4.5 0 0 0 5.516-5.517l-2.827-10.48C106.499 138.258 109 129.31 109 120c0-28.167-22.833-51-51-51S7 91.833 7 120s22.833 51 51 51c11.859 0 23.096-4.064 32.102-11.37l9.048 2.442zm-10.817-6.169C79.909 163.027 69.263 167 58 167c-25.957 0-47-21.043-47-47s21.043-47 47-47 47 21.043 47 47c0 8.859-2.453 17.351-7.016 24.716l-.456.737 3.277 12.144c-.072.527-.347.685-.613.613l-11.059-2.984-.8.677z"/><g fill-rule="nonzero"><path fill="#FEE1D3" d="M55.47 94.47l-16.148 6.688a4 4 0 0 0-2.164 2.164l-6.689 16.147a4 4 0 0 0 0 3.062l6.689 16.147a4 4 0 0 0 2.164 2.164l16.147 6.689a4 4 0 0 0 3.062 0l16.147-6.689a4 4 0 0 0 2.164-2.164l6.689-16.147a4 4 0 0 0 0-3.062l-6.689-16.147a4 4 0 0 0-2.164-2.164L58.53 94.469a4 4 0 0 0-3.062 0zM57 98.164l16.147 6.688L79.835 121l-6.688 16.147L57 143.835l-16.147-6.688L34.165 121l6.688-16.147L57 98.165zM57 107a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm12 4a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm4 12a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-4 11a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-12 6a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-12-6a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-4-11a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm4-11a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm12 20c6.075 0 11-4.925 11-11s-4.925-11-11-11-11 4.925-11 11 4.925 11 11 11zm0-4a7 7 0 1 1 0-14 7 7 0 0 1 0 14z"/><path fill="#FC6D26" d="M57 126.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0-3a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5z"/></g></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/gitlab_logo.svg b/app/assets/images/illustrations/gitlab_logo.svg
deleted file mode 100644
index 8dbd75a340e15259a8d96206ef1a994333673531..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/gitlab_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="492.509" height="453.68" viewBox="0 0 492.50943 453.67966"><g fill="none" fill-rule="evenodd"><path d="M491.589 259.398l-27.559-84.814L409.413 6.486c-2.81-8.648-15.045-8.648-17.856 0l-54.619 168.098H155.572L100.952 6.486c-2.81-8.648-15.046-8.648-17.856 0L28.478 174.584.921 259.398a18.775 18.775 0 0 0 6.82 20.992l238.513 173.29L484.77 280.39a18.777 18.777 0 0 0 6.82-20.992" fill="#fc6d26"/><path d="M246.255 453.68l90.684-279.096H155.57z" fill="#e24329"/><path d="M246.255 453.68L155.57 174.583H28.479z" fill="#fc6d26"/><path d="M28.479 174.584L.92 259.4a18.773 18.773 0 0 0 6.821 20.99l238.514 173.29z" fill="#fca326"/><path d="M28.479 174.584H155.57L100.952 6.487c-2.81-8.65-15.047-8.65-17.856 0z" fill="#e24329"/><path d="M246.255 453.68l90.684-279.096H464.03z" fill="#fc6d26"/><path d="M464.03 174.584l27.56 84.815a18.773 18.773 0 0 1-6.822 20.99L246.255 453.68z" fill="#fca326"/><path d="M464.03 174.584H336.94L391.557 6.487c2.811-8.65 15.047-8.65 17.856 0z" fill="#e24329"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/image_comment_light_cursor.svg b/app/assets/images/illustrations/image_comment_light_cursor.svg
deleted file mode 100644
index ac712ea0c96bc468bd0f424f8a9b23ab74c7e9a8..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/image_comment_light_cursor.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/image_comment_light_cursor@2x.svg b/app/assets/images/illustrations/image_comment_light_cursor@2x.svg
deleted file mode 100644
index 02943acd9d727b7db17fd479a87838fab8ca7e74..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/image_comment_light_cursor@2x.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/issues.svg b/app/assets/images/illustrations/issues.svg
deleted file mode 100644
index c8e0504732d66e8c4ede98cc9e1809aaeafa3003..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/issues.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="790 253 425 254" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="25" height="8.942" x="25" y="88.423" rx="2"/><mask id="h" width="25" height="8.942" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><path id="b" d="M16 29.801h43v61.603H16z"/><mask id="i" width="43" height="61.603" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><path id="c" d="M57 60.603l13.187 9.358c.449.32.876 1.015.955 1.568l3.575 24.863c.157 1.086-.253 1.257-.912.384L66 86.436l-9-6.955"/><mask id="j" width="17.75" height="36.731" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><path id="d" d="M.25 60.603l13.186 9.358c.45.32.876 1.015.956 1.568l3.575 24.863c.156 1.086-.253 1.257-.912.384l-7.806-10.34-9-6.955"/><mask id="k" width="17.75" height="36.731" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><path id="e" d="M16 29.801L35.786 1.456c.947-1.357 2.48-1.36 3.428 0L59 29.8"/><mask id="l" width="43" height="29.364" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><rect id="f" width="26.265" height="35.509" x="6.367" rx="13.133"/><mask id="m" width="26.265" height="35.509" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><rect id="g" width="16.837" height="22.386" x="4.082" rx="8.418"/><mask id="n" width="16.837" height="22.386" x="0" y="0" fill="#fff"><use xlink:href="#g"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(792 255)"><path d="M225.437 59.587c-.059.59-.132 1.27-.22 2.03a178.367 178.367 0 0 1-.965 7.07 1.5 1.5 0 1 0 2.963.465c.4-2.553.726-4.975.982-7.19a137.446 137.446 0 0 0 .297-2.832 1.5 1.5 0 1 0-2.989-.26c-.01.123-.033.365-.068.717zm-5.563 28.354a1.5 1.5 0 0 0 2.853.929c.975-2.997 1.849-6.283 2.628-9.797a1.5 1.5 0 1 0-2.928-.65c-.76 3.426-1.61 6.62-2.553 9.518zm-9.947 15.225a1.5 1.5 0 1 0 1.001 2.828c2.98-1.055 5.542-3.68 7.78-7.627a1.5 1.5 0 0 0-2.61-1.48c-1.915 3.378-3.995 5.508-6.171 6.279zm-19.488 4.417a1.5 1.5 0 1 0 1.164 2.765c3.12-1.314 6.272-2.324 9.258-2.981a1.5 1.5 0 1 0-.645-2.93c-3.167.697-6.491 1.763-9.777 3.146zm-17.208 11.043a1.5 1.5 0 0 0 2.066 2.175c2.282-2.169 4.866-4.162 7.676-5.946a1.5 1.5 0 0 0-1.608-2.533c-2.97 1.885-5.707 3.998-8.134 6.304zm-10.777 17.623a1.5 1.5 0 1 0 2.91.732c.768-3.054 2.041-5.977 3.78-8.748a1.5 1.5 0 0 0-2.54-1.595c-1.903 3.032-3.302 6.244-4.15 9.611zm-.265 20.444a1.5 1.5 0 1 0 2.977-.375c-.367-2.91-.58-6.137-.645-9.817a1.5 1.5 0 0 0-3 .053c.067 3.783.287 7.116.668 10.139zm6.219 19.472a1.5 1.5 0 0 0 2.652-1.403c-1.674-3.162-2.903-5.995-3.848-8.943a1.5 1.5 0 1 0-2.857.916c1.003 3.127 2.302 6.12 4.053 9.43zm7.566 12.77a595.837 595.837 0 0 1 2.73 4.475 1.5 1.5 0 0 0 2.569-1.551 626.463 626.463 0 0 0-2.744-4.495c.08.13-1.954-3.173-2.486-4.04a1.5 1.5 0 1 0-2.558 1.567c.534.87 2.571 4.178 2.489 4.045zm8.856 22.447a1.5 1.5 0 0 0 3-.039 32.214 32.214 0 0 0-1.837-10.326 1.5 1.5 0 0 0-2.828.999 29.212 29.212 0 0 1 1.665 9.366zm-5.483 18.028a1.5 1.5 0 0 0 2.497 1.662 36.203 36.203 0 0 0 4.488-9.416 1.5 1.5 0 0 0-2.868-.882 33.197 33.197 0 0 1-4.117 8.636z" fill="#FDE5D8"/><g transform="rotate(60 126.799 371.622)"><path stroke="#FDE5D8" stroke-width="3" d="M19 154l10-52.66m16 0L55 154" stroke-linecap="round"/><rect width="3" height="38.75" x="35" y="99.353" fill="#FDE5D8" rx="1.5"/><use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#h)" xlink:href="#a"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#i)" xlink:href="#b"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#j)" xlink:href="#c"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#k)" transform="matrix(-1 0 0 1 18.25 0)" xlink:href="#d"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#l)" xlink:href="#e"/><ellipse cx="28.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="34.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="40.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="46.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="37.5" cy="55.138" stroke="#FDE5D8" stroke-width="3" rx="10.5" ry="10.433"/><ellipse cx="37.5" cy="55.138" stroke="#FDE5D8" stroke-width="3" rx="5.5" ry="5.465"/></g><path fill="#EEE" d="M96.043 37.21c-.152 1.688.081 3.816.997 6.147a1.016 1.016 0 0 0 1.89-.74c-.791-2.014-.99-3.832-.865-5.226.01-.114.02-.186.024-.211a1.015 1.015 0 1 0-2.002-.333 5.06 5.06 0 0 0-.044.363zm11.487 15.683c.491.24 1.098.063 1.355-.394.257-.456.068-1.02-.424-1.26-1.866-.907-3.458-1.914-4.794-3.007a1.058 1.058 0 0 0-1.417.085.888.888 0 0 0 .091 1.317c1.458 1.192 3.183 2.283 5.19 3.26zm13.131 6.06a1.032 1.032 0 0 0 1.293-.7 1.06 1.06 0 0 0-.686-1.32 376.355 376.355 0 0 1-5.915-1.882 1.031 1.031 0 0 0-1.303.681 1.06 1.06 0 0 0 .668 1.33c1.729.569 2.905.94 5.943 1.891zm11.934 3.928c.45.246 1.022.098 1.28-.33a.872.872 0 0 0-.346-1.221c-1.494-.819-3.192-1.545-5.267-2.275-.486-.17-1.025.067-1.204.53-.18.464.07.978.555 1.149 1.984.697 3.59 1.384 4.982 2.147zm9.382 10.502c.205.494.81.742 1.349.554.54-.188.81-.74.605-1.234-.85-2.048-1.853-3.796-3.037-5.305-.337-.429-.99-.527-1.459-.218-.469.308-.575.906-.238 1.335 1.074 1.368 1.992 2.97 2.78 4.868zm2.632 13.642c.018.553.568.99 1.228.975.66-.016 1.18-.477 1.163-1.03-.073-2.204-.27-4.206-.622-6.12-.101-.547-.712-.923-1.365-.838-.652.084-1.1.597-.999 1.144.336 1.825.525 3.745.595 5.869z"/><path fill="#E5E5E5" d="M144.142 95.73a244.285 244.285 0 0 0-.142 5.254c-.007.553.396 1.008.902 1.016.506.008.923-.433.93-.985.02-1.467.056-2.681.142-5.211l.026-.767c.018-.552-.377-1.016-.882-1.036-.506-.02-.931.41-.95.963l-.026.766zm.797 19.471c.12.545.673.892 1.236.777.562-.116.921-.651.802-1.196-.417-1.9-.71-3.84-.897-5.864-.052-.554-.558-.964-1.131-.914-.573.05-.996.54-.945 1.094.195 2.102.5 4.121.935 6.103zm5.056 12.324c.296.454.953.61 1.467.348.514-.261.69-.841.395-1.295a40.725 40.725 0 0 1-2.79-4.991c-.227-.485-.855-.715-1.403-.515-.548.2-.81.755-.582 1.239a42.56 42.56 0 0 0 2.913 5.214zm4.814 7.701a33.475 33.475 0 0 0 3.543 3.531 1.021 1.021 0 0 0 1.393-.066.908.908 0 0 0-.07-1.326 31.562 31.562 0 0 1-3.34-3.328 59.092 59.092 0 0 1-.576-.682 1.02 1.02 0 0 0-1.386-.152.909.909 0 0 0-.16 1.32c.196.234.394.469.596.703zm15.825 11.677c.48.242 1.052.017 1.276-.501.224-.52.016-1.136-.464-1.378a49.756 49.756 0 0 1-4.986-2.872c-.453-.298-1.044-.144-1.32.345-.276.488-.133 1.126.32 1.424a51.568 51.568 0 0 0 5.174 2.982z"/><path fill="#EEE" d="M184.733 151.97c.553.141 1.108-.226 1.239-.82.131-.595-.21-1.192-.763-1.333a72.17 72.17 0 0 1-5.863-1.763c-.54-.188-1.12.13-1.296.712-.175.581.121 1.205.662 1.393a74.018 74.018 0 0 0 6.021 1.81zm13.2 2.028c.554.04 1.03-.445 1.065-1.083.035-.639-.386-1.188-.939-1.228a71.842 71.842 0 0 1-5.92-.676c-.55-.086-1.055.358-1.13.991-.074.634.31 1.217.86 1.303a73.28 73.28 0 0 0 6.065.693zm14.188-1.392c.55-.055.94-.457.871-.9-.068-.441-.569-.755-1.118-.7-1.917.192-3.893.32-5.91.382-.554.017-.985.392-.963.837.021.445.487.792 1.04.774a88.939 88.939 0 0 0 6.08-.393zm14.245-2.657c.53-.22.776-.816.55-1.332a1.053 1.053 0 0 0-1.367-.535 44.421 44.421 0 0 1-5.777 1.923 1.012 1.012 0 0 0-.736 1.243c.15.542.721.863 1.277.717a46.532 46.532 0 0 0 6.054-2.016zm11.483-9.532c.292-.435.148-1.006-.32-1.277-.47-.27-1.087-.138-1.379.297-.957 1.424-2.225 2.734-3.784 3.92a.88.88 0 0 0-.138 1.304c.35.396.98.453 1.408.128 1.723-1.31 3.136-2.771 4.213-4.372zm7.824-9.73a.965.965 0 0 0 .09-1.358.958.958 0 0 0-1.355-.09 44.935 44.935 0 0 0-4.17 4.163.965.965 0 0 0 .089 1.359.957.957 0 0 0 1.354-.089 43.05 43.05 0 0 1 3.991-3.985zm11.808-7.817c.476-.257.657-.858.405-1.342a.967.967 0 0 0-1.319-.412 67.097 67.097 0 0 0-5.123 3.059c-.451.298-.58.913-.287 1.373.294.46.898.59 1.35.292a65.257 65.257 0 0 1 4.974-2.97zm12.795-5.948c.55-.169.851-.724.672-1.241-.179-.518-.77-.8-1.32-.632a92.308 92.308 0 0 0-5.975 2.054c-.536.205-.794.78-.576 1.283.218.504.83.746 1.366.541a90.115 90.115 0 0 1 5.833-2.005z"/><circle cx="145" cy="90" r="5" fill="#FFF" stroke="#EEE" stroke-width="2"/><circle cx="238" cy="138" r="5" fill="#FFF" stroke="#EEE" stroke-width="2"/><path stroke="#B5A7DD" stroke-width="3" d="M20.06 56s-17.47 33-12 53c5.47 20 17 32 38 44s32.44-5 60.94 6 29 43 29 43" stroke-linecap="round" stroke-dasharray="8 10"/><g stroke="#EEE" stroke-width="3" transform="translate(108 173)"><path fill="#FFF" d="M154 77c0-42.526-34.474-77-77-77S0 34.474 0 77" stroke-linecap="round"/><circle cx="108" cy="41" r="16"/><circle cx="42.5" cy="30.5" r="8.5"/><circle cx="22" cy="58" r="5"/></g><g fill="#FC8A51" transform="rotate(15 101.633 923.121)"><path d="M.398 11.298h2.388c0-4.234 3.385-7.666 7.56-7.666V1.21C4.853 1.21.399 5.727.399 11.298z"/><ellipse cx="10.745" cy="2.018" rx="1.99" ry="2.018"/></g><g fill="#FC8A51" transform="scale(-1 1) rotate(-15 -102.031 920.099)"><path d="M.398 11.298h2.388c0-4.234 3.385-7.666 7.56-7.666V1.21C4.853 1.21.399 5.727.399 11.298z"/><ellipse cx="10.745" cy="2.018" rx="1.99" ry="2.018"/></g><g transform="rotate(15 71.738 842.306)"><g fill="#FC8A51" transform="translate(29.449 11.298)"><rect width="7.959" height="2" x=".796" y="8.877" rx="1"/><rect width="7.959" height="2" x=".796" y="16.14" transform="rotate(15 4.776 17.14)" rx="1"/><rect width="7.959" height="2" x=".915" y="1.807" transform="rotate(-15 4.895 2.807)" rx="1"/></g><g fill="#FC8A51" transform="matrix(-1 0 0 1 9.551 11.298)"><rect width="7.959" height="2" x=".796" y="8.877" rx="1"/><rect width="7.959" height="2" x=".796" y="16.14" transform="rotate(15 4.776 17.14)" rx="1"/><rect width="7.959" height="2" x=".915" y="1.807" transform="rotate(-15 4.895 2.807)" rx="1"/></g><use stroke="#FC8A51" stroke-width="6" mask="url(#m)" xlink:href="#f"/><path fill="#FC8A51" d="M7.163 12.912h23.878v3H7.163z"/></g><g fill="#EEE" transform="scale(-1 1) rotate(15 -60.75 -335.206)"><path d="M.255 7.123h1.53a4.84 4.84 0 0 1 4.848-4.834V.763C3.11.763.255 3.611.255 7.123z"/><ellipse cx="6.888" cy="1.272" rx="1.276" ry="1.272"/></g><g fill="#EEE" transform="rotate(-15 60.494 -337.144)"><path d="M.255 7.123h1.53a4.84 4.84 0 0 1 4.848-4.834V.763C3.11.763.255 3.611.255 7.123z"/><ellipse cx="6.888" cy="1.272" rx="1.276" ry="1.272"/></g><g transform="scale(-1 1) rotate(15 -79.491 -386.955)"><g fill="#EEE" transform="translate(18.878 7.123)"><rect width="5.102" height="2" x=".51" y="5.596" rx="1"/><rect width="5.102" height="2" x=".51" y="10.175" transform="rotate(15 3.061 11.175)" rx="1"/><rect width="5.102" height="2" x=".587" y="1.139" transform="rotate(-15 3.138 2.14)" rx="1"/></g><g fill="#EEE" transform="matrix(-1 0 0 1 6.122 7.123)"><rect width="5.102" height="2" x=".51" y="5.596" rx="1"/><rect width="5.102" height="2" x=".51" y="10.175" transform="rotate(15 3.061 11.175)" rx="1"/><rect width="5.102" height="2" x=".587" y="1.139" transform="rotate(-15 3.138 2.14)" rx="1"/></g><use stroke="#EEE" stroke-width="4" mask="url(#n)" xlink:href="#g"/><path fill="#EEE" d="M4.592 8.14h15.306v2H4.592z"/></g><g fill="#FFF" transform="translate(0 103)"><circle cx="8.5" cy="8.5" r="8.5" stroke="#B5A7DD" stroke-width="4"/><circle cx="171.5" cy="20.5" r="6.5"/></g><g transform="translate(39 142)"><ellipse cx="12.5" cy="12.5" fill="#FFF" stroke="#6B4FBB" stroke-width="4" rx="12.5" ry="12.5"/><path fill="#FC8A51" d="M10.732 13.475l-1.766-1.767a1.5 1.5 0 1 0-2.122 2.122l2.826 2.826h.001v.001c.59.59 1.535.587 2.119.003l6.37-6.37a1.504 1.504 0 0 0-.003-2.118 1.494 1.494 0 0 0-2.118-.004l-5.307 5.307z"/></g><circle cx="171.5" cy="122.5" r="6.5" fill="#FFF" stroke="#FC8A51" stroke-width="3"/><circle cx="22" cy="52" r="6" fill="#FFF" stroke="#B5A7DD" stroke-width="3"/><path fill="#FFF" stroke="#B5A7DD" stroke-width="3.6" d="M188.151 141.596c8.704-7.746 11.013-20.925 4.862-31.578-7.02-12.16-22.405-16.422-34.362-9.518-11.958 6.904-15.96 22.358-8.939 34.518 6.236 10.8 19.068 15.37 30.238 11.42l10.899 18.879a4.765 4.765 0 0 0 6.508 1.748 4.768 4.768 0 0 0 1.74-6.51l-10.946-18.959zm-8.434-4.609c7.857-4.536 10.487-14.692 5.873-22.683-4.613-7.991-14.723-10.791-22.58-6.255-7.858 4.537-10.488 14.693-5.875 22.684 4.614 7.99 14.724 10.791 22.582 6.254z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/job_not_triggered.svg b/app/assets/images/illustrations/job_not_triggered.svg
deleted file mode 100644
index e13c1cb0a7d3c4b5602fae1cdd32356557a6e9e0..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/job_not_triggered.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 310 141" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><g fill-rule="nonzero"><path fill="#e5e5e5" d="M48 69c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 48 69m14 0c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 62 69"/><g fill="#31af64"><path d="M19 88C8.507 88 0 79.493 0 69s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19m0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15"/><path d="M17.07 71.02l-2.829-2.828a1.995 1.995 0 0 0-2.828 0 1.997 1.997 0 0 0 0 2.83l4.243 4.243a1.993 1.993 0 0 0 2.823.005l7.79-7.79a1.998 1.998 0 0 0-.007-2.822 1.99 1.99 0 0 0-2.822-.006l-6.37 6.37v-.001"/></g></g><g transform="translate(187)"><rect width="116" height="134" y="7" fill="#f9f9f9" rx="10"/><rect width="116" height="134" x="5" y="2" fill="#fff" rx="10"/><path fill="#eee" fill-rule="nonzero" d="M15 4a8 8 0 0 0-8 8v114a8 8 0 0 0 8 8h96a8 8 0 0 0 8-8V12a8 8 0 0 0-8-8H15m0-4h96c6.627 0 12 5.373 12 12v114c0 6.627-5.373 12-12 12H15c-6.627 0-12-5.373-12-12V12C3 5.373 8.373 0 15 0"/><g transform="translate(23 25)"><g fill="#e1dbf1"><rect width="16" height="4" rx="2"/><rect width="16" height="4" x="32" y="12" rx="2"/></g><rect width="16" height="4" x="44" fill="#eee" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#e1dbf1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="20" fill="#fee1d3" rx="2" id="a"/><rect width="8" height="4" x="32" y="36" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="64" fill="#fef0e8" rx="2" id="b"/><rect width="12" height="4" x="16" y="48" fill="#e1dbf1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#fc6d26" rx="2"/><g fill="#e1dbf1"><rect width="4" height="4" x="56" y="36" rx="2"/><rect width="4" height="4" x="64" y="60" rx="2"/></g><rect width="4" height="4" x="72" y="60" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="32" fill="#fc6d26" rx="2" id="c"/><g fill="#eee"><rect width="28" height="4" y="36" rx="2"/><rect width="28" height="4" x="44" y="48" rx="2"/></g><rect width="28" height="4" x="32" y="60" fill="#efedf8" rx="2"/><rect width="28" height="4" y="12" fill="#6b4fbb" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#c3b8e3" rx="2"/><rect width="8" height="4" y="24" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6b4fbb" rx="2"/><rect width="12" height="4" y="48" fill="#fc6d26" rx="2"/><g fill="#fef0e8"><rect width="12" height="4" y="60" rx="2"/><rect width="12" height="4" x="16" y="60" rx="2"/></g></g><g transform="translate(23 97)"><rect width="16" height="4" fill="#efedf8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#fc6d26" rx="2"/><rect width="16" height="4" x="44" fill="#6b4fbb" rx="2"/><use xlink:href="#a"/><rect width="8" height="4" x="38" y="12" fill="#fef0e8" rx="2"/><use xlink:href="#b"/><use xlink:href="#c"/><rect width="14" height="4" y="12" fill="#eee" rx="2"/></g></g><g fill-rule="nonzero"><path fill="#eee" d="M109 101a2 2 0 1 1 0-4c2.524 0 5-.346 7.379-1.02a2 2 0 0 1 1.091 3.849 31.007 31.007 0 0 1-8.47 1.172m18.09-5.825a31.174 31.174 0 0 0 6.187-5.899 2 2 0 1 0-3.131-2.489 27.133 27.133 0 0 1-5.393 5.142 2.001 2.001 0 0 0 2.337 3.247m11.297-15.288a30.923 30.923 0 0 0 1.576-8.407 2 2 0 1 0-3.996-.188 26.875 26.875 0 0 1-1.372 7.32 2 2 0 1 0 3.791 1.275m.283-18.89a30.855 30.855 0 0 0-3.593-7.763 2 2 0 1 0-3.362 2.166 26.905 26.905 0 0 1 3.128 6.757 2 2 0 0 0 3.828-1.16M127.875 45.41a30.973 30.973 0 0 0-7.435-4.228 2 2 0 0 0-1.477 3.717 26.936 26.936 0 0 1 6.474 3.682 2 2 0 0 0 2.438-3.172m-17.834-6.391a31.09 31.09 0 0 0-8.5.886 2 2 0 0 0 .959 3.883 27.06 27.06 0 0 1 7.408-.771 2 2 0 1 0 .132-3.998m-18.272 5.207a31.139 31.139 0 0 0-6.383 5.688 2 2 0 1 0 3.045 2.593 27.152 27.152 0 0 1 5.564-4.957 2 2 0 1 0-2.226-3.324M79.96 59.121a30.864 30.864 0 0 0-1.862 8.349 2 2 0 1 0 3.987.323c.203-2.506.75-4.946 1.62-7.268a2 2 0 1 0-3.746-1.404m-.923 18.873a30.827 30.827 0 0 0 3.327 7.881 2.001 2.001 0 0 0 3.435-2.051 26.785 26.785 0 0 1-2.895-6.859 2 2 0 0 0-3.865 1.029M89.301 93.94a31.008 31.008 0 0 0 7.286 4.476 2 2 0 1 0 1.603-3.665 26.983 26.983 0 0 1-6.346-3.899 2 2 0 0 0-2.543 3.087m17.61 6.991a2 2 0 0 1 .265-3.991c.601.04 1.205.06 1.812.06a1.999 1.999 0 1 1-.001 3.999c-.695 0-1.387-.023-2.076-.069"/><path fill="#fc0" d="M117.78 63.798c.241.268.288.563.14.884l-10.848 23.24c-.174.334-.455.502-.843.502-.054 0-.148-.014-.282-.04a.855.855 0 0 1-.512-.382.761.761 0 0 1-.09-.603l3.957-16.232-8.156 2.03a1.08 1.08 0 0 1-.241.02.93.93 0 0 1-.623-.222c-.24-.2-.328-.462-.26-.783l4.04-16.574a.858.858 0 0 1 .321-.462.917.917 0 0 1 .563-.18h6.59c.254 0 .468.083.642.25a.797.797 0 0 1 .261.593.818.818 0 0 1-.1.362l-3.435 9.301 7.955-1.969c.107-.027.187-.04.241-.04.254 0 .482.1.683.301"/><path fill="#e5e5e5" d="M148 69c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 148 69m14 0c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 162 69"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/labels.svg b/app/assets/images/illustrations/labels.svg
deleted file mode 100644
index 3a2d521323bcb9d0e3239fbe4a5e6b0f8496691e..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/labels.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="787 240 386 274" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="37" cy="107" r="8"/><mask id="e" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="37" cy="75" r="8"/><mask id="f" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="42" cy="93" r="8"/><mask id="g" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><circle id="d" cx="43" cy="75" r="8"/><mask id="h" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(791 244)"><g transform="rotate(30 49.554 229.722)"><rect width="74" height="124" x="8.6" y="95.9" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="87" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><circle cx="26.5" cy="178.5" r="3.5" fill="#FC8A51"/><circle cx="47.5" cy="178.5" r="3.5" fill="#FC8A51"/><rect width="50" height="4" x="12" y="127" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="139" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#e)" stroke-linecap="round" xlink:href="#a"/><path stroke="#EEE" stroke-width="4" d="M37.3 107S10.5 18.3 81 .6" stroke-linecap="round"/><path fill="#FDE5D8" d="M31 189c0 3.3 2.7 6 6 6s6-2.7 6-6"/></g><g transform="translate(105 47)"><rect width="74" height="124" y="64" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><rect width="50" height="4" x="12" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#f)" stroke-linecap="round" xlink:href="#b"/><path fill="#B5A7DD" d="M56 149.7c-.6-1-.2-2 .7-2.7l1.8-1c1-.6 2-.2 2.7.7.5 1 .2 2.2-.7 2.8l-1.8 1c-1 .5-2 .2-2.7-.8zm-37.8 0c.5-1 .2-2-.7-2.7l-1.8-1c-1-.6-2-.2-2.7.7-.6 1-.2 2.2.7 2.8l1.8 1c1 .5 2 .2 2.7-.8zM33 151h9v4h-9v-4z"/><path fill="#6B4FBB" d="M59 153c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6zm-8 0c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6z"/><path stroke="#EEE" stroke-width="4" d="M37 75S30 0 80 0" stroke-linecap="round"/></g><g transform="rotate(15 -82.507 752.644)"><rect width="74" height="124" x="14.6" y="81.8" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="5" y="73" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><path fill="#FDE5D8" d="M41 147c0-1 1-2 2-2s2 1 2 2v3c0 1-1 2-2 2s-2-1-2-2v-3zm16.8 6.2c.8-.7 2-.6 2.8.3.7.8.5 2-.3 2.8L58 158c-1 .8-2.2.7-3 0-.6-1-.4-2.3.4-3l2.4-1.8zm-32 3c-1-.6-1-2-.4-2.7.7-1 2-1 2.8-.3l2.4 1.8c.8.7 1 2 .3 3-.8.7-2 1-3 0l-2.3-1.7z"/><rect width="2" height="7" x="39" y="168" fill="#FC8A51" rx="1"/><rect width="2" height="7" x="45" y="168" fill="#FC8A51" rx="1"/><circle cx="40" cy="169" r="2" fill="#FC8A51"/><circle cx="46" cy="169" r="2" fill="#FC8A51"/><rect width="22" height="18" x="32" y="158" stroke="#FC8A51" stroke-width="4" rx="8"/><rect width="34" height="5" x="26" y="174" fill="#FC8A51" rx="2.5"/><rect width="50" height="4" x="17" y="113" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="23" y="125" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#g)" stroke-linecap="round" xlink:href="#c"/><path stroke="#EEE" stroke-width="4" d="M42 93S50 0 0 0" stroke-linecap="round"/></g><g transform="rotate(-15 276.18 -697.744)"><rect width="74" height="124" x="18.7" y="65.6" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="6" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><g transform="translate(25 129)"><path stroke="#B5A7DD" stroke-width="4" d="M32 14c0-7.7-6.3-14-14-14S4 6.3 4 14" stroke-linecap="round"/><path stroke="#B5A7DD" stroke-width="2" d="M33 15v13c0 4.4-3.6 8-8 8" stroke-linecap="round"/><rect width="7" height="4" x="20" y="34" fill="#6B4FBB" rx="2"/><rect width="7" height="13" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" rx="3.5"/><rect width="7" height="13" x="29" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" transform="matrix(-1 0 0 1 65 0)" rx="3.5"/></g><rect width="50" height="4" x="18" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="24" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#h)" stroke-linecap="round" xlink:href="#d"/><path stroke="#EEE" stroke-width="4" d="M43 75S50 0 0 0" stroke-linecap="round"/></g><circle cx="193" cy="47" r="12" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><circle cx="193" cy="47" r="5" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><g opacity=".2"><path fill="#FC8A51" d="M30.7 254.8l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zm344-121l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zM5.6 95H1.8c-1.3.2-2-.8-1.4-2l1.4-3.4-.2-3.8c0-1.3 1-2 2-1.4l3.6 1.4 3.7-.2c1.2 0 2 1 1.4 2L11 91.3V95c.2 1.2-.8 2-2 1.4L5.6 95z"/><path fill="#6B4FBB" d="M308.8 62l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8zm9.2 164.6h-3c-1-.2-1.4-1-1-2l1.4-2.5v-3c.2-1 1-1.4 2-1l2.6 1.4h3c1 .2 1.5 1 1 2l-1.4 2.6v3c-.2 1-1 1.5-2 1l-2.5-1.4zM121.8 8l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8z"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/logos/go_logo.svg b/app/assets/images/illustrations/logos/go_logo.svg
deleted file mode 100644
index 7fd4911800664975e475615fe7fab76d8ca276cb..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/logos/go_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 16 16"><g fill-rule="evenodd"><path d="M14 16.01h1V7.99C15 4.128 11.866.999 8 .999c-3.858 0-7 3.13-7 6.991v8.02h1V7.99c0-3.306 2.691-5.991 6-5.991 3.314 0 6 2.682 6 5.991v8.02M3.48 2.656a2 2 0 1 0-2.155 3.228c.102-.321.226-.631.371-.93a1.001 1.001 0 1 1 1.069-1.599 6.96 6.96 0 0 1 .717-.699m9.04-.002a2 2 0 1 1 2.155 3.23 6.835 6.835 0 0 0-.37-.931 1 1 0 1 0-1.068-1.599 6.96 6.96 0 0 0-.717-.699"/><path d="M5.726 8.04h1.557v.124c0 .283-.033.534-.1.752a1.583 1.583 0 0 1-.33.566c-.35.394-.795.591-1.335.591-.527 0-.979-.19-1.355-.571a1.893 1.893 0 0 1-.564-1.377c0-.547.191-1.01.574-1.391a1.902 1.902 0 0 1 1.396-.574c.295 0 .57.06.825.181.244.12.484.316.72.586l-.405.388c-.309-.412-.686-.618-1.13-.618-.399 0-.733.138-1 .413-.27.27-.405.609-.405 1.015 0 .42.151.766.452 1.037.282.252.587.378.915.378.28 0 .531-.094.754-.283.223-.19.347-.418.373-.683h-.94v-.535m2.884.061c0-.53.194-.986.583-1.367a1.919 1.919 0 0 1 1.396-.571c.537 0 .998.192 1.382.576.386.384.578.845.578 1.384 0 .542-.194 1-.581 1.379a1.944 1.944 0 0 1-1.408.569c-.487 0-.923-.168-1.311-.505-.426-.373-.64-.861-.64-1.465m.574.007c0 .417.14.759.42 1.028.278.269.6.403.964.403.395 0 .729-.137 1-.41.272-.277.408-.613.408-1.01 0-.402-.134-.739-.403-1.01a1.33 1.33 0 0 0-.991-.41c-.392 0-.723.137-.993.41a1.36 1.36 0 0 0-.405 1m-.184 3.918c.525.026.812.063.812.063.271.025.324-.096.116-.273 0 0-.775-.813-1.933-.813-1.159 0-1.923.813-1.923.813-.211.174-.153.3.12.273 0 0 .286-.037.81-.063v.477c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.252.25c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.478m-1-1.023c.552 0 1-.224 1-.5s-.448-.5-1-.5-1 .224-1 .5.448.5 1 .5"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/logos/mattermost_logo.svg b/app/assets/images/illustrations/logos/mattermost_logo.svg
deleted file mode 100644
index b577c0599aadd28a6e74c746bb6b00dff5a70d0b..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/logos/mattermost_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" version="1" viewBox="0 0 501 501"><path d="M236 .7C137.7 7.5 54 68.2 18.2 158.5c-32 81-19.6 172.8 33 242.5 39.8 53 97.2 87 164.3 97 16.5 2.7 48 3.2 63.5 1.2 48.7-6.3 92.2-24.6 129-54.2 13-10.5 33-31.2 42.2-43.7 26.4-35.5 42.8-75.8 49-120.3 1.6-12.3 1.6-48.7 0-61-4-28.3-12-54.8-24.2-79.5-12.8-26-26.5-45.3-46.8-65.8C417.8 64 400.2 49 398.4 49c-.6 0-.4 10.5.3 26l1.3 26 7 8.7c19 23.7 32.8 53.5 38.2 83 2.5 14 3 43 1 55.8-4.5 27.8-15.2 54-31 76.5-8.6 12.2-28 31.6-40.2 40.2-24 17-50 27.6-80 33-10 1.8-49 1.8-59 0-43-7.7-78.8-26-107.2-54.8-29.3-29.7-46.5-64-52.4-104.4-2-14-1.5-42 1-55C90 121.4 132 72 192 49.7c8-3 18.4-5.8 29.5-8.2 1.7-.4 34.4-38 35.3-40.6.3-1-10.2-1-20.8-.4z"/><path d="M322.2 24.6c-1.3.8-8.4 9.3-16 18.7-7.4 9.5-22.4 28-33.2 41.2-51 62.2-66 81.6-70.6 91-6 12-8.4 21-9 33-1.2 19.8 5 36 19 50C222 268 230 273 243 277.2c9 3 10.4 3.2 24 3.2 13.8 0 15 0 22.6-3 23.2-9 39-28.4 45-55.7 2-8.2 2-28.7.4-79.7l-2-72c-1-36.8-1.4-41.8-3-44-2-3-4.8-3.6-7.8-1.4z"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/manual_action.svg b/app/assets/images/illustrations/manual_action.svg
deleted file mode 100644
index 85735855b46d7fa21abfd56eee84172e44652e27..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/manual_action.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 398 151" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><path fill="#fef0e8" stroke="#fc6d26" stroke-width="4" d="M57.7 106.5h21.6a4.2 4.2 0 0 1 4.2 4.2v5.6a4.2 4.2 0 0 1-4.2 4.2H57.7a4.2 4.2 0 0 1-4.2-4.2v-5.6a4.2 4.2 0 0 1 4.2-4.2"/><g transform="translate(42 117)"><rect width="52" height="23" x=".5" y=".5" fill="#fff" stroke="#eee" stroke-width="4" rx="4.2"/><g fill="#fdc4a8"><rect width="11" height="2" x="8" y="8" rx="1"/><rect width="11" height="2" x="8" y="14" rx="1"/></g></g><g fill-rule="nonzero"><path fill="#e1dbf1" d="M96.31 132.32c1.048 0 1.648.007 4.319.042 11.523.153 18.377-.12 26.32-1.533 24.23-4.309 38.521-18.02 38.521-45.03 0-31.02 21.885-44.487 66.903-40.522l.351-3.985c-47.09-4.147-71.25 10.727-71.25 44.507 0 24.868-12.746 37.1-35.22 41.09-7.623 1.356-14.284 1.621-25.567 1.471a287.717 287.717 0 0 0-4.372-.042v4"/><path fill="#eee" d="M242 57.678c-6.29-1.373-11-6.976-11-13.678 0-6.702 4.71-12.304 11-13.678v4.136c-4.057 1.274-7 5.065-7 9.542 0 4.478 2.943 8.268 7 9.542v4.136"/></g><g transform="translate(242)"><rect width="116" height="134" y="7" fill="#f9f9f9" rx="10"/><rect width="116" height="134" x="5" y="2" fill="#fff" rx="10"/><path fill="#eee" fill-rule="nonzero" d="M15 4a8 8 0 0 0-8 8v114a8 8 0 0 0 8 8h96a8 8 0 0 0 8-8V12a8 8 0 0 0-8-8H15m0-4h96c6.627 0 12 5.373 12 12v114c0 6.627-5.373 12-12 12H15c-6.627 0-12-5.373-12-12V12C3 5.373 8.373 0 15 0"/><g transform="translate(23 25)"><g fill="#e1dbf1"><rect width="16" height="4" rx="2"/><rect width="16" height="4" x="32" y="12" rx="2"/></g><rect width="16" height="4" x="44" fill="#eee" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#e1dbf1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="20" fill="#fee1d3" rx="2" id="a"/><rect width="8" height="4" x="32" y="36" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="64" fill="#fef0e8" rx="2" id="b"/><rect width="12" height="4" x="16" y="48" fill="#e1dbf1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#fc6d26" rx="2"/><g fill="#e1dbf1"><rect width="4" height="4" x="56" y="36" rx="2"/><rect width="4" height="4" x="64" y="60" rx="2"/></g><rect width="4" height="4" x="72" y="60" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="32" fill="#fc6d26" rx="2" id="c"/><g fill="#eee"><rect width="28" height="4" y="36" rx="2"/><rect width="28" height="4" x="44" y="48" rx="2"/></g><rect width="28" height="4" x="32" y="60" fill="#efedf8" rx="2"/><rect width="28" height="4" y="12" fill="#6b4fbb" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#c3b8e3" rx="2"/><rect width="8" height="4" y="24" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6b4fbb" rx="2"/><rect width="12" height="4" y="48" fill="#fc6d26" rx="2"/><g fill="#fef0e8"><rect width="12" height="4" y="60" rx="2"/><rect width="12" height="4" x="16" y="60" rx="2"/></g><g transform="translate(0 72)"><rect width="16" height="4" fill="#efedf8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#fc6d26" rx="2"/><rect width="16" height="4" x="44" fill="#6b4fbb" rx="2"/><use xlink:href="#a"/><rect width="8" height="4" x="38" y="12" fill="#fef0e8" rx="2"/><use xlink:href="#b"/><use xlink:href="#c"/><rect width="14" height="4" y="12" fill="#eee" rx="2"/></g></g></g><g transform="translate(330 83)"><circle cx="33" cy="33" r="33" fill="#fff"/><g fill-rule="nonzero"><path fill="#eee" d="M33 68C13.67 68-2 52.33-2 33S13.67-2 33-2s35 15.67 35 35-15.67 35-35 35m0-4c17.12 0 31-13.879 31-31C64 15.88 50.121 2 33 2 15.88 2 2 15.879 2 33c0 17.12 13.879 31 31 31"/><path fill="#6b4fbb" stroke="#6b4fbb" stroke-width=".968" d="M42.383 34.655v-3.308l-2.112-.343c-.116-.456-.351-.913-.703-1.598l1.29-1.711-2.463-2.398-1.76 1.256a6.347 6.347 0 0 0-1.642-.684l-.233-2.055h-3.401l-.352 2.055c-.586.114-1.055.342-1.642.684l-1.76-1.255-2.463 2.397 1.173 1.711c-.352.57-.469 1.027-.704 1.598l-1.995.228v3.31l2.112.342c.116.57.351 1.027.703 1.598l-1.172 1.712 2.463 2.397 1.759-1.141c.469.227 1.056.456 1.642.684l.352 2.055h3.518l.352-2.055c.586-.114 1.055-.342 1.642-.684l1.76 1.255 2.463-2.397-1.29-1.712a6.03 6.03 0 0 0 .703-1.598l1.76-.344M33 36.367c-1.994 0-3.519-1.484-3.519-3.424 0-1.941 1.525-3.424 3.519-3.424 1.994 0 3.519 1.483 3.519 3.424 0 1.94-1.525 3.424-3.519 3.424" stroke-linecap="round" stroke-linejoin="bevel"/><path fill="#e1dbf1" d="M33 53.563c-11.598 0-21-9.206-21-20.563s9.402-20.563 21-20.563S54 21.643 54 33s-9.402 20.563-21 20.563m0-4.375c9.13 0 16.532-7.248 16.532-16.188 0-8.94-7.402-16.188-16.532-16.188-9.13 0-16.532 7.248-16.532 16.188 0 8.94 7.402 16.188 16.532 16.188"/></g></g><path fill="#fff" d="M164 114c14.912 0 27-12.09 27-27 0-14.912-12.09-27-27-27-14.912 0-27 12.09-27 27 0 14.912 12.09 27 27 27"/><g fill-rule="nonzero"><path fill="#eee" d="M164 118c-17.12 0-31-13.879-31-31 0-17.12 13.879-31 31-31 17.12 0 31 13.879 31 31 0 17.12-13.879 31-31 31m0-4c14.912 0 27-12.09 27-27 0-14.912-12.09-27-27-27-14.912 0-27 12.09-27 27 0 14.912 12.09 27 27 27"/><path fill="#fc0" d="M172.78 80.798c.241.268.288.563.14.884l-10.848 23.24c-.174.334-.455.502-.843.502-.054 0-.148-.014-.282-.04a.855.855 0 0 1-.512-.382.761.761 0 0 1-.09-.603l3.957-16.232-8.156 2.03a1.08 1.08 0 0 1-.241.02.93.93 0 0 1-.623-.222c-.24-.2-.328-.462-.26-.783l4.04-16.574a.858.858 0 0 1 .321-.462.917.917 0 0 1 .563-.18h6.59c.254 0 .468.083.642.25a.797.797 0 0 1 .261.593.818.818 0 0 1-.1.362l-3.435 9.301 7.955-1.969c.107-.027.187-.04.241-.04.254 0 .482.1.683.301"/></g><g><path fill="#eee" fill-rule="nonzero" d="M37.801 99.01l5.355 2.648c2.271 1.122 4.643-.252 4.809-2.778l.487-7.546a27.675 27.675 0 0 0 2.87-4.076c7.594-13.152 3.088-29.972-10.07-37.565-13.153-7.594-29.971-3.087-37.566 10.07-7.594 13.154-3.087 29.973 10.07 37.565a27.46 27.46 0 0 0 24.05 1.687m.952-3.992a2.002 2.002 0 0 0-1.698-.035 23.454 23.454 0 0 1-21.299-1.124c-11.24-6.488-15.09-20.86-8.602-32.1 6.49-11.239 20.862-15.09 32.1-8.601 11.239 6.489 15.09 20.862 8.6 32.1a23.519 23.519 0 0 1-2.849 3.939 1.995 1.995 0 0 0-.504 1.204l-.466 7.229-5.285-2.613"/><path fill="#fdc4a8" d="M21.137 70.471A7.495 7.495 0 0 0 27.5 74c2.684 0 5.04-1.41 6.363-3.529C36.377 71.869 38 74.267 38 77.674c0 5.799-2.739 9.587-10.5 9.587S17 83.473 17 77.674c0-3.407 1.622-5.804 4.137-7.203M27.5 72a5.5 5.5 0 1 1 0-11 5.5 5.5 0 1 1 0 11"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/merge_request_changes_empty.svg b/app/assets/images/illustrations/merge_request_changes_empty.svg
deleted file mode 100644
index 40efeb2de57c1702b209f891b633b685945dd1df..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/merge_request_changes_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="374" height="268" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="44" cy="44" r="44"/><circle id="b" cx="31" cy="31" r="31"/><circle id="c" cx="35" cy="35" r="35"/><rect id="d" width="230" height="176" rx="10"/><circle id="e" cx="31" cy="31" r="31"/></defs><g fill="none" fill-rule="evenodd"><g transform="translate(4 98)"><circle cx="53" cy="53" r="44" fill="#F9F9F9"/><g transform="translate(6 6)"><use fill="#FFF" xlink:href="#a"/><circle cx="44" cy="44" r="42" stroke="#EEE" stroke-width="4"/><path fill="#FEE1D3" fill-rule="nonzero" d="M34.394 55.736A4 4 0 0 1 36.706 55H56a6 6 0 0 0 6-6V35a6 6 0 0 0-6-6H34a6 6 0 0 0-6 6v25.265l6.394-4.53zM36.706 59l-7.972 5.647A3 3 0 0 1 24 62.199V35c0-5.523 4.477-10 10-10h22c5.523 0 10 4.477 10 10v14c0 5.523-4.477 10-10 10H36.706z"/><path fill="#FC6D26" d="M38 40a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm7 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm7 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/></g></g><g transform="translate(50 2)"><circle cx="39" cy="39" r="31" fill="#F9F9F9"/><g transform="translate(5 5)"><use fill="#FFF" xlink:href="#b"/><circle cx="31" cy="31" r="29" stroke="#EEE" stroke-width="4"/><rect width="20" height="4" x="21" y="29" fill="#6B4FBB" rx="2"/></g></g><path fill="#F9F9F9" d="M235.58 229H102c-6.627 0-12-5.373-12-12V65c0-6.627 5.373-12 12-12h206c6.627 0 12 5.373 12 12v18.399A34.842 34.842 0 0 1 337 79c19.33 0 35 15.67 35 35s-15.67 35-35 35a34.842 34.842 0 0 1-17-4.399V217c0 6.627-5.373 12-12 12h-11.58c.38 1.941.58 3.947.58 6 0 17.12-13.88 31-31 31s-31-13.88-31-31c0-2.053.2-4.059.58-6z"/><g transform="translate(87 50)"><g transform="translate(212 26)"><use fill="#FFF" xlink:href="#c"/><circle cx="35" cy="35" r="33" stroke="#EEE" stroke-width="4"/><g transform="translate(20 19)"><circle cx="15" cy="16" r="15" fill="#F4F1FA" stroke="#6B4FBB" stroke-width="3"/><path fill="#6B4FBB" d="M19.419 6.996h-.007L16.959 4l-2.454 2.997h-.006L12.045 4 9.59 6.998h-.003L7.132 4 4.676 7H2c2.605-4.204 7.23-7 12.502-7C19.771 0 24.394 2.793 27 6.994h-2.676L21.872 4l-2.453 2.996z"/><circle cx="9.5" cy="17.5" r="1.5" fill="#6B4FBB"/><circle cx="20.5" cy="17.5" r="1.5" fill="#6B4FBB"/></g></g><use fill="#FFF" xlink:href="#d"/><rect width="226" height="172" x="2" y="2" stroke="#EEE" stroke-width="4" rx="10"/><rect width="4" height="122" x="33" y="42" fill="#EEE" rx="2"/><g transform="translate(13 59)"><rect width="10" height="4" fill="#FEE1D3" rx="2"/><rect width="10" height="4" y="12" fill="#F0EDF8" rx="2"/><rect width="10" height="4" y="24" fill="#FEF0E9" rx="2"/><rect width="10" height="4" y="36" fill="#FEE1D3" rx="2"/><rect width="10" height="4" y="48" fill="#E1DBF1" rx="2"/><rect width="10" height="4" y="60" fill="#F0EDF8" rx="2"/><rect width="10" height="4" y="72" fill="#FEF0E9" rx="2"/><rect width="10" height="4" y="84" fill="#FEE1D3" rx="2"/></g><g transform="translate(55 59)"><rect width="14" height="4" fill="#6B4FBB" rx="2"/><rect width="14" height="4" x="20" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" fill="#FEF0E9" rx="2"/><rect width="14" height="4" y="12" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" y="24" fill="#FEF0E9" rx="2"/><rect width="14" height="4" y="48" fill="#E1DBF1" rx="2"/><rect width="14" height="4" x="40" y="36" fill="#FEF0E9" rx="2"/><rect width="7" height="4" x="20" y="12" fill="#FEE1D3" rx="2"/><rect width="7" height="4" x="27" y="36" fill="#6B4FBB" rx="2"/><rect width="7" height="4" x="20" y="48" fill="#FEE1D3" rx="2"/><rect width="7" height="4" y="24" fill="#FC6D26" rx="2"/><rect width="21" height="4" x="13" y="24" fill="#E1DBF1" rx="2"/><rect width="21" height="4" y="36" fill="#EEE" rx="2"/><rect width="7" height="4" x="33" y="12" fill="#6B4FBB" rx="2"/><g transform="translate(98)"><rect width="14" height="4" fill="#FEE1D3" rx="2"/><rect width="14" height="4" x="20" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" fill="#FC6D26" rx="2"/><rect width="14" height="4" y="12" fill="#FEF0E9" rx="2"/><rect width="14" height="4" x="40" y="24" fill="#E1DBF1" rx="2"/><rect width="14" height="4" y="48" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" y="36" fill="#FEE1D3" rx="2"/><rect width="7" height="4" x="20" y="12" fill="#FC6D26" rx="2"/><rect width="7" height="4" x="27" y="36" fill="#6B4FBB" rx="2"/><rect width="7" height="4" x="20" y="48" fill="#FC6D26" rx="2"/><rect width="7" height="4" y="24" fill="#6B4FBB" rx="2"/><rect width="21" height="4" x="13" y="24" fill="#FEE1D3" rx="2"/><rect width="21" height="4" y="36" fill="#FEF0E9" rx="2"/><rect width="7" height="4" x="33" y="12" fill="#6B4FBB" rx="2"/></g><g transform="translate(0 60)"><rect width="14" height="4" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="20" fill="#6B4FBB" rx="2"/><rect width="14" height="4" x="40" fill="#E1DBF1" rx="2"/><rect width="14" height="4" y="12" fill="#FEF0E9" rx="2"/><rect width="14" height="4" x="40" y="24" fill="#FEE1D3" rx="2"/><rect width="7" height="4" x="20" y="12" fill="#EEE" rx="2"/><rect width="7" height="4" y="24" fill="#6B4FBB" rx="2"/><rect width="21" height="4" x="13" y="24" fill="#FEF0E9" rx="2"/><rect width="7" height="4" x="33" y="12" fill="#FC6D26" rx="2"/></g><rect width="4" height="63" x="74" y="13" fill="#EEE" rx="2"/></g><rect width="230" height="4" y="27" fill="#EEE" rx="2"/></g><g transform="translate(233 201)"><use fill="#FFF" xlink:href="#e"/><circle cx="31" cy="31" r="29" stroke="#EEE" stroke-width="4"/><path fill="#FC6D26" d="M29 29v-6a2 2 0 1 1 4 0v6h6a2 2 0 1 1 0 4h-6v6a2 2 0 1 1-4 0v-6h-6a2 2 0 1 1 0-4h6z"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/merge_requests.svg b/app/assets/images/illustrations/merge_requests.svg
deleted file mode 100644
index b9b8f0058e6ab266546644d28487767f8c117bb9..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/merge_requests.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="755 221 385 225" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="278" height="179" rx="10"/><mask id="d" width="278" height="179" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><path id="b" d="M13.6 49H57c5.5 0 10-4.5 10-10V10c0-5.5-4.5-10-10-10H10C4.5 0 0 4.5 0 10v42c0 5.5 3.2 7 7.2 3l6.4-6z"/><mask id="e" width="67" height="57.2" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><path id="c" d="M13.6 49H57c5.5 0 10-4.5 10-10V10c0-5.5-4.5-10-10-10H10C4.5 0 0 4.5 0 10v42c0 5.5 3.2 7 7.2 3l6.4-6z"/><mask id="f" width="67" height="57.2" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd"><g fill="#F9F9F9" transform="translate(752 227)"><rect width="120" height="22" x="30" rx="11"/><rect width="132" height="22" y="44" rx="11"/><rect width="190" height="22" x="208" y="66" rx="11"/><rect width="158" height="22" x="129" y="197" rx="11"/><rect width="158" height="22" x="66" y="154" rx="11"/><rect width="350" height="22" x="31" y="110" rx="11"/><path d="M153 22H21h21.5c6 0 11 5 11 11s-5 11-11 11H21h132-36.5c-6 0-11-5-11-11s5-11 11-11H153zm252 66H288h36.5c6 0 11 5 11 11s-5 11-11 11H288h117-36.5c-6 0-11-5-11-11s5-11 11-11H405zm-244 44H44h36.5c6 0 11 5 11 11s-5 11-11 11H44h117-36.5c-6 0-11-5-11-11s5-11 11-11H161zm75 44H119h21.5c6 0 11 5 11 11s-5 11-11 11H119h117-51.5c-6 0-11-5-11-11s5-11 11-11H236z"/></g><g transform="translate(812 240)"><use fill="#FFF" stroke="#EEE" stroke-width="8" mask="url(#d)" xlink:href="#a"/><path fill="#EEE" d="M4 29h271v4H4z"/><g transform="translate(34 60)"><rect width="6" height="2" y="1" fill="#B5A7DD" rx="1"/><rect width="15" height="4" x="15" fill="#EEE" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#EEE" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="48" fill="#FC6D26" opacity=".5" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#EEE" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#FC6D26" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#B5A7DD" rx="1"/><rect width="6" height="2" y="23" fill="#B5A7DD" rx="1"/></g><g transform="translate(34 93)"><rect width="6" height="2" y="1" fill="#B5A7DD" rx="1"/><rect width="15" height="4" x="15" fill="#FC6D26" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#FC6D26" opacity=".5" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#EEE" rx="2"/><rect width="20" height="4" x="48" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#FC6D26" opacity=".5" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#B5A7DD" rx="1"/><rect width="6" height="2" y="23" fill="#B5A7DD" rx="1"/></g><g transform="translate(34 126)"><rect width="6" height="2" y="1" fill="#B5A7DD" rx="1"/><rect width="15" height="4" x="15" fill="#EEE" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#EEE" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#EEE" rx="2"/><rect width="20" height="4" x="48" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#EEE" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#FC6D26" opacity=".5" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#B5A7DD" rx="1"/><rect width="6" height="2" y="23" fill="#B5A7DD" rx="1"/></g><g transform="translate(157 59)"><rect width="6" height="2" y="1" fill="#FDE5D8" rx="1"/><rect width="15" height="4" x="15" fill="#EEE" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="48" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="23" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="34" fill="#FDE5D8" rx="1"/><rect width="15" height="4" x="15" y="33" fill="#EEE" rx="2"/><rect width="15" height="4" x="58" y="22" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="55" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="15" height="4" x="29" y="44" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="48" y="33" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="15" y="55" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" y="33" fill="#EEE" rx="2"/><rect width="10" height="4" x="15" y="44" fill="#EEE" rx="2"/><rect width="10" height="4" x="48" y="44" fill="#EEE" rx="2"/><rect width="10" height="4" x="62" y="44" fill="#EEE" rx="2"/><rect width="10" height="4" x="77" y="22" fill="#EEE" rx="2"/><rect width="6" height="2" y="45" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="56" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="67" fill="#FDE5D8" rx="1"/><rect width="15" height="4" x="15" y="66" fill="#6B4FBB" rx="2"/><rect width="15" height="4" x="39" y="88" fill="#EEE" rx="2"/><rect width="15" height="4" x="53" y="77" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="20" height="4" x="15" y="88" fill="#EEE" rx="2"/><rect width="20" height="4" x="29" y="77" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="34" y="66" fill="#EEE" rx="2"/><rect width="10" height="4" x="72" y="77" fill="#EEE" rx="2"/><rect width="10" height="4" x="15" y="77" fill="#EEE" rx="2"/><rect width="6" height="2" y="78" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="89" fill="#FDE5D8" rx="1"/></g></g><g transform="translate(1057 221)"><use fill="#FFF" stroke="#FDE5D8" stroke-width="8" mask="url(#e)" xlink:href="#b"/><rect width="29" height="3" x="14" y="14" fill="#FDB692" rx="1.5"/><rect width="39" height="3" x="14" y="23" fill="#FDB692" rx="1.5"/><rect width="29" height="3" x="14" y="32" fill="#FDB692" rx="1.5"/></g><g transform="translate(1046 285)"><circle cx="16" cy="15" r="15" fill="#FFF7F4" stroke="#FC6D26" stroke-width="3"/><path stroke="#FC6D26" stroke-width="2" d="M0 14h1c5 0 9.2-2.7 11.4-6.7M14 1V0"/><path stroke="#FC6D26" stroke-width="2" d="M7.8 3c3 4.3 7.8 7 13.2 7 3.3 0 6.3-1 9-2.7"/><circle cx="10.5" cy="17.5" r="1.5" fill="#FC6D26"/><circle cx="21.5" cy="17.5" r="1.5" fill="#FC6D26"/></g><g transform="translate(825 370)"><circle cx="15" cy="16" r="15" fill="#F4F1FA" stroke="#6B4FBB" stroke-width="3"/><path fill="#6B4FBB" d="M25 7h2.7C25 2.8 20.4 0 15 0 9.6 0 5 2.8 2.3 7H5l2.5-3L10 7l2.5-3L15 7l2.5-3L20 7l2.5-3L25 7z"/><circle cx="9.5" cy="17.5" r="1.5" fill="#6B4FBB"/><circle cx="20.5" cy="17.5" r="1.5" fill="#6B4FBB"/></g><g transform="matrix(-1 0 0 1 840 306)"><use fill="#FFF" stroke="#E2DCF2" stroke-width="8" mask="url(#f)" xlink:href="#c"/><rect width="29" height="3" x="24" y="14" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="19" height="3" x="34" y="23" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="19" height="3" x="34" y="32" fill="#6B4FBB" opacity=".5" rx="1.5"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/monitoring/getting_started.svg b/app/assets/images/illustrations/monitoring/getting_started.svg
deleted file mode 100644
index ff783bdd388f2ba89c16ce98591afdd24a3883bd..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/monitoring/getting_started.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="159.8" height="127.81" x=".196" y="5" rx="10"/><rect id="b" width="160" height="128" x=".666" y=".41" rx="10"/><rect id="c" width="160.19" height="128.19" x=".339" y=".59" rx="10"/><mask id="d" width="159.8" height="127.81" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><mask id="e" width="160" height="128" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><mask id="f" width="160.19" height="128.19" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(12 3)"><rect width="160" height="128" x="122.08" y="146.08" fill="#f9f9f9" transform="rotate(5 202.071 210.085)" rx="10"/><g transform="rotate(15 -104.714 891.23)"><rect width="159.8" height="127.81" x="1.64" y="10.06" fill="#f9f9f9" rx="8"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#d)" xlink:href="#a"/><path fill="#d2caea" fill-rule="nonzero" d="M96.153 81.151a2.001 2.001 0 0 0 2.184-.496l35.956-38.34a2 2 0 1 0-2.918-2.736l-35.03 37.36-41.888-16.285a2 2 0 0 0-2.16.471l-26.368 27.16a2 2 0 1 0 2.87 2.786l25.444-26.21 41.911 16.294"/><g fill="#fff" transform="translate(24.368 36.951)"><circle cx="5.716" cy="5.104" r="5" stroke="#6b4fbb" stroke-width="4" transform="translate(65.917 34.945)"/><g stroke="#fb722e"><ellipse cx="4.632" cy="50.05" stroke-width="3.2" rx="4" ry="3.999"/><g stroke-width="4"><ellipse cx="29.632" cy="27.05" rx="4" ry="3.999"/><ellipse cx="107.63" cy="4.048" rx="4" ry="3.999"/></g></g></g></g><rect width="160.19" height="128.19" x="36.28" y="86.74" fill="#f9f9f9" transform="rotate(-5 116.372 150.825)" rx="10"/><g transform="rotate(5 -1514.687 1518.752)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#e)" xlink:href="#b"/><path fill="#6b4fbb" stroke="#6b4fbb" stroke-width="3.2" d="M84.67 28.41c18.225 0 33 15.07 33 33.651h-33V28.41" stroke-linecap="round" stroke-linejoin="round"/><path fill="#d2caea" fill-rule="nonzero" d="M78.67 66.41h30a2 2 0 0 1 2 2c0 18.778-15.222 34-34 34s-34-15.222-34-34 15.222-34 34-34a2 2 0 0 1 2 2v30m-32 2c0 16.569 13.431 30 30 30 15.896 0 28.905-12.364 29.934-28H76.67a2 2 0 0 1-2-2V38.476c-15.636 1.029-28 14.04-28 29.934"/></g><g transform="rotate(-5 1023.06 -299.524)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#f)" xlink:href="#c"/><path fill="#fef0ea" d="M42 47.391c0-.768.628-1.391 1.4-1.391h9.2c.773 0 1.4.626 1.4 1.391V97H42V47.391"/><path fill="#fb722e" d="M108 55.406c0-.777.628-1.406 1.4-1.406h9.2a1.4 1.4 0 0 1 1.4 1.406V97h-12V55.406"/><path fill="#6b4fbb" d="M64 35.404c0-.776.628-1.404 1.4-1.404h9.2a1.4 1.4 0 0 1 1.4 1.404v61.6H64v-61.6"/><path fill="#d2caea" d="M86 73.4a1.4 1.4 0 0 1 1.4-1.398h9.2c.773 0 1.4.618 1.4 1.398v23.602H86V73.4"/></g><g fill="#fee8dc"><path d="M3.592 93.86l-2.454-1.562c-.93-.592-.924-1.554 0-2.143l2.454-1.562 1.562-2.454c.592-.93 1.554-.925 2.143 0l1.562 2.454 2.454 1.562c.93.591.924 1.554 0 2.143L8.86 93.86l-1.562 2.454c-.591.93-1.554.924-2.143 0L3.592 93.86M309.489 52.07l-3.14-1.998c-1.12-.713-1.128-1.863 0-2.581l3.14-2 1.999-3.14c.713-1.12 1.863-1.127 2.58 0l2 3.14 3.14 2c1.12.713 1.128 1.863 0 2.58l-3.14 2-2 3.14c-.712 1.12-1.862 1.128-2.58 0l-1.999-3.14"/></g><path fill="#e1dcf1" d="M128.073 11.066l-1.99 3.126c-.718 1.129-1.88 1.131-2.6 0l-1.99-3.126-3.126-1.989c-1.128-.718-1.13-1.88 0-2.6l3.127-1.99 1.989-3.126c.718-1.128 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.99"/><path fill="#d2caea" d="M378.07 243.068l-1.989 3.126c-.718 1.129-1.88 1.131-2.6 0l-1.99-3.126-3.126-1.989c-1.128-.718-1.13-1.88 0-2.6l3.127-1.99 1.989-3.126c.718-1.128 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.99"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/monitoring/loading.svg b/app/assets/images/illustrations/monitoring/loading.svg
deleted file mode 100644
index 1e196fc8ad170db11857ade4cc7bd8163e3a1d70..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/monitoring/loading.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="c" width="161" height="100" x="92" y="181" rx="10"/><rect id="d" width="151" height="32" x="20" rx="10"/><rect id="a" width="191" height="62" y="10" rx="10"/><circle id="b" cx="23" cy="41" r="9"/><circle id="k" cx="36.5" cy="36.5" r="36.5"/><circle id="e" cx="262.5" cy="169.5" r="15.5"/><circle id="g" cx="79.5" cy="169.5" r="15.5"/><circle id="j" cx="45" cy="41" r="9"/><circle id="f" cx="30.5" cy="30.5" r="30.5"/><circle id="h" cx="18" cy="34" r="3"/><ellipse id="i" cx="43.5" cy="43.5" rx="43.5" ry="43.5"/><mask id="t" width="191" height="62" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><mask id="u" width="18" height="18" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><mask id="r" width="161" height="100" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><mask id="s" width="151" height="32" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><mask id="p" width="31" height="31" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><mask id="l" width="61" height="61" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><mask id="q" width="31" height="31" x="0" y="0" fill="#fff"><use xlink:href="#g"/></mask><mask id="m" width="6" height="6" x="0" y="0" fill="#fff"><use xlink:href="#h"/></mask><mask id="o" width="87" height="87" x="0" y="0" fill="#fff"><use xlink:href="#i"/></mask><mask id="v" width="18" height="18" x="0" y="0" fill="#fff"><use xlink:href="#j"/></mask><mask id="n" width="73" height="73" x="0" y="0" fill="#fff"><use xlink:href="#k"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(28 2)"><g transform="translate(133 87)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#l)" xlink:href="#f"/><path stroke="#d2caea" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" d="M19 32l2-9 5 17 4-12 4 5 6-10 3 5"/><g fill="#fff" stroke="#fb722e"><use stroke-width="4" mask="url(#m)" xlink:href="#h"/><circle cx="44" cy="30" r="2" stroke-width="2"/></g></g><g transform="translate(188 29)"><circle cx="36.5" cy="41.5" r="36.5" fill="#f9f9f9"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#n)" xlink:href="#k"/><rect width="27" height="4" x="23" y="27" fill="#d2caea" rx="2"/><rect width="10.5" height="4" x="23" y="27" fill="#6b4fbb" rx="2"/><rect width="27" height="4" x="23" y="36" fill="#d2caea" rx="2"/><rect width="19" height="4" x="23" y="36" fill="#6b4fbb" rx="2"/><rect width="27" height="4" x="23" y="45" fill="#d2caea" rx="2"/><rect width="7" height="4" x="23" y="45" fill="#6b4fbb" rx="2"/></g><path fill="#eee" fill-rule="nonzero" d="M247 292v1c0 5.519-4.469 9.993-10.01 9.993H111c-5.177 0-9.436-3.927-9.954-8.96a9.96 9.96 0 0 0 4.705 1.883 6.008 6.008 0 0 0 5.248 3.077h125.99a6 6 0 0 0 5.526-3.637 10.027 10.027 0 0 0 4.48-3.359m1.947-8.962a10.001 10.001 0 0 1-9.95 8.958h-131.99a10 10 0 0 1-9.851-8.25 9.942 9.942 0 0 0 4.649 1.248 6 6 0 0 0 5.202 3h131.99a6.002 6.002 0 0 0 5.245-3.076 9.943 9.943 0 0 0 4.705-1.882"/><g transform="translate(79)"><ellipse cx="43.5" cy="47.5" fill="#f9f9f9" rx="43.5" ry="43.5"/><g fill="#fff"><g stroke="#eee"><use stroke-width="8" mask="url(#o)" xlink:href="#i"/><path stroke-width="4" d="M18.595 49C21.11 60.44 31.305 69 43.5 69 57.58 69 69 57.583 69 43.5c0-12.195-8.56-22.391-20-24.905v15.959c3 1.848 5 5.164 5 8.946C54 49.299 49.299 54 43.5 54c-3.782 0-7.098-2-8.946-5H18.595" stroke-linejoin="round"/></g><path stroke="#d2caea" stroke-width="4" d="M18 44a27.69 27.69 0 0 1-.005-.5c0-14.08 11.417-25.5 25.5-25.5.167 0 .334.002.5.005v15.01a10.365 10.365 0 0 0-.5-.012c-5.799 0-10.5 4.701-10.5 10.5 0 .168.004.334.012.5h-15.01" stroke-linejoin="round"/></g></g><g fill="#fff" stroke="#eee" stroke-width="8"><use mask="url(#p)" xlink:href="#e"/><use mask="url(#q)" xlink:href="#g"/><use mask="url(#r)" xlink:href="#c"/></g><g fill="#eee"><rect width="15" height="2" x="226" y="247" rx="1"/><rect width="15" height="2" x="226" y="242" rx="1"/><rect width="15" height="2" x="226" y="252" rx="1"/></g><rect width="10" height="52" x="118" y="196" fill="#d2caea" rx="2"/><rect width="10" height="47" x="154" y="196" fill="#6b4fbb" rx="2"/><rect width="10" height="37" x="190" y="196" fill="#d2caea" rx="2"/><g fill="#fee8dc"><rect width="10" height="52" x="132" y="185" rx="2"/><rect width="10" height="38" x="168" y="185" rx="2"/></g><rect width="10" height="58" x="204" y="185" fill="#fb722e" rx="2"/><g fill="#fff" stroke="#eee" stroke-width="8" transform="translate(76 128)"><use mask="url(#s)" xlink:href="#d"/><use mask="url(#t)" xlink:href="#a"/></g><g fill="#d2caea" transform="translate(76 128)"><rect width="16" height="4" x="156" y="35" rx="2"/><rect width="16" height="4" x="156" y="43" rx="2"/></g><g fill="#fff" stroke-width="8" transform="translate(76 128)"><use stroke="#fee8dc" mask="url(#u)" xlink:href="#b"/><use stroke="#fb722e" mask="url(#v)" xlink:href="#j"/></g><g fill="#fb722e"><path d="M3.597 219.858l-2.455-1.562c-.929-.59-.924-1.553 0-2.142l2.455-1.562 1.562-2.455c.59-.929 1.553-.924 2.142 0l1.562 2.455 2.454 1.562c.93.591.925 1.553 0 2.142l-2.454 1.562-1.562 2.455c-.591.929-1.553.924-2.142 0l-1.562-2.455M253.597 8.859l-2.454-1.562c-.93-.592-.925-1.554 0-2.143l2.454-1.562 1.562-2.454c.591-.93 1.554-.925 2.143 0l1.562 2.454 2.454 1.562c.93.591.924 1.554 0 2.143l-2.454 1.562-1.562 2.454c-.592.93-1.554.924-2.143 0l-1.562-2.454" opacity=".2"/></g><path fill="#fee8dc" d="M309.49 149.07l-3.141-1.999c-1.12-.712-1.128-1.863 0-2.58l3.14-2 2-3.14c.712-1.12 1.863-1.128 2.58 0l2 3.14 3.14 2c1.12.712 1.127 1.863 0 2.58l-3.14 2-2 3.14c-.713 1.12-1.863 1.128-2.58 0l-2-3.14"/><path fill="#6b4fbb" d="M47.068 79.067l-1.99 3.126c-.718 1.129-1.88 1.13-2.6 0l-1.99-3.126-3.125-1.99c-1.129-.718-1.131-1.88 0-2.6l3.126-1.989 1.989-3.126c.718-1.129 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.989" opacity=".2"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/monitoring/unable_to_connect.svg b/app/assets/images/illustrations/monitoring/unable_to_connect.svg
deleted file mode 100644
index 314c052f931e15dbb4f9749cd89bcf1bca59e08e..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/monitoring/unable_to_connect.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><use id="g" xlink:href="#a"/><use id="f" xlink:href="#a"/><use id="h" xlink:href="#a"/><path id="e" d="M74 93h26v47H74z"/><path id="c" d="M74 93h26v47H74z"/><rect id="b" width="65" height="14" x="55" y="135" rx="4"/><rect id="d" width="175" height="118" rx="10"/><rect id="a" width="159" rx="10" height="56"/><rect id="i" width="160" y="2" rx="10" height="56" fill="#f9f9f9"/><mask id="q" width="65" height="14" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><mask id="p" width="26" height="47" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><mask id="r" width="175" height="118" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><mask id="o" width="26" height="47" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><mask id="k" width="159" height="56" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><mask id="j" width="159" height="56" x="0" y="0" fill="#fff"><use xlink:href="#g"/></mask><mask id="l" width="159" height="56" x="0" y="0" fill="#fff"><use xlink:href="#h"/></mask></defs><g fill="none" fill-rule="evenodd"><g transform="translate(245 65)"><use xlink:href="#i"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#j)" xlink:href="#g"/><g fill-rule="nonzero"><path fill="#fb722e" d="M134 31a2 2 0 1 0 .001-3.999A2 2 0 0 0 134 31m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fee8dc" d="M117 31a2 2 0 1 0 .001-3.999A2 2 0 0 0 117 31m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12m-17-4a2 2 0 1 0 .001-3.999A2 2 0 0 0 100 31m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/></g><g fill="#d2caea"><rect width="50" height="4" x="19" y="20" rx="2"/><rect width="50" height="4" x="19" y="34" rx="2"/></g><g transform="translate(0 59)"><use xlink:href="#i"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#k)" xlink:href="#f"/><g fill-rule="nonzero"><path fill="#fee8dc" d="M134 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 134 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fb722e" d="M117 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 117 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fee8dc" d="M100 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 100 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/></g><rect width="50" height="4" x="19" y="19" fill="#d2caea" rx="2" id="m"/><rect width="50" height="4" x="19" y="33" fill="#d2caea" rx="2" id="n"/></g><g transform="translate(0 118)"><use xlink:href="#i"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#l)" xlink:href="#h"/><g fill-rule="nonzero"><path fill="#fb722e" d="M134 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 134 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fee8dc" d="M117 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 117 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12m-17-4a2 2 0 1 0 .001-3.999A2 2 0 0 0 100 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/></g><use xlink:href="#m"/><use xlink:href="#n"/></g></g><g fill="#eee" transform="translate(164 120)"><rect width="29" height="4" y="29" rx="2"/><rect width="28" height="4" x="55" y="29" rx="2"/></g><g transform="translate(180 120)"><circle cx="30" cy="30" r="24" fill="#fef0ea"/><g fill="#fb722e"><circle cx="30.5" cy="30.5" r="30.5" opacity=".1"/><circle cx="30.5" cy="30.5" r="19.5" opacity=".1"/></g><circle cx="30.5" cy="30.5" r="13.5" fill="#fff"/><path fill="#fb722e" d="M32.621 30.5l2.481-2.481a1.492 1.492 0 0 0-.006-2.115 1.491 1.491 0 0 0-2.115-.006L30.5 28.379l-2.481-2.481a1.492 1.492 0 0 0-2.115.006 1.491 1.491 0 0 0-.006 2.115l2.481 2.481-2.481 2.481a1.492 1.492 0 0 0 .006 2.115c.59.59 1.533.589 2.115.006l2.481-2.481 2.481 2.481c.586.586 1.529.58 2.115-.006.59-.59.589-1.533.006-2.115L32.621 30.5"/></g><g transform="translate(1 78)"><rect width="65" height="14" x="55" y="137" fill="#f9f9f9" rx="4"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#o)" xlink:href="#e"/><rect width="175" height="118" y="3" fill="#f9f9f9" rx="10"/><g fill="#fff" stroke="#eee" stroke-width="8"><use mask="url(#p)" xlink:href="#c"/><use mask="url(#q)" xlink:href="#b"/><use mask="url(#r)" xlink:href="#d"/></g><g fill-rule="nonzero"><path fill="#eee" d="M163 105V12H11v93h152M7 11.99A3.998 3.998 0 0 1 10.995 8h152.01A3.999 3.999 0 0 1 167 11.99v93.02a3.998 3.998 0 0 1-3.995 3.99H10.995A3.999 3.999 0 0 1 7 105.01V11.99"/><path fill="#d2caea" d="M86 92c-11.598 0-21-9.402-21-21s9.402-21 21-21 21 9.402 21 21-9.402 21-21 21m0-4c9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17 7.611 17 17 17"/></g><path fill="#6b4fbb" d="M83 63a3.001 3.001 0 0 1 6 0v7.993a3.001 3.001 0 0 1-6 0V63m3 18.997a3 3 0 1 1 0-6 3 3 0 0 1 0 6"/><g fill="#eee"><rect width="134" height="4" x="20" y="30" rx="2"/><rect width="14" height="4" x="20" y="20" rx="2"/><circle cx="87" cy="21" r="5"/></g></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/multi-editor_all_changes_committed_empty.svg b/app/assets/images/illustrations/multi-editor_all_changes_committed_empty.svg
deleted file mode 100644
index 06d73941c336ab4a2bae1c21aa5d59fffb09b706..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/multi-editor_all_changes_committed_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd"><path fill="#EEE" d="M44.242 59.348c-3.7 1.576-7.3 1.994-10.902.84a7.002 7.002 0 0 1-9.085-.699l-4.243-4.243a7 7 0 0 1-.238-9.649c-.701-3.024-.419-6.083.646-9.206l-6.287-2.426a5.6 5.6 0 0 1-2.274-8.824l8.233-9.811a5.6 5.6 0 0 1 6.306-1.625l8.045 3.105c.772-.797 1.564-1.6 2.374-2.41C44.841 6.376 55.265 2.135 68.09 1.677a10 10 0 0 1 1.119.023c5.507.42 9.63 5.226 9.209 10.733-.935 12.225-5.373 22.309-13.315 30.25a410.76 410.76 0 0 1-1.661 1.653l3.247 8.412a5.6 5.6 0 0 1-1.625 6.306l-9.81 8.233a5.6 5.6 0 0 1-8.825-2.274l-2.186-5.665zm-22.92-26.923l10.406-12.402-6.822-2.633a1.6 1.6 0 0 0-1.801.464l-8.233 9.811a1.6 1.6 0 0 0 .65 2.521l5.8 2.239zm26.646 25.4l2.239 5.8a1.6 1.6 0 0 0 2.521.649l9.81-8.232a1.6 1.6 0 0 0 .465-1.802l-2.633-6.822-12.402 10.406zm-19.69-5.627c8.751 8.752 16.065 5.587 33.995-12.343 7.25-7.25 11.292-16.433 12.155-27.727a6 6 0 0 0-6.196-6.454c-11.846.423-21.303 4.271-28.586 11.554-17.03 17.03-20.414 25.924-11.368 34.97z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M52.54 28.376a4 4 0 1 0 5.656-5.657 4 4 0 0 0-5.657 5.657zm-2.83 2.829A8 8 0 1 1 61.025 19.89a8 8 0 0 1-11.313 11.314z"/><path fill="#FEE1D3" d="M15.063 54.54a2 2 0 0 1 0 2.828L3.749 68.68A2 2 0 1 1 .92 65.853l11.314-11.314a2 2 0 0 1 2.829 0zm9.899 9.899a2 2 0 0 1 0 2.828l-8.485 8.485a2 2 0 1 1-2.829-2.828l8.486-8.485a2 2 0 0 1 2.828 0z"/><path fill="#FDC4A8" d="M20.012 59.489a2 2 0 0 1 0 2.828L4.456 77.874a2 2 0 0 1-2.829-2.829L17.184 59.49a2 2 0 0 1 2.828 0z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/multi-editor_no_changes_empty.svg b/app/assets/images/illustrations/multi-editor_no_changes_empty.svg
deleted file mode 100644
index ebeea1f3dd92fc973739387ccb14a3ec760cf382..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/multi-editor_no_changes_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(7 3)"><path fill="#EEE" fill-rule="nonzero" d="M54 18a2 2 0 1 1 0-4h4c.843 0 1.675.105 2.48.31a2 2 0 1 1-.99 3.876A6.015 6.015 0 0 0 58 18h-4zm9.735 4.228a2 2 0 0 1 3.822-1.18A10 10 0 0 1 68 24v3.513a2 2 0 1 1-4 0V24c0-.61-.09-1.204-.265-1.772zM64 35.513a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0V66a9.97 9.97 0 0 1-.963 4.286 2 2 0 1 1-3.613-1.716A5.969 5.969 0 0 0 64 66v-2.487zm-5.255 8.441a2 2 0 1 1 .49 3.97c-.401.05-.806.075-1.218.076h-5.042a2 2 0 1 1 0-4h5.038c.246 0 .49-.016.732-.046zM44.975 72a2 2 0 1 1 0 4h-6a2 2 0 1 1 0-4h6zm-14 0a2 2 0 1 1 0 4H26c-.429 0-.855-.027-1.276-.08a2 2 0 0 1 .506-3.969c.254.033.51.049.77.049h4.975zm-10.438-3.514a2 2 0 1 1-3.64 1.66A9.97 9.97 0 0 1 16 66v-2.538a2 2 0 1 1 4 0V66c0 .871.185 1.713.537 2.486zM8 2a6 6 0 0 0-6 6v42a6 6 0 0 0 6 6h32a6 6 0 0 0 6-6V8a6 6 0 0 0-6-6H8zm0-4h32c5.523 0 10 4.477 10 10v42c0 5.523-4.477 10-10 10H8C2.477 60-2 55.523-2 50V8C-2 2.477 2.477-2 8-2z"/><rect width="10" height="4" x="8" y="16" fill="#EFEDF8" rx="2"/><rect width="10" height="4" x="21" y="16" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="8" y="32" fill="#E1DBF1" rx="2"/><rect width="6" height="4" x="34" y="16" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="8" y="24" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="24" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="21" y="32" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="8" y="40" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="40" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="26" y="40" fill="#C3B8E3" rx="2"/><rect width="10" height="4" x="26" y="24" fill="#C3B8E3" rx="2"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/multi-editor_no_staged_files_empty.svg b/app/assets/images/illustrations/multi-editor_no_staged_files_empty.svg
deleted file mode 100644
index 08321ef526b870efcf7d33652af0594abefffac1..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/multi-editor_no_staged_files_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(0 3)"><path fill="#EEE" fill-rule="nonzero" d="M40.843 5.864a2 2 0 1 1 .348-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.977-.523zm13.946 1.22a2 2 0 1 1 .349-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.978-.523zm13.947 1.22a2 2 0 1 1 .349-3.984 11.952 11.952 0 0 1 6.655 2.75 2 2 0 1 1-2.569 3.066 7.953 7.953 0 0 0-4.435-1.832zm7.28 7.357a2 2 0 1 1 3.99-.301c.048.639.045 1.283-.01 1.934l-.385 4.4a2 2 0 1 1-3.985-.349l.384-4.395c.037-.433.039-.863.007-1.29zm-1.088 13.654a2 2 0 0 1 3.985.348l-.523 5.978a2 2 0 1 1-3.984-.349l.522-5.977zm-1.22 13.947a2 2 0 1 1 3.985.348l-.523 5.977a2 2 0 1 1-3.985-.348l.523-5.977zM72.305 56.7a2 2 0 0 1 3.79 1.282 11.995 11.995 0 0 1-4.253 5.81 2 2 0 0 1-2.373-3.22 7.996 7.996 0 0 0 2.836-3.872zm-9.054 5.33a2 2 0 1 1-.349 3.985l-5.977-.522a2 2 0 1 1 .349-3.985l5.977.523zM32.793 10.675a2 2 0 1 1-3.675-1.579 12.02 12.02 0 0 1 4.696-5.456 2 2 0 0 1 2.112 3.397 8.02 8.02 0 0 0-3.133 3.638z"/><rect width="48" height="58" x="2" y="14" fill="#FAFAFA" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M12 16a8 8 0 0 0-8 8v38a8 8 0 0 0 8 8h28a8 8 0 0 0 8-8V24a8 8 0 0 0-8-8H12zm0-4h28c6.627 0 12 5.373 12 12v38c0 6.627-5.373 12-12 12H12C5.373 74 0 68.627 0 62V24c0-6.627 5.373-12 12-12z"/><rect width="24" height="4" x="11" y="30" fill="#E5E5E5" rx="2"/><rect width="30" height="4" x="11" y="41" fill="#E5E5E5" rx="2"/><rect width="20" height="4" x="11" y="52" fill="#E5E5E5" rx="2"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/multi_file_editor_empty.svg b/app/assets/images/illustrations/multi_file_editor_empty.svg
deleted file mode 100644
index bd376f0a050037d4304376ac6cc5f45df26c7f8e..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/multi_file_editor_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="300"><g fill="none" fill-rule="evenodd" transform="translate(35 29)"><path fill="#EEE" fill-rule="nonzero" d="M90 23a2 2 0 1 1 0-4h10a2 2 0 0 1 0 4H90zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h1a11.98 11.98 0 0 1 9.457 4.612 2 2 0 0 1-3.151 2.464A7.981 7.981 0 0 0 331 23h-1zm9 11.39a2 2 0 0 1 4 0v10a2 2 0 0 1-4 0v-10zm0 180a2 2 0 1 1 4 0V223c0 .56-.038 1.114-.114 1.662a2 2 0 0 1-3.962-.55A8.21 8.21 0 0 0 339 223v-8.61zm-4.769 15.931a2 2 0 0 1 1.618 3.658A11.967 11.967 0 0 1 331 235h-5.782a2 2 0 0 1 0-4H331c1.13 0 2.224-.233 3.231-.679zm-19.013.679a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zM115 231a2 2 0 0 1 0 4h-10a2 2 0 0 1 0-4h10zm-26.2 4c.131-.646.2-1.315.2-2v-2h4a2 2 0 0 1 0 4h-4.2z"/><path fill="#EEE" fill-rule="nonzero" d="M103 211h258a6 6 0 0 0 6-6V63a6 6 0 0 0-6-6H166a5 5 0 0 1-5-5v-8.5a5.5 5.5 0 0 0-5.5-5.5H109a6 6 0 0 0-6 6v167zm62-167.5V52a1 1 0 0 0 1 1h195c5.523 0 10 4.477 10 10v142c0 5.523-4.477 10-10 10H99V44c0-5.523 4.477-10 10-10h46.5a9.5 9.5 0 0 1 9.5 9.5z"/><rect width="40" height="4" x="118" y="78" fill="#6B4FBB" rx="2"/><rect width="30" height="4" x="118" y="90" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="153" y="90" fill="#E1DBF1" rx="2"/><rect width="150" height="4" x="118" y="102" fill="#EFEDF8" rx="2"/><rect width="90" height="4" x="118" y="114" fill="#E1DBF1" rx="2"/><rect width="60" height="4" x="118" y="138" fill="#EFEDF8" rx="2"/><rect width="20" height="4" x="118" y="150" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="144" y="150" fill="#C3B8E3" rx="2"/><rect width="20" height="4" x="170" y="150" fill="#E1DBF1" rx="2"/><rect width="130" height="4" x="118" y="162" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="118" y="174" fill="#C3B8E3" rx="2"/><rect width="30" height="4" x="154" y="174" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="190" y="174" fill="#EFEDF8" rx="2"/><rect width="40" height="4" x="118" y="186" fill="#E1DBF1" rx="2"/><path fill="#F9F9F9" d="M89 24.292l11.434 19.326v170.326L89 226.336V24.292z"/><path fill="#EEE" fill-rule="nonzero" d="M89 229.286v-5.9l9.434-10.223V44.165L89 28.22v-7.856l13.434 22.707v171.655L89 229.286zM10 4a6 6 0 0 0-6 6v223a6 6 0 0 0 6 6h69a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h69c5.523 0 10 4.477 10 10v223c0 5.523-4.477 10-10 10H10c-5.523 0-10-4.477-10-10V10C0 4.477 4.477 0 10 0z"/><circle cx="25" cy="23" r="11" fill="#FEF0E8"/><path fill="#FEE1D3" d="M46 17h16a2 2 0 1 1 0 4H46a2 2 0 1 1 0-4zm0 8h27a2 2 0 1 1 0 4H46a2 2 0 1 1 0-4z"/><path fill="#EEE" d="M16 50h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-4 12h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H40a2 2 0 1 1 0-4zM26 78h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H40a2 2 0 1 1 0-4z"/><g transform="translate(14 110)"><rect width="8" height="8" fill="#FEE1D3" rx="2"/><rect width="28" height="4" x="14" y="2" fill="#FEF0E8" rx="2"/></g><path fill="#EEE" d="M16 140h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4z"/><g transform="translate(24 124)"><rect width="8" height="8" fill="#FEE1D3" rx="2"/><rect width="28" height="4" x="14" y="2" fill="#FEF0E8" rx="2"/></g><g fill="#FC6D26" transform="translate(24 92)"><rect width="8" height="8" rx="2"/><rect width="28" height="4" x="14" y="2" rx="2"/></g><path fill="#FDC4A8" fill-rule="nonzero" d="M152 50.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9zm0-3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/no_commits.svg b/app/assets/images/illustrations/no_commits.svg
deleted file mode 100644
index 76fa25156dd8e87ea110cb2b3af4f87baf0b46a2..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/no_commits.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="M4.01 2h1.102a1 1 0 0 0 0-2H4.01A4.001 4.001 0 0 0 0 4a1 1 0 0 0 2 0c0-1.108.892-2 2.01-2m12.702 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7M164 2c.822 0 1.554.503 1.86 1.254a1 1 0 1 0 1.853-.753 4.01 4.01 0 0 0-3.712-2.5h-2.188a1 1 0 0 0 0 2h2.188m2.01 12.518a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 11.6a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 11.6a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72a1 1 0 0 0 0 2h.72a4.001 4.001 0 0 0 4.01-4v-.382a1 1 0 0 0-2 0v.382m-14.325 2a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-8.47 0a2.01 2.01 0 0 1-1.782-1.085 1 1 0 0 0-1.775.923 4.007 4.007 0 0 0 3.556 2.162h2.57a1 1 0 0 0 0-2h-2.57m-2.01-12.136a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-11.6a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-11.6a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-6.664a1 1 0 0 0-2 0v.764a1 1 0 0 0 2 0v-.764" id="a"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="b"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="c"/><path d="M131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9a.998.998 0 0 0-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01a2.998 2.998 0 0 1 2.996 2.999v9a3.003 3.003 0 0 1-2.996 2.999h-22.01A2.998 2.998 0 0 1 129 28.999v-9A3.003 3.003 0 0 1 131.996 17" id="d"/><g transform="translate(0 59)"><use xlink:href="#a"/><circle cx="21" cy="24" r="10"/><use xlink:href="#b"/><use xlink:href="#c"/><use xlink:href="#d"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/pending_job_empty.svg b/app/assets/images/illustrations/pending_job_empty.svg
deleted file mode 100644
index 8de695afa1800a8c046cad302be18286d3449f85..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/pending_job_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="200" viewBox="0 0 430 200"><g fill="none" fill-rule="evenodd"><g transform="translate(138 65)"><path fill="#E5E5E5" fill-rule="nonzero" d="M35 70a2 2 0 1 1 0-4c2.542 0 5.042-.305 7.463-.904a2 2 0 1 1 .96 3.884A35.075 35.075 0 0 1 35 70zm18.21-5.105a2 2 0 1 1-2.083-3.414 31.143 31.143 0 0 0 5.896-4.664 2 2 0 1 1 2.842 2.815 35.143 35.143 0 0 1-6.654 5.263zM66.106 51.06a2 2 0 0 1-3.552-1.838 30.77 30.77 0 0 0 2.612-7.042 2 2 0 1 1 3.892.922 34.77 34.77 0 0 1-2.952 7.958zm3.816-18.433a2 2 0 1 1-3.991.268 30.873 30.873 0 0 0-1.407-7.38 2 2 0 0 1 3.808-1.223 34.873 34.873 0 0 1 1.59 8.335zm-6.346-17.842a2 2 0 0 1-3.264 2.312 31.188 31.188 0 0 0-5.054-5.564 2 2 0 0 1 2.615-3.027 35.188 35.188 0 0 1 5.703 6.279zM48.895 2.867a2 2 0 0 1-1.59 3.67 30.758 30.758 0 0 0-7.206-2.12 2 2 0 1 1 .653-3.946 34.758 34.758 0 0 1 8.143 2.396zM30.263.318a2 2 0 0 1 .537 3.964c-2.505.339-4.94.98-7.266 1.907a2 2 0 1 1-1.48-3.716A34.774 34.774 0 0 1 30.263.318zM12.907 7.853a2 2 0 0 1 2.527 3.1 31.196 31.196 0 0 0-5.213 5.416 2 2 0 0 1-3.196-2.406 35.196 35.196 0 0 1 5.882-6.11zM1.99 23.343a2 2 0 0 1 3.772 1.331 30.82 30.82 0 0 0-1.619 7.337 2 2 0 1 1-3.982-.38 34.82 34.82 0 0 1 1.829-8.289zM.719 42.086a2 2 0 1 1 3.917-.806 30.757 30.757 0 0 0 2.4 7.118 2 2 0 1 1-3.605 1.73 34.757 34.757 0 0 1-2.713-8.042zM9.393 58.86a2 2 0 0 1 2.926-2.728 31.167 31.167 0 0 0 5.751 4.841 2 2 0 1 1-2.187 3.349 35.167 35.167 0 0 1-6.49-5.462zm16.245 9.873a2 2 0 1 1 1.067-3.855 30.979 30.979 0 0 0 7.434 1.11 2 2 0 1 1-.11 3.998 34.979 34.979 0 0 1-8.391-1.253z"/><circle cx="35" cy="35" r="16" stroke="#E1DBF1" stroke-width="4"/><path fill="#6B4FBB" d="M37 33h5a2 2 0 1 1 0 4h-7a2 2 0 0 1-2-2v-8a2 2 0 1 1 4 0v6z"/></g><g transform="translate(247 30)"><rect width="116" height="135" y="5" fill="#F9F9F9" rx="10"/><rect width="116" height="134" x="5" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="10"/><g transform="translate(23 23)"><rect width="16" height="4" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="32" y="12" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="44" fill="#EEE" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="32" y="36" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="48" fill="#E1DBF1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#FC6D26" rx="2"/><rect width="4" height="4" x="56" y="36" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="64" y="60" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="72" y="60" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="28" height="4" y="36" fill="#EEE" rx="2"/><rect width="28" height="4" x="44" y="48" fill="#EEE" rx="2"/><rect width="28" height="4" x="32" y="60" fill="#EFEDF8" rx="2"/><rect width="28" height="4" y="12" fill="#6B4FBB" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#C3B8E3" rx="2"/><rect width="8" height="4" y="24" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6B4FBB" rx="2"/><rect width="12" height="4" y="48" fill="#FC6D26" rx="2"/><rect width="12" height="4" y="60" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="60" fill="#FEF0E8" rx="2"/></g><g transform="translate(23 95)"><rect width="16" height="4" fill="#EFEDF8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#FC6D26" rx="2"/><rect width="16" height="4" x="44" fill="#6B4FBB" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="38" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="14" height="4" y="12" fill="#EEE" rx="2"/></g></g><path fill="#FC6D26" fill-rule="nonzero" d="M81 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15zm-5-20a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2zm10 0a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2z"/><path fill="#E5E5E5" fill-rule="nonzero" d="M108 102c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm93 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/pipelines_empty.svg b/app/assets/images/illustrations/pipelines_empty.svg
deleted file mode 100644
index f3107c8f0621ce776bfd85446475d9193af29138..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/pipelines_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 150"><g fill="none" fill-rule="evenodd"><g fill="#e5e5e5" transform="translate(0 102)"><rect width="74" height="4" x="34" y="21" opacity=".5" rx="2"/><path d="M152 23c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 152 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 166 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 180 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 194 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 208 23"/></g><g fill="#31af64"><path fill-rule="nonzero" d="M19 144c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19m0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15"/><path d="M17.07 127.02l-2.829-2.829a1.995 1.995 0 0 0-2.828 0 1.995 1.995 0 0 0 0 2.828l4.243 4.243a1.995 1.995 0 0 0 2.822.006l7.79-7.79a1.997 1.997 0 0 0-.006-2.823 1.992 1.992 0 0 0-2.823-.006l-6.37 6.37"/></g><g fill="#e52c5a"><path fill-rule="nonzero" d="M126 149.5c-12.979 0-23.5-10.521-23.5-23.5s10.521-23.5 23.5-23.5 23.5 10.521 23.5 23.5-10.521 23.5-23.5 23.5m0-5c10.217 0 18.5-8.283 18.5-18.5s-8.283-18.5-18.5-18.5-18.5 8.283-18.5 18.5 8.283 18.5 18.5 18.5"/><path d="M130.24 126l2.833-2.833a3 3 0 0 0-4.243-4.243l-2.833 2.833-2.833-2.833a3 3 0 0 0-4.243 4.243l2.833 2.833-2.833 2.833a3 3 0 0 0 4.243 4.243l2.833-2.833 2.833 2.833a3 3 0 0 0 4.243-4.243L130.24 126"/></g><path fill="#e5e5e5" fill-rule="nonzero" d="M236 139c-7.732 0-14-6.268-14-14s6.268-14 14-14 14 6.268 14 14-6.268 14-14 14m0-4c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10"/><g transform="translate(73 4)"><path stroke="#e5e5e5" stroke-width="4" d="M64.82 76H98c4.419 0 8-3.579 8-7.99V7.99C106 3.577 102.417 0 98 0H8.009c-4.419 0-8 3.579-8 7.99v60.02c0 4.413 3.583 7.99 8 7.99h31.935l9.263 9.855a4.357 4.357 0 0 0 6.354 0L64.824 76"/><rect width="18" height="6" x="11" y="19" fill="#fc8a51" rx="3"/><rect width="18" height="6" x="35" y="35" fill="#e52c5a" rx="3"/><rect width="18" height="6" x="29" y="51" fill="#e5e5e5" rx="3"/><rect width="12" height="6" x="35" y="19" fill="#fde5d8" rx="3"/><rect width="12" height="6" x="53" y="51" fill="#e52c5a" rx="3"/><rect width="12" height="6" x="11" y="51" fill="#b5a7dd" rx="3"/><rect width="18" height="6" x="77" y="19" fill="#fc8a51" rx="3"/><rect width="18" height="6" x="11" y="35" fill="#fde5d8" rx="3"/><rect width="6" height="6" x="53" y="19" fill="#e52c5a" rx="3"/><g fill="#fde5d8"><rect width="6" height="6" x="65" y="19" rx="3"/><rect width="6" height="6" x="71" y="35" rx="3"/></g><rect width="6" height="6" x="59" y="35" fill="#e52c5a" rx="3"/></g><path fill="#6b4fbb" fill-rule="nonzero" d="M151.869 77.403c-13.26 9.264-31.649 7.977-43.484-3.858-13.279-13.279-13.279-34.806 0-48.084 13.278-13.278 34.805-13.278 48.083 0 11.836 11.836 13.118 30.23 3.858 43.485.133.111.262.229.387.354l15.556 15.555a6.004 6.004 0 0 1 0 8.486 5.997 5.997 0 0 1-8.486 0l-15.555-15.556a6.051 6.051 0 0 1-.355-.387m-1.06-9.512c10.154-10.154 10.154-26.617 0-36.77-10.153-10.154-26.616-10.154-36.77 0-10.153 10.153-10.153 26.616 0 36.77 10.154 10.153 26.617 10.153 36.77 0"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/pipelines_failed.svg b/app/assets/images/illustrations/pipelines_failed.svg
deleted file mode 100644
index 8daf7da86ed9fdef21cc3ec6a150f7b77272063e..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/pipelines_failed.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446 249" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M260.03 114h23.972v-.013c19.972-.53 36-16.887 36-36.987 0-20.435-16.565-37-37-37-.993 0-1.977.039-2.95.116-4.95-14.605-18.773-25.12-35.05-25.12a36.87 36.87 0 0 0-15.32 3.311c-6.649-9.841-17.909-16.311-30.68-16.311-20.435 0-37 16.565-37 37 0 .701.019 1.397.058 2.088C145.95 45.083 134 59.645 134 76.996c0 20.435 16.565 37 37 37 .324 0 .646-.004.968-.012"/><ellipse id="b" cx="41" cy="41" rx="41" ry="41"/><mask id="c" width="186" height="112" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><mask id="d" width="82" height="82" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask></defs><g fill="none" fill-rule="evenodd"><path stroke="#b5a7dd" stroke-width="4" d="M228.415 137.792c8.443 17.156 21.89 32.082 39.688 42.358"/><path fill="#fb722e" d="M284.464 183.822a2.006 2.006 0 0 1 2.74-.727l6.914 3.992a2.001 2.001 0 0 1 .741 2.737 2.006 2.006 0 0 1-2.74.727l-6.914-3.992a2.001 2.001 0 0 1-.74-2.737m-5 8.66a2.006 2.006 0 0 1 2.74-.726l6.913 3.991a2.001 2.001 0 0 1 .741 2.737 2.006 2.006 0 0 1-2.74.727l-6.914-3.991a2.001 2.001 0 0 1-.74-2.737"/><path fill="#fde5d8" fill-rule="nonzero" d="M267.072 189.947l5.196 3a5.998 5.998 0 0 0 8.195-2.194l3.005-5.205a5.995 5.995 0 0 0-2.198-8.193l-5.196-3-9 15.588m6.032-18.447a3.005 3.005 0 0 1 4.098-1.11l6.07 3.505c4.784 2.761 6.426 8.871 3.662 13.658l-3.005 5.204c-2.76 4.782-8.875 6.42-13.659 3.658l-6.07-3.505a2.999 2.999 0 0 1-1.088-4.104l9.992-17.306"/><g fill-rule="nonzero"><path fill="#e5e5e5" d="M260.597 18.747C266.208 9.657 276.116 4 287 4c17.12 0 31 13.879 31 31 0 7.02-2.34 13.685-6.58 19.1l3.149 2.466A34.855 34.855 0 0 0 322 35.001c0-19.33-15.67-35-35-35-12.286 0-23.476 6.384-29.808 16.647l3.404 2.1"/><path fill="#b5a7dd" d="M281.982 23.991l-2.526 1.154-2.992-2.993a.4.4 0 0 0-.564.009l-1.738 1.738a.392.392 0 0 0-.009.564l2.987 2.987-1.147 2.524a12.26 12.26 0 0 0-1.04 3.883l-.269 2.76-4.08 1.093a.399.399 0 0 0-.275.492l.636 2.375c.06.223.273.346.485.29l4.087-1.096 1.611 2.262a12.017 12.017 0 0 0 2.827 2.828l2.26 1.612-1.094 4.08a.399.399 0 0 0 .29.485l2.374.636a.393.393 0 0 0 .493-.275l1.093-4.08 2.763-.267a12.14 12.14 0 0 0 3.862-1.035l2.526-1.154 2.992 2.992a.4.4 0 0 0 .564-.008l1.738-1.738a.392.392 0 0 0 .009-.564l-2.987-2.987 1.147-2.524a12.26 12.26 0 0 0 1.04-3.883l.27-2.76 4.08-1.093a.399.399 0 0 0 .274-.493l-.636-2.374a.393.393 0 0 0-.485-.29l-4.087 1.096-1.611-2.262a12.017 12.017 0 0 0-2.826-2.828l-2.26-1.612 1.093-4.08a.399.399 0 0 0-.29-.485l-2.373-.636a.393.393 0 0 0-.493.274l-1.094 4.081-2.763.266c-1.336.129-2.64.48-3.862 1.036m3.48-5.02l.375-1.4a4.393 4.393 0 0 1 5.392-3.103l2.375.636a4.399 4.399 0 0 1 3.117 5.383l-.375 1.401a16.077 16.077 0 0 1 3.761 3.767l1.405-.376a4.397 4.397 0 0 1 5.386 3.118l.636 2.375a4.398 4.398 0 0 1-3.103 5.39l-1.402.376a16.217 16.217 0 0 1-1.378 5.143l1.027 1.026a4.392 4.392 0 0 1-.008 6.22l-1.739 1.738a4.4 4.4 0 0 1-6.224.008l-1.028-1.028a16.09 16.09 0 0 1-5.14 1.381l-.376 1.4a4.393 4.393 0 0 1-5.392 3.104l-2.374-.636a4.399 4.399 0 0 1-3.118-5.383l.376-1.401a16.077 16.077 0 0 1-3.762-3.767l-1.404.376a4.397 4.397 0 0 1-5.386-3.118l-.637-2.374a4.398 4.398 0 0 1 3.103-5.391l1.402-.376a16.217 16.217 0 0 1 1.378-5.143l-1.026-1.026a4.392 4.392 0 0 1 .008-6.22l1.738-1.738a4.4 4.4 0 0 1 6.224-.008l1.028 1.028a16.09 16.09 0 0 1 5.141-1.381"/><path fill="#6b4fbb" d="M286.367 37.355a2.439 2.439 0 1 0 1.262-4.711 2.439 2.439 0 0 0-1.262 4.711m-1.035 3.864a6.44 6.44 0 1 1 3.333-12.44 6.44 6.44 0 0 1-3.333 12.44"/></g><use fill="#fff" stroke="#e5e5e5" stroke-width="8" mask="url(#c)" stroke-linejoin="round" xlink:href="#a"/><g transform="translate(175 58)"><use fill="#fff" stroke="#e5e5e5" stroke-width="8" mask="url(#d)" xlink:href="#b"/><g fill-rule="nonzero"><path fill="#e5e5e5" d="M41 78c20.435 0 37-16.565 37-37S61.435 4 41 4 4 20.565 4 41s16.565 37 37 37m0 4C18.356 82 0 63.644 0 41S18.356 0 41 0s41 18.356 41 41-18.356 41-41 41"/><path fill="#b5a7dd" d="M34.363 26.44l-2.527 1.154-3.211-3.211a1.495 1.495 0 0 0-2.117-.005l-2.131 2.13a1.504 1.504 0 0 0 .005 2.117l3.206 3.206-1.147 2.524a16.09 16.09 0 0 0-.897 2.503 16.08 16.08 0 0 0-.475 2.616l-.269 2.76-4.379 1.174a1.495 1.495 0 0 0-1.063 1.83l.78 2.911a1.504 1.504 0 0 0 1.836 1.054l4.387-1.176 1.612 2.263a15.954 15.954 0 0 0 3.737 3.742l2.26 1.612-1.173 4.38a1.495 1.495 0 0 0 1.053 1.835l2.908.78a1.504 1.504 0 0 0 1.83-1.063l1.174-4.38 2.763-.266a15.977 15.977 0 0 0 5.108-1.372l2.527-1.154 3.211 3.212a1.495 1.495 0 0 0 2.117.005l2.131-2.131a1.504 1.504 0 0 0-.005-2.117l-3.206-3.206 1.147-2.524a16.09 16.09 0 0 0 .897-2.503 16.1 16.1 0 0 0 .475-2.616l.269-2.76 4.379-1.173a1.495 1.495 0 0 0 1.063-1.83l-.78-2.912a1.504 1.504 0 0 0-1.836-1.054l-4.387 1.176-1.612-2.262a15.954 15.954 0 0 0-3.737-3.743l-2.26-1.612 1.173-4.38a1.495 1.495 0 0 0-1.053-1.835l-2.908-.779a1.504 1.504 0 0 0-1.83 1.063l-1.174 4.38-2.763.265c-1.767.17-3.493.636-5.108 1.373m4.726-5.355l.455-1.699a5.504 5.504 0 0 1 6.73-3.89l2.907.778a5.495 5.495 0 0 1 3.882 6.735l-.455 1.699a19.95 19.95 0 0 1 4.673 4.68l1.704-.457a5.503 5.503 0 0 1 6.734 3.886l.78 2.91a5.493 5.493 0 0 1-3.894 6.73l-1.701.455a20.134 20.134 0 0 1-.593 3.265 20.134 20.134 0 0 1-1.119 3.124l1.245 1.246a5.507 5.507 0 0 1 .008 7.774l-2.13 2.13a5.5 5.5 0 0 1-7.775-.001l-1.248-1.248c-2 .914-4.157 1.502-6.387 1.717l-.455 1.699a5.504 5.504 0 0 1-6.73 3.89l-2.907-.778a5.495 5.495 0 0 1-3.882-6.735l.455-1.699a19.95 19.95 0 0 1-4.673-4.68l-1.704.457a5.503 5.503 0 0 1-6.734-3.886l-.78-2.91a5.493 5.493 0 0 1 3.894-6.73l1.701-.455a20.258 20.258 0 0 1 1.712-6.389l-1.245-1.246a5.507 5.507 0 0 1-.008-7.774l2.13-2.13a5.5 5.5 0 0 1 7.775.001l1.248 1.248c2-.914 4.157-1.502 6.387-1.717"/><path fill="#6b4fbb" d="M39.965 44.863a4 4 0 1 0 2.07-7.727 4 4 0 0 0-2.07 7.727m-1.036 3.864a8 8 0 1 1 4.142-15.455 8 8 0 0 1-4.142 15.455"/></g></g><path fill="#e5e5e5" fill-rule="nonzero" d="M144 169.541v30.01a4.002 4.002 0 0 0 4 3.995h20c2.209 0 4-1.789 4-3.995v-30.01a4.002 4.002 0 0 0-4-3.995h-20c-2.209 0-4 1.789-4 3.995m-4 0c0-4.416 3.583-7.995 8-7.995h20c4.416 0 8 3.584 8 7.995v30.01c0 4.416-3.583 7.995-8 7.995h-20c-4.416 0-8-3.584-8-7.995v-30.01"/><g fill="#fb722e" transform="translate(140 161)"><rect width="4" height="11" x="10" y="18.545" rx="2"/><rect width="4" height="11" x="21" y="18.545" rx="2"/></g><path fill="#e5e5e5" fill-rule="nonzero" d="M445.16 245.34c-16.874-11.778-110.62-20.336-222.14-20.336-111.61 0-205.4 8.571-222.18 20.364a2 2 0 1 0 2.3 3.272c15.756-11.07 109.46-19.636 219.88-19.636 110.34 0 203.99 8.55 219.85 19.617a2.001 2.001 0 0 0 2.29-3.28"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/pipelines_pending.svg b/app/assets/images/illustrations/pipelines_pending.svg
deleted file mode 100644
index 25038366e92fc3371ad859d91908fabc5a23a427..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/pipelines_pending.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="220" viewBox="0 0 430 220"><g fill="none" fill-rule="evenodd"><path fill="#EEE" fill-rule="nonzero" d="M189.8 182l2.4-12H114c-5.523 0-10-4.477-10-10V34c0-5.523 4.477-10 10-10h200c5.523 0 10 4.477 10 10v126c0 5.523-4.477 10-10 10h-78.2l2.4 12h22.52a9.651 9.651 0 0 1 9.28 7 5.491 5.491 0 0 1-5.28 7H164.159a5.787 5.787 0 0 1-5.659-7 8.855 8.855 0 0 1 8.659-7H189.8zM114 28a6 6 0 0 0-6 6v126a6 6 0 0 0 6 6h200a6 6 0 0 0 6-6V34a6 6 0 0 0-6-6H114zm5 6h190a5 5 0 0 1 5 5v116a5 5 0 0 1-5 5H119a5 5 0 0 1-5-5V39a5 5 0 0 1 5-5zm0 4a1 1 0 0 0-1 1v116a1 1 0 0 0 1 1h190a1 1 0 0 0 1-1V39a1 1 0 0 0-1-1H119zm112.72 132h-35.44l-2.4 12h40.24l-2.4-12zm-64.561 16c-2.29 0-4.268 1.6-4.748 3.838A1.787 1.787 0 0 0 164.16 192h100.56a1.491 1.491 0 0 0 1.435-1.901A5.651 5.651 0 0 0 260.72 186h-93.561z"/><path fill="#FEF0E8" d="M177.965 99H194a2 2 0 1 1 0 4h-16.322c-1.374 6.29-6.976 11-13.678 11-6.702 0-12.304-4.71-13.678-11h-3.365l-7.395 9.249a2 2 0 0 1-3.049.089L128.11 103h-5.844a2 2 0 1 1 0-4H129a2 2 0 0 1 1.487.662l7.423 8.248 6.523-8.159a2 2 0 0 1 1.562-.751h4.04c.513-7.265 6.57-13 13.965-13 7.396 0 13.452 5.735 13.965 13zM164 110c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z"/><path fill="#EFEDF8" d="M273.847 103c-.962 6.23-6.347 11-12.847 11-6.5 0-11.885-4.77-12.847-11H232a2 2 0 0 1 0-4h16.153c.962-6.23 6.347-11 12.847-11 6.5 0 11.885 4.77 12.847 11h3.998l8.404-9.338a2 2 0 0 1 3.048.09L296.692 99H305a2 2 0 0 1 0 4h-9.27a2 2 0 0 1-1.562-.751l-6.523-8.16-7.423 8.249a2 2 0 0 1-1.487.662h-4.888zM261 110a9 9 0 1 0 0-18 9 9 0 0 0 0 18z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M213 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15z"/><path fill="#FC6D26" d="M211.586 101.828L208.757 99a2 2 0 1 0-2.828 2.828l4.243 4.243c.39.39.902.586 1.414.586.512 0 1.023-.195 1.414-.586L220.071 99a2 2 0 1 0-2.828-2.828l-5.657 5.656z"/><path fill="#FDC4A8" d="M162.95 101.07l-1.768-1.767a1.5 1.5 0 0 0-2.121 2.121l2.828 2.829c.293.293.677.439 1.06.439.385 0 .769-.146 1.062-.44l4.242-4.242a1.5 1.5 0 1 0-2.121-2.121l-3.182 3.182z"/><path fill="#6B4FBB" d="M256.39 104.841A6 6 0 1 0 261 95v6l-4.61 3.841z"/><path fill="#FEF0E8" fill-rule="nonzero" d="M99 99h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-14.384-.078l-3.643-3.425a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-11.657-10.96l-3.642-3.425a2 2 0 1 0-2.74 2.914l3.642 3.425a2 2 0 0 0 2.74-2.914zm-11.656-10.96l-3.643-3.425a2 2 0 0 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-14.367-3.885l-3.593 3.477a2 2 0 0 0 2.782 2.875l3.593-3.477a2 2 0 0 0-2.782-2.875zM19.44 84.244l-3.593 3.477a2 2 0 1 0 2.781 2.874l3.593-3.477a2 2 0 0 0-2.781-2.874zM7.94 95.371l-3.593 3.477a2 2 0 1 0 2.782 2.874l3.593-3.477a2 2 0 1 0-2.782-2.874z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M423.611 99.56l-3.598 3.472a2 2 0 0 0 2.777 2.879l3.599-3.472a2 2 0 0 0-2.778-2.878zm-11.514 11.11l-3.598 3.472a2 2 0 0 0 2.777 2.878l3.598-3.471a2 2 0 0 0-2.777-2.879zm-11.514 11.11l-3.599 3.471a2 2 0 1 0 2.778 2.879l3.598-3.472a2 2 0 1 0-2.777-2.879zm-8.799 4.48l-3.642-3.426a2 2 0 0 0-2.74 2.915l3.642 3.425a2 2 0 0 0 2.74-2.915zm-11.656-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.426a2 2 0 1 0 2.74-2.915zm-11.657-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zM353.001 99h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/priority_labels.svg b/app/assets/images/illustrations/priority_labels.svg
deleted file mode 100644
index b79c551d3d79618e87f13121caaef614d63b1b09..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/priority_labels.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="116" height="68" viewBox="181 0 116 68"><g fill="none" fill-rule="evenodd" transform="translate(182)"><rect width="78" height="34" x="37" y="34" fill="#FAFAFA" rx="3"/><rect width="78" height="34" x="31" y="28" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="3"/><path fill="#FFF" stroke="#FC6D26" stroke-width="3" d="M34 35.8c-.6 0-1.4 0-1.8.4L29 38.8c-1 .7-1.7.4-2-.7l-.6-4c0-.5-.5-1.2-1-1.5L22 30.2c-1-.6-1-1.5 0-2l3.7-2c.5-.2 1-.8 1.2-1.3l1-4.2c.3-1 1-1.3 2-.5l3 3c.3.3 1 .6 1.6.6l4.2-.3c1 0 1.5.7 1 1.7L38 29c-.3.6-.3 1.4 0 2l1.3 3.8c.4 1 0 1.8-1.2 1.6l-4-.6z" stroke-linecap="round"/><path fill="#FDE5D8" d="M51.6 14.3c-.2-.2-.8-.4-1-.3l-2.8.5c-.7 0-1-.4-.8-1l1-2.8V9.5L46.6 7c-.3-.7 0-1.2.8-1h2.7c.3 0 .8-.2 1-.5l2-2c.6-.5 1-.4 1.3.3l.7 2.8c0 .3.4.8.7 1l2.3 1.2c.7.3.7 1 0 1.3l-2.2 1.7-.6 1-.4 3c-.2.6-.7.8-1.3.4l-2-1.7zM5.4 43.2c-.2-.2-.5-.2-.7-.2l-1.8.3c-.6 0-1-.2-.7-.7l.7-1.8V40l-1-1.7c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L6.5 36c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2zm5-34C10.2 9 10 9 9.7 9L8 9.3c-.6 0-1-.2-.7-.7L8 6.8V6L7 4.3c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L11.5 2c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2z"/><rect width="52" height="4" x="43" y="38" fill="#EEE" rx="2"/><rect width="36" height="4" x="43" y="48" fill="#EEE" rx="2"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/service_desk_callout.svg b/app/assets/images/illustrations/service_desk_callout.svg
deleted file mode 100644
index 2886388279ee438fe360d0f653e95a054581364f..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/service_desk_callout.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><rect width="7" height="1" x="59" y="38" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M60.5 42a3.5 3.5 0 0 0 0-7v7z"/><rect width="7" height="1" x="12" y="38" fill="#E1DBF2" transform="matrix(-1 0 0 1 31 0)" rx=".5"/><path fill="#6B4FBB" d="M17.5 42a3.5 3.5 0 0 1 0-7v7z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M39 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M35 56a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M26.5 40c0 4.143 3.355 7.5 7.494 7.5h10.012A7.497 7.497 0 0 0 51.5 40c0-4.143-3.355-7.5-7.494-7.5H33.994A7.497 7.497 0 0 0 26.5 40zm-3 0c0-5.799 4.698-10.5 10.494-10.5h10.012C49.802 29.5 54.5 34.2 54.5 40c0 5.799-4.698 10.5-10.494 10.5H33.994C28.198 50.5 23.5 45.8 23.5 40z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M35.255 42.406a1 1 0 1 1 1.872-.703 2.001 2.001 0 0 0 3.76-.038 1 1 0 1 1 1.886.665 4 4 0 0 1-7.518.076zM31.5 40a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm15 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/><path fill="#6B4FBB" d="M38 22h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2zm0 3h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z" style="mix-blend-mode:multiply"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/service_desk_empty.svg b/app/assets/images/illustrations/service_desk_empty.svg
deleted file mode 100644
index daaaeae6a17cc14b1f1cdd24bf4677971317fd1d..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/service_desk_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="226" height="178" viewBox="0 0 226 178"><g fill="none" fill-rule="evenodd"><path fill="#EEE" fill-rule="nonzero" d="M109.496 165.895a78.17 78.17 0 0 0 6.158.08 2 2 0 0 0-.11-4c-1.94.053-3.886.028-5.84-.074a2 2 0 0 0-2.1 1.893 1.996 1.996 0 0 0 1.89 2.102zm18.408-1.245a76 76 0 0 0 6-1.4 2 2 0 1 0-1.064-3.856c-1.875.52-3.772.96-5.686 1.327a2.001 2.001 0 0 0 .75 3.93zm17.572-5.636a76.28 76.28 0 0 0 5.486-2.803 2 2 0 1 0-1.962-3.485 72.42 72.42 0 0 1-5.2 2.656 2.003 2.003 0 0 0 1.676 3.635zm44.342-74.897a75.786 75.786 0 0 0-.674-6.127 2.002 2.002 0 0 0-3.956.598c.29 1.92.505 3.857.64 5.805a1.998 1.998 0 0 0 2.133 1.857 2 2 0 0 0 1.858-2.133zm-3.505-18.144a76.141 76.141 0 0 0-2.13-5.78 2.001 2.001 0 0 0-3.695 1.534 72.381 72.381 0 0 1 2.02 5.476 1.999 1.999 0 1 0 3.805-1.229zm-7.754-16.73a77.053 77.053 0 0 0-3.454-5.1 1.998 1.998 0 0 0-2.797-.423 1.998 1.998 0 0 0-.424 2.796 73.06 73.06 0 0 1 3.273 4.835c.58.94 1.814 1.23 2.753.647a2.001 2.001 0 0 0 .646-2.754zm-11.582-14.446a76.37 76.37 0 0 0-4.572-4.128 1.999 1.999 0 1 0-2.559 3.073 72.633 72.633 0 0 1 4.334 3.913 2.001 2.001 0 1 0 2.798-2.86zm-101.422-4.91a77.634 77.634 0 0 0-4.64 4.05 2.001 2.001 0 0 0 2.749 2.906 72.611 72.611 0 0 1 4.4-3.84 2 2 0 1 0-2.509-3.115zM52.7 43.062a75.962 75.962 0 0 0-3.546 5.04 2 2 0 1 0 3.363 2.168 72.314 72.314 0 0 1 3.36-4.777 2 2 0 0 0-3.177-2.432zm-9.373 15.924c-.82 1.882-1.56 3.8-2.226 5.745a2 2 0 1 0 3.787 1.294 72.253 72.253 0 0 1 2.108-5.443 1.998 1.998 0 0 0-1.036-2.63 2.001 2.001 0 0 0-2.633 1.036zm-5.26 17.74a76.33 76.33 0 0 0-.777 6.11 2 2 0 0 0 3.985.347c.17-1.947.415-3.88.737-5.793a2 2 0 0 0-3.945-.664zM74.87 155.55a76.028 76.028 0 0 0 5.437 2.897 2 2 0 1 0 1.737-3.603 71.34 71.34 0 0 1-5.152-2.745 1.998 1.998 0 0 0-2.737.714 2.002 2.002 0 0 0 .715 2.738zm16.97 7.34a76.606 76.606 0 0 0 5.975 1.498 2 2 0 1 0 .816-3.916 72.52 72.52 0 0 1-5.662-1.42 1.999 1.999 0 1 0-1.129 3.837z"/><path fill="#F9F9F9" d="M2.12 130c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M39 166c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 92 39 92 4 107.67 4 127s15.67 35 35 35z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M53.925 116.226A1.995 1.995 0 0 0 53 116H25a1.99 1.99 0 0 0-.898.212l14.663 13.406c.39.357.99.348 1.37-.02l13.79-13.372zm1.075 4.53L42.92 132.47a5 5 0 0 1-6.854.1L23 120.624V138a2 2 0 0 0 2 2h28a2 2 0 0 0 2-2v-17.244zM25 112h28a6 6 0 0 1 6 6v20a6 6 0 0 1-6 6H25a6 6 0 0 1-6-6v-20a6 6 0 0 1 6-6z"/><path fill="#F9F9F9" d="M150.12 131c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M187 167c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M180.51 137H199a2 2 0 0 0 2-2v-16a2 2 0 0 0-2-2h-24a2 2 0 0 0-2 2v22.743l7.51-4.743zm1.157 4l-9.6 6.062a2 2 0 0 1-3.067-1.69V119a6 6 0 0 1 6-6h24a6 6 0 0 1 6 6v16a6 6 0 0 1-6 6h-17.333z"/><path fill="#6B4FBB" d="M180 129a2 2 0 1 1-.001-3.999A2 2 0 0 1 180 129zm7 0a2 2 0 1 1-.001-3.999A2 2 0 0 1 187 129zm7 0a2 2 0 1 1-.001-3.999A2 2 0 0 1 194 129z"/><g><path fill="#F9F9F9" d="M76.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M113 78c-21.54 0-39-17.46-39-39S91.46 0 113 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S132.33 4 113 4 78 19.67 78 39s15.67 35 35 35z"/><g transform="translate(133 35)"><rect width="7" height="1" y="3" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M1.5 7a3.5 3.5 0 1 0 0-7v7z"/></g><g transform="matrix(-1 0 0 1 93 35)"><rect width="7" height="1" y="3" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M1.5 7a3.5 3.5 0 1 0 0-7v7z"/></g><path fill="#E1DBF1" fill-rule="nonzero" d="M113 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M109 56a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M97.5 40c0-5.8 4.698-10.5 10.494-10.5h10.012c5.796 0 10.494 4.7 10.494 10.5s-4.698 10.5-10.494 10.5h-10.012C102.198 50.5 97.5 45.8 97.5 40zm3 0c0 4.143 3.355 7.5 7.494 7.5h10.012A7.496 7.496 0 0 0 125.5 40c0-4.143-3.355-7.5-7.494-7.5h-10.012A7.496 7.496 0 0 0 100.5 40z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M109.255 42.406a.998.998 0 0 1 .584-1.287.997.997 0 0 1 1.287.583 2 2 0 0 0 3.76-.038 1 1 0 0 1 1.886.665 4.001 4.001 0 0 1-7.518.076zM105.5 40a1.5 1.5 0 1 1 .001-3.001A1.5 1.5 0 0 1 105.5 40zm15 0a1.5 1.5 0 1 1 .001-3.001A1.5 1.5 0 0 1 120.5 40z"/><path fill="#6B4FBB" d="M112 22h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2zm0 3h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z" style="mix-blend-mode:multiply"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/slack_logo.svg b/app/assets/images/illustrations/slack_logo.svg
deleted file mode 100644
index b8d7906c2e17f59ef495a09dbc841ae1e9dfaa69..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/slack_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 121.94154 121.84154" width="121.942" height="121.842"><style id="style200">.st0{fill:#ecb32d}.st1{fill:#63c1a0}.st2{fill:#e01a59}.st3{fill:#331433}.st4{fill:#d62027}.st5{fill:#89d3df}.st6{fill:#258b74}.st7{fill:#819c3c}</style><path class="st0" d="M79.03 7.511c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path202" fill="#ecb32d"/><path class="st1" d="M35.53 21.611c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path204" fill="#63c1a0"/><path class="st2" d="M114.43 79.011c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.5 28.2c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.5-28.1 86.5-28.1z" id="path206" fill="#e01a59"/><path class="st3" d="M39.23 103.511c5.6-1.8 12.9-4.2 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path208" fill="#331433"/><path class="st4" d="M82.83 89.311c7.8-2.5 15.1-4.9 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path210" fill="#d62027"/><path class="st5" d="M100.23 35.511c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.4 28.1c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.4-28 86.4-28z" id="path212" fill="#89d3df"/><path class="st6" d="M25.13 59.911c5.6-1.8 12.9-4.2 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path214" fill="#258b74"/><path class="st7" d="M68.63 45.811c7.8-2.5 15.1-4.9 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path216" fill="#819c3c"/></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/todos_all_done.svg b/app/assets/images/illustrations/todos_all_done.svg
deleted file mode 100644
index 6387497a6fbbeff0ec37c8f5f5a8e017686c9168..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/todos_all_done.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 293 216"><g fill="none" fill-rule="evenodd"><g transform="rotate(-5 211.388 -693.89)"><rect width="163.6" height="200" x=".2" fill="#FFF" stroke="#EEE" stroke-width="3" stroke-linecap="round" stroke-dasharray="6 9" rx="6"/><g transform="translate(24 38)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/></g><g transform="translate(24 83)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g><g transform="translate(24 130)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g></g><path fill="#FFCE29" d="M30 11l-1.8 4-2-4-4-1.8 4-2 2-4 2 4 4 2M286 60l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8M263 97l-2 4-2-4-4-2 4-2 2-4 2 4 4 2M12 85l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/todos_empty.svg b/app/assets/images/illustrations/todos_empty.svg
deleted file mode 100644
index 4de6cb403b97a7a5c3a5a30df5f1bdaa82382b39..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/todos_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 284 337" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="180" height="220" x="66.2" y="74.4" rx="6"/><mask id="l" width="180" height="220" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><rect id="b" width="180" height="220" rx="6"/><mask id="m" width="180" height="220" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><rect id="c" width="28" height="28" rx="4"/><mask id="n" width="28" height="28" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><rect id="d" width="28" height="28" rx="4"/><mask id="o" width="28" height="28" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><circle id="e" cx="21.5" cy="21.5" r="21.5"/><mask id="p" width="43" height="43" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><circle id="f" cx="26.5" cy="26.5" r="26.5"/><mask id="q" width="53" height="53" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><circle id="g" cx="9.5" cy="4.5" r="4.5"/><mask id="r" width="13" height="13" x="-2" y="-2"><path fill="#fff" d="M3-2h13v13H3z"/><use xlink:href="#g"/></mask><circle id="h" cx="26.5" cy="26.5" r="26.5"/><mask id="s" width="53" height="53" x="0" y="0" fill="#fff"><use xlink:href="#h"/></mask><circle id="i" cx="21.5" cy="21.5" r="21.5"/><mask id="t" width="43" height="43" x="0" y="0" fill="#fff"><use xlink:href="#i"/></mask><path id="j" d="M18 38h15c10.5 0 19-8.5 19-19S43.5 0 33 0H19C8.5 0 0 8.5 0 19c0 6.3 3 12 7.8 15.3l5.2 9c.6 1 1.4 1 2 0l3-5.3z"/><mask id="u" width="52" height="44" x="0" y="0" fill="#fff"><use xlink:href="#j"/></mask><circle id="k" cx="18.5" cy="18.5" r="18.5"/><mask id="v" width="37" height="37" x="0" y="0" fill="#fff"><use xlink:href="#k"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(-6 -4)"><use stroke="#EEE" stroke-width="6" mask="url(#l)" transform="rotate(-5 156.245 184.425)" xlink:href="#a"/><g transform="rotate(5 -707.333 618.042)"><use fill="#FFF" stroke="#EEE" stroke-width="6" mask="url(#m)" xlink:href="#b"/><g transform="translate(29 24)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="86" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/></g><g transform="translate(29 69)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="86" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g><g transform="translate(28 160)"><use stroke="#E5E5E5" stroke-width="6" mask="url(#n)" opacity=".7" xlink:href="#c"/><rect width="26" height="3" x="41" y="7" fill="#ECECEC" rx="1.5"/><rect width="43" height="3" x="41" y="17" fill="#ECECEC" rx="1.5"/></g><g transform="translate(28 116)"><use stroke="#E5E5E5" stroke-width="6" mask="url(#o)" xlink:href="#d"/><rect width="86" height="3" x="41" y="7" fill="#E5E5E5" rx="1.5"/><rect width="43" height="3" x="41" y="17" fill="#E5E5E5" rx="1.5"/></g></g><g transform="rotate(-15 601.917 -782.362)"><use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#p)" xlink:href="#e"/><text fill="#6B4FBB" font-family="SourceSansPro-Black, Source Sans Pro" font-size="20" font-weight="700" letter-spacing="-.1"><tspan x="12" y="27">@</tspan></text></g><g transform="rotate(15 -686.59 1035.907)"><use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#q)" xlink:href="#f"/><path fill="#FC6D26" d="M26.5 38.2c3.3 0 9.5-2.5 9.5-9.6 0-7-2.4-6.6-9.5-6.6-7 0-9.5-.4-9.5 6.6s6.2 9.6 9.5 9.6z"/><g transform="translate(17 14)"><use fill="#FC6D26" xlink:href="#g"/><use stroke="#FFF" stroke-width="4" mask="url(#r)" xlink:href="#g"/></g></g><g transform="rotate(15 -85.125 65.185)"><use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#s)" xlink:href="#h"/><path fill="#6B4FBB" d="M24 18.5c0-1.4 1-2.5 2.5-2.5 1.4 0 2.5 1 2.5 2.5v9c0 1.4-1 2.5-2.5 2.5-1.4 0-2.5-1-2.5-2.5v-9zM26.5 37c1.4 0 2.5-1 2.5-2.5 0-1.4-1-2.5-2.5-2.5-1.4 0-2.5 1-2.5 2.5 0 1.4 1 2.5 2.5 2.5z"/></g><g transform="rotate(-15 716.492 78.873)"><use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#t)" xlink:href="#i"/><path fill="#FC6D26" d="M20 23v-3h3v3h-3zm0 3v1.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-2.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-3h-1.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-2.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h3v-1.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h2.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v3h1.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v2.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-3z"/></g><g transform="rotate(-15 129.114 -585.74)"><use stroke="#FDE5D8" stroke-width="6" mask="url(#u)" xlink:href="#j"/><circle cx="16" cy="20" r="2" fill="#FC6D26"/><circle cx="27" cy="20" r="2" fill="#FC6D26"/><circle cx="38" cy="20" r="2" fill="#FC6D26"/></g><g transform="rotate(-15 1254.8 -458.986)"><use stroke="#FDE5D8" stroke-width="6" mask="url(#v)" xlink:href="#k"/><path fill="#FC6D26" d="M10.6 19l2-2c.5-.5.5-1 0-1.5-.3-.4-1-.4-1.3 0l-2.8 2.8c-.2.2-.3.4-.3.7 0 .3 0 .5.3.7l2.8 2.8c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4l-2-2zm14.8 0l-2-2c-.5-.5-.5-1 0-1.5.3-.4 1-.4 1.3 0l2.8 2.8c.2.2.3.4.3.7 0 .3 0 .5-.3.7l-2.8 2.8c-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4l2-2z"/><rect width="2" height="7" x="17" y="15.1" fill="#FC6D26" opacity=".5" transform="rotate(15 18.002 18.64)" rx="1"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/add_new_group.svg b/app/assets/images/illustrations/welcome/add_new_group.svg
deleted file mode 100644
index b10a3ae88129487a7fe0403154fd992ef052e4a8..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/welcome/add_new_group.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M59.65 32.65H60l-2-2.42-2 2.4-2-2.4-2 2.4-2-2.4-2 2.4-2-2.4-2 2.42h.77C45.57 34.6 46 36.75 46 39c0 2.84-.7 5.5-1.92 7.86 1.97 2.28 4.83 3.64 7.92 3.64 5.8 0 10.5-4.74 10.5-10.6 0-2.8-1.08-5.36-2.85-7.25zM43.18 29.6c2.4-2.1 5.52-3.3 8.82-3.3 7.46 0 13.5 6.1 13.5 13.6S59.46 53.5 52 53.5c-3.68 0-7.1-1.5-9.6-4.04C39.3 53.44 34.44 56 29 56c-9.4 0-17-7.6-17-17s7.6-17 17-17c3.22 0 6.23.9 8.8 2.45 2.13 1.3 3.97 3.05 5.38 5.16zM17 34c-.65 1.54-1 3.23-1 5 0 7.18 5.82 13 13 13s13-5.82 13-13c0-1.77-.35-3.46-1-5h-9c-.53 0-1.04-.2-1.4-.6L29 31.84l-1.6 1.58c-.36.4-.87.6-1.4.6h-9zm21.38-4a12.996 12.996 0 0 0-18.76 0h5.55l2.42-2.4c.74-.8 2-.8 2.8 0l2.4 2.4h5.54z"/><path fill="#6B4FBB" d="M47.6 42.32c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zm8.8 0c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zM25 44h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-1c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/add_new_project.svg b/app/assets/images/illustrations/welcome/add_new_project.svg
deleted file mode 100644
index 4b8dc34c088d6f968acc2a094b108366f5aa2fa8..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/welcome/add_new_project.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M30 24c-2.21 0-4 1.79-4 4v22c0 2.21 1.79 4 4 4h18c2.21 0 4-1.79 4-4V28c0-2.21-1.79-4-4-4H30zm0-4h18a8 8 0 0 1 8 8v22a8 8 0 0 1-8 8H30a8 8 0 0 1-8-8V28a8 8 0 0 1 8-8z"/><path fill="#6B4FBB" d="M33 30h8a2 2 0 1 1 0 4h-8a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/add_new_user.svg b/app/assets/images/illustrations/welcome/add_new_user.svg
deleted file mode 100644
index d4c184989bfb3f4f0363629d872bcb76e4fbc458..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/welcome/add_new_user.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M44 31l-2.5-3-2.5 3-2.5-3-2.5 3-2.5-3-2.5 3h-2.72c2.65-4.2 7.36-7 12.72-7s10.07 2.8 12.72 7H49l-2.5-3-2.5 3z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M39 57c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17zm0-4c7.18 0 13-5.82 13-13s-5.82-13-13-13-13 5.82-13 13 5.82 13 13 13z"/><path fill="#6B4FBB" d="M35 45h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/configure_server.svg b/app/assets/images/illustrations/welcome/configure_server.svg
deleted file mode 100644
index f9dda816f11b6cdd5d8eaefdc372c8fe59a25851..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/welcome/configure_server.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M24.92 35.15a4.012 4.012 0 0 1-.6-5.63l1.26-1.55c1.4-1.72 3.9-2 5.63-.6l.7.56c.7-.4 1.4-.73 2.1-1V26c0-2.2 1.8-4 4-4h2c2.2 0 4 1.8 4 4v.92c.8.28 1.5.62 2.1 1l.7-.55c1.7-1.4 4.3-1.12 5.7.6l1.3 1.55c1.4 1.72 1.2 4.23-.6 5.63l-.7.6c.3.74.4 1.5.5 2.3l.9.2c2.2.5 3.5 2.64 3 4.8L56.4 45c-.5 2.15-2.64 3.5-4.8 3l-.88-.2c-.44.63-.92 1.24-1.46 1.8l.4.82c.9 1.98.1 4.38-1.9 5.35l-1.8.87c-2 .97-4.37.15-5.34-1.84l-.46-.85c-.34.03-.74.05-1.13.05-.4 0-.8-.02-1.2-.05l-.4.85c-.95 2-3.34 2.8-5.33 1.84l-1.8-.87a4.011 4.011 0 0 1-1.83-5.35l.4-.8c-.54-.58-1.02-1.2-1.46-1.83l-.8.2c-2.2.5-4.3-.9-4.8-3l-.4-2c-.5-2.2.85-4.3 3-4.8l.9-.2c.1-.8.3-1.6.5-2.3l-.7-.6zm4.95.77c-.53 1.2-.83 2.47-.87 3.8-.02.9-.66 1.68-1.55 1.9l-2.32.53.45 1.94 2.3-.6c.9-.2 1.8.2 2.23 1 .7 1.1 1.5 2.2 2.5 3 .7.6.9 1.6.5 2.4l-1 2.1 1.8.9 1.1-2.1c.4-.8 1.3-1.3 2.2-1.1.7.1 1.3.2 2 .2s1.3-.1 2-.2c.9-.2 1.8.3 2.2 1.1l1 2.1 1.8-.9-1.2-2c-.4-.8-.2-1.8.5-2.4 1-.85 1.84-1.88 2.45-3.05.4-.82 1.33-1.24 2.2-1.04l2.33.54.45-1.95-2.32-.54c-.9-.2-1.52-.97-1.54-1.88-.03-1.4-.33-2.6-.86-3.8-.4-.9-.2-1.8.5-2.4l1.9-1.5-1.3-1.6-1.8 1.5c-.8.5-1.8.6-2.5 0-1.1-.8-2.3-1.4-3.5-1.7-.9-.2-1.5-1-1.5-1.9V26h-2v2.38c0 .9-.6 1.7-1.5 1.93-1.3.4-2.5 1-3.5 1.7-.8.6-1.8.6-2.5 0l-1.9-1.5-1.26 1.6 1.8 1.5c.7.6.94 1.6.6 2.4z"/><path fill="#FC6D26" fill-rule="nonzero" d="M39 46c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/ee_trial.svg b/app/assets/images/illustrations/welcome/ee_trial.svg
deleted file mode 100644
index 6d0dcf0020c66e29080d661a6c1bc561ec1d0a97..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/welcome/ee_trial.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="330" height="132" viewBox="0 0 330 132"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M174.12 42c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M211 78c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S230.33 4 211 4s-35 15.67-35 35 15.67 35 35 35z"/><g fill-rule="nonzero"><path fill="#FEE1D3" d="M211.5 51c-6.42 0-12.26-2.84-17.43-8.4a4.008 4.008 0 0 1-.27-5.13C199 30.57 204.92 27 211.5 27s12.5 3.56 17.7 10.47a3.994 3.994 0 0 1-.27 5.12c-5.17 5.53-11 8.4-17.43 8.4zm0-4c5.25 0 10.05-2.34 14.5-7.13-4.5-5.98-9.3-8.87-14.5-8.87-5.2 0-10 2.9-14.5 8.87 4.45 4.8 9.25 7.13 14.5 7.13z"/><path fill="#FC6D26" d="M211 47c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-4c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4zm0-1c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"/></g><path fill="#000" fill-opacity=".03" d="M88.12 83c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M125 119c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M116 86.34c2.33.83 4 3.05 4 5.66 0 3.3-2.7 6-6 6s-6-2.7-6-6c0-2.6 1.67-4.83 4-5.66V72h4v14.34zM128 66c5.52 0 10 4.48 10 10v12h-4V76c0-3.3-2.7-6-6-6v1.83c0 .55-.45 1-1 1-.24 0-.47-.1-.65-.24l-4.46-3.87c-.46-.36-.5-1-.15-1.4.03-.05.07-.1.1-.12l4.47-3.82c.42-.35 1.05-.3 1.4.1.16.2.25.43.25.66V66zm-14 28c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/><path fill="#FC6D26" fill-rule="nonzero" d="M114 74c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm22 28c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/><path fill="#000" fill-opacity=".03" d="M2.12 52C2.04 53 2 54 2 55c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 71.03 58.42 86 39 86S3.65 71.03 2.12 52z"/><path fill="#EEE" fill-rule="nonzero" d="M39 88C17.46 88 0 70.54 0 49s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 14 39 14 4 29.67 4 49s15.67 35 35 35z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M48 41h-4c0-2.76-2.24-5-5-5s-5 2.24-5 5h-4a9 9 0 0 1 18 0zm-18 0h4v3h-4v-3zm14 0h4v3h-4v-3z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M30 47c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V48c0-.55-.45-1-1-1H30zm0-4h18c2.76 0 5 2.24 5 5v12c0 2.76-2.24 5-5 5H30c-2.76 0-5-2.24-5-5V48c0-2.76 2.24-5 5-5z"/><path fill="#6B4FBB" d="M38 53.73c-.6-.34-1-1-1-1.73 0-1.1.9-2 2-2s2 .9 2 2c0 .74-.4 1.4-1 1.73V55c0 .55-.45 1-1 1s-1-.45-1-1v-1.27z"/><path fill="#000" fill-opacity=".03" d="M254.12 92c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M291 128c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#6B4BBE" fill-rule="nonzero" d="M292 78c5.52 0 10 4.48 10 10 0 2.28-.76 4.43-2.14 6.18-1.03 1.3-.8 3.2.5 4.22 1.3 1.02 3.2.8 4.2-.5 2.22-2.8 3.44-6.26 3.44-9.9 0-8.84-7.16-16-16-16v-3.13c0-.2-.06-.4-.17-.56-.3-.42-.93-.54-1.38-.23l-9.2 6.13c-.1.06-.2.16-.28.27-.3.45-.18 1.08.28 1.38l9.2 6.13c.16.1.35.17.55.17.55 0 1-.45 1-1V78z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M290 100c-5.52 0-10-4.48-10-10 0-2.25.74-4.38 2.1-6.12 1-1.3.77-3.2-.54-4.2-1.3-1.02-3.2-.78-4.2.53A15.796 15.796 0 0 0 274 90c0 8.84 7.16 16 16 16v3.13c0 .55.45 1 1 1 .2 0 .4-.06.55-.17l9.2-6.13c.46-.3.6-.93.28-1.38-.07-.1-.17-.2-.28-.28l-9.2-6.13c-.45-.3-1.08-.2-1.38.27-.1.2-.17.4-.17.6v3.1z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/globe.svg b/app/assets/images/illustrations/welcome/globe.svg
deleted file mode 100644
index c2daae5f317d8250d60132b91099aced112e03e8..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/welcome/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M30.24 27.823A14.98 14.98 0 0 0 24 40c0 2.549.636 4.949 1.757 7.051-.297-2.684.644-4.026 2.823-4.026 3.707 0 2.462 5.365 4.473 5.761 2.01.396 4.175.396 4.267 3.29.04 1.257-.265 2.157-.917 2.7a15.095 15.095 0 0 0 8.555-1.006c.035-1.91.303-4.941 2.21-5.61 2.373-.833-.55-1.431.734-3.368 1.17-1.762-3.297-5.2 0-4.832 3.477.388 5.044-.816 6.024-1.456a14.903 14.903 0 0 0-1.373-4.94c-.873.4-2.19.465-3.702-.538-.757-.502-1.084-3.944-2.107-3.944-3.823 0-4.065 3.17-5.994 3.944-1.076.431-4.193 3.773-5.614 3.596-1.126-.14-1.071-4.417-2.45-5.166-1.359-.738-2.174-1.948-2.447-3.633zM39 59c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/lightbulb.svg b/app/assets/images/illustrations/welcome/lightbulb.svg
deleted file mode 100644
index fce103120857a322ca92af8db6ac8ac84ea544c2..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/welcome/lightbulb.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#6B4FBB" d="M33 52h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm1 5h10a2 2 0 1 1 0 4H34a2 2 0 1 1 0-4z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M45.542 46.932l.346-2.36a8.004 8.004 0 0 1 1.566-3.705c3.025-3.946 4.485-7.29 4.547-9.96C52.153 24.41 46.843 20 39 20c-7.777 0-13 4.374-13 11 0 2.4 1.462 5.73 4.573 9.846a8.009 8.009 0 0 1 1.536 3.683l.353 2.456 13.08-.054zm-17.038.624L28.15 45.1a3.997 3.997 0 0 0-.768-1.842C23.794 38.51 22 34.424 22 31c0-9.39 7.61-15 17-15s17.218 5.614 17 15c-.085 3.64-1.875 7.74-5.37 12.3a3.99 3.99 0 0 0-.784 1.853l-.346 2.36a4.003 4.003 0 0 1-3.942 3.42l-13.08.053a4 4 0 0 1-3.974-3.43z"/><path fill="#6B4FBB" d="M41 38.732a2 2 0 1 1 2 0V42a1 1 0 0 1-2 0v-3.268zm-6 0a2 2 0 1 1 2 0V42a1 1 0 0 1-2 0v-3.268z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/wiki-fro-logged-out-users.svg b/app/assets/images/illustrations/wiki-fro-logged-out-users.svg
deleted file mode 100644
index c71841f72e5e42fb2f3494077eec98764ef6e82c..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/wiki-fro-logged-out-users.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="412" height="260" viewBox="0 0 412 260" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M6.447.894L12 12H0L5.553.894a.5.5 0 0 1 .894 0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#FEF0E8" fill-rule="nonzero" d="M338 50.287C322.695 41.45 303.124 46.694 294.287 62c-8.836 15.305-3.592 34.876 11.713 43.712 15.306 8.837 34.877 3.593 43.713-11.712 8.837-15.306 3.593-34.877-11.713-43.713zm2-3.464C357.22 56.763 363.118 78.78 353.177 96c-9.941 17.218-31.958 23.118-49.177 13.176-17.218-9.94-23.118-31.958-13.177-49.176C300.764 42.78 322.782 36.88 340 46.823z"/><g transform="rotate(-150 171.003 8.53)"><path fill="#FC6D26" fill-rule="nonzero" d="M4 16v25a2 2 0 1 0 4 0V16H4zm8-4v29a6 6 0 1 1-12 0V12h12z"/><use fill="#D8D8D8" xlink:href="#a"/><path stroke="#FDC4A8" stroke-width="4" d="M6 4.472L3.236 10h5.528L6 4.472z"/><path fill="#FC6D26" d="M9 6L6.447.894a.5.5 0 0 0-.894 0L3 6c.836.628 1.874 1 3 1a4.978 4.978 0 0 0 3-1z"/></g><path fill="#F9F9F9" d="M263.116 237.116A10.002 10.002 0 0 1 254 243h-86c-11.046 0-20-8.954-20-20V121c0-4.056 2.414-7.547 5.884-9.116A9.964 9.964 0 0 0 153 116v106c0 8.837 7.163 16 16 16h90c1.467 0 2.86-.316 4.116-.884z"/><path fill="#EEE" fill-rule="nonzero" d="M214.5 106H163c-5.523 0-10 4.477-10 10v106c0 8.837 7.163 16 16 16h90c5.523 0 10-4.477 10-10v-17.999a10.036 10.036 0 0 1-4 3.167V228a6 6 0 0 1-6 6h-90c-6.627 0-12-5.373-12-12V116a6 6 0 0 1 6-6h7v-4h44.5z"/><path fill="#EEE" fill-rule="nonzero" d="M260 218.268V214h-90a6 6 0 0 0 0 12h86a4 4 0 0 0 4-4v-.268a1.99 1.99 0 0 1-1 .268h-50a2 2 0 0 1 0-4h50c.364 0 .706.097 1 .268zM170 210h90.5a3.5 3.5 0 0 1 3.5 3.5v8.5a8 8 0 0 1-8 8h-86c-5.523 0-10-4.477-10-10s4.477-10 10-10z"/><path fill="#EEE" fill-rule="nonzero" d="M174 110v100h87a6 6 0 0 0 6-6v-88a6 6 0 0 0-6-6h-87zm-4-4h91c5.523 0 10 4.477 10 10v88c0 5.523-4.477 10-10 10h-91V106z"/><path fill="#EFEDF8" d="M230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M236.182 129.207a5.5 5.5 0 0 1 6.102.04l7.716 5.219V105a2 2 0 0 0-2-2h-18a2 2 0 0 0-2 2v29.584l8.182-5.377zM230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><g fill-rule="nonzero"><path fill="#EFEDF8" d="M156 74c14.912 0 27-12.088 27-27s-12.088-27-27-27-27 12.088-27 27 12.088 27 27 27zm0 4c-17.12 0-31-13.88-31-31s13.88-31 31-31 31 13.88 31 31-13.88 31-31 31z"/><path fill="#6B4FBB" d="M147.535 44.916l-.116 1.086a8.446 8.446 0 0 0 .093 2.44l.2 1.08-2.262 1.202a.495.495 0 0 0-.213.678l.941 1.77c.128.239.434.332.68.201l2.25-1.196.785.775a8.544 8.544 0 0 0 1.967 1.45l.975.522-.486 2.5a.495.495 0 0 0 .392.59l1.968.383a.504.504 0 0 0 .585-.401l.489-2.515 1.086-.13a8.584 8.584 0 0 0 2.363-.633l1.005-.43 1.68 1.933a.495.495 0 0 0 .708.055l1.513-1.315a.504.504 0 0 0 .044-.708l-1.67-1.922.583-.94c.431-.696.761-1.45.978-2.239l.292-1.063 2.547-.089a.495.495 0 0 0 .488-.515l-.07-2.003a.504.504 0 0 0-.523-.48l-2.56.09-.367-1.037a8.446 8.446 0 0 0-1.139-2.159l-.644-.882 1.509-2.076a.495.495 0 0 0-.106-.702l-1.621-1.178a.504.504 0 0 0-.7.116l-1.494 2.057-1.05-.362a8.459 8.459 0 0 0-2.398-.455l-1.1-.047-.66-2.466a.495.495 0 0 0-.613-.36l-1.936.519a.504.504 0 0 0-.35.617l.661 2.466-.93.59a8.459 8.459 0 0 0-1.848 1.594l-.728.838-2.322-1.034a.495.495 0 0 0-.665.25l-.815 1.83a.504.504 0 0 0 .26.661l2.344 1.044zm-3.565 1.697a3.504 3.504 0 0 1-1.78-4.622l.815-1.83a3.495 3.495 0 0 1 4.626-1.77l.346.154c.259-.245.529-.477.81-.697l-.106-.394a3.504 3.504 0 0 1 2.471-4.292l1.936-.519a3.495 3.495 0 0 1 4.286 2.481l.106.395c.353.05.703.116 1.05.198l.222-.306a3.504 3.504 0 0 1 4.89-.78l1.622 1.178a3.495 3.495 0 0 1 .769 4.892l-.258.355c.184.312.354.633.508.962l.42-.014a3.504 3.504 0 0 1 3.625 3.373l.07 2.003a3.495 3.495 0 0 1-3.382 3.618l-.4.014c-.127.332-.27.659-.426.978l.256.294a3.504 3.504 0 0 1-.34 4.941l-1.512 1.315a3.495 3.495 0 0 1-4.94-.351l-.283-.325a11.669 11.669 0 0 1-1.05.28l-.082.424a3.504 3.504 0 0 1-4.103 2.774l-1.967-.382a3.495 3.495 0 0 1-2.765-4.11l.075-.383a11.547 11.547 0 0 1-.858-.633l-.354.188a3.504 3.504 0 0 1-4.738-1.442l-.94-1.77a3.495 3.495 0 0 1 1.453-4.734l.37-.197a11.436 11.436 0 0 1-.041-1.088l-.4-.178zm13.326 5.608a5.5 5.5 0 1 1-2.847-10.625 5.5 5.5 0 0 1 2.847 10.625zm-.776-2.898a2.5 2.5 0 1 0-1.294-4.83 2.5 2.5 0 0 0 1.294 4.83z"/></g><g fill-rule="nonzero"><path fill="#EFEDF8" d="M326.979 222.047c14.403 3.86 29.209-4.688 33.068-19.092 3.86-14.403-4.688-29.209-19.092-33.068-14.403-3.86-29.209 4.688-33.068 19.092-3.86 14.404 4.688 29.209 19.092 33.068zm-1.035 3.864c-16.538-4.431-26.352-21.43-21.92-37.967 4.43-16.538 21.429-26.352 37.966-21.92 16.538 4.43 26.352 21.429 21.92 37.966-4.43 16.538-21.429 26.352-37.966 21.92z"/><path fill="#6B4FBB" d="M329.376 201.598c-4.668-2.621-7.155-8.157-5.706-13.566 1.715-6.402 8.295-10.201 14.697-8.486 6.402 1.716 10.2 8.296 8.485 14.697-1.45 5.41-6.371 8.96-11.725 8.897a3.03 3.03 0 0 1-.074.365l-1.812 6.761a3 3 0 0 1-5.795-1.552l1.812-6.762a3.03 3.03 0 0 1 .118-.354zm3.815-2.733a8 8 0 1 0 4.14-15.455 8 8 0 0 0-4.14 15.455z"/></g><path fill="#FEF0E8" fill-rule="nonzero" d="M91.373 193c17.071-4.574 27.202-22.12 22.628-39.191-4.575-17.071-22.121-27.202-39.192-22.628-17.071 4.574-27.202 22.121-22.628 39.192 4.574 17.071 22.121 27.202 39.192 22.627zm1.035 3.864c-19.204 5.146-38.945-6.25-44.09-25.456-5.146-19.204 6.25-38.945 25.455-44.09 19.205-5.146 38.945 6.25 44.091 25.455 5.146 19.205-6.25 38.945-25.456 44.091z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M70.067 152.122l6.73 25.114 19.318-5.176-6.73-25.114-19.318 5.176zm-1.035-3.864l19.318-5.176a4 4 0 0 1 4.9 2.828l6.729 25.114a4 4 0 0 1-2.829 4.9L77.832 181.1a4 4 0 0 1-4.9-2.829l-6.729-25.114a4 4 0 0 1 2.829-4.899z"/><path fill="#FC6D26" d="M76.898 154.433l7.727-2.07a2 2 0 0 1 1.036 3.863l-7.728 2.07a2 2 0 1 1-1.035-3.863zm1.812 6.761l5.795-1.553a2 2 0 0 1 1.035 3.864l-5.795 1.553a2 2 0 1 1-1.035-3.864zm1.811 6.762l7.728-2.07a2 2 0 0 1 1.035 3.863l-7.727 2.07a2 2 0 1 1-1.036-3.863z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/wiki_login_empty.svg b/app/assets/images/illustrations/wiki_login_empty.svg
deleted file mode 100644
index 1cfa47220a5d3825e25b200540dd4258795c289d..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/wiki_login_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="386" height="298" viewBox="0 0 386 298" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M4 51h16v15.997A5.003 5.003 0 0 1 15.003 72H8.997A5.005 5.005 0 0 1 4 66.997V51z"/><rect id="b" width="24" height="10" y="44" rx="3"/></defs><g fill="none" fill-rule="evenodd" transform="translate(0 3)"><g transform="rotate(15 23.151 968.24)"><rect width="53" height="44" fill="#FFF" stroke="#FDE5D8" stroke-width="3" stroke-linecap="round" rx="5"/><path fill="#FDE5D8" d="M29.5 28.3l2.758-3.861c.962-1.347 2.527-1.34 3.484 0l6.516 9.122c.962 1.347.399 2.439-1.252 2.439H17.994c-1.653 0-2.21-1.099-1.252-2.439l6.516-9.122c.962-1.347 2.527-1.34 3.484 0L29.5 28.3z" opacity=".6"/><circle cx="16" cy="16" r="6" fill="#FDB997"/></g><g transform="scale(-1 1) rotate(25 -75.08 -334.15)"><rect width="3" height="11" x="12.45" y="23.45" fill="#6B4FBB" transform="rotate(45 13.95 28.95)" rx="1.5"/><rect width="3" height="14" x="9.45" y="15.45" fill="#6B4FBB" transform="rotate(45 10.95 22.45)" rx="1.5"/><path fill="#FFF" stroke="#E1DCF1" stroke-width="3" d="M16 39.6C6.871 37.747 0 29.676 0 20 0 8.954 8.954 0 20 0s20 8.954 20 20c0 8.955-5.886 16.536-14 19.084v15.91A5.007 5.007 0 0 1 21 60c-2.761 0-5-2.244-5-5.006V39.6zm4-7.6c6.627 0 12-5.373 12-12S26.627 8 20 8 8 13.373 8 20s5.373 12 12 12z"/></g><g transform="scale(1 -1) rotate(-15 -383.616 -172.407)"><path stroke="#FDE5D8" stroke-width="3" d="M1.5 38.5h9V4c0-1.378-1.12-2.5-2.496-2.5H3.996A2.503 2.503 0 0 0 1.5 4v34.5z"/><rect width="2" height="27" x="5" y="7" fill="#FDA77D" opacity=".8" rx="1"/><path stroke="#FDE5D8" stroke-width="3" d="M2.427 41.553h7.146L6 48.699l-3.573-7.146z"/></g><g transform="rotate(-30 420.145 -545.422)"><path fill="#FFF" stroke="#FDE5D8" stroke-width="3" d="M9 3c0-1.657 1.347-3 3-3 1.657 0 3 1.352 3 3v43H9V3z"/><use fill="#FFF" xlink:href="#a"/><path stroke="#FDE5D8" stroke-width="3" d="M5.5 52.5v14.497A3.505 3.505 0 0 0 8.997 70.5h6.006a3.503 3.503 0 0 0 3.497-3.503V52.5h-13z"/><rect width="2" height="14" x="9" y="51" fill="#FDA77D" rx="1"/><rect width="2" height="14" x="13" y="51" fill="#FDA77D" rx="1"/><use fill="#FFF" xlink:href="#b"/><rect width="21" height="7" x="1.5" y="45.5" stroke="#FDE5D8" stroke-width="3" rx="3"/></g><g transform="translate(72 97.488)"><rect width="125" height="160" fill="#FFF" stroke="#E5E5E5" stroke-width="4" stroke-linecap="round" rx="10"/><rect width="125" height="160" x="125" fill="#FFF" stroke="#E5E5E5" stroke-width="4" stroke-linecap="round" rx="10"/><path fill="#FFF" stroke="#E5E5E5" stroke-width="4" d="M7 12.008C7 8.69 9.686 6 12.993 6H125v148H12.993C9.683 154 7 151.305 7 147.992V12.008zm236 0C243 8.69 240.314 6 237.007 6H125v148h112.007c3.31 0 5.993-2.695 5.993-6.008V12.008z" stroke-linecap="round"/><rect width="84" height="42" x="142" y="29" stroke="#EEE" stroke-width="4" rx="3"/><rect width="88" height="4" x="141" y="93" fill="#E5E5E5" rx="2"/><rect width="88" height="4" x="141" y="107" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="141" y="121" fill="#E5E5E5" rx="2"/><rect width="56" height="4" x="22" y="93" fill="#E5E5E5" rx="2"/><rect width="26" height="4" x="22" y="27" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="22" y="41" fill="#E5E5E5" rx="2"/><rect width="36" height="4" x="22" y="55" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="22" y="69" fill="#E5E5E5" rx="2"/><rect width="36" height="4" x="22" y="107" fill="#E5E5E5" rx="2"/><rect width="56" height="4" x="22" y="121" fill="#BFBFBF" rx="2"/></g><path stroke="#B5A7DD" stroke-width="2.5" d="M23.139 182.922l-1.347-.6a2.004 2.004 0 0 1-1.02-2.64l.815-1.831a1.995 1.995 0 0 1 2.645-1.01l1.308.583a9.959 9.959 0 0 1 2.177-1.876l-.376-1.402a2.004 2.004 0 0 1 1.41-2.455l1.937-.519a1.995 1.995 0 0 1 2.449 1.421l.375 1.402a9.959 9.959 0 0 1 2.824.536l.84-1.158a2.004 2.004 0 0 1 2.796-.448l1.622 1.178a1.995 1.995 0 0 1 .437 2.797l-.867 1.193a9.946 9.946 0 0 1 1.341 2.541l1.461-.05a2.004 2.004 0 0 1 2.075 1.926l.07 2.003a1.995 1.995 0 0 1-1.935 2.067l-1.445.05c-.256.93-.644 1.817-1.15 2.632l.944 1.087a2.004 2.004 0 0 1-.191 2.825l-1.513 1.315a1.995 1.995 0 0 1-2.824-.204l-.963-1.108a10.084 10.084 0 0 1-2.776.744l-.28 1.441a2.004 2.004 0 0 1-2.344 1.588l-1.967-.382a1.995 1.995 0 0 1-1.579-2.35l.275-1.414a10.044 10.044 0 0 1-2.312-1.704l-1.277.678a2.004 2.004 0 0 1-2.709-.822l-.94-1.77a1.995 1.995 0 0 1 .833-2.705l1.29-.687a9.946 9.946 0 0 1-.11-2.872zm10.98 4.93a4 4 0 1 0-2.07-7.727 4 4 0 0 0 2.07 7.728z"/><ellipse cx="197" cy="289.988" fill="#F9F9F9" rx="125" ry="4.5"/><path fill="#6B4FBB" d="M164 100.492a3.002 3.002 0 0 1 3.001-3.004H183a3.006 3.006 0 0 1 3.001 3.004v34.988c0 2.213-1.45 2.954-3.24 1.651l-7.76-5.643-7.76 5.643c-1.789 1.302-3.24.566-3.24-1.651v-34.988z"/><g opacity=".2"><path fill="#FC8A51" d="M5.747 234.768l-2.688 1.114c-1.017.422-1.803-.134-1.754-1.228l.128-2.907-1.115-2.688c-.422-1.017.135-1.803 1.229-1.754l2.907.128 2.687-1.115c1.018-.422 1.803.135 1.755 1.229l-.128 2.907 1.114 2.687c.422 1.018-.134 1.803-1.228 1.755l-2.907-.128zM191.564 37.953l-3.72.164c-1.326.059-1.992-.88-1.48-2.115l1.426-3.438-.164-3.72c-.059-1.326.88-1.992 2.115-1.48l3.438 1.426 3.72-.164c1.326-.059 1.992.88 1.48 2.114l-1.426 3.44.164 3.719c.059 1.326-.88 1.992-2.114 1.48l-3.44-1.426z"/><path fill="#6B4FBB" d="M348.789 75.876l-1.967-2.144c-.744-.812-.49-1.74.555-2.07l2.775-.873 2.144-1.967c.812-.744 1.74-.49 2.07.555l.873 2.775 1.967 2.144c.744.812.49 1.74-.555 2.07l-2.775.873-2.144 1.967c-.812.745-1.74.49-2.07-.555l-.873-2.775zm9.261 164.735l-2.907-.125c-1.1-.048-1.577-.884-1.07-1.855l1.344-2.58.126-2.908c.047-1.1.883-1.577 1.855-1.07l2.58 1.344 2.907.126c1.1.047 1.577.883 1.07 1.855l-1.344 2.58-.125 2.907c-.048 1.1-.884 1.577-1.856 1.07l-2.58-1.344zM88.789 75.876l-1.967-2.144c-.744-.812-.49-1.74.555-2.07l2.775-.873 2.144-1.967c.812-.744 1.74-.49 2.07.555l.873 2.775 1.967 2.144c.744.812.49 1.74-.555 2.07l-2.775.873-2.144 1.967c-.812.745-1.74.49-2.07-.555l-.873-2.775z"/></g></g></svg>
\ No newline at end of file
diff --git a/app/assets/images/illustrations/wiki_logout_empty.svg b/app/assets/images/illustrations/wiki_logout_empty.svg
deleted file mode 100644
index c71841f72e5e42fb2f3494077eec98764ef6e82c..0000000000000000000000000000000000000000
--- a/app/assets/images/illustrations/wiki_logout_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="412" height="260" viewBox="0 0 412 260" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M6.447.894L12 12H0L5.553.894a.5.5 0 0 1 .894 0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#FEF0E8" fill-rule="nonzero" d="M338 50.287C322.695 41.45 303.124 46.694 294.287 62c-8.836 15.305-3.592 34.876 11.713 43.712 15.306 8.837 34.877 3.593 43.713-11.712 8.837-15.306 3.593-34.877-11.713-43.713zm2-3.464C357.22 56.763 363.118 78.78 353.177 96c-9.941 17.218-31.958 23.118-49.177 13.176-17.218-9.94-23.118-31.958-13.177-49.176C300.764 42.78 322.782 36.88 340 46.823z"/><g transform="rotate(-150 171.003 8.53)"><path fill="#FC6D26" fill-rule="nonzero" d="M4 16v25a2 2 0 1 0 4 0V16H4zm8-4v29a6 6 0 1 1-12 0V12h12z"/><use fill="#D8D8D8" xlink:href="#a"/><path stroke="#FDC4A8" stroke-width="4" d="M6 4.472L3.236 10h5.528L6 4.472z"/><path fill="#FC6D26" d="M9 6L6.447.894a.5.5 0 0 0-.894 0L3 6c.836.628 1.874 1 3 1a4.978 4.978 0 0 0 3-1z"/></g><path fill="#F9F9F9" d="M263.116 237.116A10.002 10.002 0 0 1 254 243h-86c-11.046 0-20-8.954-20-20V121c0-4.056 2.414-7.547 5.884-9.116A9.964 9.964 0 0 0 153 116v106c0 8.837 7.163 16 16 16h90c1.467 0 2.86-.316 4.116-.884z"/><path fill="#EEE" fill-rule="nonzero" d="M214.5 106H163c-5.523 0-10 4.477-10 10v106c0 8.837 7.163 16 16 16h90c5.523 0 10-4.477 10-10v-17.999a10.036 10.036 0 0 1-4 3.167V228a6 6 0 0 1-6 6h-90c-6.627 0-12-5.373-12-12V116a6 6 0 0 1 6-6h7v-4h44.5z"/><path fill="#EEE" fill-rule="nonzero" d="M260 218.268V214h-90a6 6 0 0 0 0 12h86a4 4 0 0 0 4-4v-.268a1.99 1.99 0 0 1-1 .268h-50a2 2 0 0 1 0-4h50c.364 0 .706.097 1 .268zM170 210h90.5a3.5 3.5 0 0 1 3.5 3.5v8.5a8 8 0 0 1-8 8h-86c-5.523 0-10-4.477-10-10s4.477-10 10-10z"/><path fill="#EEE" fill-rule="nonzero" d="M174 110v100h87a6 6 0 0 0 6-6v-88a6 6 0 0 0-6-6h-87zm-4-4h91c5.523 0 10 4.477 10 10v88c0 5.523-4.477 10-10 10h-91V106z"/><path fill="#EFEDF8" d="M230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M236.182 129.207a5.5 5.5 0 0 1 6.102.04l7.716 5.219V105a2 2 0 0 0-2-2h-18a2 2 0 0 0-2 2v29.584l8.182-5.377zM230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><g fill-rule="nonzero"><path fill="#EFEDF8" d="M156 74c14.912 0 27-12.088 27-27s-12.088-27-27-27-27 12.088-27 27 12.088 27 27 27zm0 4c-17.12 0-31-13.88-31-31s13.88-31 31-31 31 13.88 31 31-13.88 31-31 31z"/><path fill="#6B4FBB" d="M147.535 44.916l-.116 1.086a8.446 8.446 0 0 0 .093 2.44l.2 1.08-2.262 1.202a.495.495 0 0 0-.213.678l.941 1.77c.128.239.434.332.68.201l2.25-1.196.785.775a8.544 8.544 0 0 0 1.967 1.45l.975.522-.486 2.5a.495.495 0 0 0 .392.59l1.968.383a.504.504 0 0 0 .585-.401l.489-2.515 1.086-.13a8.584 8.584 0 0 0 2.363-.633l1.005-.43 1.68 1.933a.495.495 0 0 0 .708.055l1.513-1.315a.504.504 0 0 0 .044-.708l-1.67-1.922.583-.94c.431-.696.761-1.45.978-2.239l.292-1.063 2.547-.089a.495.495 0 0 0 .488-.515l-.07-2.003a.504.504 0 0 0-.523-.48l-2.56.09-.367-1.037a8.446 8.446 0 0 0-1.139-2.159l-.644-.882 1.509-2.076a.495.495 0 0 0-.106-.702l-1.621-1.178a.504.504 0 0 0-.7.116l-1.494 2.057-1.05-.362a8.459 8.459 0 0 0-2.398-.455l-1.1-.047-.66-2.466a.495.495 0 0 0-.613-.36l-1.936.519a.504.504 0 0 0-.35.617l.661 2.466-.93.59a8.459 8.459 0 0 0-1.848 1.594l-.728.838-2.322-1.034a.495.495 0 0 0-.665.25l-.815 1.83a.504.504 0 0 0 .26.661l2.344 1.044zm-3.565 1.697a3.504 3.504 0 0 1-1.78-4.622l.815-1.83a3.495 3.495 0 0 1 4.626-1.77l.346.154c.259-.245.529-.477.81-.697l-.106-.394a3.504 3.504 0 0 1 2.471-4.292l1.936-.519a3.495 3.495 0 0 1 4.286 2.481l.106.395c.353.05.703.116 1.05.198l.222-.306a3.504 3.504 0 0 1 4.89-.78l1.622 1.178a3.495 3.495 0 0 1 .769 4.892l-.258.355c.184.312.354.633.508.962l.42-.014a3.504 3.504 0 0 1 3.625 3.373l.07 2.003a3.495 3.495 0 0 1-3.382 3.618l-.4.014c-.127.332-.27.659-.426.978l.256.294a3.504 3.504 0 0 1-.34 4.941l-1.512 1.315a3.495 3.495 0 0 1-4.94-.351l-.283-.325a11.669 11.669 0 0 1-1.05.28l-.082.424a3.504 3.504 0 0 1-4.103 2.774l-1.967-.382a3.495 3.495 0 0 1-2.765-4.11l.075-.383a11.547 11.547 0 0 1-.858-.633l-.354.188a3.504 3.504 0 0 1-4.738-1.442l-.94-1.77a3.495 3.495 0 0 1 1.453-4.734l.37-.197a11.436 11.436 0 0 1-.041-1.088l-.4-.178zm13.326 5.608a5.5 5.5 0 1 1-2.847-10.625 5.5 5.5 0 0 1 2.847 10.625zm-.776-2.898a2.5 2.5 0 1 0-1.294-4.83 2.5 2.5 0 0 0 1.294 4.83z"/></g><g fill-rule="nonzero"><path fill="#EFEDF8" d="M326.979 222.047c14.403 3.86 29.209-4.688 33.068-19.092 3.86-14.403-4.688-29.209-19.092-33.068-14.403-3.86-29.209 4.688-33.068 19.092-3.86 14.404 4.688 29.209 19.092 33.068zm-1.035 3.864c-16.538-4.431-26.352-21.43-21.92-37.967 4.43-16.538 21.429-26.352 37.966-21.92 16.538 4.43 26.352 21.429 21.92 37.966-4.43 16.538-21.429 26.352-37.966 21.92z"/><path fill="#6B4FBB" d="M329.376 201.598c-4.668-2.621-7.155-8.157-5.706-13.566 1.715-6.402 8.295-10.201 14.697-8.486 6.402 1.716 10.2 8.296 8.485 14.697-1.45 5.41-6.371 8.96-11.725 8.897a3.03 3.03 0 0 1-.074.365l-1.812 6.761a3 3 0 0 1-5.795-1.552l1.812-6.762a3.03 3.03 0 0 1 .118-.354zm3.815-2.733a8 8 0 1 0 4.14-15.455 8 8 0 0 0-4.14 15.455z"/></g><path fill="#FEF0E8" fill-rule="nonzero" d="M91.373 193c17.071-4.574 27.202-22.12 22.628-39.191-4.575-17.071-22.121-27.202-39.192-22.628-17.071 4.574-27.202 22.121-22.628 39.192 4.574 17.071 22.121 27.202 39.192 22.627zm1.035 3.864c-19.204 5.146-38.945-6.25-44.09-25.456-5.146-19.204 6.25-38.945 25.455-44.09 19.205-5.146 38.945 6.25 44.091 25.455 5.146 19.205-6.25 38.945-25.456 44.091z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M70.067 152.122l6.73 25.114 19.318-5.176-6.73-25.114-19.318 5.176zm-1.035-3.864l19.318-5.176a4 4 0 0 1 4.9 2.828l6.729 25.114a4 4 0 0 1-2.829 4.9L77.832 181.1a4 4 0 0 1-4.9-2.829l-6.729-25.114a4 4 0 0 1 2.829-4.899z"/><path fill="#FC6D26" d="M76.898 154.433l7.727-2.07a2 2 0 0 1 1.036 3.863l-7.728 2.07a2 2 0 1 1-1.035-3.863zm1.812 6.761l5.795-1.553a2 2 0 0 1 1.035 3.864l-5.795 1.553a2 2 0 1 1-1.035-3.864zm1.811 6.762l7.728-2.07a2 2 0 0 1 1.035 3.863l-7.727 2.07a2 2 0 1 1-1.036-3.863z"/></g></svg>
\ No newline at end of file
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 6a0662ba90372ea57a9d64904d940359c1771372..c117d080bda7f597ce1d811e301f744ccedd4a30 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-param-reassign, class-methods-use-this */
 
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 import Pager from './pager';
 import { localTimeAgo } from './lib/utils/datetime_utility';
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
index 2bc77859c269542283b711700bc089b4eb213c24..bd08308904ca8d4ee6a015aab74461e94fdaa9cd 100644
--- a/app/assets/javascripts/ajax_loading_spinner.js
+++ b/app/assets/javascripts/ajax_loading_spinner.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default class AjaxLoadingSpinner {
   static init() {
     const $elements = $('.js-ajax-loading-spinner');
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 464611f66f0d6d555a0442329ac5e40281407e88..8ad3d18b30223b5cfbf01df6391e9bec54173fbe 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import axios from './lib/utils/axios_utils';
 
@@ -9,6 +10,9 @@ const Api = {
   projectsPath: '/api/:version/projects.json',
   projectPath: '/api/:version/projects/:id',
   projectLabelsPath: '/:namespace_path/:project_path/labels',
+  mergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
+  mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
+  mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
   groupLabelsPath: '/groups/:namespace_path/-/labels',
   licensePath: '/api/:version/templates/licenses/:key',
   gitignorePath: '/api/:version/templates/gitignores/:key',
@@ -21,25 +25,27 @@ const Api = {
   createBranchPath: '/api/:version/projects/:id/repository/branches',
 
   group(groupId, callback) {
-    const url = Api.buildUrl(Api.groupPath)
-      .replace(':id', groupId);
-    return axios.get(url)
-      .then(({ data }) => {
-        callback(data);
+    const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
+    return axios.get(url).then(({ data }) => {
+      callback(data);
 
-        return data;
-      });
+      return data;
+    });
   },
 
   // Return groups list. Filtered by query
   groups(query, options, callback = $.noop) {
     const url = Api.buildUrl(Api.groupsPath);
-    return axios.get(url, {
-      params: Object.assign({
-        search: query,
-        per_page: 20,
-      }, options),
-    })
+    return axios
+      .get(url, {
+        params: Object.assign(
+          {
+            search: query,
+            per_page: 20,
+          },
+          options,
+        ),
+      })
       .then(({ data }) => {
         callback(data);
 
@@ -50,12 +56,13 @@ const Api = {
   // Return namespaces list. Filtered by query
   namespaces(query, callback) {
     const url = Api.buildUrl(Api.namespacesPath);
-    return axios.get(url, {
-      params: {
-        search: query,
-        per_page: 20,
-      },
-    })
+    return axios
+      .get(url, {
+        params: {
+          search: query,
+          per_page: 20,
+        },
+      })
       .then(({ data }) => callback(data));
   },
 
@@ -72,9 +79,10 @@ const Api = {
       defaults.membership = true;
     }
 
-    return axios.get(url, {
-      params: Object.assign(defaults, options),
-    })
+    return axios
+      .get(url, {
+        params: Object.assign(defaults, options),
+      })
       .then(({ data }) => {
         callback(data);
 
@@ -84,8 +92,32 @@ const Api = {
 
   // Return single project
   project(projectPath) {
-    const url = Api.buildUrl(Api.projectPath)
-            .replace(':id', encodeURIComponent(projectPath));
+    const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
+
+    return axios.get(url);
+  },
+
+  // Return Merge Request for project
+  mergeRequest(projectPath, mergeRequestId) {
+    const url = Api.buildUrl(Api.mergeRequestPath)
+      .replace(':id', encodeURIComponent(projectPath))
+      .replace(':mrid', mergeRequestId);
+
+    return axios.get(url);
+  },
+
+  mergeRequestChanges(projectPath, mergeRequestId) {
+    const url = Api.buildUrl(Api.mergeRequestChangesPath)
+      .replace(':id', encodeURIComponent(projectPath))
+      .replace(':mrid', mergeRequestId);
+
+    return axios.get(url);
+  },
+
+  mergeRequestVersions(projectPath, mergeRequestId) {
+    const url = Api.buildUrl(Api.mergeRequestVersionsPath)
+      .replace(':id', encodeURIComponent(projectPath))
+      .replace(':mrid', mergeRequestId);
 
     return axios.get(url);
   },
@@ -101,30 +133,30 @@ const Api = {
       url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
     }
 
-    return axios.post(url, {
-      label: data,
-    })
+    return axios
+      .post(url, {
+        label: data,
+      })
       .then(res => callback(res.data))
       .catch(e => callback(e.response.data));
   },
 
   // Return group projects list. Filtered by query
   groupProjects(groupId, query, callback) {
-    const url = Api.buildUrl(Api.groupProjectsPath)
-      .replace(':id', groupId);
-    return axios.get(url, {
-      params: {
-        search: query,
-        per_page: 20,
-      },
-    })
+    const url = Api.buildUrl(Api.groupProjectsPath).replace(':id', groupId);
+    return axios
+      .get(url, {
+        params: {
+          search: query,
+          per_page: 20,
+        },
+      })
       .then(({ data }) => callback(data));
   },
 
   commitMultiple(id, data) {
     // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
-    const url = Api.buildUrl(Api.commitPath)
-      .replace(':id', encodeURIComponent(id));
+    const url = Api.buildUrl(Api.commitPath).replace(':id', encodeURIComponent(id));
     return axios.post(url, JSON.stringify(data), {
       headers: {
         'Content-Type': 'application/json; charset=utf-8',
@@ -135,39 +167,34 @@ const Api = {
   branchSingle(id, branch) {
     const url = Api.buildUrl(Api.branchSinglePath)
       .replace(':id', encodeURIComponent(id))
-      .replace(':branch', branch);
+      .replace(':branch', encodeURIComponent(branch));
 
     return axios.get(url);
   },
 
   // Return text for a specific license
   licenseText(key, data, callback) {
-    const url = Api.buildUrl(Api.licensePath)
-      .replace(':key', key);
-    return axios.get(url, {
-      params: data,
-    })
+    const url = Api.buildUrl(Api.licensePath).replace(':key', key);
+    return axios
+      .get(url, {
+        params: data,
+      })
       .then(res => callback(res.data));
   },
 
   gitignoreText(key, callback) {
-    const url = Api.buildUrl(Api.gitignorePath)
-      .replace(':key', key);
-    return axios.get(url)
-      .then(({ data }) => callback(data));
+    const url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
+    return axios.get(url).then(({ data }) => callback(data));
   },
 
   gitlabCiYml(key, callback) {
-    const url = Api.buildUrl(Api.gitlabCiYmlPath)
-      .replace(':key', key);
-    return axios.get(url)
-      .then(({ data }) => callback(data));
+    const url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
+    return axios.get(url).then(({ data }) => callback(data));
   },
 
   dockerfileYml(key, callback) {
     const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
-    return axios.get(url)
-      .then(({ data }) => callback(data));
+    return axios.get(url).then(({ data }) => callback(data));
   },
 
   issueTemplate(namespacePath, projectPath, key, type, callback) {
@@ -176,7 +203,8 @@ const Api = {
       .replace(':type', type)
       .replace(':project_path', projectPath)
       .replace(':namespace_path', namespacePath);
-    return axios.get(url)
+    return axios
+      .get(url)
       .then(({ data }) => callback(null, data))
       .catch(callback);
   },
@@ -184,10 +212,13 @@ const Api = {
   users(query, options) {
     const url = Api.buildUrl(this.usersPath);
     return axios.get(url, {
-      params: Object.assign({
-        search: query,
-        per_page: 20,
-      }, options),
+      params: Object.assign(
+        {
+          search: query,
+          per_page: 20,
+        },
+        options,
+      ),
     });
   },
 
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 26e62732b335d5fdbaab20c464019277137ba5f8..976d32abe9b169e7fa38940d465815fae4da0468 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,8 +1,11 @@
 /* eslint-disable class-methods-use-this */
+
+import $ from 'jquery';
 import _ from 'underscore';
 import Cookies from 'js-cookie';
 import { __ } from './locale';
-import { isInIssuePage, isInMRPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils';
+import { updateTooltipTitle } from './lib/utils/common_utils';
+import { isInVueNoteablePage } from './lib/utils/dom_utils';
 import flash from './flash';
 import axios from './lib/utils/axios_utils';
 
@@ -241,7 +244,7 @@ class AwardsHandler {
   addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
     const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length;
 
-    if (this.isInVueNoteablePage() && !isMainAwardsBlock) {
+    if (isInVueNoteablePage() && !isMainAwardsBlock) {
       const id = votesBlock.attr('id').replace('note_', '');
 
       this.hideMenuElement($('.emoji-menu'));
@@ -293,16 +296,8 @@ class AwardsHandler {
     }
   }
 
-  isVueMRDiscussions() {
-    return isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible');
-  }
-
-  isInVueNoteablePage() {
-    return isInIssuePage() || this.isVueMRDiscussions();
-  }
-
   getVotesBlock() {
-    if (this.isInVueNoteablePage()) {
+    if (isInVueNoteablePage()) {
       const $el = $('.js-add-award.is-active').closest('.note.timeline-entry');
 
       if ($el.length) {
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6e6cb31e3ac8dc1b3b741449eb492ac44bbe8bd0
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge.vue
@@ -0,0 +1,121 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import Tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  name: 'Badge',
+  components: {
+    Icon,
+    LoadingIcon,
+    Tooltip,
+  },
+  directives: {
+    Tooltip,
+  },
+  props: {
+    imageUrl: {
+      type: String,
+      required: true,
+    },
+    linkUrl: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      hasError: false,
+      isLoading: true,
+      numRetries: 0,
+    };
+  },
+  computed: {
+    imageUrlWithRetries() {
+      if (this.numRetries === 0) {
+        return this.imageUrl;
+      }
+
+      return `${this.imageUrl}#retries=${this.numRetries}`;
+    },
+  },
+  watch: {
+    imageUrl() {
+      this.hasError = false;
+      this.isLoading = true;
+      this.numRetries = 0;
+    },
+  },
+  methods: {
+    onError() {
+      this.isLoading = false;
+      this.hasError = true;
+    },
+    onLoad() {
+      this.isLoading = false;
+    },
+    reloadImage() {
+      this.hasError = false;
+      this.isLoading = true;
+      this.numRetries += 1;
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <a
+      v-show="!isLoading && !hasError"
+      :href="linkUrl"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      <img
+        class="project-badge"
+        :src="imageUrlWithRetries"
+        @load="onLoad"
+        @error="onError"
+        aria-hidden="true"
+      />
+    </a>
+
+    <loading-icon
+      v-show="isLoading"
+      :inline="true"
+    />
+
+    <div
+      v-show="hasError"
+      class="btn-group"
+    >
+      <div class="btn btn-default btn-xs disabled">
+        <icon
+          class="prepend-left-8 append-right-8"
+          name="doc_image"
+          :size="16"
+          aria-hidden="true"
+        />
+      </div>
+      <div
+        class="btn btn-default btn-xs disabled"
+      >
+        <span class="prepend-left-8 append-right-8">{{ s__('Badges|No badge image') }}</span>
+      </div>
+    </div>
+
+    <button
+      v-show="hasError"
+      class="btn btn-transparent btn-xs text-primary"
+      type="button"
+      v-tooltip
+      :title="s__('Badges|Reload badge image')"
+      @click="reloadImage"
+    >
+      <icon
+        name="retry"
+        :size="16"
+      />
+    </button>
+  </div>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ae942b2c1a7be68b731833859095969406d06e45
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -0,0 +1,219 @@
+<script>
+import _ from 'underscore';
+import { mapActions, mapState } from 'vuex';
+import createFlash from '~/flash';
+import { s__, sprintf } from '~/locale';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import createEmptyBadge from '../empty_badge';
+import Badge from './badge.vue';
+
+const badgePreviewDelayInMilliseconds = 1500;
+
+export default {
+  name: 'BadgeForm',
+  components: {
+    Badge,
+    LoadingButton,
+    LoadingIcon,
+  },
+  props: {
+    isEditing: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  computed: {
+    ...mapState([
+      'badgeInAddForm',
+      'badgeInEditForm',
+      'docsUrl',
+      'isRendering',
+      'isSaving',
+      'renderedBadge',
+    ]),
+    badge() {
+      if (this.isEditing) {
+        return this.badgeInEditForm;
+      }
+
+      return this.badgeInAddForm;
+    },
+    canSubmit() {
+      return (
+        this.badge !== null &&
+        this.badge.imageUrl &&
+        this.badge.imageUrl.trim() !== '' &&
+        this.badge.linkUrl &&
+        this.badge.linkUrl.trim() !== '' &&
+        !this.isSaving
+      );
+    },
+    helpText() {
+      const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
+        .map(placeholder => `<code>%{${placeholder}}</code>`)
+        .join(', ');
+      return sprintf(
+        s__('Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}'),
+        {
+          docsLinkEnd: '</a>',
+          docsLinkStart: `<a href="${_.escape(this.docsUrl)}">`,
+          placeholders,
+        },
+        false,
+      );
+    },
+    renderedImageUrl() {
+      return this.renderedBadge ? this.renderedBadge.renderedImageUrl : '';
+    },
+    renderedLinkUrl() {
+      return this.renderedBadge ? this.renderedBadge.renderedLinkUrl : '';
+    },
+    imageUrl: {
+      get() {
+        return this.badge ? this.badge.imageUrl : '';
+      },
+      set(imageUrl) {
+        const badge = this.badge || createEmptyBadge();
+        this.updateBadgeInForm({
+          ...badge,
+          imageUrl,
+        });
+      },
+    },
+    linkUrl: {
+      get() {
+        return this.badge ? this.badge.linkUrl : '';
+      },
+      set(linkUrl) {
+        const badge = this.badge || createEmptyBadge();
+        this.updateBadgeInForm({
+          ...badge,
+          linkUrl,
+        });
+      },
+    },
+    submitButtonLabel() {
+      if (this.isEditing) {
+        return s__('Badges|Save changes');
+      }
+      return s__('Badges|Add badge');
+    },
+  },
+  methods: {
+    ...mapActions(['addBadge', 'renderBadge', 'saveBadge', 'stopEditing', 'updateBadgeInForm']),
+    debouncedPreview: _.debounce(function preview() {
+      this.renderBadge();
+    }, badgePreviewDelayInMilliseconds),
+    onCancel() {
+      this.stopEditing();
+    },
+    onSubmit() {
+      if (!this.canSubmit) {
+        return Promise.resolve();
+      }
+
+      if (this.isEditing) {
+        return this.saveBadge()
+          .then(() => {
+            createFlash(s__('Badges|The badge was saved.'), 'notice');
+          })
+          .catch(error => {
+            createFlash(
+              s__('Badges|Saving the badge failed, please check the entered URLs and try again.'),
+            );
+            throw error;
+          });
+      }
+
+      return this.addBadge()
+        .then(() => {
+          createFlash(s__('Badges|A new badge was added.'), 'notice');
+        })
+        .catch(error => {
+          createFlash(
+            s__('Badges|Adding the badge failed, please check the entered URLs and try again.'),
+          );
+          throw error;
+        });
+    },
+  },
+  badgeImageUrlPlaceholder:
+    'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg',
+  badgeLinkUrlPlaceholder: 'https://example.gitlab.com/%{project_path}',
+};
+</script>
+
+<template>
+  <form
+    class="prepend-top-default append-bottom-default"
+    @submit.prevent.stop="onSubmit"
+  >
+    <div class="form-group">
+      <label for="badge-link-url">{{ s__('Badges|Link') }}</label>
+      <input
+        id="badge-link-url"
+        type="text"
+        class="form-control"
+        v-model="linkUrl"
+        :placeholder="$options.badgeLinkUrlPlaceholder"
+        @input="debouncedPreview"
+      />
+      <span
+        class="help-block"
+        v-html="helpText"
+      ></span>
+    </div>
+
+    <div class="form-group">
+      <label for="badge-image-url">{{ s__('Badges|Badge image URL') }}</label>
+      <input
+        id="badge-image-url"
+        type="text"
+        class="form-control"
+        v-model="imageUrl"
+        :placeholder="$options.badgeImageUrlPlaceholder"
+        @input="debouncedPreview"
+      />
+      <span
+        class="help-block"
+        v-html="helpText"
+      ></span>
+    </div>
+
+    <div class="form-group">
+      <label for="badge-preview">{{ s__('Badges|Badge image preview') }}</label>
+      <badge
+        id="badge-preview"
+        v-show="renderedBadge && !isRendering"
+        :image-url="renderedImageUrl"
+        :link-url="renderedLinkUrl"
+      />
+      <p v-show="isRendering">
+        <loading-icon
+          :inline="true"
+        />
+      </p>
+      <p
+        v-show="!renderedBadge && !isRendering"
+        class="disabled-content"
+      >{{ s__('Badges|No image to preview') }}</p>
+    </div>
+
+    <div class="row-content-block">
+      <loading-button
+        type="submit"
+        container-class="btn btn-success"
+        :disabled="!canSubmit"
+        :loading="isSaving"
+        :label="submitButtonLabel"
+      />
+      <button
+        class="btn btn-cancel"
+        type="button"
+        v-if="isEditing"
+        @click="onCancel"
+      >{{ __('Cancel') }}</button>
+    </div>
+  </form>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ca7197e1e0f1bbe2dfab91eba767fc7012fbd3d2
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -0,0 +1,57 @@
+<script>
+import { mapState } from 'vuex';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import BadgeListRow from './badge_list_row.vue';
+import { GROUP_BADGE } from '../constants';
+
+export default {
+  name: 'BadgeList',
+  components: {
+    BadgeListRow,
+    LoadingIcon,
+  },
+  computed: {
+    ...mapState(['badges', 'isLoading', 'kind']),
+    hasNoBadges() {
+      return !this.isLoading && (!this.badges || !this.badges.length);
+    },
+    isGroupBadge() {
+      return this.kind === GROUP_BADGE;
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      {{ s__('Badges|Your badges') }}
+      <span
+        v-show="!isLoading"
+        class="badge"
+      >{{ badges.length }}</span>
+    </div>
+    <loading-icon
+      v-show="isLoading"
+      class="panel-body"
+      size="2"
+    />
+    <div
+      v-if="hasNoBadges"
+      class="panel-body"
+    >
+      <span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span>
+      <span v-else>{{ s__('Badges|This project has no badges') }}</span>
+    </div>
+    <div
+      v-else
+      class="panel-body"
+    >
+      <badge-list-row
+        v-for="badge in badges"
+        :key="badge.id"
+        :badge="badge"
+      />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
new file mode 100644
index 0000000000000000000000000000000000000000..af062bdf8c64f206b83623e82759cc4e21b44f4b
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -0,0 +1,89 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import { PROJECT_BADGE } from '../constants';
+import Badge from './badge.vue';
+
+export default {
+  name: 'BadgeListRow',
+  components: {
+    Badge,
+    Icon,
+    LoadingIcon,
+  },
+  props: {
+    badge: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    ...mapState(['kind']),
+    badgeKindText() {
+      if (this.badge.kind === PROJECT_BADGE) {
+        return s__('Badges|Project Badge');
+      }
+
+      return s__('Badges|Group Badge');
+    },
+    canEditBadge() {
+      return this.badge.kind === this.kind;
+    },
+  },
+  methods: {
+    ...mapActions(['editBadge', 'updateBadgeInModal']),
+  },
+};
+</script>
+
+<template>
+  <div class="gl-responsive-table-row-layout gl-responsive-table-row">
+    <badge
+      class="table-section section-30"
+      :image-url="badge.renderedImageUrl"
+      :link-url="badge.renderedLinkUrl"
+    />
+    <span class="table-section section-50 str-truncated">{{ badge.linkUrl }}</span>
+    <div class="table-section section-10">
+      <span class="badge">{{ badgeKindText }}</span>
+    </div>
+    <div class="table-section section-10 table-button-footer">
+      <div
+        v-if="canEditBadge"
+        class="table-action-buttons">
+        <button
+          class="btn btn-default append-right-8"
+          type="button"
+          :disabled="badge.isDeleting"
+          @click="editBadge(badge)"
+        >
+          <icon
+            name="pencil"
+            :size="16"
+            :aria-label="__('Edit')"
+          />
+        </button>
+        <button
+          class="btn btn-danger"
+          type="button"
+          data-toggle="modal"
+          data-target="#delete-badge-modal"
+          :disabled="badge.isDeleting"
+          @click="updateBadgeInModal(badge)"
+        >
+          <icon
+            name="remove"
+            :size="16"
+            :aria-label="__('Delete')"
+          />
+        </button>
+        <loading-icon
+          v-show="badge.isDeleting"
+          :inline="true"
+        />
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_settings.vue b/app/assets/javascripts/badges/components/badge_settings.vue
new file mode 100644
index 0000000000000000000000000000000000000000..83f783942389593e2f7491d2b4ad5d8c221dcea8
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_settings.vue
@@ -0,0 +1,70 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import Badge from './badge.vue';
+import BadgeForm from './badge_form.vue';
+import BadgeList from './badge_list.vue';
+
+export default {
+  name: 'BadgeSettings',
+  components: {
+    Badge,
+    BadgeForm,
+    BadgeList,
+    GlModal,
+  },
+  computed: {
+    ...mapState(['badgeInModal', 'isEditing']),
+    deleteModalText() {
+      return s__(
+        'Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored.',
+      );
+    },
+  },
+  methods: {
+    ...mapActions(['deleteBadge']),
+    onSubmitModal() {
+      this.deleteBadge(this.badgeInModal)
+        .then(() => {
+          createFlash(s__('Badges|The badge was deleted.'), 'notice');
+        })
+        .catch(error => {
+          createFlash(s__('Badges|Deleting the badge failed, please try again.'));
+          throw error;
+        });
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="badge-settings">
+    <gl-modal
+      id="delete-badge-modal"
+      :header-title-text="s__('Badges|Delete badge?')"
+      footer-primary-button-variant="danger"
+      :footer-primary-button-text="s__('Badges|Delete badge')"
+      @submit="onSubmitModal">
+      <div class="well">
+        <badge
+          :image-url="badgeInModal ? badgeInModal.renderedImageUrl : ''"
+          :link-url="badgeInModal ? badgeInModal.renderedLinkUrl : ''"
+        />
+      </div>
+      <p v-html="deleteModalText"></p>
+    </gl-modal>
+
+    <badge-form
+      v-show="isEditing"
+      :is-editing="true"
+    />
+
+    <badge-form
+      v-show="!isEditing"
+      :is-editing="false"
+    />
+    <badge-list v-show="!isEditing" />
+  </div>
+</template>
diff --git a/app/assets/javascripts/badges/constants.js b/app/assets/javascripts/badges/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..8fbe3db5ef186c8859bb637bc3edb66c55020ffa
--- /dev/null
+++ b/app/assets/javascripts/badges/constants.js
@@ -0,0 +1,2 @@
+export const GROUP_BADGE = 'group';
+export const PROJECT_BADGE = 'project';
diff --git a/app/assets/javascripts/badges/empty_badge.js b/app/assets/javascripts/badges/empty_badge.js
new file mode 100644
index 0000000000000000000000000000000000000000..49a9b5e1be80f09df537f3dcbd5fe89e0bde3bb9
--- /dev/null
+++ b/app/assets/javascripts/badges/empty_badge.js
@@ -0,0 +1,7 @@
+export default () => ({
+  imageUrl: '',
+  isDeleting: false,
+  linkUrl: '',
+  renderedImageUrl: '',
+  renderedLinkUrl: '',
+});
diff --git a/app/assets/javascripts/badges/store/actions.js b/app/assets/javascripts/badges/store/actions.js
new file mode 100644
index 0000000000000000000000000000000000000000..5542278b3e0af8795ef87da8588df61000f11a69
--- /dev/null
+++ b/app/assets/javascripts/badges/store/actions.js
@@ -0,0 +1,167 @@
+import axios from '~/lib/utils/axios_utils';
+import types from './mutation_types';
+
+export const transformBackendBadge = badge => ({
+  id: badge.id,
+  imageUrl: badge.image_url,
+  kind: badge.kind,
+  linkUrl: badge.link_url,
+  renderedImageUrl: badge.rendered_image_url,
+  renderedLinkUrl: badge.rendered_link_url,
+  isDeleting: false,
+});
+
+export default {
+  requestNewBadge({ commit }) {
+    commit(types.REQUEST_NEW_BADGE);
+  },
+  receiveNewBadge({ commit }, newBadge) {
+    commit(types.RECEIVE_NEW_BADGE, newBadge);
+  },
+  receiveNewBadgeError({ commit }) {
+    commit(types.RECEIVE_NEW_BADGE_ERROR);
+  },
+  addBadge({ dispatch, state }) {
+    const newBadge = state.badgeInAddForm;
+    const endpoint = state.apiEndpointUrl;
+    dispatch('requestNewBadge');
+    return axios
+      .post(endpoint, {
+        image_url: newBadge.imageUrl,
+        link_url: newBadge.linkUrl,
+      })
+      .catch(error => {
+        dispatch('receiveNewBadgeError');
+        throw error;
+      })
+      .then(res => {
+        dispatch('receiveNewBadge', transformBackendBadge(res.data));
+      });
+  },
+  requestDeleteBadge({ commit }, badgeId) {
+    commit(types.REQUEST_DELETE_BADGE, badgeId);
+  },
+  receiveDeleteBadge({ commit }, badgeId) {
+    commit(types.RECEIVE_DELETE_BADGE, badgeId);
+  },
+  receiveDeleteBadgeError({ commit }, badgeId) {
+    commit(types.RECEIVE_DELETE_BADGE_ERROR, badgeId);
+  },
+  deleteBadge({ dispatch, state }, badge) {
+    const badgeId = badge.id;
+    dispatch('requestDeleteBadge', badgeId);
+    const endpoint = `${state.apiEndpointUrl}/${badgeId}`;
+    return axios
+      .delete(endpoint)
+      .catch(error => {
+        dispatch('receiveDeleteBadgeError', badgeId);
+        throw error;
+      })
+      .then(() => {
+        dispatch('receiveDeleteBadge', badgeId);
+      });
+  },
+
+  editBadge({ commit }, badge) {
+    commit(types.START_EDITING, badge);
+  },
+
+  requestLoadBadges({ commit }, data) {
+    commit(types.REQUEST_LOAD_BADGES, data);
+  },
+  receiveLoadBadges({ commit }, badges) {
+    commit(types.RECEIVE_LOAD_BADGES, badges);
+  },
+  receiveLoadBadgesError({ commit }) {
+    commit(types.RECEIVE_LOAD_BADGES_ERROR);
+  },
+
+  loadBadges({ dispatch, state }, data) {
+    dispatch('requestLoadBadges', data);
+    const endpoint = state.apiEndpointUrl;
+    return axios
+      .get(endpoint)
+      .catch(error => {
+        dispatch('receiveLoadBadgesError');
+        throw error;
+      })
+      .then(res => {
+        dispatch('receiveLoadBadges', res.data.map(transformBackendBadge));
+      });
+  },
+
+  requestRenderedBadge({ commit }) {
+    commit(types.REQUEST_RENDERED_BADGE);
+  },
+  receiveRenderedBadge({ commit }, renderedBadge) {
+    commit(types.RECEIVE_RENDERED_BADGE, renderedBadge);
+  },
+  receiveRenderedBadgeError({ commit }) {
+    commit(types.RECEIVE_RENDERED_BADGE_ERROR);
+  },
+
+  renderBadge({ dispatch, state }) {
+    const badge = state.isEditing ? state.badgeInEditForm : state.badgeInAddForm;
+    const { linkUrl, imageUrl } = badge;
+    if (!linkUrl || linkUrl.trim() === '' || !imageUrl || imageUrl.trim() === '') {
+      return Promise.resolve(badge);
+    }
+
+    dispatch('requestRenderedBadge');
+
+    const parameters = [
+      `link_url=${encodeURIComponent(linkUrl)}`,
+      `image_url=${encodeURIComponent(imageUrl)}`,
+    ].join('&');
+    const renderEndpoint = `${state.apiEndpointUrl}/render?${parameters}`;
+    return axios
+      .get(renderEndpoint)
+      .catch(error => {
+        dispatch('receiveRenderedBadgeError');
+        throw error;
+      })
+      .then(res => {
+        dispatch('receiveRenderedBadge', transformBackendBadge(res.data));
+      });
+  },
+
+  requestUpdatedBadge({ commit }) {
+    commit(types.REQUEST_UPDATED_BADGE);
+  },
+  receiveUpdatedBadge({ commit }, updatedBadge) {
+    commit(types.RECEIVE_UPDATED_BADGE, updatedBadge);
+  },
+  receiveUpdatedBadgeError({ commit }) {
+    commit(types.RECEIVE_UPDATED_BADGE_ERROR);
+  },
+
+  saveBadge({ dispatch, state }) {
+    const badge = state.badgeInEditForm;
+    const endpoint = `${state.apiEndpointUrl}/${badge.id}`;
+    dispatch('requestUpdatedBadge');
+    return axios
+      .put(endpoint, {
+        image_url: badge.imageUrl,
+        link_url: badge.linkUrl,
+      })
+      .catch(error => {
+        dispatch('receiveUpdatedBadgeError');
+        throw error;
+      })
+      .then(res => {
+        dispatch('receiveUpdatedBadge', transformBackendBadge(res.data));
+      });
+  },
+
+  stopEditing({ commit }) {
+    commit(types.STOP_EDITING);
+  },
+
+  updateBadgeInForm({ commit }, badge) {
+    commit(types.UPDATE_BADGE_IN_FORM, badge);
+  },
+
+  updateBadgeInModal({ commit }, badge) {
+    commit(types.UPDATE_BADGE_IN_MODAL, badge);
+  },
+};
diff --git a/app/assets/javascripts/badges/store/index.js b/app/assets/javascripts/badges/store/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..7a5df403a0e04e7717eb046a0fe8d9dadc4db446
--- /dev/null
+++ b/app/assets/javascripts/badges/store/index.js
@@ -0,0 +1,13 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+  state: createState(),
+  actions,
+  mutations,
+});
diff --git a/app/assets/javascripts/badges/store/mutation_types.js b/app/assets/javascripts/badges/store/mutation_types.js
new file mode 100644
index 0000000000000000000000000000000000000000..d73f91b6005f14953a4e405d549a8c01cf7fb025
--- /dev/null
+++ b/app/assets/javascripts/badges/store/mutation_types.js
@@ -0,0 +1,21 @@
+export default {
+  RECEIVE_DELETE_BADGE: 'RECEIVE_DELETE_BADGE',
+  RECEIVE_DELETE_BADGE_ERROR: 'RECEIVE_DELETE_BADGE_ERROR',
+  RECEIVE_LOAD_BADGES: 'RECEIVE_LOAD_BADGES',
+  RECEIVE_LOAD_BADGES_ERROR: 'RECEIVE_LOAD_BADGES_ERROR',
+  RECEIVE_NEW_BADGE: 'RECEIVE_NEW_BADGE',
+  RECEIVE_NEW_BADGE_ERROR: 'RECEIVE_NEW_BADGE_ERROR',
+  RECEIVE_RENDERED_BADGE: 'RECEIVE_RENDERED_BADGE',
+  RECEIVE_RENDERED_BADGE_ERROR: 'RECEIVE_RENDERED_BADGE_ERROR',
+  RECEIVE_UPDATED_BADGE: 'RECEIVE_UPDATED_BADGE',
+  RECEIVE_UPDATED_BADGE_ERROR: 'RECEIVE_UPDATED_BADGE_ERROR',
+  REQUEST_DELETE_BADGE: 'REQUEST_DELETE_BADGE',
+  REQUEST_LOAD_BADGES: 'REQUEST_LOAD_BADGES',
+  REQUEST_NEW_BADGE: 'REQUEST_NEW_BADGE',
+  REQUEST_RENDERED_BADGE: 'REQUEST_RENDERED_BADGE',
+  REQUEST_UPDATED_BADGE: 'REQUEST_UPDATED_BADGE',
+  START_EDITING: 'START_EDITING',
+  STOP_EDITING: 'STOP_EDITING',
+  UPDATE_BADGE_IN_FORM: 'UPDATE_BADGE_IN_FORM',
+  UPDATE_BADGE_IN_MODAL: 'UPDATE_BADGE_IN_MODAL',
+};
diff --git a/app/assets/javascripts/badges/store/mutations.js b/app/assets/javascripts/badges/store/mutations.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd84e68c00fdccb081e2b7be2674f59ad91d2870
--- /dev/null
+++ b/app/assets/javascripts/badges/store/mutations.js
@@ -0,0 +1,158 @@
+import types from './mutation_types';
+import { PROJECT_BADGE } from '../constants';
+
+const reorderBadges = badges =>
+  badges.sort((a, b) => {
+    if (a.kind !== b.kind) {
+      return a.kind === PROJECT_BADGE ? 1 : -1;
+    }
+
+    return a.id - b.id;
+  });
+
+export default {
+  [types.RECEIVE_NEW_BADGE](state, newBadge) {
+    Object.assign(state, {
+      badgeInAddForm: null,
+      badges: reorderBadges(state.badges.concat(newBadge)),
+      isSaving: false,
+      renderedBadge: null,
+    });
+  },
+  [types.RECEIVE_NEW_BADGE_ERROR](state) {
+    Object.assign(state, {
+      isSaving: false,
+    });
+  },
+  [types.REQUEST_NEW_BADGE](state) {
+    Object.assign(state, {
+      isSaving: true,
+    });
+  },
+
+  [types.RECEIVE_UPDATED_BADGE](state, updatedBadge) {
+    const badges = state.badges.map(badge => {
+      if (badge.id === updatedBadge.id) {
+        return updatedBadge;
+      }
+      return badge;
+    });
+    Object.assign(state, {
+      badgeInEditForm: null,
+      badges,
+      isEditing: false,
+      isSaving: false,
+      renderedBadge: null,
+    });
+  },
+  [types.RECEIVE_UPDATED_BADGE_ERROR](state) {
+    Object.assign(state, {
+      isSaving: false,
+    });
+  },
+  [types.REQUEST_UPDATED_BADGE](state) {
+    Object.assign(state, {
+      isSaving: true,
+    });
+  },
+
+  [types.RECEIVE_LOAD_BADGES](state, badges) {
+    Object.assign(state, {
+      badges: reorderBadges(badges),
+      isLoading: false,
+    });
+  },
+  [types.RECEIVE_LOAD_BADGES_ERROR](state) {
+    Object.assign(state, {
+      isLoading: false,
+    });
+  },
+  [types.REQUEST_LOAD_BADGES](state, data) {
+    Object.assign(state, {
+      kind: data.kind, // project or group
+      apiEndpointUrl: data.apiEndpointUrl,
+      docsUrl: data.docsUrl,
+      isLoading: true,
+    });
+  },
+
+  [types.RECEIVE_DELETE_BADGE](state, badgeId) {
+    const badges = state.badges.filter(badge => badge.id !== badgeId);
+    Object.assign(state, {
+      badges,
+    });
+  },
+  [types.RECEIVE_DELETE_BADGE_ERROR](state, badgeId) {
+    const badges = state.badges.map(badge => {
+      if (badge.id === badgeId) {
+        return {
+          ...badge,
+          isDeleting: false,
+        };
+      }
+
+      return badge;
+    });
+    Object.assign(state, {
+      badges,
+    });
+  },
+  [types.REQUEST_DELETE_BADGE](state, badgeId) {
+    const badges = state.badges.map(badge => {
+      if (badge.id === badgeId) {
+        return {
+          ...badge,
+          isDeleting: true,
+        };
+      }
+
+      return badge;
+    });
+    Object.assign(state, {
+      badges,
+    });
+  },
+
+  [types.RECEIVE_RENDERED_BADGE](state, renderedBadge) {
+    Object.assign(state, { isRendering: false, renderedBadge });
+  },
+  [types.RECEIVE_RENDERED_BADGE_ERROR](state) {
+    Object.assign(state, { isRendering: false });
+  },
+  [types.REQUEST_RENDERED_BADGE](state) {
+    Object.assign(state, { isRendering: true });
+  },
+
+  [types.START_EDITING](state, badge) {
+    Object.assign(state, {
+      badgeInEditForm: { ...badge },
+      isEditing: true,
+      renderedBadge: { ...badge },
+    });
+  },
+  [types.STOP_EDITING](state) {
+    Object.assign(state, {
+      badgeInEditForm: null,
+      isEditing: false,
+      renderedBadge: null,
+    });
+  },
+
+  [types.UPDATE_BADGE_IN_FORM](state, badge) {
+    if (state.isEditing) {
+      Object.assign(state, {
+        badgeInEditForm: badge,
+      });
+    } else {
+      Object.assign(state, {
+        badgeInAddForm: badge,
+      });
+    }
+  },
+
+  [types.UPDATE_BADGE_IN_MODAL](state, badge) {
+    Object.assign(state, {
+      badgeInModal: badge,
+    });
+  },
+};
diff --git a/app/assets/javascripts/badges/store/state.js b/app/assets/javascripts/badges/store/state.js
new file mode 100644
index 0000000000000000000000000000000000000000..43413aeb5bb8140a43d6c087c70ad386635b7d0a
--- /dev/null
+++ b/app/assets/javascripts/badges/store/state.js
@@ -0,0 +1,13 @@
+export default () => ({
+  apiEndpointUrl: null,
+  badgeInAddForm: null,
+  badgeInEditForm: null,
+  badgeInModal: null,
+  badges: [],
+  docsUrl: null,
+  renderedBadge: null,
+  isEditing: false,
+  isLoading: false,
+  isRendering: false,
+  isSaving: false,
+});
diff --git a/app/assets/javascripts/behaviors/copy_as_gfm.js b/app/assets/javascripts/behaviors/copy_as_gfm.js
deleted file mode 100644
index ffe90595b5de47bbeeae7be0527df2b6aa131e42..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/behaviors/copy_as_gfm.js
+++ /dev/null
@@ -1,500 +0,0 @@
-/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
-
-import _ from 'underscore';
-import { insertText, getSelectedFragment, nodeMatchesSelector } from '../lib/utils/common_utils';
-import { placeholderImage } from '../lazy_loader';
-
-const gfmRules = {
-  // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
-  // GitLab Flavored Markdown (GFM) to HTML.
-  // These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
-  // Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
-  // from GFM should have a handler here, in reverse order.
-  // The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
-  InlineDiffFilter: {
-    'span.idiff.addition'(el, text) {
-      return `{+${text}+}`;
-    },
-    'span.idiff.deletion'(el, text) {
-      return `{-${text}-}`;
-    },
-  },
-  TaskListFilter: {
-    'input[type=checkbox].task-list-item-checkbox'(el) {
-      return `[${el.checked ? 'x' : ' '}]`;
-    },
-  },
-  ReferenceFilter: {
-    '.tooltip'(el) {
-      return '';
-    },
-    'a.gfm:not([data-link=true])'(el, text) {
-      return el.dataset.original || text;
-    },
-  },
-  AutolinkFilter: {
-    'a'(el, text) {
-      // Fallback on the regular MarkdownFilter's `a` handler.
-      if (text !== el.getAttribute('href')) return false;
-
-      return text;
-    },
-  },
-  TableOfContentsFilter: {
-    'ul.section-nav'(el) {
-      return '[[_TOC_]]';
-    },
-  },
-  EmojiFilter: {
-    'img.emoji'(el) {
-      return el.getAttribute('alt');
-    },
-    'gl-emoji'(el) {
-      return `:${el.getAttribute('data-name')}:`;
-    },
-  },
-  ImageLinkFilter: {
-    'a.no-attachment-icon'(el, text) {
-      return text;
-    },
-  },
-  ImageLazyLoadFilter: {
-    'img'(el, text) {
-      return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
-    },
-  },
-  VideoLinkFilter: {
-    '.video-container'(el) {
-      const videoEl = el.querySelector('video');
-      if (!videoEl) return false;
-
-      return CopyAsGFM.nodeToGFM(videoEl);
-    },
-    'video'(el) {
-      return `![${el.dataset.title}](${el.getAttribute('src')})`;
-    },
-  },
-  MermaidFilter: {
-    'svg.mermaid'(el, text) {
-      const sourceEl = el.querySelector('text.source');
-      if (!sourceEl) return false;
-
-      return `\`\`\`mermaid\n${CopyAsGFM.nodeToGFM(sourceEl)}\n\`\`\``;
-    },
-    'svg.mermaid style, svg.mermaid g'(el, text) {
-      // We don't want to include the content of these elements in the copied text.
-      return '';
-    },
-  },
-  MathFilter: {
-    'pre.code.math[data-math-style=display]'(el, text) {
-      return `\`\`\`math\n${text.trim()}\n\`\`\``;
-    },
-    'code.code.math[data-math-style=inline]'(el, text) {
-      return `$\`${text}\`$`;
-    },
-    'span.katex-display span.katex-mathml'(el) {
-      const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
-      if (!mathAnnotation) return false;
-
-      return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
-    },
-    'span.katex-mathml'(el) {
-      const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
-      if (!mathAnnotation) return false;
-
-      return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
-    },
-    'span.katex-html'(el) {
-      // We don't want to include the content of this element in the copied text.
-      return '';
-    },
-    'annotation[encoding="application/x-tex"]'(el, text) {
-      return text.trim();
-    },
-  },
-  SanitizationFilter: {
-    'a[name]:not([href]):empty'(el) {
-      return el.outerHTML;
-    },
-    'dl'(el, text) {
-      let lines = text.trim().split('\n');
-      // Add two spaces to the front of subsequent list items lines,
-      // or leave the line entirely blank.
-      lines = lines.map((l) => {
-        const line = l.trim();
-        if (line.length === 0) return '';
-
-        return `  ${line}`;
-      });
-
-      return `<dl>\n${lines.join('\n')}\n</dl>`;
-    },
-    'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr, summary, details'(el, text) {
-      const tag = el.nodeName.toLowerCase();
-      return `<${tag}>${text}</${tag}>`;
-    },
-  },
-  SyntaxHighlightFilter: {
-    'pre.code.highlight'(el, t) {
-      const text = t.trimRight();
-
-      let lang = el.getAttribute('lang');
-      if (!lang || lang === 'plaintext') {
-        lang = '';
-      }
-
-      // Prefixes lines with 4 spaces if the code contains triple backticks
-      if (lang === '' && text.match(/^```/gm)) {
-        return text.split('\n').map((l) => {
-          const line = l.trim();
-          if (line.length === 0) return '';
-
-          return `    ${line}`;
-        }).join('\n');
-      }
-
-      return `\`\`\`${lang}\n${text}\n\`\`\``;
-    },
-    'pre > code'(el, text) {
-       // Don't wrap code blocks in ``
-      return text;
-    },
-  },
-  MarkdownFilter: {
-    'br'(el) {
-      // Two spaces at the end of a line are turned into a BR
-      return '  ';
-    },
-    'code'(el, text) {
-      let backtickCount = 1;
-      const backtickMatch = text.match(/`+/);
-      if (backtickMatch) {
-        backtickCount = backtickMatch[0].length + 1;
-      }
-
-      const backticks = Array(backtickCount + 1).join('`');
-      const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
-
-      return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
-    },
-    'blockquote'(el, text) {
-      return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
-    },
-    'img'(el) {
-      const imageSrc = el.src;
-      const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
-      return `![${el.getAttribute('alt')}](${imageUrl})`;
-    },
-    'a.anchor'(el, text) {
-      // Don't render a Markdown link for the anchor link inside a heading
-      return text;
-    },
-    'a'(el, text) {
-      return `[${text}](${el.getAttribute('href')})`;
-    },
-    'li'(el, text) {
-      const lines = text.trim().split('\n');
-      const firstLine = `- ${lines.shift()}`;
-      // Add four spaces to the front of subsequent list items lines,
-      // or leave the line entirely blank.
-      const nextLines = lines.map((s) => {
-        if (s.trim().length === 0) return '';
-
-        return `    ${s}`;
-      });
-
-      return `${firstLine}\n${nextLines.join('\n')}`;
-    },
-    'ul'(el, text) {
-      return text;
-    },
-    'ol'(el, text) {
-      // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
-      return text.replace(/^- /mg, '1. ');
-    },
-    'h1'(el, text) {
-      return `# ${text.trim()}`;
-    },
-    'h2'(el, text) {
-      return `## ${text.trim()}`;
-    },
-    'h3'(el, text) {
-      return `### ${text.trim()}`;
-    },
-    'h4'(el, text) {
-      return `#### ${text.trim()}`;
-    },
-    'h5'(el, text) {
-      return `##### ${text.trim()}`;
-    },
-    'h6'(el, text) {
-      return `###### ${text.trim()}`;
-    },
-    'strong'(el, text) {
-      return `**${text}**`;
-    },
-    'em'(el, text) {
-      return `_${text}_`;
-    },
-    'del'(el, text) {
-      return `~~${text}~~`;
-    },
-    'sup'(el, text) {
-      return `^${text}`;
-    },
-    'hr'(el) {
-      return '-----';
-    },
-    'table'(el) {
-      const theadEl = el.querySelector('thead');
-      const tbodyEl = el.querySelector('tbody');
-      if (!theadEl || !tbodyEl) return false;
-
-      const theadText = CopyAsGFM.nodeToGFM(theadEl);
-      const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
-
-      return [theadText, tbodyText].join('\n');
-    },
-    'thead'(el, text) {
-      const cells = _.map(el.querySelectorAll('th'), (cell) => {
-        let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
-
-        let before = '';
-        let after = '';
-        switch (cell.style.textAlign) {
-          case 'center':
-            before = ':';
-            after = ':';
-            chars -= 2;
-            break;
-          case 'right':
-            after = ':';
-            chars -= 1;
-            break;
-          default:
-            break;
-        }
-
-        chars = Math.max(chars, 3);
-
-        const middle = Array(chars + 1).join('-');
-
-        return before + middle + after;
-      });
-
-      const separatorRow = `|${cells.join('|')}|`;
-
-      return [text, separatorRow].join('\n');
-    },
-    'tr'(el) {
-      const cellEls = el.querySelectorAll('td, th');
-      if (cellEls.length === 0) return false;
-
-      const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
-      return `| ${cells.join(' | ')} |`;
-    },
-  },
-};
-
-export class CopyAsGFM {
-  constructor() {
-    // iOS currently does not support clipboardData.setData(). This bug should
-    // be fixed in iOS 12, but for now we'll disable this for all iOS browsers
-    // ref: https://trac.webkit.org/changeset/222228/webkit
-    const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
-    const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
-    if (isIOS) return;
-
-    $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
-    $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
-    $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
-  }
-
-  static copyAsGFM(e, transformer) {
-    const clipboardData = e.originalEvent.clipboardData;
-    if (!clipboardData) return;
-
-    const documentFragment = getSelectedFragment();
-    if (!documentFragment) return;
-
-    const el = transformer(documentFragment.cloneNode(true), e.currentTarget);
-    if (!el) return;
-
-    e.preventDefault();
-    e.stopPropagation();
-
-    clipboardData.setData('text/plain', el.textContent);
-    clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
-  }
-
-  static pasteGFM(e) {
-    const clipboardData = e.originalEvent.clipboardData;
-    if (!clipboardData) return;
-
-    const text = clipboardData.getData('text/plain');
-    const gfm = clipboardData.getData('text/x-gfm');
-    if (!gfm) return;
-
-    e.preventDefault();
-
-    window.gl.utils.insertText(e.target, (textBefore, textAfter) => {
-      // If the text before the cursor contains an odd number of backticks,
-      // we are either inside an inline code span that starts with 1 backtick
-      // or a code block that starts with 3 backticks.
-      // This logic still holds when there are one or more _closed_ code spans
-      // or blocks that will have 2 or 6 backticks.
-      // This will break down when the actual code block contains an uneven
-      // number of backticks, but this is a rare edge case.
-      const backtickMatch = textBefore.match(/`/g);
-      const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
-
-      if (insideCodeBlock) {
-        return text;
-      }
-
-      return gfm;
-    });
-  }
-
-  static transformGFMSelection(documentFragment) {
-    const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
-    switch (gfmElements.length) {
-      case 0: {
-        return documentFragment;
-      }
-      case 1: {
-        return gfmElements[0];
-      }
-      default: {
-        const allGfmElement = document.createElement('div');
-
-        for (let i = 0; i < gfmElements.length; i += 1) {
-          const gfmElement = gfmElements[i];
-          allGfmElement.appendChild(gfmElement);
-          allGfmElement.appendChild(document.createTextNode('\n\n'));
-        }
-
-        return allGfmElement;
-      }
-    }
-  }
-
-  static transformCodeSelection(documentFragment, target) {
-    let lineSelector = '.line';
-
-    if (target) {
-      const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
-      if (lineClass) {
-        lineSelector = `.line_content.${lineClass} ${lineSelector}`;
-      }
-    }
-
-    const lineElements = documentFragment.querySelectorAll(lineSelector);
-
-    let codeElement;
-    if (lineElements.length > 1) {
-      codeElement = document.createElement('pre');
-      codeElement.className = 'code highlight';
-
-      const lang = lineElements[0].getAttribute('lang');
-      if (lang) {
-        codeElement.setAttribute('lang', lang);
-      }
-    } else {
-      codeElement = document.createElement('code');
-    }
-
-    if (lineElements.length > 0) {
-      for (let i = 0; i < lineElements.length; i += 1) {
-        const lineElement = lineElements[i];
-        codeElement.appendChild(lineElement);
-        codeElement.appendChild(document.createTextNode('\n'));
-      }
-    } else {
-      codeElement.appendChild(documentFragment);
-    }
-
-    return codeElement;
-  }
-
-  static nodeToGFM(node, respectWhitespaceParam = false) {
-    if (node.nodeType === Node.COMMENT_NODE) {
-      return '';
-    }
-
-    if (node.nodeType === Node.TEXT_NODE) {
-      return node.textContent;
-    }
-
-    const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
-
-    const text = this.innerGFM(node, respectWhitespace);
-
-    if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
-      return text;
-    }
-
-    for (const filter in gfmRules) {
-      const rules = gfmRules[filter];
-
-      for (const selector in rules) {
-        const func = rules[selector];
-
-        if (!nodeMatchesSelector(node, selector)) continue;
-
-        let result;
-        if (func.length === 2) {
-          // if `func` takes 2 arguments, it depends on text.
-          // if there is no text, we don't need to generate GFM for this node.
-          if (text.length === 0) continue;
-
-          result = func(node, text);
-        } else {
-          result = func(node);
-        }
-
-        if (result === false) continue;
-
-        return result;
-      }
-    }
-
-    return text;
-  }
-
-  static innerGFM(parentNode, respectWhitespace = false) {
-    const nodes = parentNode.childNodes;
-
-    const clonedParentNode = parentNode.cloneNode(true);
-    const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
-
-    for (let i = 0; i < nodes.length; i += 1) {
-      const node = nodes[i];
-      const clonedNode = clonedNodes[i];
-
-      const text = this.nodeToGFM(node, respectWhitespace);
-
-      // `clonedNode.replaceWith(text)` is not yet widely supported
-      clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
-    }
-
-    let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
-
-    if (!respectWhitespace) {
-      nodeText = nodeText.trim();
-    }
-
-    return nodeText;
-  }
-}
-
-// Export CopyAsGFM as a global for rspec to access
-// see /spec/features/copy_as_gfm_spec.rb
-if (process.env.NODE_ENV !== 'production') {
-  window.CopyAsGFM = CopyAsGFM;
-}
-
-export default function initCopyAsGFM() {
-  return new CopyAsGFM();
-}
diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js
index b669b63d23c49ce195b8fe3b8a4eb38d1fdc3a0e..e2a73a1797c7cc96d2cf147e7caf944c6588cfed 100644
--- a/app/assets/javascripts/behaviors/copy_to_clipboard.js
+++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Clipboard from 'clipboard';
 
 function showTooltip(target, title) {
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index 7c9dbcc8d6e43f02401671b18d97659616ac7ac4..1d63f5baeee14af6ce3a04731a718518a0ecdc51 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 
 $(() => {
   $('body').on('click', '.js-details-target', function target() {
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 8d021de799854c7ebeb7b91cea8a6a09293d15bc..84fef4d8b4f7be596ec9f498451ab3f102b205d8 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -1,6 +1,7 @@
 import './autosize';
 import './bind_in_out';
-import initCopyAsGFM from './copy_as_gfm';
+import './markdown/render_gfm';
+import initCopyAsGFM from './markdown/copy_as_gfm';
 import initCopyToClipboard from './copy_to_clipboard';
 import './details_behavior';
 import installGlEmojiElement from './gl_emoji';
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
new file mode 100644
index 0000000000000000000000000000000000000000..75cf90de0b5bd11ac76437315dab1fe88d206b5a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -0,0 +1,501 @@
+/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
+
+import $ from 'jquery';
+import _ from 'underscore';
+import { insertText, getSelectedFragment, nodeMatchesSelector } from '~/lib/utils/common_utils';
+import { placeholderImage } from '~/lazy_loader';
+
+const gfmRules = {
+  // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
+  // GitLab Flavored Markdown (GFM) to HTML.
+  // These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
+  // Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
+  // from GFM should have a handler here, in reverse order.
+  // The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
+  InlineDiffFilter: {
+    'span.idiff.addition'(el, text) {
+      return `{+${text}+}`;
+    },
+    'span.idiff.deletion'(el, text) {
+      return `{-${text}-}`;
+    },
+  },
+  TaskListFilter: {
+    'input[type=checkbox].task-list-item-checkbox'(el) {
+      return `[${el.checked ? 'x' : ' '}]`;
+    },
+  },
+  ReferenceFilter: {
+    '.tooltip'(el) {
+      return '';
+    },
+    'a.gfm:not([data-link=true])'(el, text) {
+      return el.dataset.original || text;
+    },
+  },
+  AutolinkFilter: {
+    'a'(el, text) {
+      // Fallback on the regular MarkdownFilter's `a` handler.
+      if (text !== el.getAttribute('href')) return false;
+
+      return text;
+    },
+  },
+  TableOfContentsFilter: {
+    'ul.section-nav'(el) {
+      return '[[_TOC_]]';
+    },
+  },
+  EmojiFilter: {
+    'img.emoji'(el) {
+      return el.getAttribute('alt');
+    },
+    'gl-emoji'(el) {
+      return `:${el.getAttribute('data-name')}:`;
+    },
+  },
+  ImageLinkFilter: {
+    'a.no-attachment-icon'(el, text) {
+      return text;
+    },
+  },
+  ImageLazyLoadFilter: {
+    'img'(el, text) {
+      return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+    },
+  },
+  VideoLinkFilter: {
+    '.video-container'(el) {
+      const videoEl = el.querySelector('video');
+      if (!videoEl) return false;
+
+      return CopyAsGFM.nodeToGFM(videoEl);
+    },
+    'video'(el) {
+      return `![${el.dataset.title}](${el.getAttribute('src')})`;
+    },
+  },
+  MermaidFilter: {
+    'svg.mermaid'(el, text) {
+      const sourceEl = el.querySelector('text.source');
+      if (!sourceEl) return false;
+
+      return `\`\`\`mermaid\n${CopyAsGFM.nodeToGFM(sourceEl)}\n\`\`\``;
+    },
+    'svg.mermaid style, svg.mermaid g'(el, text) {
+      // We don't want to include the content of these elements in the copied text.
+      return '';
+    },
+  },
+  MathFilter: {
+    'pre.code.math[data-math-style=display]'(el, text) {
+      return `\`\`\`math\n${text.trim()}\n\`\`\``;
+    },
+    'code.code.math[data-math-style=inline]'(el, text) {
+      return `$\`${text}\`$`;
+    },
+    'span.katex-display span.katex-mathml'(el) {
+      const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
+      if (!mathAnnotation) return false;
+
+      return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
+    },
+    'span.katex-mathml'(el) {
+      const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
+      if (!mathAnnotation) return false;
+
+      return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
+    },
+    'span.katex-html'(el) {
+      // We don't want to include the content of this element in the copied text.
+      return '';
+    },
+    'annotation[encoding="application/x-tex"]'(el, text) {
+      return text.trim();
+    },
+  },
+  SanitizationFilter: {
+    'a[name]:not([href]):empty'(el) {
+      return el.outerHTML;
+    },
+    'dl'(el, text) {
+      let lines = text.trim().split('\n');
+      // Add two spaces to the front of subsequent list items lines,
+      // or leave the line entirely blank.
+      lines = lines.map((l) => {
+        const line = l.trim();
+        if (line.length === 0) return '';
+
+        return `  ${line}`;
+      });
+
+      return `<dl>\n${lines.join('\n')}\n</dl>`;
+    },
+    'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr, summary, details'(el, text) {
+      const tag = el.nodeName.toLowerCase();
+      return `<${tag}>${text}</${tag}>`;
+    },
+  },
+  SyntaxHighlightFilter: {
+    'pre.code.highlight'(el, t) {
+      const text = t.trimRight();
+
+      let lang = el.getAttribute('lang');
+      if (!lang || lang === 'plaintext') {
+        lang = '';
+      }
+
+      // Prefixes lines with 4 spaces if the code contains triple backticks
+      if (lang === '' && text.match(/^```/gm)) {
+        return text.split('\n').map((l) => {
+          const line = l.trim();
+          if (line.length === 0) return '';
+
+          return `    ${line}`;
+        }).join('\n');
+      }
+
+      return `\`\`\`${lang}\n${text}\n\`\`\``;
+    },
+    'pre > code'(el, text) {
+       // Don't wrap code blocks in ``
+      return text;
+    },
+  },
+  MarkdownFilter: {
+    'br'(el) {
+      // Two spaces at the end of a line are turned into a BR
+      return '  ';
+    },
+    'code'(el, text) {
+      let backtickCount = 1;
+      const backtickMatch = text.match(/`+/);
+      if (backtickMatch) {
+        backtickCount = backtickMatch[0].length + 1;
+      }
+
+      const backticks = Array(backtickCount + 1).join('`');
+      const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
+
+      return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
+    },
+    'blockquote'(el, text) {
+      return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
+    },
+    'img'(el) {
+      const imageSrc = el.src;
+      const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
+      return `![${el.getAttribute('alt')}](${imageUrl})`;
+    },
+    'a.anchor'(el, text) {
+      // Don't render a Markdown link for the anchor link inside a heading
+      return text;
+    },
+    'a'(el, text) {
+      return `[${text}](${el.getAttribute('href')})`;
+    },
+    'li'(el, text) {
+      const lines = text.trim().split('\n');
+      const firstLine = `- ${lines.shift()}`;
+      // Add four spaces to the front of subsequent list items lines,
+      // or leave the line entirely blank.
+      const nextLines = lines.map((s) => {
+        if (s.trim().length === 0) return '';
+
+        return `    ${s}`;
+      });
+
+      return `${firstLine}\n${nextLines.join('\n')}`;
+    },
+    'ul'(el, text) {
+      return text;
+    },
+    'ol'(el, text) {
+      // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
+      return text.replace(/^- /mg, '1. ');
+    },
+    'h1'(el, text) {
+      return `# ${text.trim()}`;
+    },
+    'h2'(el, text) {
+      return `## ${text.trim()}`;
+    },
+    'h3'(el, text) {
+      return `### ${text.trim()}`;
+    },
+    'h4'(el, text) {
+      return `#### ${text.trim()}`;
+    },
+    'h5'(el, text) {
+      return `##### ${text.trim()}`;
+    },
+    'h6'(el, text) {
+      return `###### ${text.trim()}`;
+    },
+    'strong'(el, text) {
+      return `**${text}**`;
+    },
+    'em'(el, text) {
+      return `_${text}_`;
+    },
+    'del'(el, text) {
+      return `~~${text}~~`;
+    },
+    'sup'(el, text) {
+      return `^${text}`;
+    },
+    'hr'(el) {
+      return '-----';
+    },
+    'table'(el) {
+      const theadEl = el.querySelector('thead');
+      const tbodyEl = el.querySelector('tbody');
+      if (!theadEl || !tbodyEl) return false;
+
+      const theadText = CopyAsGFM.nodeToGFM(theadEl);
+      const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
+
+      return [theadText, tbodyText].join('\n');
+    },
+    'thead'(el, text) {
+      const cells = _.map(el.querySelectorAll('th'), (cell) => {
+        let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
+
+        let before = '';
+        let after = '';
+        switch (cell.style.textAlign) {
+          case 'center':
+            before = ':';
+            after = ':';
+            chars -= 2;
+            break;
+          case 'right':
+            after = ':';
+            chars -= 1;
+            break;
+          default:
+            break;
+        }
+
+        chars = Math.max(chars, 3);
+
+        const middle = Array(chars + 1).join('-');
+
+        return before + middle + after;
+      });
+
+      const separatorRow = `|${cells.join('|')}|`;
+
+      return [text, separatorRow].join('\n');
+    },
+    'tr'(el) {
+      const cellEls = el.querySelectorAll('td, th');
+      if (cellEls.length === 0) return false;
+
+      const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
+      return `| ${cells.join(' | ')} |`;
+    },
+  },
+};
+
+export class CopyAsGFM {
+  constructor() {
+    // iOS currently does not support clipboardData.setData(). This bug should
+    // be fixed in iOS 12, but for now we'll disable this for all iOS browsers
+    // ref: https://trac.webkit.org/changeset/222228/webkit
+    const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
+    const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
+    if (isIOS) return;
+
+    $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
+    $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
+    $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
+  }
+
+  static copyAsGFM(e, transformer) {
+    const clipboardData = e.originalEvent.clipboardData;
+    if (!clipboardData) return;
+
+    const documentFragment = getSelectedFragment();
+    if (!documentFragment) return;
+
+    const el = transformer(documentFragment.cloneNode(true), e.currentTarget);
+    if (!el) return;
+
+    e.preventDefault();
+    e.stopPropagation();
+
+    clipboardData.setData('text/plain', el.textContent);
+    clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
+  }
+
+  static pasteGFM(e) {
+    const clipboardData = e.originalEvent.clipboardData;
+    if (!clipboardData) return;
+
+    const text = clipboardData.getData('text/plain');
+    const gfm = clipboardData.getData('text/x-gfm');
+    if (!gfm) return;
+
+    e.preventDefault();
+
+    window.gl.utils.insertText(e.target, (textBefore, textAfter) => {
+      // If the text before the cursor contains an odd number of backticks,
+      // we are either inside an inline code span that starts with 1 backtick
+      // or a code block that starts with 3 backticks.
+      // This logic still holds when there are one or more _closed_ code spans
+      // or blocks that will have 2 or 6 backticks.
+      // This will break down when the actual code block contains an uneven
+      // number of backticks, but this is a rare edge case.
+      const backtickMatch = textBefore.match(/`/g);
+      const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
+
+      if (insideCodeBlock) {
+        return text;
+      }
+
+      return gfm;
+    });
+  }
+
+  static transformGFMSelection(documentFragment) {
+    const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
+    switch (gfmElements.length) {
+      case 0: {
+        return documentFragment;
+      }
+      case 1: {
+        return gfmElements[0];
+      }
+      default: {
+        const allGfmElement = document.createElement('div');
+
+        for (let i = 0; i < gfmElements.length; i += 1) {
+          const gfmElement = gfmElements[i];
+          allGfmElement.appendChild(gfmElement);
+          allGfmElement.appendChild(document.createTextNode('\n\n'));
+        }
+
+        return allGfmElement;
+      }
+    }
+  }
+
+  static transformCodeSelection(documentFragment, target) {
+    let lineSelector = '.line';
+
+    if (target) {
+      const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
+      if (lineClass) {
+        lineSelector = `.line_content.${lineClass} ${lineSelector}`;
+      }
+    }
+
+    const lineElements = documentFragment.querySelectorAll(lineSelector);
+
+    let codeElement;
+    if (lineElements.length > 1) {
+      codeElement = document.createElement('pre');
+      codeElement.className = 'code highlight';
+
+      const lang = lineElements[0].getAttribute('lang');
+      if (lang) {
+        codeElement.setAttribute('lang', lang);
+      }
+    } else {
+      codeElement = document.createElement('code');
+    }
+
+    if (lineElements.length > 0) {
+      for (let i = 0; i < lineElements.length; i += 1) {
+        const lineElement = lineElements[i];
+        codeElement.appendChild(lineElement);
+        codeElement.appendChild(document.createTextNode('\n'));
+      }
+    } else {
+      codeElement.appendChild(documentFragment);
+    }
+
+    return codeElement;
+  }
+
+  static nodeToGFM(node, respectWhitespaceParam = false) {
+    if (node.nodeType === Node.COMMENT_NODE) {
+      return '';
+    }
+
+    if (node.nodeType === Node.TEXT_NODE) {
+      return node.textContent;
+    }
+
+    const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
+
+    const text = this.innerGFM(node, respectWhitespace);
+
+    if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+      return text;
+    }
+
+    for (const filter in gfmRules) {
+      const rules = gfmRules[filter];
+
+      for (const selector in rules) {
+        const func = rules[selector];
+
+        if (!nodeMatchesSelector(node, selector)) continue;
+
+        let result;
+        if (func.length === 2) {
+          // if `func` takes 2 arguments, it depends on text.
+          // if there is no text, we don't need to generate GFM for this node.
+          if (text.length === 0) continue;
+
+          result = func(node, text);
+        } else {
+          result = func(node);
+        }
+
+        if (result === false) continue;
+
+        return result;
+      }
+    }
+
+    return text;
+  }
+
+  static innerGFM(parentNode, respectWhitespace = false) {
+    const nodes = parentNode.childNodes;
+
+    const clonedParentNode = parentNode.cloneNode(true);
+    const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
+
+    for (let i = 0; i < nodes.length; i += 1) {
+      const node = nodes[i];
+      const clonedNode = clonedNodes[i];
+
+      const text = this.nodeToGFM(node, respectWhitespace);
+
+      // `clonedNode.replaceWith(text)` is not yet widely supported
+      clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
+    }
+
+    let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
+
+    if (!respectWhitespace) {
+      nodeText = nodeText.trim();
+    }
+
+    return nodeText;
+  }
+}
+
+// Export CopyAsGFM as a global for rspec to access
+// see /spec/features/copy_as_gfm_spec.rb
+if (process.env.NODE_ENV !== 'production') {
+  window.CopyAsGFM = CopyAsGFM;
+}
+
+export default function initCopyAsGFM() {
+  return new CopyAsGFM();
+}
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
new file mode 100644
index 0000000000000000000000000000000000000000..dbff2bd4b10c2107bf9ef9d3120b80d38eb9048a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -0,0 +1,17 @@
+import $ from 'jquery';
+import syntaxHighlight from '~/syntax_highlight';
+import renderMath from './render_math';
+import renderMermaid from './render_mermaid';
+
+// Render Gitlab flavoured Markdown
+//
+// Delegates to syntax highlight and render math & mermaid diagrams.
+//
+$.fn.renderGFM = function renderGFM() {
+  syntaxHighlight(this.find('.js-syntax-highlight'));
+  renderMath(this.find('.js-render-math'));
+  renderMermaid(this.find('.js-render-mermaid'));
+  return this;
+};
+
+$(() => $('body').renderGFM());
diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js
new file mode 100644
index 0000000000000000000000000000000000000000..eb4e59d12b148d99abd45c81d71e4ae5f41044ff
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/render_math.js
@@ -0,0 +1,38 @@
+import $ from 'jquery';
+import { __ } from '~/locale';
+import flash from '~/flash';
+
+// Renders math using KaTeX in any element with the
+// `js-render-math` class
+//
+// ### Example Markup
+//
+//   <code class="js-render-math"></div>
+//
+
+// Loop over all math elements and render math
+function renderWithKaTeX(elements, katex) {
+  elements.each(function katexElementsLoop() {
+    const mathNode = $('<span></span>');
+    const $this = $(this);
+
+    const display = $this.attr('data-math-style') === 'display';
+    try {
+      katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
+      mathNode.insertAfter($this);
+      $this.remove();
+    } catch (err) {
+      throw err;
+    }
+  });
+}
+
+export default function renderMath($els) {
+  if (!$els.length) return;
+  Promise.all([
+    import(/* webpackChunkName: 'katex' */ 'katex'),
+    import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
+  ]).then(([katex]) => {
+    renderWithKaTeX($els, katex);
+  }).catch(() => flash(__('An error occurred while rendering KaTeX')));
+}
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
new file mode 100644
index 0000000000000000000000000000000000000000..56b1896e9f15328b54940af5207271f108377778
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -0,0 +1,57 @@
+import flash from '~/flash';
+
+// Renders diagrams and flowcharts from text using Mermaid in any element with the
+// `js-render-mermaid` class.
+//
+// Example markup:
+//
+// <pre class="js-render-mermaid">
+//  graph TD;
+//    A-- > B;
+//    A-- > C;
+//    B-- > D;
+//    C-- > D;
+// </pre>
+//
+
+export default function renderMermaid($els) {
+  if (!$els.length) return;
+
+  import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
+    mermaid.initialize({
+      // mermaid core options
+      mermaid: {
+        startOnLoad: false,
+      },
+      // mermaidAPI options
+      theme: 'neutral',
+    });
+
+    $els.each((i, el) => {
+      const source = el.textContent;
+
+      // Remove any extra spans added by the backend syntax highlighting.
+      Object.assign(el, { textContent: source });
+
+      mermaid.init(undefined, el, (id) => {
+        const svg = document.getElementById(id);
+
+        svg.classList.add('mermaid');
+
+        // pre > code > svg
+        svg.closest('pre').replaceWith(svg);
+
+        // We need to add the original source into the DOM to allow Copy-as-GFM
+        // to access it.
+        const sourceEl = document.createElement('text');
+        sourceEl.classList.add('source');
+        sourceEl.setAttribute('display', 'none');
+        sourceEl.textContent = source;
+
+        svg.appendChild(sourceEl);
+      });
+    });
+  }).catch((err) => {
+    flash(`Can't load mermaid module: ${err}`);
+  });
+}
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 312edc0cd692706288acfdfef63b3d9757b3d085..3ec932bdb7341d4b233ced20ee108e5cd66da98c 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import '../commons/bootstrap';
 import { isInIssuePage } from '../lib/utils/common_utils';
 
@@ -72,5 +73,5 @@ $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-q
     title,
     trigger: 'manual',
   });
-  $this.tooltip('show').one('blur', () => $this.tooltip('hide'));
+  $this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
 });
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index e10cb2e3dc46e90f715377034501d1986ae67c16..ffff4ddb71a3a3b4cd052a04b6daaa98c7e160e3 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import '../commons/bootstrap';
 
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 417ac31fc86e39bd5e278e5976256051c02541f4..4446be0e52f7d09b20c05c6728767ea1d25a741b 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,3 +1,6 @@
+import $ from 'jquery';
+import { getLocationHash } from '../lib/utils/url_utility';
+
 // Toggle button. Show/hide content inside parent container.
 // Button does not change visibility. If button has icon - it changes chevron style.
 //
@@ -5,14 +8,13 @@
 //   %button.js-toggle-button
 //   %div.js-toggle-content
 //
-import { getLocationHash } from '../lib/utils/url_utility';
 
 $(() => {
   function toggleContainer(container, toggleState) {
     const $container = $(container);
 
     $container
-      .find('.js-toggle-button .fa')
+      .find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down')
       .toggleClass('fa-chevron-up', toggleState)
       .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
 
@@ -22,7 +24,7 @@ $(() => {
   }
 
   $('body').on('click', '.js-toggle-button', function toggleButton(e) {
-    e.target.classList.toggle('open');
+    e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'open');
     toggleContainer($(this).closest('.js-toggle-container'));
 
     const targetTag = e.currentTarget.tagName.toLowerCase();
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 83cac896f8616b8b5f1aee1df5dadf8b9838ee37..ff1739b16793691e614774f0223f398ede3743c6 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
+
+import $ from 'jquery';
 import Dropzone from 'dropzone';
 import { visitUrl } from '../lib/utils/url_utility';
 import { HIDDEN_CLASS } from '../lib/utils/constants';
diff --git a/app/assets/javascripts/blob/blob_fork_suggestion.js b/app/assets/javascripts/blob/blob_fork_suggestion.js
index 47c431fb809cbe3bd0bd594622624a22eabba1b9..476b9405a9ede0e5d3a93f72e7a998bc96f0c855 100644
--- a/app/assets/javascripts/blob/blob_fork_suggestion.js
+++ b/app/assets/javascripts/blob/blob_fork_suggestion.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 const defaults = {
   // Buttons that will show the `suggestionSections`
   // has `data-fork-path`, and `data-action`
diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
index 37074301b516b05ad390fbf313e487a37233464a..ff1cbcad145cce45f8bdc5754d19004fd12e4832 100644
--- a/app/assets/javascripts/blob/file_template_mediator.js
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -1,4 +1,6 @@
 /* eslint-disable class-methods-use-this */
+
+import $ from 'jquery';
 import Flash from '../flash';
 import FileTemplateTypeSelector from './template_selectors/type_selector';
 import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
@@ -92,7 +94,7 @@ export default class FileTemplateMediator {
       const hash = urlPieces[1];
       if (hash === 'preview') {
         this.hideTemplateSelectorMenu();
-      } else if (hash === 'editor') {
+      } else if (hash === 'editor' && !this.typeSelector.isHidden()) {
         this.showTemplateSelectorMenu();
       }
     });
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
index 5ae30990aeaf54f5613519aafe2182d9d8ddf0e6..02228434a29339eb4e3fca66c8eb3591265fd153 100644
--- a/app/assets/javascripts/blob/file_template_selector.js
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default class FileTemplateSelector {
   constructor(mediator) {
     this.mediator = mediator;
@@ -30,6 +32,10 @@ export default class FileTemplateSelector {
     }
   }
 
+  isHidden() {
+    return this.$wrapper.hasClass('hidden');
+  }
+
   getToggleText() {
     return this.$dropdownToggleText.text();
   }
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index 888883163c5b27a924c888bd81ad70d9902c6768..9dfdb06007d79965295a577a9812705ee4d8a505 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -1,5 +1,7 @@
 /* eslint-disable class-methods-use-this, no-unused-vars */
 
+import $ from 'jquery';
+
 export default class TemplateSelector {
   constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) {
     this.pattern = pattern;
@@ -76,7 +78,7 @@ export default class TemplateSelector {
 
     if (!skipFocus) this.editor.focus();
 
-    if (this.editor instanceof jQuery) {
+    if (this.editor instanceof $) {
       this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
     }
   }
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index 92ea91c45a83d03ef752332befdd13d02618817e..137e1f5a09953a5855871a4317f996ee1a1c1810 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Flash from '../../flash';
 import { handleLocationHash } from '../../lib/utils/common_utils';
 import axios from '../../lib/utils/axios_utils';
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 931ed042dfd4d6ac6bc113f1dfd0446ab24015bb..4424232f64220439b185a6508448ca9569bc92f1 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -1,5 +1,7 @@
 /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
 /* global EditBlob */
+
+import $ from 'jquery';
 import NewCommitForm from '../new_commit_form';
 import EditBlob from './edit_blob';
 import BlobFileDropzone from '../blob/blob_file_dropzone';
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index d4f6adaccbcfed9be19d2f8c9494b80e2cc4ebee..82a3d494b67b3055e60fa30885e306bbab6a103b 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -1,5 +1,6 @@
 /* global ace */
 
+import $ from 'jquery';
 import axios from '~/lib/utils/axios_utils';
 import createFlash from '~/flash';
 import { __ } from '~/locale';
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 9c4cc2338c80b8324bbf020f6c694851d742dd0e..bea818010a4efe7f1997ce1a2e0447670f2efc20 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -1,9 +1,11 @@
 /* eslint-disable comma-dangle, space-before-function-paren, one-var */
+
+import $ from 'jquery';
 import Sortable from 'vendor/Sortable';
 import Vue from 'vue';
 import AccessorUtilities from '../../lib/utils/accessor';
 import boardList from './board_list.vue';
-import boardBlankState from './board_blank_state';
+import BoardBlankState from './board_blank_state.vue';
 import './board_delete';
 
 const Store = gl.issueBoards.BoardsStore;
@@ -16,7 +18,7 @@ gl.issueBoards.Board = Vue.extend({
   components: {
     boardList,
     'board-delete': gl.issueBoards.BoardDelete,
-    boardBlankState,
+    BoardBlankState,
   },
   props: {
     list: Object,
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js
deleted file mode 100644
index 72db626d3c7326e796e665d3ba2d84f7b183476d..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/boards/components/board_blank_state.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/* global ListLabel */
-
-import _ from 'underscore';
-import Cookies from 'js-cookie';
-
-const Store = gl.issueBoards.BoardsStore;
-
-export default {
-  template: `
-    <div class="board-blank-state">
-      <p>
-        Add the following default lists to your Issue Board with one click:
-      </p>
-      <ul class="board-blank-state-list">
-        <li v-for="label in predefinedLabels">
-          <span
-            class="label-color"
-            :style="{ backgroundColor: label.color }">
-          </span>
-          {{ label.title }}
-        </li>
-      </ul>
-      <p>
-        Starting out with the default set of lists will get you right on the way to making the most of your board.
-      </p>
-      <button
-        class="btn btn-create btn-inverted btn-block"
-        type="button"
-        @click.stop="addDefaultLists">
-        Add default lists
-      </button>
-      <button
-        class="btn btn-default btn-block"
-        type="button"
-        @click.stop="clearBlankState">
-        Nevermind, I'll use my own
-      </button>
-    </div>
-  `,
-  data() {
-    return {
-      predefinedLabels: [
-        new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
-        new ListLabel({ title: 'Doing', color: '#5CB85C' }),
-      ],
-    };
-  },
-  methods: {
-    addDefaultLists() {
-      this.clearBlankState();
-
-      this.predefinedLabels.forEach((label, i) => {
-        Store.addList({
-          title: label.title,
-          position: i,
-          list_type: 'label',
-          label: {
-            title: label.title,
-            color: label.color,
-          },
-        });
-      });
-
-      Store.state.lists = _.sortBy(Store.state.lists, 'position');
-
-      // Save the labels
-      gl.boardService.generateDefaultLists()
-        .then(res => res.data)
-        .then((data) => {
-          data.forEach((listObj) => {
-            const list = Store.findList('title', listObj.title);
-
-            list.id = listObj.id;
-            list.label.id = listObj.label.id;
-            list.getIssues()
-              .catch(() => {
-                // TODO: handle request error
-              });
-          });
-        })
-        .catch(() => {
-          Store.removeList(undefined, 'label');
-          Cookies.remove('issue_board_welcome_hidden', {
-            path: '',
-          });
-          Store.addBlankState();
-        });
-    },
-    clearBlankState: Store.removeBlankState.bind(Store),
-  },
-};
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2049eeb9c308357f12697eb0f8c5622db1c095d7
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -0,0 +1,98 @@
+<script>
+/* global ListLabel */
+import _ from 'underscore';
+import Cookies from 'js-cookie';
+
+const Store = gl.issueBoards.BoardsStore;
+
+export default {
+  data() {
+    return {
+      predefinedLabels: [
+        new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
+        new ListLabel({ title: 'Doing', color: '#5CB85C' }),
+      ],
+    };
+  },
+  methods: {
+    addDefaultLists() {
+      this.clearBlankState();
+
+      this.predefinedLabels.forEach((label, i) => {
+        Store.addList({
+          title: label.title,
+          position: i,
+          list_type: 'label',
+          label: {
+            title: label.title,
+            color: label.color,
+          },
+        });
+      });
+
+      Store.state.lists = _.sortBy(Store.state.lists, 'position');
+
+      // Save the labels
+      gl.boardService.generateDefaultLists()
+        .then(res => res.data)
+        .then((data) => {
+          data.forEach((listObj) => {
+            const list = Store.findList('title', listObj.title);
+
+            list.id = listObj.id;
+            list.label.id = listObj.label.id;
+            list.getIssues()
+              .catch(() => {
+                // TODO: handle request error
+              });
+          });
+        })
+        .catch(() => {
+          Store.removeList(undefined, 'label');
+          Cookies.remove('issue_board_welcome_hidden', {
+            path: '',
+          });
+          Store.addBlankState();
+        });
+    },
+    clearBlankState: Store.removeBlankState.bind(Store),
+  },
+};
+
+</script>
+
+<template>
+  <div class="board-blank-state">
+    <p>
+      Add the following default lists to your Issue Board with one click:
+    </p>
+    <ul class="board-blank-state-list">
+      <li
+        v-for="(label, index) in predefinedLabels"
+        :key="index"
+      >
+        <span
+          class="label-color"
+          :style="{ backgroundColor: label.color }">
+        </span>
+        {{ label.title }}
+      </li>
+    </ul>
+    <p>
+      Starting out with the default set of lists will get you
+      right on the way to making the most of your board.
+    </p>
+    <button
+      class="btn btn-create btn-inverted btn-block"
+      type="button"
+      @click.stop="addDefaultLists">
+      Add default lists
+    </button>
+    <button
+      class="btn btn-default btn-block"
+      type="button"
+      @click.stop="clearBlankState">
+      Nevermind, I'll use my own
+    </button>
+  </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 23fec503586c905b34ce2fe176559febdaae35f9..84885ca9306df57bd79e320f0a9a111a2c856c54 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -1,4 +1,5 @@
 <script>
+/* eslint-disable vue/require-default-prop */
 import './issue_card_inner';
 import eventHub from '../eventhub';
 
@@ -34,6 +35,9 @@ export default {
       type: String,
       default: '',
     },
+    groupId: {
+      type: Number,
+    },
   },
   data() {
     return {
@@ -88,6 +92,7 @@ export default {
       :list="list"
       :issue="issue"
       :issue-link-base="issueLinkBase"
+      :group-id="groupId"
       :root-path="rootPath"
       :update-filters="true"
     />
diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js
index 8a1b177bba8c31110e40b004e6b6a452ce4b410e..7be98825fda149f51a135bbb36639aa09b622bbd 100644
--- a/app/assets/javascripts/boards/components/board_delete.js
+++ b/app/assets/javascripts/boards/components/board_delete.js
@@ -1,5 +1,6 @@
 /* eslint-disable comma-dangle, space-before-function-paren, no-alert */
 
+import $ from 'jquery';
 import Vue from 'vue';
 
 window.gl = window.gl || {};
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 6637904d87d8a6c78e0deaa5b3b07651fcaf2f51..0d03c1c419cab4971cd1c95366cccf38565cd0e0 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -15,6 +15,11 @@ export default {
     loadingIcon,
   },
   props: {
+    groupId: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
     disabled: {
       type: Boolean,
       required: true,
@@ -170,6 +175,7 @@ export default {
       <loading-icon />
     </div>
     <board-new-issue
+      :group-id="groupId"
       :list="list"
       v-if="list.type !== 'closed' && showIssueForm"/>
     <ul
@@ -185,6 +191,7 @@ export default {
         :list="list"
         :issue="issue"
         :issue-link-base="issueLinkBase"
+        :group-id="groupId"
         :root-path="rootPath"
         :disabled="disabled"
         :key="issue.id" />
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index efface7143d694cf5778efd12e0dcb2dae4b96a6..8d84c1735b851db2bb075192c12d6b4958898c55 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,12 +1,22 @@
 <script>
+import $ from 'jquery';
 import eventHub from '../eventhub';
+import ProjectSelect from './project_select.vue';
 import ListIssue from '../models/issue';
 
 const Store = gl.issueBoards.BoardsStore;
 
 export default {
   name: 'BoardNewIssue',
+  components: {
+    ProjectSelect,
+  },
   props: {
+    groupId: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
     list: {
       type: Object,
       required: true,
@@ -16,10 +26,20 @@ export default {
     return {
       title: '',
       error: false,
+      selectedProject: {},
     };
   },
+  computed: {
+    disabled() {
+      if (this.groupId) {
+        return this.title === '' || !this.selectedProject.name;
+      }
+      return this.title === '';
+    },
+  },
   mounted() {
     this.$refs.input.focus();
+    eventHub.$on('setSelectedProject', this.setSelectedProject);
   },
   methods: {
     submit(e) {
@@ -34,6 +54,7 @@ export default {
         labels,
         subscribed: true,
         assignees: [],
+        project_id: this.selectedProject.id,
       });
 
       eventHub.$emit(`scroll-board-list-${this.list.id}`);
@@ -62,52 +83,62 @@ export default {
       this.title = '';
       eventHub.$emit(`hide-issue-form-${this.list.id}`);
     },
+    setSelectedProject(selectedProject) {
+      this.selectedProject = selectedProject;
+    },
   },
 };
 </script>
 
 <template>
-  <div class="card board-new-issue-form">
-    <form @submit="submit($event)">
-      <div
-        class="flash-container"
-        v-if="error"
-      >
-        <div class="flash-alert">
-          An error occurred. Please try again.
-        </div>
-      </div>
-      <label
-        class="label-light"
-        :for="list.id + '-title'"
-      >
-        Title
-      </label>
-      <input
-        class="form-control"
-        type="text"
-        v-model="title"
-        ref="input"
-        autocomplete="off"
-        :id="list.id + '-title'"
-      />
-      <div class="clearfix prepend-top-10">
-        <button
-          class="btn btn-success pull-left"
-          type="submit"
-          :disabled="title === ''"
-          ref="submit-button"
+  <div class="board-new-issue-form">
+    <div class="card">
+      <form @submit="submit($event)">
+        <div
+          class="flash-container"
+          v-if="error"
         >
-          Submit issue
-        </button>
-        <button
-          class="btn btn-default pull-right"
-          type="button"
-          @click="cancel"
+          <div class="flash-alert">
+            An error occurred. Please try again.
+          </div>
+        </div>
+        <label
+          class="label-light"
+          :for="list.id + '-title'"
         >
-          Cancel
-        </button>
-      </div>
-    </form>
+          Title
+        </label>
+        <input
+          class="form-control"
+          type="text"
+          v-model="title"
+          ref="input"
+          autocomplete="off"
+          :id="list.id + '-title'"
+        />
+        <project-select
+          v-if="groupId"
+          :group-id="groupId"
+        />
+        <div class="clearfix prepend-top-10">
+          <button
+            class="btn btn-success pull-left"
+            type="submit"
+            :disabled="disabled"
+            ref="submit-button"
+          >
+            Submit issue
+          </button>
+          <button
+            class="btn btn-default pull-right"
+            type="button"
+            @click="cancel"
+          >
+            Cancel
+          </button>
+        </div>
+      </form>
+    </div>
   </div>
 </template>
+
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 9501e35b178f052855b86271b2f38ca2c1958e9c..c4ee4f6c855a63697ce6e7dd7f7907fe4806822e 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -1,11 +1,12 @@
 /* eslint-disable comma-dangle, space-before-function-paren, no-new */
 
+import $ from 'jquery';
 import Vue from 'vue';
 import Flash from '../../flash';
 import { __ } from '../../locale';
 import Sidebar from '../../right_sidebar';
 import eventHub from '../../sidebar/event_hub';
-import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
+import assigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
 import assignees from '../../sidebar/components/assignees/assignees.vue';
 import DueDateSelectors from '../../due_date_select';
 import './sidebar/remove_issue';
@@ -59,10 +60,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({
 
         this.issue = this.detail.issue;
         this.list = this.detail.list;
-
-        this.$nextTick(() => {
-          this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate;
-        });
       },
       deep: true
     },
@@ -90,7 +87,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
     saveAssignees () {
       this.loadingAssignees = true;
 
-      gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint)
+      gl.issueBoards.BoardsStore.detail.issue.update()
         .then(() => {
           this.loadingAssignees = false;
         })
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index bf474879024498902a644584fb5b72bfa56a3672..84fe9b1288a4cfb3eebcb5a90bc81694b6c954f6 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -1,5 +1,6 @@
+import $ from 'jquery';
 import Vue from 'vue';
-import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
 import eventHub from '../eventhub';
 
 const Store = gl.issueBoards.BoardsStore;
@@ -31,6 +32,10 @@ gl.issueBoards.IssueCardInner = Vue.extend({
       required: false,
       default: false,
     },
+    groupId: {
+      type: Number,
+      required: false,
+    },
   },
   data() {
     return {
@@ -40,7 +45,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
     };
   },
   components: {
-    userAvatarLink,
+    UserAvatarLink,
   },
   computed: {
     numberOverLimit() {
@@ -63,9 +68,6 @@ gl.issueBoards.IssueCardInner = Vue.extend({
 
       return this.issue.assignees.length > this.numberOverLimit;
     },
-    cardUrl() {
-      return `${this.issueLinkBase}/${this.issue.iid}`;
-    },
     issueId() {
       if (this.issue.iid) {
         return `#${this.issue.iid}`;
@@ -142,13 +144,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
           />
           <a
             class="js-no-trigger"
-            :href="cardUrl"
+            :href="issue.path"
             :title="issue.title">{{ issue.title }}</a>
           <span
             class="card-number"
             v-if="issueId"
           >
-            {{ issueId }}
+            {{ issue.referencePath }}
           </span>
         </h4>
         <div class="card-assignee">
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js
index e571b11a83d6c005d5b5c11cc70299cdd9f9c7cf..9e37f95cdd6af8c67bb91356b6c4c0060ae81a22 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.js
+++ b/app/assets/javascripts/boards/components/modal/empty_state.js
@@ -1,9 +1,9 @@
 import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
 
 gl.issueBoards.ModalEmptyState = Vue.extend({
-  mixins: [gl.issueBoards.ModalMixins],
+  mixins: [modalMixin],
   data() {
     return ModalStore.store;
   },
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 03cd7ef65cb3e7c258ef6d61162657863d6cc484..9735e0ddacc5d00e416b37e7183df5acccda44df 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -3,11 +3,11 @@ import Flash from '../../../flash';
 import { __ } from '../../../locale';
 import './lists_dropdown';
 import { pluralize } from '../../../lib/utils/text_utility';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
 
 gl.issueBoards.ModalFooter = Vue.extend({
-  mixins: [gl.issueBoards.ModalMixins],
+  mixins: [modalMixin],
   data() {
     return {
       modal: ModalStore.store,
diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js
index 31f59d295bf48a852fa5f858f700e00020bbb4fa..67c29ebca721dc0d18f5ff1973638c3dc8300cfa 100644
--- a/app/assets/javascripts/boards/components/modal/header.js
+++ b/app/assets/javascripts/boards/components/modal/header.js
@@ -1,11 +1,11 @@
 import Vue from 'vue';
 import modalFilters from './filters';
 import './tabs';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
 
 gl.issueBoards.ModalHeader = Vue.extend({
-  mixins: [gl.issueBoards.ModalMixins],
+  mixins: [modalMixin],
   props: {
     projectId: {
       type: Number,
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index d825ff38587719226414674325e3da242867601b..3083b3e4405777a126c9b52b027effa7a20eac44 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -7,8 +7,7 @@ import './header';
 import './list';
 import './footer';
 import './empty_state';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
 
 gl.issueBoards.IssuesModal = Vue.extend({
   props: {
diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js
index 7c62134b3a3e434a47e5fef6e1c966f2317bd3f6..6b04a6c7a6c192e6f560fb9b4a4ed67decdad39a 100644
--- a/app/assets/javascripts/boards/components/modal/list.js
+++ b/app/assets/javascripts/boards/components/modal/list.js
@@ -2,8 +2,7 @@
 
 import Vue from 'vue';
 import bp from '../../../breakpoints';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
 
 gl.issueBoards.ModalList = Vue.extend({
   props: {
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
index 4684ea76647f2b0bea42214ba00a2d4b72ac33b4..e644de2d4fc076537aca86e00abc7911518d155d 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
@@ -1,6 +1,5 @@
 import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
 
 gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
   data() {
diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js
index 3e5d08e3d755a6a3201dfc987135ead43367217c..b6465a88e5ecf50432d9ecf989c2f8c3aef31501 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.js
+++ b/app/assets/javascripts/boards/components/modal/tabs.js
@@ -1,9 +1,9 @@
 import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
 
 gl.issueBoards.ModalTabs = Vue.extend({
-  mixins: [gl.issueBoards.ModalMixins],
+  mixins: [modalMixin],
   data() {
     return ModalStore.store;
   },
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 362ef43e6f79cd2ac3b432636c09a1e8c0176791..71f49319c36be378d0e922732ca11c50eddd3e61 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -1,5 +1,6 @@
-/* eslint-disable func-names, no-new, space-before-function-paren, one-var,
-   promise/catch-or-return */
+/* eslint-disable func-names, no-new, space-before-function-paren, one-var, promise/catch-or-return, max-len */
+
+import $ from 'jquery';
 import axios from '~/lib/utils/axios_utils';
 import _ from 'underscore';
 import CreateLabelDropdown from '../../create_label';
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
new file mode 100644
index 0000000000000000000000000000000000000000..371774098b9bb9b69380b98360b98f6f69e55899
--- /dev/null
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -0,0 +1,129 @@
+<script>
+  /* global ListIssue */
+
+  import $ from 'jquery';
+  import _ from 'underscore';
+  import eventHub from '../eventhub';
+  import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+  import Api from '../../api';
+
+  export default {
+    name: 'BoardProjectSelect',
+    components: {
+      loadingIcon,
+    },
+    props: {
+      groupId: {
+        type: Number,
+        required: true,
+        default: 0,
+      },
+    },
+    data() {
+      return {
+        loading: true,
+        selectedProject: {},
+      };
+    },
+    computed: {
+      selectedProjectName() {
+        return this.selectedProject.name || 'Select a project';
+      },
+    },
+    mounted() {
+      $(this.$refs.projectsDropdown).glDropdown({
+        filterable: true,
+        filterRemote: true,
+        search: {
+          fields: ['name_with_namespace'],
+        },
+        clicked: ({ $el, e }) => {
+          e.preventDefault();
+          this.selectedProject = {
+            id: $el.data('project-id'),
+            name: $el.data('project-name'),
+          };
+          eventHub.$emit('setSelectedProject', this.selectedProject);
+        },
+        selectable: true,
+        data: (term, callback) => {
+          this.loading = true;
+          return Api.groupProjects(this.groupId, term, (projects) => {
+            this.loading = false;
+            callback(projects);
+          });
+        },
+        renderRow(project) {
+          return `
+            <li>
+              <a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}">
+                ${_.escape(project.name)}
+              </a>
+            </li>
+          `;
+        },
+        text: project => project.name,
+      });
+    },
+  };
+</script>
+
+<template>
+  <div>
+    <label class="label-light prepend-top-10">
+      Project
+    </label>
+    <div
+      ref="projectsDropdown"
+      class="dropdown"
+    >
+      <button
+        class="dropdown-menu-toggle wide"
+        type="button"
+        data-toggle="dropdown"
+        aria-expanded="false"
+      >
+        {{ selectedProjectName }}
+        <i
+          class="fa fa-chevron-down"
+          aria-hidden="true"
+        >
+        </i>
+      </button>
+      <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
+        <div class="dropdown-title">
+          <span>Projects</span>
+          <button
+            aria-label="Close"
+            type="button"
+            class="dropdown-title-button dropdown-menu-close"
+          >
+            <i
+              aria-hidden="true"
+              data-hidden="true"
+              class="fa fa-times dropdown-menu-close-icon"
+            >
+            </i>
+          </button>
+        </div>
+        <div class="dropdown-input">
+          <input
+            class="dropdown-input-field"
+            type="search"
+            placeholder="Search projects"
+          />
+          <i
+            aria-hidden="true"
+            data-hidden="true"
+            class="fa fa-search dropdown-input-search"
+          >
+          </i>
+        </div>
+        <div class="dropdown-content"></div>
+        <div class="dropdown-loading">
+          <loading-icon />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 0ae32bb4d0ab5bc8c875a0cb67c893703b00d7b3..0a0820ec5fdd268b478ce4282b44951b2f55192f 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -17,14 +17,10 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
       type: Object,
       required: true,
     },
-    issueUpdate: {
-      type: String,
-      required: true,
-    },
   },
   computed: {
     updateUrl() {
-      return this.issueUpdate;
+      return this.issue.path;
     },
   },
   methods: {
@@ -32,17 +28,21 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
       const issue = this.issue;
       const lists = issue.getLists();
       const listLabelIds = lists.map(list => list.label.id);
-      let labelIds = this.issue.labels
+
+      let labelIds = issue.labels
         .map(label => label.id)
         .filter(id => !listLabelIds.includes(id));
       if (labelIds.length === 0) {
         labelIds = [''];
       }
+
       const data = {
         issue: {
           label_ids: labelIds,
         },
       };
+
+      // Post the remove data
       Vue.http.patch(this.updateUrl, data).catch(() => {
         Flash(__('Failed to remove issue from board, please try again.'));
 
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 57a7cc4ca30d20558cd28d5f0eba37a2fbab9f61..70367c4f711b1086e37cf22bb55edfdffbe84778 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -6,6 +6,8 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
   constructor(store, updateUrl = false, cantEdit = []) {
     super({
       page: 'boards',
+      isGroupDecendent: true,
+      stateFiltersSelector: '.issues-state-filters',
     });
 
     this.store = store;
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 8e31f1865f067976f31c165d8a307586d3e43d06..a6f8681cfac7fad6d25151e4b569fe99e34a8ddf 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,23 +1,25 @@
 /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
 
+import $ from 'jquery';
 import _ from 'underscore';
 import Vue from 'vue';
 
 import Flash from '~/flash';
 import { __ } from '~/locale';
+import '~/vue_shared/models/label';
 
 import FilteredSearchBoards from './filtered_search_boards';
 import eventHub from './eventhub';
 import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
 import './models/issue';
-import './models/label';
 import './models/list';
 import './models/milestone';
+import './models/project';
 import './models/assignee';
 import './stores/boards_store';
-import './stores/modal_store';
+import ModalStore from './stores/modal_store';
 import BoardService from './services/board_service';
-import './mixins/modal_mixins';
+import modalMixin from './mixins/modal_mixins';
 import './mixins/sortable_default_options';
 import './filters/due_date_filters';
 import './components/board';
@@ -29,7 +31,6 @@ import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/fi
 export default () => {
   const $boardApp = document.getElementById('board-app');
   const Store = gl.issueBoards.BoardsStore;
-  const ModalStore = gl.issueBoards.ModalStore;
 
   window.gl = window.gl || {};
 
@@ -89,7 +90,7 @@ export default () => {
       sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
     },
     mounted () {
-      this.filterManager = new FilteredSearchBoards(Store.filter, true);
+      this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit);
       this.filterManager.setup();
 
       Store.disabled = this.disabled;
@@ -174,11 +175,12 @@ export default () => {
 
   gl.IssueBoardsModalAddBtn = new Vue({
     el: document.getElementById('js-add-issues-btn'),
-    mixins: [gl.issueBoards.ModalMixins],
+    mixins: [modalMixin],
     data() {
       return {
         modal: ModalStore.store,
         store: Store.state,
+        canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
       };
     },
     computed: {
@@ -232,6 +234,7 @@ export default () => {
           :class="{ 'disabled': disabled }"
           :title="tooltipTitle"
           :aria-disabled="disabled"
+          v-if="canAdminList"
           @click="openModal">
           Add issues
         </button>
diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js b/app/assets/javascripts/boards/mixins/modal_mixins.js
index 2b0a1aaa89ffb31c6a7165e079091b78700ce666..6c97e1629bf418f5464d1f1c0ede1622b14d3b5e 100644
--- a/app/assets/javascripts/boards/mixins/modal_mixins.js
+++ b/app/assets/javascripts/boards/mixins/modal_mixins.js
@@ -1,6 +1,6 @@
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../stores/modal_store';
 
-gl.issueBoards.ModalMixins = {
+export default {
   methods: {
     toggleModal(toggle) {
       ModalStore.store.showAddIssuesModal = toggle;
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index 38a0eb12f920a40d9cda4ef773ac7dbc2ac80b13..ac316c31debca7220a69152941075d282c057c71 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -1,6 +1,9 @@
 /* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
 /* global DocumentTouch */
 
+import $ from 'jquery';
+import sortableConfig from '../../sortable/sortable_config';
+
 window.gl = window.gl || {};
 window.gl.issueBoards = window.gl.issueBoards || {};
 
@@ -18,19 +21,14 @@ gl.issueBoards.onEnd = () => {
 gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
 
 gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
-  const defaultSortOptions = {
-    animation: 200,
-    forceFallback: true,
-    fallbackClass: 'is-dragging',
-    fallbackOnBody: true,
-    ghostClass: 'is-ghost',
+  const defaultSortOptions = Object.assign({}, sortableConfig, {
     filter: '.board-delete, .btn',
     delay: gl.issueBoards.touchEnabled ? 100 : 0,
     scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
     scrollSpeed: 20,
     onStart: gl.issueBoards.onStart,
-    onEnd: gl.issueBoards.onEnd
-  };
+    onEnd: gl.issueBoards.onEnd,
+  });
 
   Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
   return defaultSortOptions;
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 3bfb6d39ad515e238376b6cc15b3edc22119a79f..b381d48d625f3b9a1d9b06c4807446fe3471e501 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -4,6 +4,7 @@
 /* global ListAssignee */
 
 import Vue from 'vue';
+import IssueProject from './project';
 
 class ListIssue {
   constructor (obj, defaultAvatar) {
@@ -22,7 +23,15 @@ class ListIssue {
     };
     this.isLoading = {};
     this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
+    this.referencePath = obj.reference_path;
+    this.path = obj.real_path;
     this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
+    this.milestone_id = obj.milestone_id;
+    this.project_id = obj.project_id;
+
+    if (obj.project) {
+      this.project = new IssueProject(obj.project);
+    }
 
     if (obj.milestone) {
       this.milestone = new ListMilestone(obj.milestone);
@@ -91,7 +100,7 @@ class ListIssue {
     this.isLoading[key] = value;
   }
 
-  update (url) {
+  update () {
     const data = {
       issue: {
         milestone_id: this.milestone ? this.milestone.id : null,
@@ -105,7 +114,8 @@ class ListIssue {
       data.issue.label_ids = [''];
     }
 
-    return Vue.http.patch(url, data);
+    const projectPath = this.project ? this.project.path : '';
+    return Vue.http.patch(`${this.path}.json`, data);
   }
 }
 
diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/boards/models/label.js
deleted file mode 100644
index 98c1ec014c4cf834e2d7d88962a9f91b77ec825e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/boards/models/label.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* eslint-disable no-unused-vars, space-before-function-paren */
-
-class ListLabel {
-  constructor (obj) {
-    this.id = obj.id;
-    this.title = obj.title;
-    this.type = obj.type;
-    this.color = obj.color;
-    this.textColor = obj.text_color;
-    this.description = obj.description;
-    this.priority = (obj.priority !== null) ? obj.priority : Infinity;
-  }
-}
-
-window.ListLabel = ListLabel;
diff --git a/app/assets/javascripts/boards/models/project.js b/app/assets/javascripts/boards/models/project.js
new file mode 100644
index 0000000000000000000000000000000000000000..a3d5c7af7acee96301b8abb037df7824ed8a47d7
--- /dev/null
+++ b/app/assets/javascripts/boards/models/project.js
@@ -0,0 +1,6 @@
+export default class IssueProject {
+  constructor(obj) {
+    this.id = obj.id;
+    this.path = obj.path;
+  }
+}
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index d78d470197464aa71e05a413e1ba41c6f27d13ff..7c90597f77c2ff0a62a14119e6a96e9150fa6a8c 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -19,7 +19,7 @@ export default class BoardService {
   }
 
   static generateIssuePath(boardId, id) {
-    return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
+    return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
   }
 
   all() {
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 348cdeec73765c1ea4b9c7b290583fa4de1c8612..20e78edf2a275eb3edf5ed233c4b8eccba34b473 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -1,5 +1,7 @@
 /* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */
 /* global List */
+
+import $ from 'jquery';
 import _ from 'underscore';
 import Cookies from 'js-cookie';
 import { getUrlParamsArray } from '~/lib/utils/common_utils';
diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js
index 4fdc925c825de136764e9e4e7b78ae972333f971..a4220cd840d8fc28bedb7674a181a6c4a2304a23 100644
--- a/app/assets/javascripts/boards/stores/modal_store.js
+++ b/app/assets/javascripts/boards/stores/modal_store.js
@@ -1,6 +1,3 @@
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
 class ModalStore {
   constructor() {
     this.store = {
@@ -95,4 +92,4 @@ class ModalStore {
   }
 }
 
-gl.issueBoards.ModalStore = new ModalStore();
+export default new ModalStore();
diff --git a/app/assets/javascripts/branches/branches_delete_modal.js b/app/assets/javascripts/branches/branches_delete_modal.js
index cbc28374b807cb1bb18bbd9110c491eb36297377..f34496f84c61f38c393524e695b4a8e136717db6 100644
--- a/app/assets/javascripts/branches/branches_delete_modal.js
+++ b/app/assets/javascripts/branches/branches_delete_modal.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 const MODAL_SELECTOR = '#modal-delete-branch';
 
 class DeleteModal {
@@ -14,6 +16,7 @@ class DeleteModal {
   bindEvents() {
     this.$toggleBtns.on('click', this.setModalData.bind(this));
     this.$confirmInput.on('input', this.setDeleteDisabled.bind(this));
+    this.$deleteBtn.on('click', this.setDisableDeleteButton.bind(this));
   }
 
   setModalData(e) {
@@ -28,6 +31,16 @@ class DeleteModal {
     this.$deleteBtn.attr('disabled', e.currentTarget.value !== this.branchName);
   }
 
+  setDisableDeleteButton(e) {
+    if (this.$deleteBtn.is('[disabled]')) {
+      e.preventDefault();
+      e.stopPropagation();
+      return false;
+    }
+
+    return true;
+  }
+
   updateModal() {
     this.$branchName.text(this.branchName);
     this.$confirmInput.val('');
diff --git a/app/assets/javascripts/breadcrumb.js b/app/assets/javascripts/breadcrumb.js
index 10fbcfe96cfd62143e5eb42d61c6240968cd1b7d..1474d93dde6b65dc37de15f218e464be61672cbb 100644
--- a/app/assets/javascripts/breadcrumb.js
+++ b/app/assets/javascripts/breadcrumb.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export const addTooltipToEl = (el) => {
   const textEl = el.querySelector('.js-breadcrumb-item-text');
 
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index ace89398943943d5a27b84b67bd5eaedbddcfc2f..3fa16517388166f2db458bee8a65dee363ccd719 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, prefer-arrow-callback, no-return-assign */
+
+import $ from 'jquery';
 import { visitUrl } from './lib/utils/url_utility';
 import { convertPermissionToBoolean } from './lib/utils/common_utils';
 
diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js
index 35edf3e001755416b972a85fe673f1a3ffadcb47..d398e4a4c83230c319570f33e0c62bc62fd963ea 100644
--- a/app/assets/javascripts/build_variables.js
+++ b/app/assets/javascripts/build_variables.js
@@ -1,9 +1,9 @@
-/* eslint-disable func-names*/
+import $ from 'jquery';
 
 export default function handleRevealVariables() {
   $('.js-reveal-variables')
     .off('click')
-    .on('click', function () {
+    .on('click', function click() {
       $('.js-build-variables').toggle();
       $(this).hide();
     });
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 745f3404295bbb925ec3bccfc9ab30104727a61e..e177a3bfdc781abce89686422281aa3d47280da0 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -33,7 +33,7 @@ export default class VariableList {
         selector: '.js-ci-variable-input-key',
         default: '',
       },
-      value: {
+      secret_value: {
         selector: '.js-ci-variable-input-value',
         default: '',
       },
@@ -105,7 +105,7 @@ export default class VariableList {
     setupToggleButtons($row[0]);
 
     // Reset the resizable textarea
-    $row.find(this.inputMap.value.selector).css('height', '');
+    $row.find(this.inputMap.secret_value.selector).css('height', '');
 
     const $environmentSelect = $row.find('.js-variable-environment-toggle');
     if ($environmentSelect.length) {
diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
index d54ea7df1c316b3b27648ac3d5e73ac345dca54b..7cd5916ac9c07b489c59fa4feabe6ceb711930f6 100644
--- a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import VariableList from './ci_variable_list';
 
 // Used for the variable list on scheduled pipeline edit page
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 1325a2682143fca2400f914eca455e1d11f63401..9c12b89240c005fbb7417910d84414cbc4444b2f 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -1,96 +1,102 @@
 <script>
-  import _ from 'underscore';
-  import { s__, sprintf } from '../../locale';
-  import applicationRow from './application_row.vue';
-  import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
-  import {
-    APPLICATION_INSTALLED,
-    INGRESS,
-  } from '../constants';
+import _ from 'underscore';
+import { s__, sprintf } from '../../locale';
+import applicationRow from './application_row.vue';
+import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
+import { APPLICATION_INSTALLED, INGRESS } from '../constants';
 
-  export default {
-    components: {
-      applicationRow,
-      clipboardButton,
+export default {
+  components: {
+    applicationRow,
+    clipboardButton,
+  },
+  props: {
+    applications: {
+      type: Object,
+      required: false,
+      default: () => ({}),
     },
-    props: {
-      applications: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
-      helpPath: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      ingressHelpPath: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      ingressDnsHelpPath: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      managePrometheusPath: {
-        type: String,
-        required: false,
-        default: '',
-      },
+    helpPath: {
+      type: String,
+      required: false,
+      default: '',
     },
-    computed: {
-      generalApplicationDescription() {
-        return sprintf(
-          _.escape(s__(
+    ingressHelpPath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    ingressDnsHelpPath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    managePrometheusPath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
+  computed: {
+    generalApplicationDescription() {
+      return sprintf(
+        _.escape(
+          s__(
             `ClusterIntegration|Install applications on your Kubernetes cluster.
             Read more about %{helpLink}`,
-          )), {
-            helpLink: `<a href="${this.helpPath}">
+          ),
+        ),
+        {
+          helpLink: `<a href="${this.helpPath}">
               ${_.escape(s__('ClusterIntegration|installing applications'))}
             </a>`,
-          },
-          false,
-        );
-      },
-      ingressId() {
-        return INGRESS;
-      },
-      ingressInstalled() {
-        return this.applications.ingress.status === APPLICATION_INSTALLED;
-      },
-      ingressExternalIp() {
-        return this.applications.ingress.externalIp;
-      },
-      ingressDescription() {
-        const extraCostParagraph = sprintf(
-          _.escape(s__(
+        },
+        false,
+      );
+    },
+    ingressId() {
+      return INGRESS;
+    },
+    ingressInstalled() {
+      return this.applications.ingress.status === APPLICATION_INSTALLED;
+    },
+    ingressExternalIp() {
+      return this.applications.ingress.externalIp;
+    },
+    ingressDescription() {
+      const extraCostParagraph = sprintf(
+        _.escape(
+          s__(
             `ClusterIntegration|%{boldNotice} This will add some extra resources
             like a load balancer, which may incur additional costs depending on
-            the hosting provider your Kubernetes cluster is installed on. If you are using GKE,
-            you can %{pricingLink}.`,
-          )), {
-            boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
-            pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
+            the hosting provider your Kubernetes cluster is installed on. If you are using
+            Google Kubernetes Engine, you can %{pricingLink}.`,
+          ),
+        ),
+        {
+          boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
+          pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
               ${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
-          },
-          false,
-        );
+        },
+        false,
+      );
 
-        const externalIpParagraph = sprintf(
-          _.escape(s__(
+      const externalIpParagraph = sprintf(
+        _.escape(
+          s__(
             `ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS
             at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}`,
-          )), {
-            ingressHelpLink: `<a href="${this.ingressHelpPath}">
+          ),
+        ),
+        {
+          ingressHelpLink: `<a href="${this.ingressHelpPath}">
               ${_.escape(s__('ClusterIntegration|More information'))}
             </a>`,
-          },
-          false,
-        );
+        },
+        false,
+      );
 
-        return `
+      return `
           <p>
             ${extraCostParagraph}
           </p>
@@ -98,26 +104,32 @@
             ${externalIpParagraph}
           </p>
         `;
-      },
-      prometheusDescription() {
-        return sprintf(
-          _.escape(s__(
+    },
+    prometheusDescription() {
+      return sprintf(
+        _.escape(
+          s__(
             `ClusterIntegration|Prometheus is an open-source monitoring system
             with %{gitlabIntegrationLink} to monitor deployed applications.`,
-          )), {
-            gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
+          ),
+        ),
+        {
+          gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
               target="_blank" rel="noopener noreferrer">
               ${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
-          },
-          false,
-        );
-      },
+        },
+        false,
+      );
     },
-  };
+  },
+};
 </script>
 
 <template>
-  <section class="settings no-animate expanded">
+  <section
+    id="cluster-applications"
+    class="settings no-animate expanded"
+  >
     <div class="settings-header">
       <h4>
         {{ s__('ClusterIntegration|Applications') }}
@@ -183,7 +195,7 @@
                     <clipboard-button
                       :text="ingressExternalIp"
                       :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')"
-                      css-class="btn btn-default js-clipboard-btn"
+                      class="js-clipboard-btn"
                     />
                   </span>
                 </div>
@@ -202,7 +214,7 @@
               >
                 {{ s__(`ClusterIntegration|The IP address is in
                 the process of being assigned. Please check your Kubernetes
-                cluster or Quotas on GKE if it takes a long time.`) }}
+                cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) }}
 
                 <a
                   :href="ingressHelpPath"
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 6504a0bbbfc9f1ebdeafcb24a510b0e63fa33d9a..7f3d04655a797f78c966d8a5c7bd9e81579a2c13 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,5 +1,7 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
 
+import $ from 'jquery';
+
 // Width where images must fits in, for 2-up this gets divided by 2
 const availWidth = 900;
 const viewModes = ['two-up', 'swipe'];
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index ce19069f10364d90d3e7a6d9be27eb42e7edd435..24d63b99a292dd928f11c15a8becf7c9dde334f1 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -20,10 +20,6 @@
         type: String,
         required: true,
       },
-      emptyStateSvgPath: {
-        type: String,
-        required: true,
-      },
       errorStateSvgPath: {
         type: String,
         required: true,
@@ -45,45 +41,34 @@
     },
 
     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;
       },
+      shouldRenderErrorState() {
+        return this.hasError && !this.isLoading;
+      },
     },
     created() {
       this.service = new PipelinesService(this.endpoint);
     },
     methods: {
       successCallback(resp) {
-        return resp.json().then((response) => {
-          // depending of the endpoint the response can either bring a `pipelines` key or not.
-          const pipelines = response.pipelines || response;
-          this.setCommonData(pipelines);
-
-          const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
-            detail: {
-              pipelines: response,
-            },
-          });
+        // depending of the endpoint the response can either bring a `pipelines` key or not.
+        const pipelines = resp.data.pipelines || resp.data;
+        this.setCommonData(pipelines);
 
-          // notifiy to update the count in tabs
-          if (this.$el.parentElement) {
-            this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
-          }
+        const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
+          detail: {
+            pipelines: resp.data,
+          },
         });
+
+        // notifiy to update the count in tabs
+        if (this.$el.parentElement) {
+          this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
+        }
       },
     },
   };
@@ -92,25 +77,22 @@
   <div class="content-list pipelines">
 
     <loading-icon
-      label="Loading pipelines"
+      :label="s__('Pipelines|Loading Pipelines')"
       size="3"
       v-if="isLoading"
+      class="prepend-top-20"
     />
 
-    <empty-state
-      v-if="shouldRenderEmptyState"
-      :help-page-path="helpPagePath"
-      :empty-state-svg-path="emptyStateSvgPath"
-    />
-
-    <error-state
-      v-if="shouldRenderErrorState"
-      :error-state-svg-path="errorStateSvgPath"
+    <svg-blank-state
+      v-else-if="shouldRenderErrorState"
+      :svg-path="errorStateSvgPath"
+      :message="s__(`Pipelines|There was an error fetching the pipelines.
+      Try again in a few moments or contact your support team.`)"
     />
 
     <div
       class="table-holder"
-      v-if="shouldRenderTable"
+      v-else-if="shouldRenderTable"
     >
       <pipelines-table-component
         :pipelines="state.pipelines"
diff --git a/app/assets/javascripts/commit_merge_requests.js b/app/assets/javascripts/commit_merge_requests.js
index f76c9b7e6909e460edf057ca112f26c2388619cf..102b4ee8463b20d1f39f245673203e7ee5dcf1dc 100644
--- a/app/assets/javascripts/commit_merge_requests.js
+++ b/app/assets/javascripts/commit_merge_requests.js
@@ -1,5 +1,6 @@
 /* global Flash */
 
+import $ from 'jquery';
 import axios from './lib/utils/axios_utils';
 import { n__, s__ } from './locale';
 
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 2be63bd8c76f10e2e285adaefc539184baff5717..7e2a3573f81e34a418cf912ec733645aaa76e5cb 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { pluralize } from './lib/utils/text_utility';
 import { localTimeAgo } from './lib/utils/datetime_utility';
 import Pager from './pager';
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 46232726510fe1ff0aeed023ff2bba93c5635630..d62d3c236542c346fa66dc7397c6389aacb628e9 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -1,4 +1,5 @@
 // ECMAScript polyfills
+import 'core-js/fn/array/fill';
 import 'core-js/fn/array/find';
 import 'core-js/fn/array/find-index';
 import 'core-js/fn/array/from';
diff --git a/app/assets/javascripts/commons/vue.js b/app/assets/javascripts/commons/vue.js
index 8b62d78c043b084752ee80d57b9b2c178b1a83a3..798623b94fb9b8d007e6d71d88a17f4977479d25 100644
--- a/app/assets/javascripts/commons/vue.js
+++ b/app/assets/javascripts/commons/vue.js
@@ -1,4 +1,5 @@
 import Vue from 'vue';
+import '../vue_shared/vue_resource_interceptor';
 
 if (process.env.NODE_ENV !== 'production') {
   Vue.config.productionTip = false;
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index d5a35ed81a62c1f9d6a738cf32468b9751a0d3fb..303a5bf4a53f726bdb4edf0609d252a88fd0cfce 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
+
+import $ from 'jquery';
 import { localTimeAgo } from './lib/utils/datetime_utility';
 import axios from './lib/utils/axios_utils';
 
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index fa341918fc13dabd0d8ad55a5d8e35f5a67e5081..260c91cac2480e2f1d3e855ca34be71507acc138 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
+
+import $ from 'jquery';
 import { __ } from './locale';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index eae4a7eab555fd3ac25533a0c9c9cf06abdec60e..1638e09132b4cd5b35a7ff848ccd2eec4ede78a7 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -1,31 +1,32 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */
+import $ from 'jquery';
 import { rstrip } from './lib/utils/common_utils';
 
-window.ConfirmDangerModal = (function() {
-  function ConfirmDangerModal(form, text) {
-    var project_path, submit;
-    this.form = form;
-    $('.js-confirm-text').text(text || '');
-    $('.js-confirm-danger-input').val('');
-    $('#modal-confirm-danger').modal('show');
-    project_path = $('.js-confirm-danger-match').text();
-    submit = $('.js-confirm-danger-submit');
-    submit.disable();
-    $('.js-confirm-danger-input').off('input');
-    $('.js-confirm-danger-input').on('input', function() {
-      if (rstrip($(this).val()) === project_path) {
-        return submit.enable();
-      } else {
-        return submit.disable();
-      }
-    });
-    $('.js-confirm-danger-submit').off('click');
-    $('.js-confirm-danger-submit').on('click', (function(_this) {
-      return function() {
-        return _this.form.submit();
-      };
-    })(this));
-  }
+function openConfirmDangerModal($form, text) {
+  $('.js-confirm-text').text(text || '');
+  $('.js-confirm-danger-input').val('');
+  $('#modal-confirm-danger').modal('show');
 
-  return ConfirmDangerModal;
-})();
+  const confirmTextMatch = $('.js-confirm-danger-match').text();
+  const $submit = $('.js-confirm-danger-submit');
+  $submit.disable();
+
+  $('.js-confirm-danger-input').off('input').on('input', function handleInput() {
+    const confirmText = rstrip($(this).val());
+    if (confirmText === confirmTextMatch) {
+      $submit.enable();
+    } else {
+      $submit.disable();
+    }
+  });
+  $('.js-confirm-danger-submit').off('click').on('click', () => $form.submit());
+}
+
+export default function initConfirmDangerModal() {
+  $(document).on('click', '.js-confirm-danger', (e) => {
+    e.preventDefault();
+    const $btn = $(e.target);
+    const $form = $btn.closest('form');
+    const text = $btn.data('confirmDangerMessage');
+    openConfirmDangerModal($form, text);
+  });
+}
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 74520675a7c394725f04152420d8d2aa7a4ce5df..3a50e73ad85b3bf98a20165c837bb185a54592de 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 import _ from 'underscore';
 import bp from './breakpoints';
diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js
index 9a4c9bfcc80a49d4132e6b2d63bdb109f47dfcc6..a999c21b2e97af5b4371883e3631275fa08a33fc 100644
--- a/app/assets/javascripts/create_label.js
+++ b/app/assets/javascripts/create_label.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, prefer-arrow-callback */
+
+import $ from 'jquery';
 import Api from './api';
 import { humanize } from './lib/utils/text_utility';
 
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 46d89c825f9c727f3c6dae5530f323b83054472e..87f8854f9407fb0ed95df53fc80e6df90c606b92 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import Cookies from 'js-cookie';
 import Flash from '../flash';
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 3df082e8c0c467e98ff133f73dc136721b2deb05..a044fc1ab428f29dba8fb7310bd813fd81d6a2e3 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import axios from '~/lib/utils/axios_utils';
 import flash from '~/flash';
 import { __ } from '~/locale';
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
index aed7cac4e6201b248b4200b3259edb44e6e8b7fc..d1260ff5373da8245b44bb829d782b1069d7b727 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
@@ -1,6 +1,7 @@
 /* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
 /* global CommentsStore */
 
+import $ from 'jquery';
 import Vue from 'vue';
 
 const CommentAndResolveBtn = Vue.extend({
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 300b02da663561cf5b0fafd9331d012bee406a74..180a6bd67e74a7c9dc756b670c25234c91c4162e 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -1,5 +1,6 @@
 /* global CommentsStore */
 
+import $ from 'jquery';
 import Vue from 'vue';
 import collapseIcon from '../icons/collapse_icon.svg';
 import Notes from '../../notes';
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
index fadc34959e114dcde6157da0fa8840c3a7cd6603..8f9186dfb9aa896b12987ab03671b1b22d9d90f0 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
@@ -2,6 +2,7 @@
 /* global DiscussionMixins */
 /* global CommentsStore */
 
+import $ from 'jquery';
 import Vue from 'vue';
 
 import '../mixins/discussion';
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index cc9192deae30ce191182b6b36520f77393182529..df4c72ba0ed442ab83b53f0712d436c48f65adce 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
@@ -2,6 +2,7 @@
 /* global CommentsStore */
 /* global ResolveService */
 
+import $ from 'jquery';
 import Vue from 'vue';
 import Flash from '../../flash';
 
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index 5f49609fe88d657c773eebacdd7fe5b01f50292b..e17daec6a92d73e53dfbfc88eaed9130f8c16101 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -1,6 +1,7 @@
 /* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */
 /* global ResolveCount */
 
+import $ from 'jquery';
 import Vue from 'vue';
 import './models/discussion';
 import './models/note';
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js
index 1b8a9af93905b1c3d90f02d1e104840505eed279..c97c559dd143cb0f0beda49edd40136873e0d01d 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js
+++ b/app/assets/javascripts/diff_notes/models/discussion.js
@@ -1,6 +1,7 @@
 /* eslint-disable space-before-function-paren, camelcase, guard-for-in, no-restricted-syntax, no-unused-vars, max-len */
 /* global NoteModel */
 
+import $ from 'jquery';
 import Vue from 'vue';
 import { localTimeAgo } from '../../lib/utils/datetime_utility';
 
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 1ccf96a75dc41659972bac6b0988f8d810a67712..72f21f138608c677c17ca183c5c351cb1715322b 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
+
+import $ from 'jquery';
 import Flash from './flash';
 import GfmAutoComplete from './gfm_auto_complete';
 import { convertPermissionToBoolean } from './lib/utils/common_utils';
@@ -51,8 +53,12 @@ function initPageShortcuts(page) {
 
 function initGFMInput() {
   $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
-    const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
-    const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
+    const gfm = new GfmAutoComplete(
+      gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
+    );
+    const enableGFM = convertPermissionToBoolean(
+      el.dataset.supportsAutocomplete,
+    );
     gfm.setup($(el), {
       emojis: true,
       members: enableGFM,
@@ -65,9 +71,9 @@ function initGFMInput() {
 }
 
 function initPerformanceBar() {
-  if (document.querySelector('#peek')) {
+  if (document.querySelector('#js-peek')) {
     import('./performance_bar')
-      .then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
+      .then(m => new m.default({ container: '#js-peek' })) // eslint-disable-line new-cap
       .catch(() => Flash('Error loading performance bar module'));
   }
 }
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index ba89e5726fafd1b26ec25e962a7ab3aada96061d..5528ad9f38d08248929510c17c877ce1a45c21a2 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Dropzone from 'dropzone';
 import _ from 'underscore';
 import './preview_markdown';
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 417258e0092ba7b2ba8f93b39a8fb96dfaeed79c..4164149dd0664b01b22e7450c4b585d8e31248d2 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -1,7 +1,10 @@
 /* global dateFormat */
 
+import $ from 'jquery';
 import Pikaday from 'pikaday';
+import { __ } from '~/locale';
 import axios from './lib/utils/axios_utils';
+import { timeFor } from './lib/utils/datetime_utility';
 import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
 
 class DueDateSelect {
@@ -13,6 +16,7 @@ class DueDateSelect {
     this.$dropdownParent = $dropdownParent;
     this.$datePicker = $dropdownParent.find('.js-due-date-calendar');
     this.$block = $block;
+    this.$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
     this.$selectbox = $dropdown.closest('.selectbox');
     this.$value = $block.find('.value');
     this.$valueContent = $block.find('.value-content');
@@ -127,7 +131,8 @@ class DueDateSelect {
 
   submitSelectedDate(isDropdown) {
     const selectedDateValue = this.datePayload[this.abilityName].due_date;
-    const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
+    const hasDueDate = this.displayedDate !== 'No due date';
+    const displayedDateStyle = hasDueDate ? 'bold' : 'no-value';
 
     this.$loading.removeClass('hidden').fadeIn();
 
@@ -144,10 +149,13 @@ class DueDateSelect {
 
     return axios.put(this.issueUpdateURL, this.datePayload)
       .then(() => {
+        const tooltipText = hasDueDate ? `${__('Due date')}<br />${selectedDateValue} (${timeFor(selectedDateValue)})` : __('Due date');
         if (isDropdown) {
           this.$dropdown.trigger('loaded.gl.dropdown');
           this.$dropdown.dropdown('toggle');
         }
+        this.$sidebarCollapsedValue.attr('data-original-title', tooltipText);
+
         return this.$loading.fadeOut();
       });
   }
diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue
index 1eef17bf1fec7b7cc0a89675db245218d9d23bc9..dda7429a726af0db5a57234a4f702daf8259d727 100644
--- a/app/assets/javascripts/environments/components/environment_stop.vue
+++ b/app/assets/javascripts/environments/components/environment_stop.vue
@@ -3,6 +3,8 @@
   * Renders the stop "button" that allows stop an environment.
   * Used in environments table.
   */
+
+  import $ from 'jquery';
   import eventHub from '../event_hub';
   import loadingIcon from '../../vue_shared/components/loading_icon.vue';
   import tooltip from '../../vue_shared/directives/tooltip';
diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js
index 6ee65ca72f93829f2a5442e40c2e01e0affdf324..1d60847147b297a9fdef163d3e5677c347ddd154 100644
--- a/app/assets/javascripts/experimental_flags.js
+++ b/app/assets/javascripts/experimental_flags.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 
 export default () => {
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js
index d65cc6d5d7d56c896b7114019d5d718eb981f266..2d5bae9a9c4ce758f06137d4354d18c4f80a9455 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight.js
@@ -1,18 +1,19 @@
-import _ from 'underscore';
+import $ from 'jquery';
 import {
   getSelector,
-  togglePopover,
   inserted,
-  mouseenter,
-  mouseleave,
 } from './feature_highlight_helper';
+import {
+  togglePopover,
+  mouseenter,
+  debouncedMouseleave,
+} from '../shared/popover';
 
 export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
   const $selector = $(getSelector(id));
   const $parent = $selector.parent();
   const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
   const hideOnScroll = togglePopover.bind($selector, false);
-  const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
 
   $selector
     // Setup popover
@@ -28,13 +29,10 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
       `,
     })
     .on('mouseenter', mouseenter)
-    .on('mouseleave', debouncedMouseleave)
+    .on('mouseleave', debouncedMouseleave(debounceTimeout))
     .on('inserted.bs.popover', inserted)
     .on('show.bs.popover', () => {
-      window.addEventListener('scroll', hideOnScroll);
-    })
-    .on('hide.bs.popover', () => {
-      window.removeEventListener('scroll', hideOnScroll);
+      window.addEventListener('scroll', hideOnScroll, { once: true });
     })
     // Display feature highlight
     .removeAttr('disabled');
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
index 939d12237f3a0541451d20b6e5e7832a1abef0bc..d5b97ebb26409177e83004f63ec5e25fdc7af020 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
@@ -1,21 +1,12 @@
+import $ from 'jquery';
 import axios from '../lib/utils/axios_utils';
 import { __ } from '../locale';
 import Flash from '../flash';
 import LazyLoader from '../lazy_loader';
+import { togglePopover } from '../shared/popover';
 
 export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
 
-export function togglePopover(show) {
-  const isAlreadyShown = this.hasClass('js-popover-show');
-  if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
-    return false;
-  }
-  this.popover(show ? 'show' : 'hide');
-  this.toggleClass('disable-animation js-popover-show', show);
-
-  return true;
-}
-
 export function dismiss(highlightId) {
   axios.post(this.attr('data-dismiss-endpoint'), {
     feature_name: highlightId,
@@ -26,23 +17,6 @@ export function dismiss(highlightId) {
   this.hide();
 }
 
-export function mouseleave() {
-  if (!$('.popover:hover').length > 0) {
-    const $featureHighlight = $(this);
-    togglePopover.call($featureHighlight, false);
-  }
-}
-
-export function mouseenter() {
-  const $featureHighlight = $(this);
-
-  const showedPopover = togglePopover.call($featureHighlight, true);
-  if (showedPopover) {
-    $('.popover')
-      .on('mouseleave', mouseleave.bind($featureHighlight));
-  }
-}
-
 export function inserted() {
   const popoverId = this.getAttribute('aria-describedby');
   const highlightId = this.dataset.highlight;
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index a10f027de5384677c1e9d6ca0b1da3f2dbec8bab..b17ba3c21db0cbe9bfab95659e8d439dd2772913 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import axios from './lib/utils/axios_utils';
 
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index e6390f0855beb8dcdce3c039d3af69d6c50aee26..d7e1de18d09312ad6a656474b0dafc131f7a8f27 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -26,8 +26,8 @@ export default class FilteredSearchDropdownManager {
     this.filteredSearchInput = this.container.querySelector('.filtered-search');
     this.page = page;
     this.groupsOnly = isGroup;
-    this.groupAncestor = isGroupAncestor;
-    this.isGroupDecendent = isGroupDecendent;
+    this.includeAncestorGroups = isGroupAncestor;
+    this.includeDescendantGroups = isGroupDecendent;
 
     this.setupMapping();
 
@@ -108,7 +108,19 @@ export default class FilteredSearchDropdownManager {
   }
 
   getLabelsEndpoint() {
-    const endpoint = `${this.baseEndpoint}/labels.json`;
+    let endpoint = `${this.baseEndpoint}/labels.json?`;
+
+    if (this.groupsOnly) {
+      endpoint = `${endpoint}only_group_labels=true&`;
+    }
+
+    if (this.includeAncestorGroups) {
+      endpoint = `${endpoint}include_ancestor_groups=true&`;
+    }
+
+    if (this.includeDescendantGroups) {
+      endpoint = `${endpoint}include_descendant_groups=true`;
+    }
 
     return endpoint;
   }
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 71b7e80335bd7cb8fe29e003f9373e054e4be805..cf5ba1e1771f66285ab7f5b9e9d814c7bd1bd465 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -21,7 +21,7 @@ export default class FilteredSearchManager {
   constructor({
     page,
     isGroup = false,
-    isGroupAncestor = false,
+    isGroupAncestor = true,
     isGroupDecendent = false,
     filteredSearchTokenKeys = FilteredSearchTokenKeys,
     stateFiltersSelector = '.issues-state-filters',
@@ -86,6 +86,7 @@ export default class FilteredSearchManager {
         page: this.page,
         isGroup: this.isGroup,
         isGroupAncestor: this.isGroupAncestor,
+        isGroupDecendent: this.isGroupDecendent,
         filteredSearchTokenKeys: this.filteredSearchTokenKeys,
       });
 
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 57a1fa107e57b3ae5d36ececd98cf6365a19fa2b..7e9770a9ea2ff93228cca243fd1d312344981965 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import glRegexp from './lib/utils/regexp';
 import AjaxCache from './lib/utils/ajax_cache';
@@ -53,6 +54,7 @@ class GfmAutoComplete {
       alias: 'commands',
       searchKey: 'search',
       skipSpecialCharacterTest: true,
+      skipMarkdownCharacterTest: true,
       data: GfmAutoComplete.defaultLoadingData,
       displayTpl(value) {
         if (GfmAutoComplete.isLoading(value)) return GfmAutoComplete.Loading.template;
@@ -131,9 +133,8 @@ class GfmAutoComplete {
       callbacks: {
         ...this.getDefaultCallbacks(),
         matcher(flag, subtext) {
-          const relevantText = subtext.trim().split(/\s/).pop();
           const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi');
-          const match = regexp.exec(relevantText);
+          const match = regexp.exec(subtext);
 
           return match && match.length ? match[1] : null;
         },
@@ -376,15 +377,23 @@ class GfmAutoComplete {
         return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
       },
       beforeInsert(value) {
-        let resultantValue = value;
+        let withoutAt = value.substring(1);
+        const at = value.charAt();
+
         if (value && !this.setting.skipSpecialCharacterTest) {
-          const withoutAt = value.substring(1);
-          const regex = value.charAt() === '~' ? /\W|^\d+$/ : /\W/;
+          const regex = at === '~' ? /\W|^\d+$/ : /\W/;
           if (withoutAt && regex.test(withoutAt)) {
-            resultantValue = `${value.charAt()}"${withoutAt}"`;
+            withoutAt = `"${withoutAt}"`;
           }
         }
-        return resultantValue;
+
+        // We can ignore this for quick actions because they are processed
+        // before Markdown.
+        if (!this.setting.skipMarkdownCharacterTest) {
+          withoutAt = withoutAt.replace(/([~\-_*`])/g, '\\$&');
+        }
+
+        return `${at}${withoutAt}`;
       },
       matcher(flag, subtext) {
         const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 6cf78bab6ad62fb1c2c386c6df3343df9e488d6d..fa48d7d191583fc8d2f7e2278bb53bb3b5f371b0 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,5 +1,7 @@
 /* eslint-disable func-names, no-underscore-dangle, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
 /* global fuzzaldrinPlus */
+
+import $ from 'jquery';
 import _ from 'underscore';
 import fuzzaldrinPlus from 'fuzzaldrin-plus';
 import axios from './lib/utils/axios_utils';
@@ -576,7 +578,7 @@ GitLabDropdown = (function() {
       for (var i = 0; i < html.length; i += 1) {
         var el = html[i];
 
-        if (el instanceof jQuery) {
+        if (el instanceof $) {
           el = el.get(0);
         }
 
@@ -751,7 +753,7 @@ GitLabDropdown = (function() {
     }
 
     if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
-      return;
+      return [selectedObject];
     }
 
     if (el.hasClass(ACTIVE_CLASS) && value !== 0) {
diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js
index bd63f6f16f07013cfb60374c5bb610b5322814a5..972b2252acb6fe4f2824d8990f5b9b8a3ec838c0 100644
--- a/app/assets/javascripts/gl_field_error.js
+++ b/app/assets/javascripts/gl_field_error.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 /**
  * This class overrides the browser's validation error bubbles, displaying custom
  * error messages for invalid fields instead. To begin validating any form, add the
diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js
index 73bcbd93565188208c3873f6112126c5f9b29577..b9c51045b1db2c583e32f3857452b9314bb12c94 100644
--- a/app/assets/javascripts/gl_field_errors.js
+++ b/app/assets/javascripts/gl_field_errors.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import GlFieldError from './gl_field_error';
 
 const customValidationFlag = 'gl-field-error-ignore';
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 2d40856e0386e4dbb2541082a4adcde856003610..9f5eba353d7fc9272e98533e93f839c2b7dc5ed0 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -1,7 +1,8 @@
+import $ from 'jquery';
 import autosize from 'autosize';
 import GfmAutoComplete from './gfm_auto_complete';
 import dropzoneInput from './dropzone_input';
-import textUtils from './lib/utils/text_markdown';
+import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
 
 export default class GLForm {
   constructor(form, enableGFM = false) {
@@ -46,7 +47,7 @@ export default class GLForm {
     }
     // form and textarea event listeners
     this.addEventListeners();
-    textUtils.init(this.form);
+    addMarkdownListeners(this.form);
     // hide discard button
     this.form.find('.js-note-discard').hide();
     this.form.show();
@@ -85,7 +86,7 @@ export default class GLForm {
   clearEventListeners() {
     this.textarea.off('focus');
     this.textarea.off('blur');
-    textUtils.removeListeners(this.form);
+    removeMarkdownListeners(this.form);
   }
 
   addEventListeners() {
diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js
index 6bf21f4f27d4aded747ee91721467f66ad6c38b4..502e35693213cf2ff72a7de5a96ab5945590c772 100644
--- a/app/assets/javascripts/gpg_badges.js
+++ b/app/assets/javascripts/gpg_badges.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { parseQueryStringIntoObject } from '~/lib/utils/common_utils';
 import axios from '~/lib/utils/axios_utils';
 import flash from '~/flash';
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
index 7732edde1e74406c7f2b0057c5e3d4cb76e5c746..4365305c1684d888b6ca514cdce45ce07215a01d 100644
--- a/app/assets/javascripts/group.js
+++ b/app/assets/javascripts/group.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default class Group {
   constructor() {
     this.groupPath = $('#group_path');
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
index 2168ff3a8ba06f3b8cc77aaff2fcb1c9c04b06ce..beaac61e887a7047f47e73f91f050b457b0fc9c4 100644
--- a/app/assets/javascripts/group_avatar.js
+++ b/app/assets/javascripts/group_avatar.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default function groupAvatar() {
   $('.js-choose-group-avatar-button').on('click', function onClickGroupAvatar() {
     const form = $(this).closest('form');
diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js
index df9429b1e02104840fa2dcf97c5db930d828b520..5648cb9a8887e57f185d0378a667ba0123cca98e 100644
--- a/app/assets/javascripts/group_label_subscription.js
+++ b/app/assets/javascripts/group_label_subscription.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
 import { __ } from './locale';
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index b8f0566f48c33cf71cf31028626f16c1274ea74e..22eb7bd44c545c9553d940011829e22b7f8efcb5 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -1,9 +1,10 @@
 <script>
 /* global Flash */
 
+import $ from 'jquery';
 import { s__ } from '~/locale';
 import loadingIcon from '~/vue_shared/components/loading_icon.vue';
-import modal from '~/vue_shared/components/modal.vue';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
 import { getParameterByName } from '~/lib/utils/common_utils';
 import { mergeUrlParams } from '~/lib/utils/url_utility';
 
@@ -14,7 +15,7 @@ import groupsComponent from './groups.vue';
 export default {
   components: {
     loadingIcon,
-    modal,
+    DeprecatedModal,
     groupsComponent,
   },
   props: {
@@ -51,8 +52,9 @@ export default {
     },
   },
   created() {
-    this.searchEmptyMessage = this.hideProjects ?
-      COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
+    this.searchEmptyMessage = this.hideProjects
+      ? COMMON_STR.GROUP_SEARCH_EMPTY
+      : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
 
     eventHub.$on('fetchPage', this.fetchPage);
     eventHub.$on('toggleChildren', this.toggleChildren);
@@ -71,22 +73,30 @@ export default {
     eventHub.$off('updateGroups', this.updateGroups);
   },
   methods: {
-    fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
-      return this.service.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
-                .then((res) => {
-                  if (updatePagination) {
-                    this.updatePagination(res.headers);
-                  }
+    fetchGroups({
+      parentId,
+      page,
+      filterGroupsBy,
+      sortBy,
+      archived,
+      updatePagination,
+    }) {
+      return this.service
+        .getGroups(parentId, page, filterGroupsBy, sortBy, archived)
+        .then(res => {
+          if (updatePagination) {
+            this.updatePagination(res.headers);
+          }
 
-                  return res;
-                })
-                .then(res => res.json())
-                .catch(() => {
-                  this.isLoading = false;
-                  $.scrollTo(0);
+          return res;
+        })
+        .then(res => res.json())
+        .catch(() => {
+          this.isLoading = false;
+          $.scrollTo(0);
 
-                  Flash(COMMON_STR.FAILURE);
-                });
+          Flash(COMMON_STR.FAILURE);
+        });
     },
     fetchAllGroups() {
       const page = getParameterByName('page') || null;
@@ -102,7 +112,7 @@ export default {
         sortBy,
         archived,
         updatePagination: true,
-      }).then((res) => {
+      }).then(res => {
         this.isLoading = false;
         this.updateGroups(res, Boolean(filterGroupsBy));
       });
@@ -117,14 +127,18 @@ export default {
         sortBy,
         archived,
         updatePagination: true,
-      }).then((res) => {
+      }).then(res => {
         this.isLoading = false;
         $.scrollTo(0);
 
         const currentPath = mergeUrlParams({ page }, window.location.href);
-        window.history.replaceState({
-          page: currentPath,
-        }, document.title, currentPath);
+        window.history.replaceState(
+          {
+            page: currentPath,
+          },
+          document.title,
+          currentPath,
+        );
 
         this.updateGroups(res);
       });
@@ -137,11 +151,13 @@ export default {
           // eslint-disable-next-line promise/catch-or-return
           this.fetchGroups({
             parentId: parentGroup.id,
-          }).then((res) => {
-            this.store.setGroupChildren(parentGroup, res);
-          }).catch(() => {
-            parentGroup.isChildrenLoading = false;
-          });
+          })
+            .then(res => {
+              this.store.setGroupChildren(parentGroup, res);
+            })
+            .catch(() => {
+              parentGroup.isChildrenLoading = false;
+            });
         } else {
           parentGroup.isOpen = true;
         }
@@ -152,23 +168,28 @@ export default {
     showLeaveGroupModal(group, parentGroup) {
       this.targetGroup = group;
       this.targetParentGroup = parentGroup;
-      this.updateModal = true;
-      this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
+      this.showModal = true;
+      this.groupLeaveConfirmationMessage = s__(
+        `GroupsTree|Are you sure you want to leave the "${
+          group.fullName
+        }" group?`,
+      );
     },
     hideLeaveGroupModal() {
-      this.updateModal = false;
+      this.showModal = false;
     },
     leaveGroup() {
-      this.updateModal = false;
+      this.showModal = false;
       this.targetGroup.isBeingRemoved = true;
-      this.service.leaveGroup(this.targetGroup.leavePath)
+      this.service
+        .leaveGroup(this.targetGroup.leavePath)
         .then(res => res.json())
-        .then((res) => {
+        .then(res => {
           $.scrollTo(0);
           this.store.removeGroup(this.targetGroup, this.targetParentGroup);
           Flash(res.notice, 'notice');
         })
-        .catch((err) => {
+        .catch(err => {
           let message = COMMON_STR.FAILURE;
           if (err.status === 403) {
             message = COMMON_STR.LEAVE_FORBIDDEN;
@@ -207,10 +228,10 @@ export default {
       :search-empty-message="searchEmptyMessage"
       :page-info="pageInfo"
     />
-    <modal
+    <deprecated-modal
       v-show="showModal"
-      :primary-button-label="__('Leave')"
       kind="warning"
+      :primary-button-label="__('Leave')"
       :title="__('Are you sure?')"
       :text="groupLeaveConfirmationMessage"
       @cancel="hideLeaveGroupModal"
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index 31d56d15c234013dcc6e1306dfc8472ffabed80b..e6db1746487fde053cfdae09f5db77958e0b0138 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import FilterableList from '~/filterable_list';
 import eventHub from './event_hub';
 import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
diff --git a/app/assets/javascripts/groups/transfer_dropdown.js b/app/assets/javascripts/groups/transfer_dropdown.js
index 85b7b08db4d0a24b9ab85bd3b6d4f3fec648abcf..e0eb118ddf7d2cde17b8e188efbf704f27fc9ebb 100644
--- a/app/assets/javascripts/groups/transfer_dropdown.js
+++ b/app/assets/javascripts/groups/transfer_dropdown.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default class TransferDropdown {
   constructor() {
     this.groupDropdown = $('.js-groups-dropdown');
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index 12fc5f9b5c96503337aeb94bcc4c4a5bdc35c0fc..310f6fe06cf1264ae030afc4f0652fa7c77aa4dc 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import axios from './lib/utils/axios_utils';
 import Api from './api';
 import { normalizeHeaders } from './lib/utils/common_utils';
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index 33a352e158a52c72a8b5e7b6d93c66057ad240f5..4ae3a714beed3ce3bffb0d4519e94219e92c7613 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { highCountTrim } from '~/lib/utils/text_utility';
 
 /**
diff --git a/app/assets/javascripts/help/help.js b/app/assets/javascripts/help/help.js
index d02477b19a2fd040a37a2dc490054af6eec7847f..f5333042bb89237c436682a957e4b60f6ea8c33e 100644
--- a/app/assets/javascripts/help/help.js
+++ b/app/assets/javascripts/help/help.js
@@ -1,4 +1,7 @@
 // We will render the icons list here
+
+import $ from 'jquery';
+
 export default () => {
   if ($('#user-content-gitlab-icons').length > 0) {
     const $iconsHeader = $('#user-content-gitlab-icons');
diff --git a/app/assets/javascripts/how_to_merge.js b/app/assets/javascripts/how_to_merge.js
index 12e6f24595a702b6d3c30978f240baf74ea84925..bb734246584aa3fd44575be2ca190b70c5f8543d 100644
--- a/app/assets/javascripts/how_to_merge.js
+++ b/app/assets/javascripts/how_to_merge.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default () => {
   const modal = $('#modal_merge_info');
 
diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1fc11c84639b4a37923d88260ddf13648dc0eada
--- /dev/null
+++ b/app/assets/javascripts/ide/components/changed_file_icon.vue
@@ -0,0 +1,87 @@
+<script>
+import tooltip from '~/vue_shared/directives/tooltip';
+import Icon from '~/vue_shared/components/icon.vue';
+import { pluralize } from '~/lib/utils/text_utility';
+import { __, sprintf } from '~/locale';
+
+export default {
+  components: {
+    Icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    file: {
+      type: Object,
+      required: true,
+    },
+    showTooltip: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    showStagedIcon: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    changedIcon() {
+      const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : '';
+      return this.file.tempFile ? `file-addition${suffix}` : `file-modified${suffix}`;
+    },
+    stagedIcon() {
+      return `${this.changedIcon}-solid`;
+    },
+    changedIconClass() {
+      return `multi-${this.changedIcon} prepend-left-5 pull-left`;
+    },
+    tooltipTitle() {
+      if (!this.showTooltip) return undefined;
+
+      const type = this.file.tempFile ? 'addition' : 'modification';
+
+      if (this.file.changed && !this.file.staged) {
+        return sprintf(__('Unstaged %{type}'), {
+          type,
+        });
+      } else if (!this.file.changed && this.file.staged) {
+        return sprintf(__('Staged %{type}'), {
+          type,
+        });
+      } else if (this.file.changed && this.file.staged) {
+        return sprintf(__('Unstaged and staged %{type}'), {
+          type: pluralize(type),
+        });
+      }
+
+      return undefined;
+    },
+  },
+};
+</script>
+
+<template>
+  <span
+    v-tooltip
+    :title="tooltipTitle"
+    data-container="body"
+    data-placement="right"
+    class="ide-file-changed-icon"
+  >
+    <icon
+      v-if="file.staged && showStagedIcon"
+      :name="stagedIcon"
+      :size="12"
+      :css-classes="changedIconClass"
+    />
+    <icon
+      v-if="file.changed || file.tempFile || (file.staged && !showStagedIcon)"
+      :name="changedIcon"
+      :size="12"
+      :css-classes="changedIconClass"
+    />
+  </span>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
new file mode 100644
index 0000000000000000000000000000000000000000..45321df191cb9781e16fec11918631beaa458ca2
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -0,0 +1,49 @@
+<script>
+import { mapState } from 'vuex';
+import { sprintf, __ } from '~/locale';
+import * as consts from '../../stores/modules/commit/constants';
+import RadioGroup from './radio_group.vue';
+
+export default {
+  components: {
+    RadioGroup,
+  },
+  computed: {
+    ...mapState(['currentBranchId']),
+    commitToCurrentBranchText() {
+      return sprintf(
+        __('Commit to %{branchName} branch'),
+        { branchName: `<strong class="monospace">${this.currentBranchId}</strong>` },
+        false,
+      );
+    },
+  },
+  commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
+  commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
+  commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
+};
+</script>
+
+<template>
+  <div class="append-bottom-15 ide-commit-radios">
+    <radio-group
+      :value="$options.commitToCurrentBranch"
+      :checked="true"
+    >
+      <span
+        v-html="commitToCurrentBranchText"
+      >
+      </span>
+    </radio-group>
+    <radio-group
+      :value="$options.commitToNewBranch"
+      :label="__('Create a new branch')"
+      :show-input="true"
+    />
+    <radio-group
+      :value="$options.commitToNewBranchMR"
+      :label="__('Create a new branch and merge request')"
+      :show-input="true"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6424b93ce543bf3a5a19876a0cd9a934828ced98
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue
@@ -0,0 +1,93 @@
+<script>
+import { mapActions, mapState, mapGetters } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  components: {
+    Icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    noChangesStateSvgPath: {
+      type: String,
+      required: true,
+    },
+    committedStateSvgPath: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    ...mapState(['lastCommitMsg', 'rightPanelCollapsed']),
+    ...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']),
+    statusSvg() {
+      return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath;
+    },
+  },
+  methods: {
+    ...mapActions(['toggleRightPanelCollapsed']),
+  },
+};
+</script>
+
+<template>
+  <div
+    class="multi-file-commit-panel-section ide-commit-empty-state js-empty-state"
+  >
+    <header
+      class="multi-file-commit-panel-header"
+      :class="{
+        'is-collapsed': rightPanelCollapsed,
+      }"
+    >
+      <button
+        v-tooltip
+        :title="collapseButtonTooltip"
+        data-container="body"
+        data-placement="left"
+        type="button"
+        class="btn btn-transparent multi-file-commit-panel-collapse-btn"
+        :aria-label="__('Toggle sidebar')"
+        @click.stop="toggleRightPanelCollapsed"
+      >
+        <icon
+          :name="collapseButtonIcon"
+          :size="18"
+        />
+      </button>
+    </header>
+    <div
+      class="ide-commit-empty-state-container"
+      v-if="!rightPanelCollapsed"
+    >
+      <div class="svg-content svg-80">
+        <img :src="statusSvg" />
+      </div>
+      <div class="append-right-default prepend-left-default">
+        <div
+          class="text-content text-center"
+          v-if="!lastCommitMsg"
+        >
+          <h4>
+            {{ __('No changes') }}
+          </h4>
+          <p>
+            {{ __('Edit files in the editor and commit changes here') }}
+          </p>
+        </div>
+        <div
+          class="text-content text-center"
+          v-else
+        >
+          <h4>
+            {{ __('All changes are committed') }}
+          </h4>
+          <p v-html="lastCommitMsg"></p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index a8459b011df589971f41e08ef65db91611fc85f9..ff05ee8682a699534e7e368a26197e77408ea240 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -1,49 +1,132 @@
 <script>
-  import { mapState } from 'vuex';
-  import icon from '../../../vue_shared/components/icon.vue';
-  import listItem from './list_item.vue';
-  import listCollapsed from './list_collapsed.vue';
+import { mapActions, mapState, mapGetters } from 'vuex';
+import { __, sprintf } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import ListItem from './list_item.vue';
+import ListCollapsed from './list_collapsed.vue';
 
-  export default {
-    components: {
-      icon,
-      listItem,
-      listCollapsed,
+export default {
+  components: {
+    Icon,
+    ListItem,
+    ListCollapsed,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    title: {
+      type: String,
+      required: true,
     },
-    props: {
-      title: {
-        type: String,
-        required: true,
-      },
-      fileList: {
-        type: Array,
-        required: true,
-      },
+    fileList: {
+      type: Array,
+      required: true,
     },
-    computed: {
-      ...mapState([
-        'currentProjectId',
-        'currentBranchId',
-        'rightPanelCollapsed',
-      ]),
+    showToggle: {
+      type: Boolean,
+      required: false,
+      default: true,
     },
-    methods: {
-      toggleCollapsed() {
-        this.$emit('toggleCollapsed');
-      },
+    iconName: {
+      type: String,
+      required: true,
     },
-  };
+    action: {
+      type: String,
+      required: true,
+    },
+    actionBtnText: {
+      type: String,
+      required: true,
+    },
+    itemActionComponent: {
+      type: String,
+      required: true,
+    },
+    stagedList: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    ...mapState(['rightPanelCollapsed']),
+    ...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']),
+    titleText() {
+      return sprintf(__('%{title} changes'), {
+        title: this.title,
+      });
+    },
+  },
+  methods: {
+    ...mapActions(['toggleRightPanelCollapsed', 'stageAllChanges', 'unstageAllChanges']),
+    actionBtnClicked() {
+      this[this.action]();
+    },
+  },
+};
 </script>
 
 <template>
-  <div class="multi-file-commit-list">
+  <div
+    class="ide-commit-list-container"
+    :class="{
+      'is-collapsed': rightPanelCollapsed,
+    }"
+  >
+    <header
+      class="multi-file-commit-panel-header"
+    >
+      <div
+        v-if="!rightPanelCollapsed"
+        class="multi-file-commit-panel-header-title"
+        :class="{
+          'append-right-10': showToggle,
+        }"
+      >
+        <icon
+          v-once
+          :name="iconName"
+          :size="18"
+        />
+        {{ titleText }}
+        <button
+          type="button"
+          class="btn btn-blank btn-link ide-staged-action-btn"
+          @click="actionBtnClicked"
+        >
+          {{ actionBtnText }}
+        </button>
+      </div>
+      <button
+        v-if="showToggle"
+        v-tooltip
+        :title="collapseButtonTooltip"
+        data-container="body"
+        data-placement="left"
+        type="button"
+        class="btn btn-transparent multi-file-commit-panel-collapse-btn"
+        :aria-label="__('Toggle sidebar')"
+        @click.stop="toggleRightPanelCollapsed"
+      >
+        <icon
+          :name="collapseButtonIcon"
+          :size="18"
+        />
+      </button>
+    </header>
     <list-collapsed
       v-if="rightPanelCollapsed"
+      :files="fileList"
+      :icon-name="iconName"
+      :title="title"
     />
     <template v-else>
       <ul
         v-if="fileList.length"
-        class="list-unstyled append-bottom-0"
+        class="multi-file-commit-list list-unstyled append-bottom-0"
       >
         <li
           v-for="file in fileList"
@@ -51,15 +134,18 @@
         >
           <list-item
             :file="file"
+            :action-component="itemActionComponent"
+            :key-prefix="title"
+            :staged-list="stagedList"
           />
         </li>
       </ul>
-      <div
+      <p
         v-else
-        class="help-block prepend-top-0"
+        class="multi-file-commit-list help-block"
       >
-        No changes
-      </div>
+        {{ __('No changes') }}
+      </p>
     </template>
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
index 6a0262f271b966513ee1f6231bdc7d67a3eba781..2254271c679b8b4ab4e5a05e0d4b80b13a8f13fa 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
@@ -1,35 +1,110 @@
 <script>
-  import { mapGetters } from 'vuex';
-  import icon from '../../../vue_shared/components/icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import { sprintf, n__, __ } from '~/locale';
 
-  export default {
-    components: {
-      icon,
+export default {
+  components: {
+    Icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    files: {
+      type: Array,
+      required: true,
     },
-    computed: {
-      ...mapGetters([
-        'addedFiles',
-        'modifiedFiles',
-      ]),
+    iconName: {
+      type: String,
+      required: true,
     },
-  };
+    title: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    addedFilesLength() {
+      return this.files.filter(f => f.tempFile).length;
+    },
+    modifiedFilesLength() {
+      return this.files.filter(f => !f.tempFile).length;
+    },
+    addedFilesIconClass() {
+      return this.addedFilesLength ? 'multi-file-addition' : '';
+    },
+    modifiedFilesClass() {
+      return this.modifiedFilesLength ? 'multi-file-modified' : '';
+    },
+    additionsTooltip() {
+      return sprintf(n__('1 %{type} addition', '%d %{type} additions', this.addedFilesLength), {
+        type: this.title.toLowerCase(),
+      });
+    },
+    modifiedTooltip() {
+      return sprintf(
+        n__('1 %{type} modification', '%d %{type} modifications', this.modifiedFilesLength),
+        { type: this.title.toLowerCase() },
+      );
+    },
+    titleTooltip() {
+      return sprintf(__('%{title} changes'), { title: this.title });
+    },
+    additionIconName() {
+      return this.title.toLowerCase() === 'staged' ? 'file-addition-solid' : 'file-addition';
+    },
+    modifiedIconName() {
+      return this.title.toLowerCase() === 'staged' ? 'file-modified-solid' : 'file-modified';
+    },
+  },
+};
 </script>
 
 <template>
   <div
     class="multi-file-commit-list-collapsed text-center"
   >
-    <icon
-      name="file-addition"
-      :size="18"
-      css-classes="multi-file-addition append-bottom-10"
-    />
-    {{ addedFiles.length }}
-    <icon
-      name="file-modified"
-      :size="18"
-      css-classes="multi-file-modified prepend-top-10 append-bottom-10"
-    />
-    {{ modifiedFiles.length }}
+    <div
+      v-tooltip
+      :title="titleTooltip"
+      data-container="body"
+      data-placement="left"
+      class="append-bottom-15"
+    >
+      <icon
+        v-once
+        :name="iconName"
+        :size="18"
+      />
+    </div>
+    <div
+      v-tooltip
+      :title="additionsTooltip"
+      data-container="body"
+      data-placement="left"
+      class="append-bottom-10"
+    >
+      <icon
+        :name="additionIconName"
+        :size="18"
+        :css-classes="addedFilesIconClass"
+      />
+    </div>
+    {{ addedFilesLength }}
+    <div
+      v-tooltip
+      :title="modifiedTooltip"
+      data-container="body"
+      data-placement="left"
+      class="prepend-top-10 append-bottom-10"
+    >
+      <icon
+        :name="modifiedIconName"
+        :size="18"
+        :css-classes="modifiedFilesClass"
+      />
+    </div>
+    {{ modifiedFilesLength }}
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 742f746e02ff7fecfae5e2af4085b08d250e1144..ad4713c40d5549102e57bf3d193241b993e73b26 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -1,36 +1,92 @@
 <script>
-  import icon from '../../../vue_shared/components/icon.vue';
+import { mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+import StageButton from './stage_button.vue';
+import UnstageButton from './unstage_button.vue';
 
-  export default {
-    components: {
-      icon,
-    },
-    props: {
-      file: {
-        type: Object,
-        required: true,
-      },
-    },
-    computed: {
-      iconName() {
-        return this.file.tempFile ? 'file-addition' : 'file-modified';
-      },
-      iconClass() {
-        return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
-      },
-    },
-  };
+export default {
+  components: {
+    Icon,
+    StageButton,
+    UnstageButton,
+  },
+  props: {
+    file: {
+      type: Object,
+      required: true,
+    },
+    actionComponent: {
+      type: String,
+      required: true,
+    },
+    keyPrefix: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    stagedList: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    iconName() {
+      const prefix = this.stagedList ? '-solid' : '';
+      return this.file.tempFile ? `file-addition${prefix}` : `file-modified${prefix}`;
+    },
+    iconClass() {
+      return `multi-file-${this.file.tempFile ? 'additions' : 'modified'} append-right-8`;
+    },
+  },
+  methods: {
+    ...mapActions([
+      'discardFileChanges',
+      'updateViewer',
+      'openPendingTab',
+      'unstageChange',
+      'stageChange',
+    ]),
+    openFileInEditor() {
+      return this.openPendingTab({
+        file: this.file,
+        keyPrefix: this.keyPrefix.toLowerCase(),
+      }).then(changeViewer => {
+        if (changeViewer) {
+          this.updateViewer('diff');
+        }
+      });
+    },
+    fileAction() {
+      if (this.file.staged) {
+        this.unstageChange(this.file.path);
+      } else {
+        this.stageChange(this.file.path);
+      }
+    },
+  },
+};
 </script>
 
 <template>
   <div class="multi-file-commit-list-item">
-    <icon
-      :name="iconName"
-      :size="16"
-      :css-classes="iconClass"
+    <button
+      type="button"
+      class="multi-file-commit-list-path"
+      @dblclick="fileAction"
+      @click="openFileInEditor"
+    >
+      <span class="multi-file-commit-list-file-path">
+        <icon
+          :name="iconName"
+          :size="16"
+          :css-classes="iconClass"
+        />{{ file.path }}
+      </span>
+    </button>
+    <component
+      :is="actionComponent"
+      :path="file.path"
     />
-    <span class="multi-file-commit-list-path">
-      {{ file.path }}
-    </span>
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
new file mode 100644
index 0000000000000000000000000000000000000000..dcd934f76b784f1dd7ab970c5b2f231e9880dfe0
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
@@ -0,0 +1,130 @@
+<script>
+import { __, sprintf } from '../../../locale';
+import Icon from '../../../vue_shared/components/icon.vue';
+import popover from '../../../vue_shared/directives/popover';
+import { MAX_TITLE_LENGTH, MAX_BODY_LENGTH } from '../../constants';
+
+export default {
+  directives: {
+    popover,
+  },
+  components: {
+    Icon,
+  },
+  props: {
+    text: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      scrollTop: 0,
+      isFocused: false,
+    };
+  },
+  computed: {
+    allLines() {
+      return this.text.split('\n').map((line, i) => ({
+        text: line.substr(0, this.getLineLength(i)) || ' ',
+        highlightedText: line.substr(this.getLineLength(i)),
+      }));
+    },
+  },
+  methods: {
+    handleScroll() {
+      if (this.$refs.textarea) {
+        this.$nextTick(() => {
+          this.scrollTop = this.$refs.textarea.scrollTop;
+        });
+      }
+    },
+    getLineLength(i) {
+      return i === 0 ? MAX_TITLE_LENGTH : MAX_BODY_LENGTH;
+    },
+    onInput(e) {
+      this.$emit('input', e.target.value);
+    },
+    updateIsFocused(isFocused) {
+      this.isFocused = isFocused;
+    },
+  },
+  popoverOptions: {
+    trigger: 'hover',
+    placement: 'top',
+    content: sprintf(
+      __(`
+        The character highligher helps you keep the subject line to %{titleLength} characters
+        and wrap the body at %{bodyLength} so they are readable in git.
+      `),
+      { titleLength: MAX_TITLE_LENGTH, bodyLength: MAX_BODY_LENGTH },
+    ),
+  },
+};
+</script>
+
+<template>
+  <fieldset class="common-note-form ide-commit-message-field">
+    <div
+      class="md-area"
+      :class="{
+        'is-focused': isFocused
+      }"
+    >
+      <div
+        v-once
+        class="md-header"
+      >
+        <ul class="nav-links">
+          <li>
+            {{ __('Commit Message') }}
+            <span
+              v-popover="$options.popoverOptions"
+              class="help-block prepend-left-10"
+            >
+              <icon
+                name="question"
+              />
+            </span>
+          </li>
+        </ul>
+      </div>
+      <div class="ide-commit-message-textarea-container">
+        <div class="ide-commit-message-highlights-container">
+          <div
+            class="note-textarea highlights monospace"
+            :style="{
+              transform: `translate3d(0, ${-scrollTop}px, 0)`
+            }"
+          >
+            <div
+              v-for="(line, index) in allLines"
+              :key="index"
+            >
+              <span
+                v-text="line.text"
+              >
+              </span><mark
+                v-show="line.highlightedText"
+                v-text="line.highlightedText"
+              >
+              </mark>
+            </div>
+          </div>
+        </div>
+        <textarea
+          class="note-textarea ide-commit-message-textarea"
+          name="commit-message"
+          :placeholder="__('Write a commit message...')"
+          :value="text"
+          @scroll="handleScroll"
+          @input="onInput"
+          @focus="updateIsFocused(true)"
+          @blur="updateIsFocused(false)"
+          ref="textarea"
+        >
+        </textarea>
+      </div>
+    </div>
+  </fieldset>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b660a2961cba9d556014ae0d9a2f37a471889ed3
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -0,0 +1,70 @@
+<script>
+import { mapActions, mapState, mapGetters } from 'vuex';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  directives: {
+    tooltip,
+  },
+  props: {
+    value: {
+      type: String,
+      required: true,
+    },
+    label: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    checked: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    showInput: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    ...mapState('commit', ['commitAction']),
+    ...mapGetters('commit', ['newBranchName']),
+  },
+  methods: {
+    ...mapActions('commit', ['updateCommitAction', 'updateBranchName']),
+  },
+};
+</script>
+
+<template>
+  <fieldset>
+    <label>
+      <input
+        type="radio"
+        name="commit-action"
+        :value="value"
+        @change="updateCommitAction($event.target.value)"
+        :checked="checked"
+        v-once
+      />
+      <span class="prepend-left-10">
+        <template v-if="label">
+          {{ label }}
+        </template>
+        <slot v-else></slot>
+      </span>
+    </label>
+    <div
+      v-if="commitAction === value && showInput"
+      class="ide-commit-new-branch"
+    >
+      <input
+        type="text"
+        class="form-control monospace"
+        :placeholder="newBranchName"
+        @input="updateBranchName($event.target.value)"
+      />
+    </div>
+  </fieldset>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..52dce8412ab1b29c6644340b087bc2944385bc77
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -0,0 +1,59 @@
+<script>
+import { mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  components: {
+    Icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    path: {
+      type: String,
+      required: true,
+    },
+  },
+  methods: {
+    ...mapActions(['stageChange', 'discardFileChanges']),
+  },
+};
+</script>
+
+<template>
+  <div
+    v-once
+    class="multi-file-discard-btn"
+  >
+    <button
+      v-tooltip
+      type="button"
+      class="btn btn-blank append-right-5"
+      :aria-label="__('Stage changes')"
+      :title="__('Stage changes')"
+      data-container="body"
+      @click.stop="stageChange(path)"
+    >
+      <icon
+        name="mobile-issue-close"
+        :size="12"
+      />
+    </button>
+    <button
+      v-tooltip
+      type="button"
+      class="btn btn-blank"
+      :aria-label="__('Discard changes')"
+      :title="__('Discard changes')"
+      data-container="body"
+      @click.stop="discardFileChanges(path)"
+    >
+      <icon
+        name="remove"
+        :size="12"
+      />
+    </button>
+  </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..123d60da47e6393979d16c20ecd39661183409d1
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
@@ -0,0 +1,45 @@
+<script>
+import { mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  components: {
+    Icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    path: {
+      type: String,
+      required: true,
+    },
+  },
+  methods: {
+    ...mapActions(['unstageChange']),
+  },
+};
+</script>
+
+<template>
+  <div
+    v-once
+    class="multi-file-discard-btn"
+  >
+    <button
+      v-tooltip
+      type="button"
+      class="btn btn-blank"
+      :aria-label="__('Unstage changes')"
+      :title="__('Unstage changes')"
+      data-container="body"
+      @click="unstageChange(path)"
+    >
+      <icon
+        name="history"
+        :size="12"
+      />
+    </button>
+  </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0c44a755f56f3dfafa39e630d86107a81b7dc1a0
--- /dev/null
+++ b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
@@ -0,0 +1,130 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import { __, sprintf } from '~/locale';
+
+export default {
+  components: {
+    Icon,
+  },
+  props: {
+    hasChanges: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    mergeRequestId: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    viewer: {
+      type: String,
+      required: true,
+    },
+    showShadow: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  computed: {
+    mergeReviewLine() {
+      return sprintf(__('Reviewing (merge request !%{mergeRequestId})'), {
+        mergeRequestId: this.mergeRequestId,
+      });
+    },
+  },
+  methods: {
+    changeMode(mode) {
+      this.$emit('click', mode);
+    },
+  },
+};
+</script>
+
+<template>
+  <div
+    class="dropdown"
+    :class="{
+      shadow: showShadow,
+    }"
+  >
+    <button
+      type="button"
+      class="btn btn-primary btn-sm"
+      :class="{
+        'btn-inverted': hasChanges,
+      }"
+      data-toggle="dropdown"
+    >
+      <template v-if="viewer === 'mrdiff' && mergeRequestId">
+        {{ mergeReviewLine }}
+      </template>
+      <template v-else-if="viewer === 'editor'">
+        {{ __('Editing') }}
+      </template>
+      <template v-else>
+        {{ __('Reviewing') }}
+      </template>
+      <icon
+        name="angle-down"
+        :size="12"
+        css-classes="caret-down"
+      />
+    </button>
+    <div class="dropdown-menu dropdown-menu-selectable dropdown-open-left">
+      <ul>
+        <template v-if="mergeRequestId">
+          <li>
+            <a
+              href="#"
+              @click.prevent="changeMode('mrdiff')"
+              :class="{
+                'is-active': viewer === 'mrdiff',
+              }"
+            >
+              <strong class="dropdown-menu-inner-title">
+                {{ mergeReviewLine }}
+              </strong>
+              <span class="dropdown-menu-inner-content">
+                {{ __('Compare changes with the merge request target branch') }}
+              </span>
+            </a>
+          </li>
+          <li
+            role="separator"
+            class="divider"
+          >
+          </li>
+        </template>
+        <li>
+          <a
+            href="#"
+            @click.prevent="changeMode('editor')"
+            :class="{
+              'is-active': viewer === 'editor',
+            }"
+          >
+            <strong class="dropdown-menu-inner-title">{{ __('Editing') }}</strong>
+            <span class="dropdown-menu-inner-content">
+              {{ __('View and edit lines') }}
+            </span>
+          </a>
+        </li>
+        <li>
+          <a
+            href="#"
+            @click.prevent="changeMode('diff')"
+            :class="{
+              'is-active': viewer === 'diff',
+            }"
+          >
+            <strong class="dropdown-menu-inner-title">{{ __('Reviewing') }}</strong>
+            <span class="dropdown-menu-inner-content">
+              {{ __('Compare changes with the last commit') }}
+            </span>
+          </a>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 89981ab2c65b240758a39b953abcd5492d7d72d6..1c237c0ec97c90442bde20473d1cf1df4e06fdbc 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -1,51 +1,49 @@
 <script>
-  import { mapState, mapGetters } from 'vuex';
-  import ideSidebar from './ide_side_bar.vue';
-  import ideContextbar from './ide_context_bar.vue';
-  import repoTabs from './repo_tabs.vue';
-  import repoFileButtons from './repo_file_buttons.vue';
-  import ideStatusBar from './ide_status_bar.vue';
-  import repoPreview from './repo_preview.vue';
-  import repoEditor from './repo_editor.vue';
+import { mapState, mapGetters } from 'vuex';
+import ideSidebar from './ide_side_bar.vue';
+import ideContextbar from './ide_context_bar.vue';
+import repoTabs from './repo_tabs.vue';
+import ideStatusBar from './ide_status_bar.vue';
+import repoEditor from './repo_editor.vue';
 
-  export default {
-    components: {
-      ideSidebar,
-      ideContextbar,
-      repoTabs,
-      repoFileButtons,
-      ideStatusBar,
-      repoEditor,
-      repoPreview,
+export default {
+  components: {
+    ideSidebar,
+    ideContextbar,
+    repoTabs,
+    ideStatusBar,
+    repoEditor,
+  },
+  props: {
+    emptyStateSvgPath: {
+      type: String,
+      required: true,
     },
-    props: {
-      emptyStateSvgPath: {
-        type: String,
-        required: true,
-      },
+    noChangesStateSvgPath: {
+      type: String,
+      required: true,
     },
-    computed: {
-      ...mapState([
-        'currentBlobView',
-        'selectedFile',
-      ]),
-      ...mapGetters([
-        'changedFiles',
-        'activeFile',
-      ]),
+    committedStateSvgPath: {
+      type: String,
+      required: true,
     },
-    mounted() {
-      const returnValue = 'Are you sure you want to lose unsaved changes?';
-      window.onbeforeunload = (e) => {
-        if (!this.changedFiles.length) return undefined;
+  },
+  computed: {
+    ...mapState(['changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId']),
+    ...mapGetters(['activeFile', 'hasChanges']),
+  },
+  mounted() {
+    const returnValue = 'Are you sure you want to lose unsaved changes?';
+    window.onbeforeunload = e => {
+      if (!this.changedFiles.length) return undefined;
 
-        Object.assign(e, {
-          returnValue,
-        });
-        return returnValue;
-      };
-    },
-  };
+      Object.assign(e, {
+        returnValue,
+      });
+      return returnValue;
+    };
+  },
+};
 </script>
 
 <template>
@@ -59,20 +57,28 @@
       <template
         v-if="activeFile"
       >
-        <repo-tabs/>
-        <component
+        <repo-tabs
+          :active-file="activeFile"
+          :files="openFiles"
+          :viewer="viewer"
+          :has-changes="hasChanges"
+          :merge-request-id="currentMergeRequestId"
+        />
+        <repo-editor
           class="multi-file-edit-pane-content"
-          :is="currentBlobView"
+          :file="activeFile"
         />
-        <repo-file-buttons />
         <ide-status-bar
-          :file="selectedFile"
+          :file="activeFile"
         />
       </template>
       <template
         v-else
       >
-        <div class="ide-empty-state">
+        <div
+          v-once
+          class="ide-empty-state"
+        >
           <div class="row js-empty-state">
             <div class="col-xs-12">
               <div class="svg-content svg-250">
@@ -94,6 +100,9 @@
         </div>
       </template>
     </div>
-    <ide-contextbar/>
+    <ide-contextbar
+      :no-changes-state-svg-path="noChangesStateSvgPath"
+      :committed-state-svg-path="committedStateSvgPath"
+    />
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue
index 9d933b8891de96647fc05f6fd6cd04e8717f9b94..627fbeb9adf53287d9ee6e9e3d0a791cfa3c2bc7 100644
--- a/app/assets/javascripts/ide/components/ide_context_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_context_bar.vue
@@ -1,108 +1,42 @@
 <script>
-  import { mapGetters, mapState, mapActions } from 'vuex';
-  import icon from '~/vue_shared/components/icon.vue';
-  import panelResizer from '~/vue_shared/components/panel_resizer.vue';
-  import repoCommitSection from './repo_commit_section.vue';
+import icon from '~/vue_shared/components/icon.vue';
+import panelResizer from '~/vue_shared/components/panel_resizer.vue';
+import repoCommitSection from './repo_commit_section.vue';
+import ResizablePanel from './resizable_panel.vue';
 
-  export default {
-    components: {
-      repoCommitSection,
-      icon,
-      panelResizer,
+export default {
+  components: {
+    repoCommitSection,
+    icon,
+    panelResizer,
+    ResizablePanel,
+  },
+  props: {
+    noChangesStateSvgPath: {
+      type: String,
+      required: true,
     },
-    data() {
-      return {
-        width: 290,
-      };
+    committedStateSvgPath: {
+      type: String,
+      required: true,
     },
-    computed: {
-      ...mapState([
-        'rightPanelCollapsed',
-      ]),
-      ...mapGetters([
-        'changedFiles',
-      ]),
-      currentIcon() {
-        return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
-      },
-      maxSize() {
-        return window.innerWidth / 2;
-      },
-      panelStyle() {
-        if (!this.rightPanelCollapsed) {
-          return { width: `${this.width}px` };
-        }
-        return {};
-      },
-    },
-    methods: {
-      ...mapActions([
-        'setPanelCollapsedStatus',
-        'setResizingStatus',
-      ]),
-      toggleCollapsed() {
-        this.setPanelCollapsedStatus({
-          side: 'right',
-          collapsed: !this.rightPanelCollapsed,
-        });
-      },
-      resizingStarted() {
-        this.setResizingStatus(true);
-      },
-      resizingEnded() {
-        this.setResizingStatus(false);
-      },
-    },
-  };
+  },
+};
 </script>
 
 <template>
-  <div
-    class="multi-file-commit-panel"
-    :class="{
-      'is-collapsed': rightPanelCollapsed,
-    }"
-    :style="panelStyle"
+  <resizable-panel
+    :collapsible="true"
+    :initial-width="340"
+    side="right"
   >
-    <div class="multi-file-commit-panel-section">
-      <header
-        class="multi-file-commit-panel-header"
-        :class="{
-          'is-collapsed': rightPanelCollapsed,
-        }"
-      >
-        <div
-          class="multi-file-commit-panel-header-title"
-          v-if="!rightPanelCollapsed"
-        >
-          <icon
-            name="list-bulleted"
-            :size="18"
-          />
-          Staged
-        </div>
-        <button
-          type="button"
-          class="btn btn-transparent multi-file-commit-panel-collapse-btn"
-          @click="toggleCollapsed"
-        >
-          <icon
-            :name="currentIcon"
-            :size="18"
-          />
-        </button>
-      </header>
-      <repo-commit-section />
+    <div
+      class="multi-file-commit-panel-section"
+    >
+      <repo-commit-section
+        :no-changes-state-svg-path="noChangesStateSvgPath"
+        :committed-state-svg-path="committedStateSvgPath"
+      />
     </div>
-    <panel-resizer
-      :size.sync="width"
-      :enabled="!rightPanelCollapsed"
-      :start-size="290"
-      :min-size="200"
-      :max-size="maxSize"
-      @resize-start="resizingStarted"
-      @resize-end="resizingEnded"
-      side="left"
-    />
-  </div>
+  </resizable-panel>
 </template>
diff --git a/app/assets/javascripts/ide/components/ide_external_links.vue b/app/assets/javascripts/ide/components/ide_external_links.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c6f6e0d234869167e15dad49fe0b21390177bee9
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_external_links.vue
@@ -0,0 +1,43 @@
+<script>
+import icon from '~/vue_shared/components/icon.vue';
+
+export default {
+  components: {
+    icon,
+  },
+  props: {
+    projectUrl: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    goBackUrl() {
+      return document.referrer || this.projectUrl;
+    },
+  },
+};
+</script>
+
+<template>
+  <nav
+    class="ide-external-links"
+    v-once
+  >
+    <p>
+      <a
+        :href="goBackUrl"
+        class="ide-sidebar-link"
+      >
+        <icon
+          :size="16"
+          class="append-right-8"
+          name="go-back"
+        />
+        <span class="ide-external-links-text">
+          {{ s__('Go back') }}
+        </span>
+      </a>
+    </p>
+  </nav>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_file_buttons.vue b/app/assets/javascripts/ide/components/ide_file_buttons.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a6c6f46a14418a5ad9c169c69aab7916a8048c86
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_file_buttons.vue
@@ -0,0 +1,84 @@
+<script>
+import { __ } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+  components: {
+    Icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    file: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    showButtons() {
+      return (
+        this.file.rawPath || this.file.blamePath || this.file.commitsPath || this.file.permalink
+      );
+    },
+    rawDownloadButtonLabel() {
+      return this.file.binary ? __('Download') : __('Raw');
+    },
+  },
+};
+</script>
+
+<template>
+  <div
+    v-if="showButtons"
+    class="pull-right ide-btn-group"
+  >
+    <a
+      v-tooltip
+      v-if="!file.binary"
+      :href="file.blamePath"
+      :title="__('Blame')"
+      class="btn btn-xs btn-transparent blame"
+    >
+      <icon
+        name="blame"
+        :size="16"
+      />
+    </a>
+    <a
+      v-tooltip
+      :href="file.commitsPath"
+      :title="__('History')"
+      class="btn btn-xs btn-transparent history"
+    >
+      <icon
+        name="history"
+        :size="16"
+      />
+    </a>
+    <a
+      v-tooltip
+      :href="file.permalink"
+      :title="__('Permalink')"
+      class="btn btn-xs btn-transparent permalink"
+    >
+      <icon
+        name="link"
+        :size="16"
+      />
+    </a>
+    <a
+      v-tooltip
+      :href="file.rawPath"
+      target="_blank"
+      class="btn btn-xs btn-transparent prepend-left-10 raw"
+      rel="noopener noreferrer"
+      :title="rawDownloadButtonLabel">
+      <icon
+        name="download"
+        :size="16"
+      />
+    </a>
+  </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
index 2fbff2bd789799213f28f4f30c0d7d5ed7563a9a..eb2749e6151e0973aad605726c51f1c998065a76 100644
--- a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
@@ -1,31 +1,31 @@
 <script>
-import icon from '~/vue_shared/components/icon.vue';
-import repoTree from './ide_repo_tree.vue';
-import newDropdown from './new_dropdown/index.vue';
+  import icon from '~/vue_shared/components/icon.vue';
+  import repoTree from './ide_repo_tree.vue';
+  import newDropdown from './new_dropdown/index.vue';
 
-export default {
-  components: {
-    repoTree,
-    icon,
-    newDropdown,
-  },
-  props: {
-    projectId: {
-      type: String,
-      required: true,
+  export default {
+    components: {
+      repoTree,
+      icon,
+      newDropdown,
     },
-    branch: {
-      type: Object,
-      required: true,
+    props: {
+      projectId: {
+        type: String,
+        required: true,
+      },
+      branch: {
+        type: Object,
+        required: true,
+      },
     },
-  },
-};
+  };
 </script>
 
 <template>
   <div class="branch-container">
     <div class="branch-header">
-      <div class="branch-header-title">
+      <div class="branch-header-title str-truncated ref-name">
         <icon
           name="branch"
           :size="12"
@@ -40,8 +40,8 @@ export default {
         />
       </div>
     </div>
-    <div>
-      <repo-tree :tree-id="branch.treeId" />
-    </div>
+    <repo-tree
+      :tree="branch.tree"
+    />
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue
index 32bf7175c88cbae690ee74e5c9eb2aa3f4f4c86e..a6f40286ac1190fa690caf0ccb52540bf68ebf4f 100644
--- a/app/assets/javascripts/ide/components/ide_project_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_project_tree.vue
@@ -1,11 +1,15 @@
 <script>
-import projectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
-import branchesTree from './ide_project_branches_tree.vue';
+import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
+import Identicon from '../../vue_shared/components/identicon.vue';
+import BranchesTree from './ide_project_branches_tree.vue';
+import ExternalLinks from './ide_external_links.vue';
 
 export default {
   components: {
-    branchesTree,
-    projectAvatarImage,
+    BranchesTree,
+    ExternalLinks,
+    ProjectAvatarImage,
+    Identicon,
   },
   props: {
     project: {
@@ -23,7 +27,10 @@ export default {
         :title="project.name"
         :href="project.web_url"
       >
-        <div class="avatar-container s40 project-avatar">
+        <div
+          v-if="project.avatar_url"
+          class="avatar-container s40 project-avatar"
+        >
           <project-avatar-image
             class="avatar-container project-avatar"
             :link-href="project.path"
@@ -32,11 +39,20 @@ export default {
             :img-size="40"
           />
         </div>
+        <identicon
+          v-else
+          size-class="s40"
+          :entity-id="project.id"
+          :entity-name="project.name"
+        />
         <div class="sidebar-context-title">
           {{ project.name }}
         </div>
       </a>
     </div>
+    <external-links
+      :project-url="project.web_url"
+    />
     <div class="multi-file-commit-panel-inner-scroll">
       <branches-tree
         v-for="branch in project.branches"
diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue
index 4a324264992fef3f681ff4ed45e353cf747b2d2f..e6af88e04bc8459ada2c11454e94ada1b1b72a48 100644
--- a/app/assets/javascripts/ide/components/ide_repo_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_repo_tree.vue
@@ -1,74 +1,41 @@
 <script>
-import { mapState } from 'vuex';
-import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-import repoPreviousDirectory from './repo_prev_directory.vue';
-import repoFile from './repo_file.vue';
-import { treeList } from '../stores/utils';
+import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
+import RepoFile from './repo_file.vue';
 
 export default {
   components: {
-    repoPreviousDirectory,
-    repoFile,
-    skeletonLoadingContainer,
+    RepoFile,
+    SkeletonLoadingContainer,
   },
   props: {
-    treeId: {
-      type: String,
+    tree: {
+      type: Object,
       required: true,
     },
   },
-  computed: {
-    ...mapState([
-      'trees',
-      'isRoot',
-    ]),
-    ...mapState({
-      projectName(state) {
-        return state.project.name;
-      },
-    }),
-    fetchedList() {
-      return treeList(this.$store.state, this.treeId);
-    },
-    hasPreviousDirectory() {
-      return !this.isRoot && this.fetchedList.length;
-    },
-    showLoading() {
-      if (this.trees[this.treeId]) {
-        return this.trees[this.treeId].loading;
-      }
-      return true;
-    },
-  },
 };
 </script>
 
 <template>
-  <div>
-    <div class="ide-file-list">
-      <table class="table">
-        <tbody
-          v-if="treeId"
-        >
-          <repo-previous-directory
-            v-if="hasPreviousDirectory"
-          />
-          <template v-if="showLoading">
-            <div
-              class="multi-file-loading-container"
-              v-for="n in 3"
-              :key="n"
-            >
-              <skeleton-loading-container />
-            </div>
-          </template>
-          <repo-file
-            v-for="file in fetchedList"
-            :key="file.key"
-            :file="file"
-          />
-        </tbody>
-      </table>
-    </div>
+  <div
+    class="ide-file-list"
+  >
+    <template v-if="tree.loading">
+      <div
+        class="multi-file-loading-container"
+        v-for="n in 3"
+        :key="n"
+      >
+        <skeleton-loading-container />
+      </div>
+    </template>
+    <template v-else>
+      <repo-file
+        v-for="file in tree.tree"
+        :key="file.key"
+        :file="file"
+        :level="0"
+      />
+    </template>
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index 18b5059a17ff02b0a1ae0a7c5dc15cd4ddbef15b..8cf1ccb4fce2f0873479f8f09c1a846759202c8f 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -1,9 +1,10 @@
 <script>
-  import { mapState, mapActions } from 'vuex';
+  import { mapState, mapGetters } from 'vuex';
   import icon from '~/vue_shared/components/icon.vue';
   import panelResizer from '~/vue_shared/components/panel_resizer.vue';
   import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
   import projectTree from './ide_project_tree.vue';
+  import ResizablePanel from './resizable_panel.vue';
 
   export default {
     components: {
@@ -11,65 +12,27 @@
       icon,
       panelResizer,
       skeletonLoadingContainer,
-    },
-    data() {
-      return {
-        width: 290,
-      };
+      ResizablePanel,
     },
     computed: {
       ...mapState([
         'loading',
-        'projects',
-        'leftPanelCollapsed',
       ]),
-      currentIcon() {
-        return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
-      },
-      maxSize() {
-        return window.innerWidth / 2;
-      },
-      panelStyle() {
-        if (!this.leftPanelCollapsed) {
-          return { width: `${this.width}px` };
-        }
-        return {};
-      },
-      showLoading() {
-        return this.loading;
-      },
-    },
-    methods: {
-      ...mapActions([
-        'setPanelCollapsedStatus',
-        'setResizingStatus',
+      ...mapGetters([
+        'projectsWithTrees',
       ]),
-      toggleCollapsed() {
-        this.setPanelCollapsedStatus({
-          side: 'left',
-          collapsed: !this.leftPanelCollapsed,
-        });
-      },
-      resizingStarted() {
-        this.setResizingStatus(true);
-      },
-      resizingEnded() {
-        this.setResizingStatus(false);
-      },
     },
   };
 </script>
 
 <template>
-  <div
-    class="multi-file-commit-panel"
-    :class="{
-      'is-collapsed': leftPanelCollapsed,
-    }"
-    :style="panelStyle"
+  <resizable-panel
+    :collapsible="false"
+    :initial-width="290"
+    side="left"
   >
     <div class="multi-file-commit-panel-inner">
-      <template v-if="showLoading">
+      <template v-if="loading">
         <div
           class="multi-file-loading-container"
           v-for="n in 3"
@@ -79,36 +42,10 @@
         </div>
       </template>
       <project-tree
-        v-for="project in projects"
+        v-for="project in projectsWithTrees"
         :key="project.id"
         :project="project"
       />
     </div>
-    <button
-      type="button"
-      class="btn btn-transparent left-collapse-btn"
-      @click="toggleCollapsed"
-    >
-      <icon
-        :name="currentIcon"
-        :size="18"
-      />
-      <span
-        v-if="!leftPanelCollapsed"
-        class="collapse-text"
-      >
-        Collapse sidebar
-      </span>
-    </button>
-    <panel-resizer
-      :size.sync="width"
-      :enabled="!leftPanelCollapsed"
-      :start-size="290"
-      :min-size="200"
-      :max-size="maxSize"
-      @resize-start="resizingStarted"
-      @resize-end="resizingEnded"
-      side="right"
-    />
-  </div>
+  </resizable-panel>
 </template>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 97ae64b206d2aff7cc46bd2f026c3523b39aabf7..c13eeeace3f0a8728d6419249c59ebf12ae6dd34 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -1,66 +1,53 @@
 <script>
-  import { mapState } from 'vuex';
-  import icon from '~/vue_shared/components/icon.vue';
-  import tooltip from '~/vue_shared/directives/tooltip';
-  import timeAgoMixin from '~/vue_shared/mixins/timeago';
+import icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import timeAgoMixin from '~/vue_shared/mixins/timeago';
 
-  export default {
-    components: {
-      icon,
+export default {
+  components: {
+    icon,
+  },
+  directives: {
+    tooltip,
+  },
+  mixins: [timeAgoMixin],
+  props: {
+    file: {
+      type: Object,
+      required: true,
     },
-    directives: {
-      tooltip,
-    },
-    mixins: [
-      timeAgoMixin,
-    ],
-    props: {
-      file: {
-        type: Object,
-        required: true,
-      },
-    },
-    computed: {
-      ...mapState([
-        'selectedFile',
-      ]),
-    },
-  };
+  },
+};
 </script>
 
 <template>
   <div class="ide-status-bar">
     <div>
-      <icon
-        name="branch"
-        :size="12"
-      />
-      {{ selectedFile.branchId }}
-    </div>
-    <div>
-      <div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
+      <div v-if="file.lastCommit && file.lastCommit.id">
         Last commit:
         <a
           v-tooltip
-          :title="selectedFile.lastCommit.message"
-          :href="selectedFile.lastCommit.url"
+          :title="file.lastCommit.message"
+          :href="file.lastCommit.url"
         >
-          {{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
-          {{ selectedFile.lastCommit.author }}
+          {{ timeFormated(file.lastCommit.updatedAt) }} by
+          {{ file.lastCommit.author }}
         </a>
       </div>
     </div>
     <div class="text-right">
-      {{ selectedFile.name }}
+      {{ file.name }}
     </div>
     <div class="text-right">
-      {{ selectedFile.eol }}
+      {{ file.eol }}
     </div>
-    <div class="text-right">
+    <div
+      class="text-right"
+      v-if="!file.binary">
       {{ file.editorRow }}:{{ file.editorColumn }}
     </div>
     <div class="text-right">
-      {{ selectedFile.fileLanguage }}
+      {{ file.fileLanguage }}
     </div>
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/mr_file_icon.vue b/app/assets/javascripts/ide/components/mr_file_icon.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8a440902dfc7af539798db3383e42ca7e067fef6
--- /dev/null
+++ b/app/assets/javascripts/ide/components/mr_file_icon.vue
@@ -0,0 +1,23 @@
+<script>
+import icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  components: {
+    icon,
+  },
+  directives: {
+    tooltip,
+  },
+};
+</script>
+
+<template>
+  <icon
+    name="git-merge"
+    v-tooltip
+    title="__('Part of merge request changes')"
+    css-classes="ide-file-changed-icon"
+    :size="12"
+  />
+</template>
diff --git a/app/assets/javascripts/ide/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue
deleted file mode 100644
index 1e8d5bb64537b79f10a2ea0885008f71dbccee2b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/ide/components/new_branch_form.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-<script>
-  import { mapState, mapActions } from 'vuex';
-  import flash, { hideFlash } from '~/flash';
-  import loadingIcon from '~/vue_shared/components/loading_icon.vue';
-
-  export default {
-    components: {
-      loadingIcon,
-    },
-    data() {
-      return {
-        branchName: '',
-        loading: false,
-      };
-    },
-    computed: {
-      ...mapState([
-        'currentBranch',
-      ]),
-      btnDisabled() {
-        return this.loading || this.branchName === '';
-      },
-    },
-    created() {
-      // Dropdown is outside of Vue instance & is controlled by Bootstrap
-      this.$dropdown = $('.git-revision-dropdown');
-
-      // text element is outside Vue app
-      this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
-    },
-    methods: {
-      ...mapActions([
-        'createNewBranch',
-      ]),
-      toggleDropdown() {
-        this.$dropdown.dropdown('toggle');
-      },
-      submitNewBranch() {
-        // need to query as the element is appended outside of Vue
-        const flashEl = this.$refs.flashContainer.querySelector('.flash-alert');
-
-        this.loading = true;
-
-        if (flashEl) {
-          hideFlash(flashEl, false);
-        }
-
-        this.createNewBranch(this.branchName)
-          .then(() => {
-            this.loading = false;
-            this.branchName = '';
-
-            if (this.dropdownText) {
-              this.dropdownText.textContent = this.currentBranchId;
-            }
-
-            this.toggleDropdown();
-          })
-          .catch(res => res.json().then((data) => {
-            this.loading = false;
-            flash(data.message, 'alert', this.$el);
-          }));
-      },
-    },
-  };
-</script>
-
-<template>
-  <div>
-    <div
-      class="flash-container"
-      ref="flashContainer"
-    >
-    </div>
-    <p>
-      Create from:
-      <code>{{ currentBranch }}</code>
-    </p>
-    <input
-      class="form-control js-new-branch-name"
-      type="text"
-      placeholder="Name new branch"
-      v-model="branchName"
-      @keyup.enter.stop.prevent="submitNewBranch"
-    />
-    <div class="prepend-top-default clearfix">
-      <button
-        type="button"
-        class="btn btn-primary pull-left"
-        :disabled="btnDisabled"
-        @click.stop.prevent="submitNewBranch"
-      >
-        <loading-icon
-          v-if="loading"
-          :inline="true"
-        />
-        <span>Create</span>
-      </button>
-      <button
-        type="button"
-        class="btn btn-default pull-right"
-        @click.stop.prevent="toggleDropdown"
-      >
-        Cancel
-      </button>
-    </div>
-  </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index ef653357f5f21e68a917374f41492ad741e23f4f..769e9b79cadbd5a8d8fdc4262b11970085fc497e 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -1,7 +1,8 @@
 <script>
+  import { mapActions } from 'vuex';
+  import icon from '~/vue_shared/components/icon.vue';
   import newModal from './modal.vue';
   import upload from './upload.vue';
-  import icon from '../../../vue_shared/components/icon.vue';
 
   export default {
     components: {
@@ -18,37 +19,46 @@
         type: String,
         required: true,
       },
-      parent: {
-        type: Object,
-        default: null,
-      },
     },
     data() {
       return {
         openModal: false,
         modalType: '',
+        dropdownOpen: false,
       };
     },
     methods: {
+      ...mapActions([
+        'createTempEntry',
+      ]),
       createNewItem(type) {
         this.modalType = type;
         this.openModal = true;
+        this.dropdownOpen = false;
       },
       hideModal() {
         this.openModal = false;
       },
+      openDropdown() {
+        this.dropdownOpen = !this.dropdownOpen;
+      },
     },
   };
 </script>
 
 <template>
-  <div class="repo-new-btn pull-right">
-    <div class="dropdown">
+  <div class="ide-new-btn">
+    <div
+      class="dropdown"
+      :class="{
+        open: dropdownOpen,
+      }"
+    >
       <button
         type="button"
         class="btn btn-sm btn-default dropdown-toggle add-to-tree"
-        data-toggle="dropdown"
         aria-label="Create new file or directory"
+        @click.stop="openDropdown()"
       >
         <icon
           name="plus"
@@ -66,7 +76,7 @@
           <a
             href="#"
             role="button"
-            @click.prevent="createNewItem('blob')"
+            @click.stop.prevent="createNewItem('blob')"
           >
             {{ __('New file') }}
           </a>
@@ -75,14 +85,14 @@
           <upload
             :branch-id="branch"
             :path="path"
-            :parent="parent"
+            @create="createTempEntry"
           />
         </li>
         <li>
           <a
             href="#"
             role="button"
-            @click.prevent="createNewItem('tree')"
+            @click.stop.prevent="createNewItem('tree')"
           >
             {{ __('New directory') }}
           </a>
@@ -94,8 +104,8 @@
       :type="modalType"
       :branch-id="branch"
       :path="path"
-      :parent="parent"
       @hide="hideModal"
+      @create="createTempEntry"
     />
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 36cd825c6dddb4ec1c5cecd08f5e6f2a8838f6bf..4b5a50785b611ba49c5fe4eaec9eac63c4a8a96c 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -1,88 +1,75 @@
 <script>
-  import { mapActions, mapState } from 'vuex';
-  import { __ } from '../../../locale';
-  import modal from '../../../vue_shared/components/modal.vue';
+import { __ } from '~/locale';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
 
-  export default {
-    components: {
-      modal,
+export default {
+  components: {
+    DeprecatedModal,
+  },
+  props: {
+    branchId: {
+      type: String,
+      required: true,
     },
-    props: {
-      branchId: {
-        type: String,
-        required: true,
-      },
-      parent: {
-        type: Object,
-        default: null,
-      },
-      type: {
-        type: String,
-        required: true,
-      },
-      path: {
-        type: String,
-        required: true,
-      },
+    type: {
+      type: String,
+      required: true,
     },
-    data() {
-      return {
-        entryName: this.path !== '' ? `${this.path}/` : '',
-      };
+    path: {
+      type: String,
+      required: true,
     },
-    computed: {
-      ...mapState([
-        'currentProjectId',
-      ]),
-      modalTitle() {
-        if (this.type === 'tree') {
-          return __('Create new directory');
-        }
+  },
+  data() {
+    return {
+      entryName: this.path !== '' ? `${this.path}/` : '',
+    };
+  },
+  computed: {
+    modalTitle() {
+      if (this.type === 'tree') {
+        return __('Create new directory');
+      }
 
-        return __('Create new file');
-      },
-      buttonLabel() {
-        if (this.type === 'tree') {
-          return __('Create directory');
-        }
-
-        return __('Create file');
-      },
-      formLabelName() {
-        if (this.type === 'tree') {
-          return __('Directory name');
-        }
+      return __('Create new file');
+    },
+    buttonLabel() {
+      if (this.type === 'tree') {
+        return __('Create directory');
+      }
 
-        return __('File name');
-      },
+      return __('Create file');
     },
-    mounted() {
-      this.$refs.fieldName.focus();
+    formLabelName() {
+      if (this.type === 'tree') {
+        return __('Directory name');
+      }
+
+      return __('File name');
     },
-    methods: {
-      ...mapActions([
-        'createTempEntry',
-      ]),
-      createEntryInStore() {
-        this.createTempEntry({
-          projectId: this.currentProjectId,
-          branchId: this.branchId,
-          parent: this.parent,
-          name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
-          type: this.type,
-        });
+  },
+  mounted() {
+    this.$refs.fieldName.focus();
+  },
+  methods: {
+    createEntryInStore() {
+      this.$emit('create', {
+        branchId: this.branchId,
+        name: this.entryName,
+        type: this.type,
+      });
 
-        this.hideModal();
-      },
-      hideModal() {
-        this.$emit('hide');
-      },
+      this.hideModal();
+    },
+    hideModal() {
+      this.$emit('hide');
     },
-  };
+  },
+};
 </script>
 
 <template>
-  <modal
+  <deprecated-modal
     :title="modalTitle"
     :primary-button-label="buttonLabel"
     kind="success"
@@ -108,5 +95,5 @@
         </div>
       </fieldset>
     </form>
-  </modal>
+  </deprecated-modal>
 </template>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index 6244737fa43414c464efb7d5fae2e2c42773ef24..c165af5ce52b97785bcbd8410138baa60e40231c 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -1,23 +1,16 @@
 <script>
-  import { mapActions, mapState } from 'vuex';
-
   export default {
     props: {
       branchId: {
         type: String,
         required: true,
       },
-      parent: {
-        type: Object,
-        default: null,
+      path: {
+        type: String,
+        required: false,
+        default: '',
       },
     },
-    computed: {
-      ...mapState([
-        'trees',
-        'currentProjectId',
-      ]),
-    },
     mounted() {
       this.$refs.fileUpload.addEventListener('change', this.openFile);
     },
@@ -25,9 +18,6 @@
       this.$refs.fileUpload.removeEventListener('change', this.openFile);
     },
     methods: {
-      ...mapActions([
-        'createTempEntry',
-      ]),
       createFile(target, file, isText) {
         const { name } = file;
         let { result } = target;
@@ -36,11 +26,9 @@
           result = result.split('base64,')[1];
         }
 
-        this.createTempEntry({
-          name,
-          projectId: this.currentProjectId,
+        this.$emit('create', {
+          name: `${(this.path ? `${this.path}/` : '')}${name}`,
           branchId: this.branchId,
-          parent: this.parent,
           type: 'blob',
           content: result,
           base64: !isText,
@@ -73,7 +61,7 @@
     <a
       href="#"
       role="button"
-      @click.prevent="startFileUpload"
+      @click.stop.prevent="startFileUpload"
     >
       {{ __('Upload file') }}
     </a>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 37f2cf30a29cdb54639647c1dcb112b18b29b4c5..877d1b5e026f5d6bfe7fdce162f0e81cb1149d4a 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -1,171 +1,127 @@
 <script>
-import { mapGetters, mapState, mapActions } from 'vuex';
+import { mapState, mapActions, mapGetters } from 'vuex';
 import tooltip from '~/vue_shared/directives/tooltip';
-import icon from '~/vue_shared/components/icon.vue';
-import modal from '~/vue_shared/components/modal.vue';
-import commitFilesList from './commit_sidebar/list.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import CommitFilesList from './commit_sidebar/list.vue';
+import EmptyState from './commit_sidebar/empty_state.vue';
+import CommitMessageField from './commit_sidebar/message_field.vue';
+import * as consts from '../stores/modules/commit/constants';
+import Actions from './commit_sidebar/actions.vue';
 
 export default {
   components: {
-    modal,
-    icon,
-    commitFilesList,
+    DeprecatedModal,
+    Icon,
+    CommitFilesList,
+    EmptyState,
+    Actions,
+    LoadingButton,
+    CommitMessageField,
   },
   directives: {
     tooltip,
   },
-  data() {
-    return {
-      showNewBranchModal: false,
-      submitCommitsLoading: false,
-      startNewMR: false,
-      commitMessage: '',
-    };
-  },
-  computed: {
-    ...mapState([
-      'currentProjectId',
-      'currentBranchId',
-      'rightPanelCollapsed',
-    ]),
-    ...mapGetters([
-      'changedFiles',
-    ]),
-    commitButtonDisabled() {
-      return this.commitMessage === '' || this.submitCommitsLoading || !this.changedFiles.length;
+  props: {
+    noChangesStateSvgPath: {
+      type: String,
+      required: true,
     },
-    commitMessageCount() {
-      return this.commitMessage.length;
+    committedStateSvgPath: {
+      type: String,
+      required: true,
     },
   },
+  computed: {
+    ...mapState(['changedFiles', 'stagedFiles', 'rightPanelCollapsed']),
+    ...mapState('commit', ['commitMessage', 'submitCommitLoading']),
+    ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']),
+  },
   methods: {
-    ...mapActions([
-      'checkCommitStatus',
+    ...mapActions('commit', [
+      'updateCommitMessage',
+      'discardDraft',
       'commitChanges',
-      'getTreeData',
-      'setPanelCollapsedStatus',
+      'updateCommitAction',
     ]),
-    makeCommit(newBranch = false) {
-      const createNewBranch = newBranch || this.startNewMR;
-
-      const payload = {
-        branch: createNewBranch ?
-          `${this.currentBranchId}-${new Date().getTime().toString()}` :
-          this.currentBranchId,
-        commit_message: this.commitMessage,
-        actions: this.changedFiles.map(f => ({
-          action: f.tempFile ? 'create' : 'update',
-          file_path: f.path,
-          content: f.content,
-          encoding: f.base64 ? 'base64' : 'text',
-        })),
-        start_branch: createNewBranch ? this.currentBranchId : undefined,
-      };
-
-      this.showNewBranchModal = false;
-      this.submitCommitsLoading = true;
-
-      this.commitChanges({ payload, newMr: this.startNewMR })
-        .then(() => {
-          this.submitCommitsLoading = false;
-          this.commitMessage = '';
-          this.startNewMR = false;
-        })
-        .catch(() => {
-          this.submitCommitsLoading = false;
-        });
-    },
-    tryCommit() {
-      this.submitCommitsLoading = true;
-
-      this.checkCommitStatus()
-        .then((branchChanged) => {
-          if (branchChanged) {
-            this.showNewBranchModal = true;
-          } else {
-            this.makeCommit();
-          }
-        })
-        .catch(() => {
-          this.submitCommitsLoading = false;
-        });
-    },
-    toggleCollapsed() {
-      this.setPanelCollapsedStatus({
-        side: 'right',
-        collapsed: !this.rightPanelCollapsed,
-      });
+    forceCreateNewBranch() {
+      return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => this.commitChanges());
     },
   },
 };
 </script>
 
 <template>
-  <div class="multi-file-commit-panel-section">
-    <modal
-      v-if="showNewBranchModal"
+  <div
+    class="multi-file-commit-panel-section"
+  >
+    <deprecated-modal
+      id="ide-create-branch-modal"
       :primary-button-label="__('Create new branch')"
-      kind="primary"
+      kind="success"
       :title="__('Branch has changed')"
-      :text="__(`This branch has changed since
-you started editing. Would you like to create a new branch?`)"
-      @cancel="showNewBranchModal = false"
-      @submit="makeCommit(true)"
-    />
-    <commit-files-list
-      title="Staged"
-      :file-list="changedFiles"
-      :collapsed="rightPanelCollapsed"
-      @toggleCollapsed="toggleCollapsed"
-    />
-    <form
-      class="form-horizontal multi-file-commit-form"
-      @submit.prevent="tryCommit"
-      v-if="!rightPanelCollapsed"
+      @submit="forceCreateNewBranch"
     >
-      <div class="multi-file-commit-fieldset">
-        <textarea
-          class="form-control multi-file-commit-message"
-          name="commit-message"
-          v-model="commitMessage"
-          placeholder="Commit message"
-        >
-        </textarea>
-      </div>
-      <div class="multi-file-commit-fieldset">
-        <label
-          v-tooltip
-          title="Create a new merge request with these changes"
-          data-container="body"
-          data-placement="top"
-        >
-          <input
-            type="checkbox"
-            v-model="startNewMR"
+      <template slot="body">
+        {{ __(`This branch has changed since you started editing.
+          Would you like to create a new branch?`) }}
+      </template>
+    </deprecated-modal>
+    <template
+      v-if="changedFiles.length || stagedFiles.length"
+    >
+      <commit-files-list
+        icon-name="unstaged"
+        :title="__('Unstaged')"
+        :file-list="changedFiles"
+        action="stageAllChanges"
+        :action-btn-text="__('Stage all')"
+        item-action-component="stage-button"
+      />
+      <commit-files-list
+        icon-name="staged"
+        :title="__('Staged')"
+        :file-list="stagedFiles"
+        action="unstageAllChanges"
+        :action-btn-text="__('Unstage all')"
+        item-action-component="unstage-button"
+        :show-toggle="false"
+        :staged-list="true"
+      />
+      <form
+        class="form-horizontal multi-file-commit-form"
+        @submit.prevent.stop="commitChanges"
+        v-if="!rightPanelCollapsed"
+      >
+        <commit-message-field
+          :text="commitMessage"
+          @input="updateCommitMessage"
+        />
+        <div class="clearfix prepend-top-15">
+          <actions />
+          <loading-button
+            :loading="submitCommitLoading"
+            :disabled="commitButtonDisabled"
+            container-class="btn btn-success btn-sm pull-left"
+            :label="__('Commit')"
+            @click="commitChanges"
           />
-          Merge Request
-        </label>
-        <button
-          type="submit"
-          :disabled="commitButtonDisabled"
-          class="btn btn-default btn-sm append-right-10 prepend-left-10"
-          :class="{ disabled: submitCommitsLoading }"
-        >
-          <i
-            v-if="submitCommitsLoading"
-            class="js-commit-loading-icon fa fa-spinner fa-spin"
-            aria-hidden="true"
-            aria-label="loading"
+          <button
+            v-if="!discardDraftButtonDisabled"
+            type="button"
+            class="btn btn-default btn-sm pull-right"
+            @click="discardDraft"
           >
-          </i>
-          Commit
-        </button>
-        <div
-          class="multi-file-commit-message-count"
-        >
-          {{ commitMessageCount }}
+            {{ __('Discard draft') }}
+          </button>
         </div>
-      </div>
-    </form>
+      </form>
+    </template>
+    <empty-state
+      v-else
+      :no-changes-state-svg-path="noChangesStateSvgPath"
+      :committed-state-svg-path="committedStateSvgPath"
+    />
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue
deleted file mode 100644
index fe4320731d9d99821ab7237da84271ab31ff05e8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/ide/components/repo_edit_button.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<script>
-import { mapGetters, mapActions, mapState } from 'vuex';
-import modal from '~/vue_shared/components/modal.vue';
-
-export default {
-  components: {
-    modal,
-  },
-  computed: {
-    ...mapState([
-      'editMode',
-      'discardPopupOpen',
-    ]),
-    ...mapGetters([
-      'canEditFile',
-    ]),
-    buttonLabel() {
-      return this.editMode ? this.__('Cancel edit') : this.__('Edit');
-    },
-  },
-  methods: {
-    ...mapActions([
-      'toggleEditMode',
-      'closeDiscardPopup',
-    ]),
-  },
-};
-</script>
-
-<template>
-  <div class="editable-mode">
-    <button
-      v-if="canEditFile"
-      class="btn btn-default"
-      type="button"
-      @click.prevent="toggleEditMode()">
-      <i
-        v-if="!editMode"
-        class="fa fa-pencil"
-        aria-hidden="true">
-      </i>
-      <span>
-        {{ buttonLabel }}
-      </span>
-    </button>
-    <modal
-      v-if="discardPopupOpen"
-      class="text-left"
-      :primary-button-label="__('Discard changes')"
-      kind="warning"
-      :title="__('Are you sure?')"
-      :text="__('Are you sure you want to discard your changes?')"
-      @cancel="closeDiscardPopup"
-      @submit="toggleEditMode(true)"
-    />
-  </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f31cc12339b3bb335bf89e23070cae27fc5c79f9..3a04cdd8e461cabed2b1b3f7a149215a6c20830b 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -2,38 +2,54 @@
 /* global monaco */
 import { mapState, mapGetters, mapActions } from 'vuex';
 import flash from '~/flash';
+import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
 import monacoLoader from '../monaco_loader';
 import Editor from '../lib/editor';
+import IdeFileButtons from './ide_file_buttons.vue';
 
 export default {
+  components: {
+    ContentViewer,
+    IdeFileButtons,
+  },
+  props: {
+    file: {
+      type: Object,
+      required: true,
+    },
+  },
   computed: {
-    ...mapGetters([
-      'activeFile',
-      'activeFileExtension',
-    ]),
-    ...mapState([
-      'leftPanelCollapsed',
-      'rightPanelCollapsed',
-      'panelResizing',
-    ]),
+    ...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']),
+    ...mapGetters(['currentMergeRequest', 'getStagedFile']),
     shouldHideEditor() {
-      return this.activeFile.binary && !this.activeFile.raw;
+      return this.file && this.file.binary && !this.file.content;
+    },
+    editTabCSS() {
+      return {
+        active: this.file.viewMode === 'edit',
+      };
+    },
+    previewTabCSS() {
+      return {
+        active: this.file.viewMode === 'preview',
+      };
     },
   },
   watch: {
-    activeFile(oldVal, newVal) {
-      if (newVal && !newVal.active) {
+    file(oldVal, newVal) {
+      // Compare key to allow for files opened in review mode to be cached differently
+      if (newVal.key !== this.file.key) {
         this.initMonaco();
       }
     },
-    leftPanelCollapsed() {
-      this.editor.updateDimensions();
-    },
     rightPanelCollapsed() {
       this.editor.updateDimensions();
     },
-    panelResizing(isResizing) {
-      if (isResizing === false) {
+    viewer() {
+      this.createEditorInstance();
+    },
+    panelResizing() {
+      if (!this.panelResizing) {
         this.editor.updateDimensions();
       }
     },
@@ -58,35 +74,74 @@ export default {
       'changeFileContent',
       'setFileLanguage',
       'setEditorPosition',
+      'setFileViewMode',
       'setFileEOL',
+      'updateViewer',
+      'updateDelayViewerUpdated',
     ]),
     initMonaco() {
       if (this.shouldHideEditor) return;
 
       this.editor.clearEditor();
 
-      this.getRawFileData(this.activeFile)
+      this.getRawFileData({
+        path: this.file.path,
+        baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
+      })
         .then(() => {
-          this.editor.createInstance(this.$refs.editor);
+          const viewerPromise = this.delayViewerUpdated
+            ? this.updateViewer(this.file.pending ? 'diff' : 'editor')
+            : Promise.resolve();
+
+          return viewerPromise;
         })
-        .then(() => this.setupEditor())
-        .catch((err) => {
+        .then(() => {
+          this.updateDelayViewerUpdated(false);
+          this.createEditorInstance();
+        })
+        .catch(err => {
           flash('Error setting up monaco. Please try again.', 'alert', document, null, false, true);
           throw err;
         });
     },
+    createEditorInstance() {
+      this.editor.dispose();
+
+      this.$nextTick(() => {
+        if (this.viewer === 'editor') {
+          this.editor.createInstance(this.$refs.editor);
+        } else {
+          this.editor.createDiffInstance(this.$refs.editor);
+        }
+
+        this.setupEditor();
+      });
+    },
     setupEditor() {
-      if (!this.activeFile) return;
+      if (!this.file || !this.editor.instance) return;
 
-      const model = this.editor.createModel(this.activeFile);
+      const head = this.getStagedFile(this.file.path);
 
-      this.editor.attachModel(model);
+      this.model = this.editor.createModel(
+        this.file,
+        this.file.staged && this.file.key.indexOf('unstaged-') === 0 ? head : null,
+      );
 
-      model.onChange((m) => {
-        this.changeFileContent({
-          file: this.activeFile,
-          content: m.getValue(),
-        });
+      if (this.viewer === 'mrdiff') {
+        this.editor.attachMergeRequestModel(this.model);
+      } else {
+        this.editor.attachModel(this.model);
+      }
+
+      this.model.onChange(model => {
+        const { file } = model;
+
+        if (file.active) {
+          this.changeFileContent({
+            path: file.path,
+            content: model.getModel().getValue(),
+          });
+        }
       });
 
       // Handle Cursor Position
@@ -98,18 +153,18 @@ export default {
       });
 
       this.editor.setPosition({
-        lineNumber: this.activeFile.editorRow,
-        column: this.activeFile.editorColumn,
+        lineNumber: this.file.editorRow,
+        column: this.file.editorColumn,
       });
 
       // Handle File Language
       this.setFileLanguage({
-        fileLanguage: model.language,
+        fileLanguage: this.model.language,
       });
 
       // Get File eol
       this.setFileEOL({
-        eol: model.eol,
+        eol: this.model.eol,
       });
     },
   },
@@ -121,16 +176,49 @@ export default {
     id="ide"
     class="blob-viewer-container blob-editor-container"
   >
-    <div
-      v-if="shouldHideEditor"
-      v-html="activeFile.html"
-    >
+    <div class="ide-mode-tabs clearfix">
+      <ul
+        class="nav-links pull-left"
+        v-if="!shouldHideEditor">
+        <li :class="editTabCSS">
+          <a
+            href="javascript:void(0);"
+            role="button"
+            @click.prevent="setFileViewMode({ file, viewMode: 'edit' })">
+            <template v-if="viewer === 'editor'">
+              {{ __('Edit') }}
+            </template>
+            <template v-else>
+              {{ __('Review') }}
+            </template>
+          </a>
+        </li>
+        <li
+          v-if="file.previewMode"
+          :class="previewTabCSS">
+          <a
+            href="javascript:void(0);"
+            role="button"
+            @click.prevent="setFileViewMode({ file, viewMode:'preview' })">
+            {{ file.previewMode.previewTitle }}
+          </a>
+        </li>
+      </ul>
+      <ide-file-buttons
+        :file="file"
+      />
     </div>
     <div
-      v-show="!shouldHideEditor"
+      v-show="!shouldHideEditor && file.viewMode === 'edit'"
       ref="editor"
       class="multi-file-editor-holder"
     >
     </div>
+    <content-viewer
+      v-if="shouldHideEditor || file.viewMode === 'preview'"
+      :content="file.content || file.raw"
+      :path="file.rawPath || file.path"
+      :file-size="file.size"
+      :project-path="file.projectId"/>
   </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index cbbab765e1c089ea6674256bea7ff2c724b1b135..8b18c7d28b4d6f80804d5d7a8c9c7c167e85da75 100644
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -1,165 +1,130 @@
 <script>
-  import { mapState } from 'vuex';
-  import timeAgoMixin from '~/vue_shared/mixins/timeago';
-  import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-  import fileIcon from '~/vue_shared/components/file_icon.vue';
-  import newDropdown from './new_dropdown/index.vue';
+import { mapActions } from 'vuex';
+import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
+import fileIcon from '~/vue_shared/components/file_icon.vue';
+import router from '../ide_router';
+import newDropdown from './new_dropdown/index.vue';
+import fileStatusIcon from './repo_file_status_icon.vue';
+import changedFileIcon from './changed_file_icon.vue';
+import mrFileIcon from './mr_file_icon.vue';
 
-  export default {
-    components: {
-      skeletonLoadingContainer,
-      newDropdown,
-      fileIcon,
+export default {
+  name: 'RepoFile',
+  components: {
+    skeletonLoadingContainer,
+    newDropdown,
+    fileStatusIcon,
+    fileIcon,
+    changedFileIcon,
+    mrFileIcon,
+  },
+  props: {
+    file: {
+      type: Object,
+      required: true,
     },
-    mixins: [
-      timeAgoMixin,
-    ],
-    props: {
-      file: {
-        type: Object,
-        required: true,
-      },
-      showExtraColumns: {
-        type: Boolean,
-        default: false,
-      },
+    level: {
+      type: Number,
+      required: true,
     },
-    computed: {
-      ...mapState([
-        'leftPanelCollapsed',
-      ]),
-      isSubmodule() {
-        return this.file.type === 'submodule';
-      },
-      isTree() {
-        return this.file.type === 'tree';
-      },
-      levelIndentation() {
-        if (this.file.level > 0) {
-          return {
-            marginLeft: `${this.file.level * 16}px`,
-          };
-        }
-        return {};
-      },
-      shortId() {
-        return this.file.id.substr(0, 8);
-      },
-      submoduleColSpan() {
-        return !this.leftPanelCollapsed && this.isSubmodule ? 3 : 1;
-      },
-      fileClass() {
-        if (this.file.type === 'blob') {
-          if (this.file.active) {
-            return 'file-open file-active';
-          }
-          return this.file.opened ? 'file-open' : '';
-        }
-        return '';
-      },
-      changedClass() {
-        return {
-          'fa-circle unsaved-icon': this.file.changed || this.file.tempFile,
-        };
-      },
+  },
+  computed: {
+    isTree() {
+      return this.file.type === 'tree';
     },
-    updated() {
-      if (this.file.type === 'blob' && this.file.active) {
-        this.$el.scrollIntoView();
-      }
+    isBlob() {
+      return this.file.type === 'blob';
+    },
+    levelIndentation() {
+      return {
+        marginLeft: `${this.level * 16}px`,
+      };
+    },
+    fileClass() {
+      return {
+        'file-open': this.isBlob && this.file.opened,
+        'file-active': this.isBlob && this.file.active,
+        folder: this.isTree,
+        'is-open': this.file.opened,
+      };
     },
-    methods: {
-      clickFile(row) {
-        // Manual Action if a tree is selected/opened
-        if (this.file.type === 'tree' && this.$router.currentRoute.path === `/project${row.url}`) {
-          this.$store.dispatch('toggleTreeOpen', {
-            endpoint: this.file.url,
-            tree: this.file,
-          });
-        }
-        this.$router.push(`/project${row.url}`);
-      },
+  },
+  updated() {
+    if (this.file.type === 'blob' && this.file.active) {
+      this.$el.scrollIntoView();
+    }
+  },
+  methods: {
+    ...mapActions(['toggleTreeOpen', 'updateDelayViewerUpdated']),
+    clickFile() {
+      // Manual Action if a tree is selected/opened
+      if (this.isTree && this.$router.currentRoute.path === `/project${this.file.url}`) {
+        this.toggleTreeOpen(this.file.path);
+      }
+
+      return this.updateDelayViewerUpdated(true).then(() => {
+        router.push(`/project${this.file.url}`);
+      });
     },
-  };
+  },
+};
 </script>
 
 <template>
-  <tr
-    class="file"
-    :class="fileClass"
-    @click="clickFile(file)">
-    <td
-      class="multi-file-table-name"
-      :colspan="submoduleColSpan"
+  <div>
+    <div
+      class="file"
+      :class="fileClass"
     >
-      <a
-        class="repo-file-name"
-      >
-        <file-icon
-          :file-name="file.name"
-          :loading="file.loading"
-          :folder="file.type === 'tree'"
-          :opened="file.opened"
-          :style="levelIndentation"
-          :size="16"
-        />
-        {{ file.name }}
-      </a>
-      <new-dropdown
-        v-if="isTree"
-        :project-id="file.projectId"
-        :branch="file.branchId"
-        :path="file.path"
-        :parent="file"
-      />
-      <i
-        class="fa"
-        v-if="file.changed || file.tempFile"
-        :class="changedClass"
-        aria-hidden="true"
+      <div
+        class="file-name"
+        @click="clickFile"
+        role="button"
       >
-      </i>
-      <template v-if="isSubmodule && file.id">
-        @
-        <span class="commit-sha">
-          <a
-            @click.stop
-            :href="file.tree_url"
-          >
-            {{ shortId }}
-          </a>
-        </span>
-      </template>
-    </td>
-
-    <template v-if="showExtraColumns && !isSubmodule">
-      <td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
-        <a
-          v-if="file.lastCommit.message"
-          @click.stop
-          :href="file.lastCommit.url"
-        >
-          {{ file.lastCommit.message }}
-        </a>
-        <skeleton-loading-container
-          v-else
-          :small="true"
-        />
-      </td>
-
-      <td class="commit-update hidden-xs text-right">
         <span
-          v-if="file.lastCommit.updatedAt"
-          :title="tooltipTitle(file.lastCommit.updatedAt)"
+          class="ide-file-name str-truncated"
+          :style="levelIndentation"
         >
-          {{ timeFormated(file.lastCommit.updatedAt) }}
+          <file-icon
+            :file-name="file.name"
+            :loading="file.loading"
+            :folder="isTree"
+            :opened="file.opened"
+            :size="16"
+          />
+          {{ file.name }}
+          <file-status-icon
+            :file="file"
+          />
         </span>
-        <skeleton-loading-container
-          v-else
-          class="animation-container-right"
-          :small="true"
+        <span class="pull-right">
+          <mr-file-icon
+            v-if="file.mrChange"
+          />
+          <changed-file-icon
+            v-if="file.changed || file.tempFile || file.staged"
+            :file="file"
+            :show-tooltip="true"
+            :show-staged-icon="true"
+            class="prepend-top-5 pull-right"
+          />
+        </span>
+        <new-dropdown
+          v-if="isTree"
+          :project-id="file.projectId"
+          :branch="file.branchId"
+          :path="file.path"
+          class="pull-right prepend-left-8"
         />
-      </td>
+      </div>
+    </div>
+    <template v-if="file.opened">
+      <repo-file
+        v-for="childFile in file.tree"
+        :key="childFile.key"
+        :file="childFile"
+        :level="level + 1"
+      />
     </template>
-  </tr>
+  </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue
deleted file mode 100644
index aabc0d8eada44382931bbb19d660e67cad39a4a0..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/ide/components/repo_file_buttons.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-<script>
-import { mapGetters } from 'vuex';
-
-export default {
-  computed: {
-    ...mapGetters([
-      'activeFile',
-    ]),
-    showButtons() {
-      return this.activeFile.rawPath ||
-        this.activeFile.blamePath ||
-        this.activeFile.commitsPath ||
-        this.activeFile.permalink;
-    },
-    rawDownloadButtonLabel() {
-      return this.activeFile.binary ? 'Download' : 'Raw';
-    },
-  },
-};
-</script>
-
-<template>
-  <div
-    v-if="showButtons"
-    class="multi-file-editor-btn-group"
-  >
-    <a
-      :href="activeFile.rawPath"
-      target="_blank"
-      class="btn btn-default btn-sm raw"
-      rel="noopener noreferrer">
-      {{ rawDownloadButtonLabel }}
-    </a>
-
-    <div
-      class="btn-group"
-      role="group"
-      aria-label="File actions"
-    >
-      <a
-        :href="activeFile.blamePath"
-        class="btn btn-default btn-sm blame"
-      >
-        Blame
-      </a>
-      <a
-        :href="activeFile.commitsPath"
-        class="btn btn-default btn-sm history"
-      >
-        History
-      </a>
-      <a
-        :href="activeFile.permalink"
-        class="btn btn-default btn-sm permalink"
-      >
-        Permalink
-      </a>
-    </div>
-  </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_file_status_icon.vue b/app/assets/javascripts/ide/components/repo_file_status_icon.vue
new file mode 100644
index 0000000000000000000000000000000000000000..97589e116c58be138d8524ca5a660450ac6d152f
--- /dev/null
+++ b/app/assets/javascripts/ide/components/repo_file_status_icon.vue
@@ -0,0 +1,39 @@
+<script>
+import icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import '~/lib/utils/datetime_utility';
+
+export default {
+  components: {
+    icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    file: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    lockTooltip() {
+      return `Locked by ${this.file.file_lock.user.name}`;
+    },
+  },
+};
+</script>
+
+<template>
+  <span
+    v-if="file.file_lock"
+    v-tooltip
+    :title="lockTooltip"
+    data-container="body"
+  >
+    <icon
+      name="lock"
+      css-classes="file-status-icon"
+    />
+  </span>
+</template>
diff --git a/app/assets/javascripts/ide/components/repo_prev_directory.vue b/app/assets/javascripts/ide/components/repo_prev_directory.vue
deleted file mode 100644
index 7cd359ea4ed3c1531307ee90efbf6ba56d4e6f30..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/ide/components/repo_prev_directory.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<script>
-  import { mapState, mapActions } from 'vuex';
-
-  export default {
-    computed: {
-      ...mapState([
-        'parentTreeUrl',
-        'leftPanelCollapsed',
-      ]),
-      colSpanCondition() {
-        return this.leftPanelCollapsed ? undefined : 3;
-      },
-    },
-    methods: {
-      ...mapActions([
-        'getTreeData',
-      ]),
-    },
-  };
-</script>
-
-<template>
-  <tr class="file prev-directory">
-    <td
-      :colspan="colSpanCondition"
-      class="table-cell"
-      @click.prevent="getTreeData({ endpoint: parentTreeUrl })"
-    >
-      <a :href="parentTreeUrl">...</a>
-    </td>
-  </tr>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue
deleted file mode 100644
index a216269e29238f65b748b71aad5c9cd5e465337d..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/ide/components/repo_preview.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<script>
-  import { mapGetters } from 'vuex';
-  import LineHighlighter from '~/line_highlighter';
-  import syntaxHighlight from '~/syntax_highlight';
-
-  export default {
-    computed: {
-      ...mapGetters([
-        'activeFile',
-      ]),
-      renderErrorTooLarge() {
-        return this.activeFile.renderError === 'too_large';
-      },
-    },
-    mounted() {
-      this.highlightFile();
-      this.lineHighlighter = new LineHighlighter({
-        fileHolderSelector: '.blob-viewer-container',
-        scrollFileHolder: true,
-      });
-    },
-    updated() {
-      this.$nextTick(() => {
-        this.highlightFile();
-      });
-    },
-    methods: {
-      highlightFile() {
-        syntaxHighlight($(this.$el).find('.file-content'));
-      },
-    },
-  };
-</script>
-
-<template>
-  <div>
-    <div
-      v-if="!activeFile.renderError"
-      v-html="activeFile.html"
-      class="multi-file-preview-holder"
-    >
-    </div>
-    <div
-      v-else-if="activeFile.tempFile"
-      class="vertical-center render-error">
-      <p class="text-center">
-        The source could not be displayed for this temporary file.
-      </p>
-    </div>
-    <div
-      v-else-if="renderErrorTooLarge"
-      class="vertical-center render-error">
-      <p class="text-center">
-        The source could not be displayed because it is too large.
-        You can <a
-          :href="activeFile.rawPath"
-          download>download</a> it instead.
-      </p>
-    </div>
-    <div
-      v-else
-      class="vertical-center render-error">
-      <p class="text-center">
-        The source could not be displayed because a rendering error occurred.
-        You can <a
-          :href="activeFile.rawPath"
-          download>download</a> it instead.
-      </p>
-    </div>
-  </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index 5656081c598e552a4fddea570b8bf08cf671d2d8..35a362b01e0ff6b885b0de76e8c636b44e0cdb5e 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -1,67 +1,97 @@
 <script>
-  import { mapActions } from 'vuex';
-  import fileIcon from '~/vue_shared/components/file_icon.vue';
+import { mapActions } from 'vuex';
 
-  export default {
-    components: {
-      fileIcon,
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import FileStatusIcon from './repo_file_status_icon.vue';
+import ChangedFileIcon from './changed_file_icon.vue';
+
+export default {
+  components: {
+    FileStatusIcon,
+    FileIcon,
+    Icon,
+    ChangedFileIcon,
+  },
+  props: {
+    tab: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      tabMouseOver: false,
+    };
+  },
+  computed: {
+    closeLabel() {
+      if (this.fileHasChanged) {
+        return `${this.tab.name} changed`;
+      }
+      return `Close ${this.tab.name}`;
     },
-    props: {
-      tab: {
-        type: Object,
-        required: true,
-      },
+    showChangedIcon() {
+      return this.fileHasChanged ? !this.tabMouseOver : false;
     },
-    computed: {
-      closeLabel() {
-        if (this.tab.changed || this.tab.tempFile) {
-          return `${this.tab.name} changed`;
-        }
-        return `Close ${this.tab.name}`;
-      },
-      changedClass() {
-        const tabChangedObj = {
-          'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
-          'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
-        };
-        return tabChangedObj;
-      },
+    fileHasChanged() {
+      return this.tab.changed || this.tab.tempFile || this.tab.staged;
     },
+  },
+
+  methods: {
+    ...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
+    clickFile(tab) {
+      this.updateDelayViewerUpdated(true);
 
-    methods: {
-      ...mapActions([
-        'closeFile',
-      ]),
-      clickFile(tab) {
+      if (tab.pending) {
+        this.openPendingTab({ file: tab, keyPrefix: tab.staged ? 'staged' : 'unstaged' });
+      } else {
         this.$router.push(`/project${tab.url}`);
-      },
+      }
     },
-  };
+    mouseOverTab() {
+      if (this.fileHasChanged) {
+        this.tabMouseOver = true;
+      }
+    },
+    mouseOutTab() {
+      if (this.fileHasChanged) {
+        this.tabMouseOver = false;
+      }
+    },
+  },
+};
 </script>
 
 <template>
-  <li @click="clickFile(tab)">
+  <li
+    @click="clickFile(tab)"
+    @mouseover="mouseOverTab"
+    @mouseout="mouseOutTab"
+  >
     <button
       type="button"
       class="multi-file-tab-close"
-      @click.stop.prevent="closeFile({ file: tab })"
+      @click.stop.prevent="closeFile(tab)"
       :aria-label="closeLabel"
-      :class="{
-        'modified': tab.changed,
-      }"
-      :disabled="tab.changed"
     >
-      <i
-        class="fa"
-        :class="changedClass"
-        aria-hidden="true"
-      >
-      </i>
+      <icon
+        v-if="!showChangedIcon"
+        name="close"
+        :size="12"
+      />
+      <changed-file-icon
+        v-else
+        :file="tab"
+      />
     </button>
 
     <div
       class="multi-file-tab"
-      :class="{active : tab.active }"
+      :class="{
+        active: tab.active
+      }"
       :title="tab.url"
     >
       <file-icon
@@ -69,6 +99,9 @@
         :size="16"
       />
       {{ tab.name }}
+      <file-status-icon
+        :file="tab"
+      />
     </div>
   </li>
 </template>
diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue
index ca363bba0eff323afa7983b175eb99a81fdfc3d1..7bd646ba9b0597ba6a4c9eacfd7279058118ab9e 100644
--- a/app/assets/javascripts/ide/components/repo_tabs.vue
+++ b/app/assets/javascripts/ide/components/repo_tabs.vue
@@ -1,27 +1,82 @@
 <script>
-  import { mapState } from 'vuex';
-  import RepoTab from './repo_tab.vue';
+import { mapActions } from 'vuex';
+import RepoTab from './repo_tab.vue';
+import EditorMode from './editor_mode_dropdown.vue';
+import router from '../ide_router';
 
-  export default {
-    components: {
-      'repo-tab': RepoTab,
+export default {
+  components: {
+    RepoTab,
+    EditorMode,
+  },
+  props: {
+    activeFile: {
+      type: Object,
+      required: true,
     },
-    computed: {
-      ...mapState([
-        'openFiles',
-      ]),
+    files: {
+      type: Array,
+      required: true,
     },
-  };
+    viewer: {
+      type: String,
+      required: true,
+    },
+    hasChanges: {
+      type: Boolean,
+      required: true,
+    },
+    mergeRequestId: {
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
+  data() {
+    return {
+      showShadow: false,
+    };
+  },
+  updated() {
+    if (!this.$refs.tabsScroller) return;
+
+    this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
+  },
+  methods: {
+    ...mapActions(['updateViewer', 'removePendingTab']),
+    openFileViewer(viewer) {
+      this.updateViewer(viewer);
+
+      if (this.activeFile.pending) {
+        return this.removePendingTab(this.activeFile).then(() => {
+          router.push(`/project${this.activeFile.url}`);
+        });
+      }
+
+      return null;
+    },
+  },
+};
 </script>
 
 <template>
-  <ul
-    class="multi-file-tabs list-unstyled append-bottom-0"
-  >
-    <repo-tab
-      v-for="tab in openFiles"
-      :key="tab.key"
-      :tab="tab"
+  <div class="multi-file-tabs">
+    <ul
+      class="list-unstyled append-bottom-0"
+      ref="tabsScroller"
+    >
+      <repo-tab
+        v-for="tab in files"
+        :key="tab.key"
+        :tab="tab"
+      />
+    </ul>
+    <editor-mode
+      :viewer="viewer"
+      :show-shadow="showShadow"
+      :has-changes="hasChanges"
+      :merge-request-id="mergeRequestId"
+      @click="openFileViewer"
     />
-  </ul>
+  </div>
 </template>
diff --git a/app/assets/javascripts/ide/components/resizable_panel.vue b/app/assets/javascripts/ide/components/resizable_panel.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5ea2a2f68258ed9dc5596db17041b42a3d08b3e9
--- /dev/null
+++ b/app/assets/javascripts/ide/components/resizable_panel.vue
@@ -0,0 +1,85 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
+
+export default {
+  components: {
+    PanelResizer,
+  },
+  props: {
+    collapsible: {
+      type: Boolean,
+      required: true,
+    },
+    initialWidth: {
+      type: Number,
+      required: true,
+    },
+    minSize: {
+      type: Number,
+      required: false,
+      default: 340,
+    },
+    side: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      width: this.initialWidth,
+    };
+  },
+  computed: {
+    ...mapState({
+      collapsed(state) {
+        return state[`${this.side}PanelCollapsed`];
+      },
+    }),
+    panelStyle() {
+      if (!this.collapsed) {
+        return {
+          width: `${this.width}px`,
+        };
+      }
+
+      return {};
+    },
+  },
+  methods: {
+    ...mapActions(['setPanelCollapsedStatus', 'setResizingStatus']),
+    toggleFullbarCollapsed() {
+      if (this.collapsed && this.collapsible) {
+        this.setPanelCollapsedStatus({
+          side: this.side,
+          collapsed: !this.collapsed,
+        });
+      }
+    },
+  },
+  maxSize: window.innerWidth / 2,
+};
+</script>
+
+<template>
+  <div
+    class="multi-file-commit-panel"
+    :class="{
+      'is-collapsed': collapsed && collapsible,
+    }"
+    :style="panelStyle"
+    @click="toggleFullbarCollapsed"
+  >
+    <slot></slot>
+    <panel-resizer
+      :size.sync="width"
+      :enabled="!collapsed"
+      :start-size="initialWidth"
+      :min-size="minSize"
+      :max-size="$options.maxSize"
+      @resize-start="setResizingStatus(true)"
+      @resize-end="setResizingStatus(false)"
+      :side="side === 'right' ? 'left' : 'right'"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..b60d042e0be37fb33c8fc96d1a18776b3abfa709
--- /dev/null
+++ b/app/assets/javascripts/ide/constants.js
@@ -0,0 +1,3 @@
+// Fuzzy file finder
+export const MAX_TITLE_LENGTH = 50;
+export const MAX_BODY_LENGTH = 72;
diff --git a/app/assets/javascripts/ide/eventhub.js b/app/assets/javascripts/ide/eventhub.js
new file mode 100644
index 0000000000000000000000000000000000000000..0948c2e53524a736a55c060600868ce89ee7687a
--- /dev/null
+++ b/app/assets/javascripts/ide/eventhub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index a7fb9e0588a837b59a65d1b98165831780abc5a2..4a0a303d5a6e6569db260c070fcdda0b23f63aeb 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -1,10 +1,7 @@
 import Vue from 'vue';
 import VueRouter from 'vue-router';
+import flash from '~/flash';
 import store from './stores';
-import flash from '../flash';
-import {
-  getTreeEntry,
-} from './stores/utils';
 
 Vue.use(VueRouter);
 
@@ -39,15 +36,15 @@ const router = new VueRouter({
   base: `${gon.relative_url_root}/-/ide/`,
   routes: [
     {
-      path: '/project/:namespace/:project',
+      path: '/project/:namespace/:project+',
       component: EmptyRouterComponent,
       children: [
         {
-          path: ':targetmode/:branch/*',
+          path: ':targetmode(edit|tree|blob)/:branch/*',
           component: EmptyRouterComponent,
         },
         {
-          path: 'mr/:mrid',
+          path: 'merge_requests/:mrid',
           component: EmptyRouterComponent,
         },
       ],
@@ -57,42 +54,117 @@ const router = new VueRouter({
 
 router.beforeEach((to, from, next) => {
   if (to.params.namespace && to.params.project) {
-    store.dispatch('getProjectData', {
-      namespace: to.params.namespace,
-      projectId: to.params.project,
-    })
-    .then(() => {
-      const fullProjectId = `${to.params.namespace}/${to.params.project}`;
-
-      if (to.params.branch) {
-        store.dispatch('getBranchData', {
-          projectId: fullProjectId,
-          branchId: to.params.branch,
-        });
-
-        store.dispatch('getTreeData', {
-          projectId: fullProjectId,
-          branch: to.params.branch,
-          endpoint: `/tree/${to.params.branch}`,
-        })
-        .then(() => {
-          if (to.params[0]) {
-            const treeEntry = getTreeEntry(store, `${to.params.namespace}/${to.params.project}/${to.params.branch}`, to.params[0]);
-            if (treeEntry) {
-              store.dispatch('handleTreeEntryAction', treeEntry);
-            }
-          }
-        })
-        .catch((e) => {
-          flash('Error while loading the branch files. Please try again.', 'alert', document, null, false, true);
-          throw e;
-        });
-      }
-    })
-    .catch((e) => {
-      flash('Error while loading the project data. Please try again.', 'alert', document, null, false, true);
-      throw e;
-    });
+    store
+      .dispatch('getProjectData', {
+        namespace: to.params.namespace,
+        projectId: to.params.project,
+      })
+      .then(() => {
+        const fullProjectId = `${to.params.namespace}/${to.params.project}`;
+
+        if (to.params.branch) {
+          store.dispatch('getBranchData', {
+            projectId: fullProjectId,
+            branchId: to.params.branch,
+          });
+
+          store
+            .dispatch('getFiles', {
+              projectId: fullProjectId,
+              branchId: to.params.branch,
+            })
+            .then(() => {
+              if (to.params[0]) {
+                const path =
+                  to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0];
+                const treeEntryKey = Object.keys(store.state.entries).find(
+                  key => key === path && !store.state.entries[key].pending,
+                );
+                const treeEntry = store.state.entries[treeEntryKey];
+
+                if (treeEntry) {
+                  store.dispatch('handleTreeEntryAction', treeEntry);
+                }
+              }
+            })
+            .catch(e => {
+              flash(
+                'Error while loading the branch files. Please try again.',
+                'alert',
+                document,
+                null,
+                false,
+                true,
+              );
+              throw e;
+            });
+        } else if (to.params.mrid) {
+          store.dispatch('updateViewer', 'mrdiff');
+
+          store
+            .dispatch('getMergeRequestData', {
+              projectId: fullProjectId,
+              mergeRequestId: to.params.mrid,
+            })
+            .then(mr => {
+              store.dispatch('getBranchData', {
+                projectId: fullProjectId,
+                branchId: mr.source_branch,
+              });
+
+              return store.dispatch('getFiles', {
+                projectId: fullProjectId,
+                branchId: mr.source_branch,
+              });
+            })
+            .then(() =>
+              store.dispatch('getMergeRequestVersions', {
+                projectId: fullProjectId,
+                mergeRequestId: to.params.mrid,
+              }),
+            )
+            .then(() =>
+              store.dispatch('getMergeRequestChanges', {
+                projectId: fullProjectId,
+                mergeRequestId: to.params.mrid,
+              }),
+            )
+            .then(mrChanges => {
+              mrChanges.changes.forEach((change, ind) => {
+                const changeTreeEntry = store.state.entries[change.new_path];
+
+                if (changeTreeEntry) {
+                  store.dispatch('setFileMrChange', {
+                    file: changeTreeEntry,
+                    mrChange: change,
+                  });
+
+                  if (ind < 10) {
+                    store.dispatch('getFileData', {
+                      path: change.new_path,
+                      makeFileActive: ind === 0,
+                    });
+                  }
+                }
+              });
+            })
+            .catch(e => {
+              flash('Error while loading the merge request. Please try again.');
+              throw e;
+            });
+        }
+      })
+      .catch(e => {
+        flash(
+          'Error while loading the project data. Please try again.',
+          'alert',
+          document,
+          null,
+          false,
+          true,
+        );
+        throw e;
+      });
   }
 
   next();
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index e8a19f47ceea62045b19a57d0520e6574e880b7c..cbfb3dc54f259ea5b2b6a3b8c7f9b4eb2ecfeba2 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -1,8 +1,8 @@
 import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
 import ide from './components/ide.vue';
 import store from './stores';
 import router from './ide_router';
-import Translate from '../vue_shared/translate';
 
 function initIde(el) {
   if (!el) return null;
@@ -18,6 +18,8 @@ function initIde(el) {
       return createElement('ide', {
         props: {
           emptyStateSvgPath: el.dataset.emptyStateSvgPath,
+          noChangesStateSvgPath: el.dataset.noChangesStateSvgPath,
+          committedStateSvgPath: el.dataset.committedStateSvgPath,
         },
       });
     },
diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
index 14d9fe4771e798b251f7602073387c978adf55cd..016dcda1fa1fdb615b3c231eaf78e24a8cd53a7f 100644
--- a/app/assets/javascripts/ide/lib/common/model.js
+++ b/app/assets/javascripts/ide/lib/common/model.js
@@ -1,27 +1,46 @@
 /* global monaco */
 import Disposable from './disposable';
+import eventHub from '../../eventhub';
 
 export default class Model {
-  constructor(monaco, file) {
+  constructor(monaco, file, head = null) {
     this.monaco = monaco;
     this.disposable = new Disposable();
     this.file = file;
+    this.head = head;
     this.content = file.content !== '' ? file.content : file.raw;
 
     this.disposable.add(
-      this.originalModel = this.monaco.editor.createModel(
-        this.file.raw,
+      (this.originalModel = this.monaco.editor.createModel(
+        head ? head.content : this.file.raw,
         undefined,
-        new this.monaco.Uri(null, null, `original/${this.file.path}`),
-      ),
-      this.model = this.monaco.editor.createModel(
+        new this.monaco.Uri(null, null, `original/${this.file.key}`),
+      )),
+      (this.model = this.monaco.editor.createModel(
         this.content,
         undefined,
-        new this.monaco.Uri(null, null, this.file.path),
-      ),
+        new this.monaco.Uri(null, null, this.file.key),
+      )),
     );
+    if (this.file.mrChange) {
+      this.disposable.add(
+        (this.baseModel = this.monaco.editor.createModel(
+          this.file.baseRaw,
+          undefined,
+          new this.monaco.Uri(null, null, `target/${this.file.path}`),
+        )),
+      );
+    }
+
+    this.events = new Set();
+
+    this.updateContent = this.updateContent.bind(this);
+    this.updateNewContent = this.updateNewContent.bind(this);
+    this.dispose = this.dispose.bind(this);
 
-    this.events = new Map();
+    eventHub.$on(`editor.update.model.dispose.${this.file.key}`, this.dispose);
+    eventHub.$on(`editor.update.model.content.${this.file.key}`, this.updateContent);
+    eventHub.$on(`editor.update.model.new.content.${this.file.key}`, this.updateNewContent);
   }
 
   get url() {
@@ -37,7 +56,7 @@ export default class Model {
   }
 
   get path() {
-    return this.file.path;
+    return this.file.key;
   }
 
   getModel() {
@@ -48,17 +67,45 @@ export default class Model {
     return this.originalModel;
   }
 
+  getBaseModel() {
+    return this.baseModel;
+  }
+
+  setValue(value) {
+    this.getModel().setValue(value);
+  }
+
   onChange(cb) {
-    this.events.set(
-      this.path,
-      this.disposable.add(
-        this.model.onDidChangeContent(e => cb(this.model, e)),
-      ),
-    );
+    this.events.add(this.disposable.add(this.model.onDidChangeContent(e => cb(this, e))));
+  }
+
+  onDispose(cb) {
+    this.events.add(cb);
+  }
+
+  updateContent({ content, changed }) {
+    this.getOriginalModel().setValue(content);
+
+    if (!changed) {
+      this.getModel().setValue(content);
+    }
+  }
+
+  updateNewContent(content) {
+    this.getModel().setValue(content);
   }
 
   dispose() {
     this.disposable.dispose();
+
+    this.events.forEach(cb => {
+      if (typeof cb === 'function') cb();
+    });
+
     this.events.clear();
+
+    eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose);
+    eventHub.$off(`editor.update.model.content.${this.file.key}`, this.updateContent);
+    eventHub.$off(`editor.update.model.new.content.${this.file.key}`, this.updateNewContent);
   }
 }
diff --git a/app/assets/javascripts/ide/lib/common/model_manager.js b/app/assets/javascripts/ide/lib/common/model_manager.js
index fd46225279561a1f8b7a4761c6c44a8c03ac4f67..7f6439694802dd31db7ad5381af45e89768e3995 100644
--- a/app/assets/javascripts/ide/lib/common/model_manager.js
+++ b/app/assets/javascripts/ide/lib/common/model_manager.js
@@ -1,3 +1,4 @@
+import eventHub from '../../eventhub';
 import Disposable from './disposable';
 import Model from './model';
 
@@ -8,22 +9,37 @@ export default class ModelManager {
     this.models = new Map();
   }
 
-  hasCachedModel(path) {
-    return this.models.has(path);
+  hasCachedModel(key) {
+    return this.models.has(key);
   }
 
-  addModel(file) {
-    if (this.hasCachedModel(file.path)) {
-      return this.models.get(file.path);
+  getModel(key) {
+    return this.models.get(key);
+  }
+
+  addModel(file, head = null) {
+    if (this.hasCachedModel(file.key)) {
+      return this.getModel(file.key);
     }
 
-    const model = new Model(this.monaco, file);
+    const model = new Model(this.monaco, file, head);
     this.models.set(model.path, model);
     this.disposable.add(model);
 
+    eventHub.$on(
+      `editor.update.model.dispose.${file.key}`,
+      this.removeCachedModel.bind(this, file),
+    );
+
     return model;
   }
 
+  removeCachedModel(file) {
+    this.models.delete(file.key);
+
+    eventHub.$off(`editor.update.model.dispose.${file.key}`, this.removeCachedModel);
+  }
+
   dispose() {
     // dispose of all the models
     this.disposable.dispose();
diff --git a/app/assets/javascripts/ide/lib/decorations/controller.js b/app/assets/javascripts/ide/lib/decorations/controller.js
index 0954b7973c43d9458e54a5ca65183c36ccfcc917..13d477bb2cf91d62bf6152e9cd821a0a36507020 100644
--- a/app/assets/javascripts/ide/lib/decorations/controller.js
+++ b/app/assets/javascripts/ide/lib/decorations/controller.js
@@ -27,6 +27,8 @@ export default class DecorationsController {
   }
 
   decorate(model) {
+    if (!this.editor.instance) return;
+
     const decorations = this.getAllDecorationsForModel(model);
     const oldDecorations = this.editorDecorations.get(model.url) || [];
 
@@ -36,6 +38,15 @@ export default class DecorationsController {
     );
   }
 
+  hasDecorations(model) {
+    return this.decorations.has(model.url);
+  }
+
+  removeDecorations(model) {
+    this.decorations.delete(model.url);
+    this.editorDecorations.delete(model.url);
+  }
+
   dispose() {
     this.decorations.clear();
     this.editorDecorations.clear();
diff --git a/app/assets/javascripts/ide/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js
index dc0b1c95e59058f01ecf9f2107e31ca990452af7..f579424cf333f6ef272ba4f4d6bf5afd4b239fbe 100644
--- a/app/assets/javascripts/ide/lib/diff/controller.js
+++ b/app/assets/javascripts/ide/lib/diff/controller.js
@@ -3,7 +3,7 @@ import { throttle } from 'underscore';
 import DirtyDiffWorker from './diff_worker';
 import Disposable from '../common/disposable';
 
-export const getDiffChangeType = (change) => {
+export const getDiffChangeType = change => {
   if (change.modified) {
     return 'modified';
   } else if (change.added) {
@@ -16,12 +16,7 @@ export const getDiffChangeType = (change) => {
 };
 
 export const getDecorator = change => ({
-  range: new monaco.Range(
-    change.lineNumber,
-    1,
-    change.endLineNumber,
-    1,
-  ),
+  range: new monaco.Range(change.lineNumber, 1, change.endLineNumber, 1),
   options: {
     isWholeLine: true,
     linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`,
@@ -31,6 +26,7 @@ export const getDecorator = change => ({
 export default class DirtyDiffController {
   constructor(modelManager, decorationsController) {
     this.disposable = new Disposable();
+    this.models = new Map();
     this.editorSimpleWorker = null;
     this.modelManager = modelManager;
     this.decorationsController = decorationsController;
@@ -42,7 +38,15 @@ export default class DirtyDiffController {
   }
 
   attachModel(model) {
+    if (this.models.has(model.url)) return;
+
     model.onChange(() => this.throttledComputeDiff(model));
+    model.onDispose(() => {
+      this.decorationsController.removeDecorations(model);
+      this.models.delete(model.url);
+    });
+
+    this.models.set(model.url, model);
   }
 
   computeDiff(model) {
@@ -54,16 +58,22 @@ export default class DirtyDiffController {
   }
 
   reDecorate(model) {
-    this.decorationsController.decorate(model);
+    if (this.decorationsController.hasDecorations(model)) {
+      this.decorationsController.decorate(model);
+    } else {
+      this.computeDiff(model);
+    }
   }
 
   decorate({ data }) {
     const decorations = data.changes.map(change => getDecorator(change));
-    this.decorationsController.addDecorations(data.path, 'dirtyDiff', decorations);
+    const model = this.modelManager.getModel(data.path);
+    this.decorationsController.addDecorations(model, 'dirtyDiff', decorations);
   }
 
   dispose() {
     this.disposable.dispose();
+    this.models.clear();
 
     this.dirtyDiffWorker.removeEventListener('message', this.decorate);
     this.dirtyDiffWorker.terminate();
diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js
index 51255f156583670a4c11bed10a3e0215f1271b26..2d3ee7d4f48bb7ea64fd86645626b2151334a1f9 100644
--- a/app/assets/javascripts/ide/lib/editor.js
+++ b/app/assets/javascripts/ide/lib/editor.js
@@ -3,10 +3,21 @@ import DecorationsController from './decorations/controller';
 import DirtyDiffController from './diff/controller';
 import Disposable from './common/disposable';
 import ModelManager from './common/model_manager';
-import editorOptions from './editor_options';
+import editorOptions, { defaultEditorOptions } from './editor_options';
+import gitlabTheme from './themes/gl_theme';
+
+export const clearDomElement = el => {
+  if (!el || !el.firstChild) return;
+
+  while (el.firstChild) {
+    el.removeChild(el.firstChild);
+  }
+};
 
 export default class Editor {
   static create(monaco) {
+    if (this.editorInstance) return this.editorInstance;
+
     this.editorInstance = new Editor(monaco);
 
     return this.editorInstance;
@@ -18,59 +29,104 @@ export default class Editor {
     this.instance = null;
     this.dirtyDiffController = null;
     this.disposable = new Disposable();
+    this.modelManager = new ModelManager(this.monaco);
+    this.decorationsController = new DecorationsController(this);
 
-    this.disposable.add(
-      this.modelManager = new ModelManager(this.monaco),
-      this.decorationsController = new DecorationsController(this),
-    );
+    this.setupMonacoTheme();
 
     this.debouncedUpdate = _.debounce(() => {
       this.updateDimensions();
     }, 200);
-    window.addEventListener('resize', this.debouncedUpdate, false);
   }
 
   createInstance(domElement) {
     if (!this.instance) {
+      clearDomElement(domElement);
+
+      this.disposable.add(
+        (this.instance = this.monaco.editor.create(domElement, {
+          ...defaultEditorOptions,
+        })),
+        (this.dirtyDiffController = new DirtyDiffController(
+          this.modelManager,
+          this.decorationsController,
+        )),
+      );
+
+      window.addEventListener('resize', this.debouncedUpdate, false);
+    }
+  }
+
+  createDiffInstance(domElement) {
+    if (!this.instance) {
+      clearDomElement(domElement);
+
       this.disposable.add(
-        this.instance = this.monaco.editor.create(domElement, {
-          model: null,
-          readOnly: false,
-          contextmenu: true,
-          scrollBeyondLastLine: false,
-          minimap: {
-            enabled: false,
-          },
-        }),
-        this.dirtyDiffController = new DirtyDiffController(
-          this.modelManager, this.decorationsController,
-        ),
+        (this.instance = this.monaco.editor.createDiffEditor(domElement, {
+          ...defaultEditorOptions,
+          readOnly: true,
+          quickSuggestions: false,
+          occurrencesHighlight: false,
+          renderLineHighlight: 'none',
+          hideCursorInOverviewRuler: true,
+          renderSideBySide: Editor.renderSideBySide(domElement),
+        })),
       );
+
+      window.addEventListener('resize', this.debouncedUpdate, false);
     }
   }
 
-  createModel(file) {
-    return this.modelManager.addModel(file);
+  createModel(file, head = null) {
+    return this.modelManager.addModel(file, head);
   }
 
   attachModel(model) {
+    if (this.isDiffEditorType) {
+      this.instance.setModel({
+        original: model.getOriginalModel(),
+        modified: model.getModel(),
+      });
+
+      return;
+    }
+
     this.instance.setModel(model.getModel());
     if (this.dirtyDiffController) this.dirtyDiffController.attachModel(model);
 
     this.currentModel = model;
 
-    this.instance.updateOptions(editorOptions.reduce((acc, obj) => {
-      Object.keys(obj).forEach((key) => {
-        Object.assign(acc, {
-          [key]: obj[key](model),
+    this.instance.updateOptions(
+      editorOptions.reduce((acc, obj) => {
+        Object.keys(obj).forEach(key => {
+          Object.assign(acc, {
+            [key]: obj[key](model),
+          });
         });
-      });
-      return acc;
-    }, {}));
+        return acc;
+      }, {}),
+    );
 
     if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model);
   }
 
+  attachMergeRequestModel(model) {
+    this.instance.setModel({
+      original: model.getBaseModel(),
+      modified: model.getModel(),
+    });
+
+    this.monaco.editor.createDiffNavigator(this.instance, {
+      alwaysRevealFirst: true,
+    });
+  }
+
+  setupMonacoTheme() {
+    this.monaco.editor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme);
+
+    this.monaco.editor.setTheme('gitlab');
+  }
+
   clearEditor() {
     if (this.instance) {
       this.instance.setModel(null);
@@ -78,17 +134,27 @@ export default class Editor {
   }
 
   dispose() {
-    this.disposable.dispose();
     window.removeEventListener('resize', this.debouncedUpdate);
 
-    // dispose main monaco instance
-    if (this.instance) {
+    // catch any potential errors with disposing the error
+    // this is mainly for tests caused by elements not existing
+    try {
+      this.disposable.dispose();
+
       this.instance = null;
+    } catch (e) {
+      this.instance = null;
+
+      if (process.env.NODE_ENV !== 'test') {
+        // eslint-disable-next-line no-console
+        console.error(e);
+      }
     }
   }
 
   updateDimensions() {
     this.instance.layout();
+    this.updateDiffView();
   }
 
   setPosition({ lineNumber, column }) {
@@ -103,8 +169,24 @@ export default class Editor {
   }
 
   onPositionChange(cb) {
-    this.disposable.add(
-      this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)),
-    );
+    if (!this.instance.onDidChangeCursorPosition) return;
+
+    this.disposable.add(this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)));
+  }
+
+  updateDiffView() {
+    if (!this.isDiffEditorType) return;
+
+    this.instance.updateOptions({
+      renderSideBySide: Editor.renderSideBySide(this.instance.getDomNode()),
+    });
+  }
+
+  get isDiffEditorType() {
+    return this.instance.getEditorType() === 'vs.editor.IDiffEditor';
+  }
+
+  static renderSideBySide(domElement) {
+    return domElement.offsetWidth >= 700;
   }
 }
diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js
index 701affc466e3a6f4e90284108fc9cec4ddca5000..9f895d49f2efd6568aa6ddfe66113d511ed10610 100644
--- a/app/assets/javascripts/ide/lib/editor_options.js
+++ b/app/assets/javascripts/ide/lib/editor_options.js
@@ -1,2 +1,16 @@
-export default [{
-}];
+export const defaultEditorOptions = {
+  model: null,
+  readOnly: false,
+  contextmenu: true,
+  scrollBeyondLastLine: false,
+  minimap: {
+    enabled: false,
+  },
+  wordWrap: 'on',
+};
+
+export default [
+  {
+    readOnly: model => !!model.file.file_lock,
+  },
+];
diff --git a/app/assets/javascripts/ide/lib/themes/gl_theme.js b/app/assets/javascripts/ide/lib/themes/gl_theme.js
new file mode 100644
index 0000000000000000000000000000000000000000..2fc96250c7dbfa15d2ea24f95ca8bc97e2eadf35
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/themes/gl_theme.js
@@ -0,0 +1,14 @@
+export default {
+  themeName: 'gitlab',
+  monacoTheme: {
+    base: 'vs',
+    inherit: true,
+    rules: [],
+    colors: {
+      'editorLineNumber.foreground': '#CCCCCC',
+      'diffEditor.insertedTextBackground': '#ddfbe6',
+      'diffEditor.removedTextBackground': '#f9d7dc',
+      'editor.selectionBackground': '#aad6f8',
+    },
+  },
+};
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index 1fb24e93f2e102ecb146a44f6d16809645ebc28b..a12e637616a70d05472ac352b3637fadb23ceaf8 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -1,6 +1,6 @@
 import Vue from 'vue';
 import VueResource from 'vue-resource';
-import Api from '../../api';
+import Api from '~/api';
 
 Vue.use(VueResource);
 
@@ -20,12 +20,35 @@ export default {
       return Promise.resolve(file.raw);
     }
 
-    return Vue.http.get(file.rawPath, { params: { format: 'json' } })
+    return Vue.http.get(file.rawPath, { params: { format: 'json' } }).then(res => res.text());
+  },
+  getBaseRawFileData(file, sha) {
+    if (file.tempFile) {
+      return Promise.resolve(file.baseRaw);
+    }
+
+    if (file.baseRaw) {
+      return Promise.resolve(file.baseRaw);
+    }
+
+    return Vue.http
+      .get(file.rawPath.replace(`/raw/${file.branchId}/${file.path}`, `/raw/${sha}/${file.path}`), {
+        params: { format: 'json' },
+      })
       .then(res => res.text());
   },
   getProjectData(namespace, project) {
     return Api.project(`${namespace}/${project}`);
   },
+  getProjectMergeRequestData(projectId, mergeRequestId) {
+    return Api.mergeRequest(projectId, mergeRequestId);
+  },
+  getProjectMergeRequestChanges(projectId, mergeRequestId) {
+    return Api.mergeRequestChanges(projectId, mergeRequestId);
+  },
+  getProjectMergeRequestVersions(projectId, mergeRequestId) {
+    return Api.mergeRequestVersions(projectId, mergeRequestId);
+  },
   getBranchData(projectId, currentBranchId) {
     return Api.branchSingle(projectId, currentBranchId);
   },
@@ -44,4 +67,12 @@ export default {
       },
     });
   },
+  getFiles(projectUrl, branchId) {
+    const url = `${projectUrl}/files/${branchId}`;
+    return Vue.http.get(url, {
+      params: {
+        format: 'json',
+      },
+    });
+  },
 };
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 2c690b1f635f249325f248b38c407a5fe09435a9..cecb4d215bab2c4c816dd794d1ab355aee2087d8 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -1,59 +1,28 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import { visitUrl } from '~/lib/utils/url_utility';
 import flash from '~/flash';
-import service from '../services';
 import * as types from './mutation_types';
-import { stripHtml } from '../../lib/utils/text_utility';
+import FilesDecoratorWorker from './workers/files_decorator_worker';
 
 export const redirectToUrl = (_, url) => visitUrl(url);
 
-export const setInitialData = ({ commit }, data) =>
-  commit(types.SET_INITIAL_DATA, data);
+export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
 
-export const closeDiscardPopup = ({ commit }) =>
-  commit(types.TOGGLE_DISCARD_POPUP, false);
-
-export const discardAllChanges = ({ commit, getters, dispatch }) => {
-  const changedFiles = getters.changedFiles;
-
-  changedFiles.forEach((file) => {
-    commit(types.DISCARD_FILE_CHANGES, file);
+export const discardAllChanges = ({ state, commit, dispatch }) => {
+  state.changedFiles.forEach(file => {
+    commit(types.DISCARD_FILE_CHANGES, file.path);
 
     if (file.tempFile) {
-      dispatch('closeFile', { file, force: true });
+      dispatch('closeFile', file.path);
     }
   });
-};
-
-export const closeAllFiles = ({ state, dispatch }) => {
-  state.openFiles.forEach(file => dispatch('closeFile', { file }));
-};
-
-export const toggleEditMode = (
-  { state, commit, getters, dispatch },
-  force = false,
-) => {
-  const changedFiles = getters.changedFiles;
-
-  if (changedFiles.length && !force) {
-    commit(types.TOGGLE_DISCARD_POPUP, true);
-  } else {
-    commit(types.TOGGLE_EDIT_MODE);
-    commit(types.TOGGLE_DISCARD_POPUP, false);
-    dispatch('toggleBlobView');
 
-    if (!state.editMode) {
-      dispatch('discardAllChanges');
-    }
-  }
+  commit(types.REMOVE_ALL_CHANGES_FILES);
 };
 
-export const toggleBlobView = ({ commit, state }) => {
-  if (state.editMode) {
-    commit(types.SET_EDIT_MODE);
-  } else {
-    commit(types.SET_PREVIEW_MODE);
-  }
+export const closeAllFiles = ({ state, dispatch }) => {
+  state.openFiles.forEach(file => dispatch('closeFile', file));
 };
 
 export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
@@ -64,119 +33,81 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
   }
 };
 
+export const toggleRightPanelCollapsed = (
+  { dispatch, state },
+  e = undefined,
+) => {
+  if (e) {
+    $(e.currentTarget)
+      .tooltip('hide')
+      .blur();
+  }
+
+  dispatch('setPanelCollapsedStatus', {
+    side: 'right',
+    collapsed: !state.rightPanelCollapsed,
+  });
+};
+
 export const setResizingStatus = ({ commit }, resizing) => {
   commit(types.SET_RESIZING_STATUS, resizing);
 };
 
-export const checkCommitStatus = ({ state }) =>
-  service
-    .getBranchData(state.currentProjectId, state.currentBranchId)
-    .then(({ data }) => {
-      const { id } = data.commit;
-      const selectedBranch =
-        state.projects[state.currentProjectId].branches[state.currentBranchId];
-
-      if (selectedBranch.workingReference !== id) {
-        return true;
-      }
-
-      return false;
-    })
-    .catch(() => flash('Error checking branch data. Please try again.', 'alert', document, null, false, true));
-
-export const commitChanges = (
-  { commit, state, dispatch, getters },
-  { payload, newMr },
+export const createTempEntry = (
+  { state, commit, dispatch },
+  { branchId, name, type, content = '', base64 = false },
 ) =>
-  service
-    .commit(state.currentProjectId, payload)
-    .then(({ data }) => {
-      const { branch } = payload;
-      if (!data.short_id) {
-        flash(data.message, 'alert', document, null, false, true);
-        return;
-      }
-
-      const selectedProject = state.projects[state.currentProjectId];
-      const lastCommit = {
-        commit_path: `${selectedProject.web_url}/commit/${data.id}`,
-        commit: {
-          message: data.message,
-          authored_date: data.committed_date,
-        },
-      };
-
-      let commitMsg = `Your changes have been committed. Commit ${data.short_id}`;
-      if (data.stats) {
-        commitMsg += ` with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`;
-      }
+  new Promise(resolve => {
+    const worker = new FilesDecoratorWorker();
+    const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
 
+    if (state.entries[name]) {
       flash(
-        commitMsg,
-        'notice',
+        `The name "${name.split('/').pop()}" is already taken in this directory.`,
+        'alert',
         document,
         null,
         false,
-        true);
-      window.dispatchEvent(new Event('resize'));
-
-      if (newMr) {
-        dispatch('discardAllChanges');
-        dispatch(
-          'redirectToUrl',
-          `${selectedProject.web_url}/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
-        );
-      } else {
-        commit(types.SET_BRANCH_WORKING_REFERENCE, {
-          projectId: state.currentProjectId,
-          branchId: state.currentBranchId,
-          reference: data.id,
-        });
-
-        getters.changedFiles.forEach((entry) => {
-          commit(types.SET_LAST_COMMIT_DATA, {
-            entry,
-            lastCommit,
-          });
-        });
-
-        dispatch('discardAllChanges');
-
-        window.scrollTo(0, 0);
-      }
-    })
-    .catch((err) => {
-      let errMsg = 'Error committing changes. Please try again.';
-      if (err.response.data && err.response.data.message) {
-        errMsg += ` (${stripHtml(err.response.data.message)})`;
+        true,
+      );
+
+      resolve();
+
+      return null;
+    }
+
+    worker.addEventListener('message', ({ data }) => {
+      const { file } = data;
+
+      worker.terminate();
+
+      commit(types.CREATE_TMP_ENTRY, {
+        data,
+        projectId: state.currentProjectId,
+        branchId,
+      });
+
+      if (type === 'blob') {
+        commit(types.TOGGLE_FILE_OPEN, file.path);
+        commit(types.ADD_FILE_TO_CHANGED, file.path);
+        dispatch('setFileActive', file.path);
       }
-      flash(errMsg, 'alert', document, null, false, true);
-      window.dispatchEvent(new Event('resize'));
-    });
 
-export const createTempEntry = (
-  { state, dispatch },
-  { projectId, branchId, parent, name, type, content = '', base64 = false },
-) => {
-  const selectedParent = parent || state.trees[`${projectId}/${branchId}`];
-  if (type === 'tree') {
-    dispatch('createTempTree', {
-      projectId,
-      branchId,
-      parent: selectedParent,
-      name,
+      resolve(file);
     });
-  } else if (type === 'blob') {
-    dispatch('createTempFile', {
-      projectId,
+
+    worker.postMessage({
+      data: [fullName],
+      projectId: state.currentProjectId,
       branchId,
-      parent: selectedParent,
-      name,
+      type,
+      tempFile: true,
       base64,
       content,
     });
-  }
-};
+
+    return null;
+  });
 
 export const scrollToTab = () => {
   Vue.nextTick(() => {
@@ -190,7 +121,23 @@ export const scrollToTab = () => {
   });
 };
 
+export const stageAllChanges = ({ state, commit }) => {
+  state.changedFiles.forEach(file => commit(types.STAGE_CHANGE, file.path));
+};
+
+export const unstageAllChanges = ({ state, commit }) => {
+  state.stagedFiles.forEach(file => commit(types.UNSTAGE_CHANGE, file.path));
+};
+
+export const updateViewer = ({ commit }, viewer) => {
+  commit(types.UPDATE_VIEWER, viewer);
+};
+
+export const updateDelayViewerUpdated = ({ commit }, delay) => {
+  commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
+};
+
 export * from './actions/tree';
 export * from './actions/file';
 export * from './actions/project';
-export * from './actions/branch';
+export * from './actions/merge_request';
diff --git a/app/assets/javascripts/ide/stores/actions/branch.js b/app/assets/javascripts/ide/stores/actions/branch.js
deleted file mode 100644
index bc6fd2d41635d0f80eadf00a65ab0dde762e5bde..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/ide/stores/actions/branch.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import service from '../../services';
-import flash from '../../../flash';
-import * as types from '../mutation_types';
-
-export const getBranchData = (
-  { commit, state, dispatch },
-  { projectId, branchId, force = false } = {},
-) => new Promise((resolve, reject) => {
-  if ((typeof state.projects[`${projectId}`] === 'undefined' ||
-        !state.projects[`${projectId}`].branches[branchId])
-        || force) {
-    service.getBranchData(`${projectId}`, branchId)
-      .then(({ data }) => {
-        const { id } = data.commit;
-        commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
-        commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
-        resolve(data);
-      })
-      .catch(() => {
-        flash('Error loading branch data. Please try again.', 'alert', document, null, false, true);
-        reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
-      });
-  } else {
-    resolve(state.projects[`${projectId}`].branches[branchId]);
-  }
-});
-
-export const createNewBranch = ({ state, commit }, branch) => service.createBranch(
-  state.currentProjectId,
-  {
-    branch,
-    ref: state.currentBranchId,
-  },
-)
-.then(res => res.json())
-.then((data) => {
-  const branchName = data.name;
-  const url = location.href.replace(state.currentBranchId, branchName);
-
-  if (this.$router) this.$router.push(url);
-
-  commit(types.SET_CURRENT_BRANCH, branchName);
-});
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 670af2fb89efc2b354104a5e898a286763f12301..d782e0a84d2a44afa0335e7501f2d1f9654d7819 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -1,137 +1,213 @@
-import { normalizeHeaders } from '../../../lib/utils/common_utils';
-import flash from '../../../flash';
+import { normalizeHeaders } from '~/lib/utils/common_utils';
+import flash from '~/flash';
+import eventHub from '../../eventhub';
 import service from '../../services';
 import * as types from '../mutation_types';
 import router from '../../ide_router';
-import {
-  findEntry,
-  setPageTitle,
-  createTemp,
-  findIndexOfFile,
-} from '../utils';
+import { setPageTitle } from '../utils';
 
-export const closeFile = ({ commit, state, dispatch }, { file, force = false }) => {
-  if ((file.changed || file.tempFile) && !force) return;
-
-  const indexOfClosedFile = findIndexOfFile(state.openFiles, file);
+export const closeFile = ({ commit, state, dispatch }, file) => {
+  const path = file.path;
+  const indexOfClosedFile = state.openFiles.findIndex(f => f.key === file.key);
   const fileWasActive = file.active;
 
-  commit(types.TOGGLE_FILE_OPEN, file);
-  commit(types.SET_FILE_ACTIVE, { file, active: false });
+  if (file.pending) {
+    commit(types.REMOVE_PENDING_TAB, file);
+  } else {
+    commit(types.TOGGLE_FILE_OPEN, path);
+    commit(types.SET_FILE_ACTIVE, { path, active: false });
+  }
 
   if (state.openFiles.length > 0 && fileWasActive) {
     const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
     const nextFileToOpen = state.openFiles[nextIndexToOpen];
 
-    dispatch('setFileActive', nextFileToOpen);
+    if (nextFileToOpen.pending) {
+      dispatch('updateViewer', 'diff');
+      dispatch('openPendingTab', {
+        file: nextFileToOpen,
+        keyPrefix: nextFileToOpen.staged ? 'staged' : 'unstaged',
+      });
+    } else {
+      dispatch('updateDelayViewerUpdated', true);
+      router.push(`/project${nextFileToOpen.url}`);
+    }
   } else if (!state.openFiles.length) {
     router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
   }
 
-  dispatch('getLastCommitData');
+  eventHub.$emit(`editor.update.model.dispose.${file.key}`);
 };
 
-export const setFileActive = ({ commit, state, getters, dispatch }, file) => {
+export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
+  const file = state.entries[path];
   const currentActiveFile = getters.activeFile;
 
   if (file.active) return;
 
   if (currentActiveFile) {
-    commit(types.SET_FILE_ACTIVE, { file: currentActiveFile, active: false });
+    commit(types.SET_FILE_ACTIVE, {
+      path: currentActiveFile.path,
+      active: false,
+    });
   }
 
-  commit(types.SET_FILE_ACTIVE, { file, active: true });
+  commit(types.SET_FILE_ACTIVE, { path, active: true });
   dispatch('scrollToTab');
 
-  // reset hash for line highlighting
-  location.hash = '';
-
   commit(types.SET_CURRENT_PROJECT, file.projectId);
   commit(types.SET_CURRENT_BRANCH, file.branchId);
 };
 
-export const getFileData = ({ state, commit, dispatch }, file) => {
-  commit(types.TOGGLE_LOADING, file);
-
-  service.getFileData(file.url)
-    .then((res) => {
+export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
+  const file = state.entries[path];
+  commit(types.TOGGLE_LOADING, { entry: file });
+  return service
+    .getFileData(file.url)
+    .then(res => {
       const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
-
       setPageTitle(pageTitle);
 
       return res.json();
     })
-    .then((data) => {
+    .then(data => {
       commit(types.SET_FILE_DATA, { data, file });
-      commit(types.TOGGLE_FILE_OPEN, file);
-      dispatch('setFileActive', file);
-      commit(types.TOGGLE_LOADING, file);
+      commit(types.TOGGLE_FILE_OPEN, path);
+      if (makeFileActive) dispatch('setFileActive', path);
+      commit(types.TOGGLE_LOADING, { entry: file });
     })
     .catch(() => {
-      commit(types.TOGGLE_LOADING, file);
+      commit(types.TOGGLE_LOADING, { entry: file });
       flash('Error loading file data. Please try again.', 'alert', document, null, false, true);
     });
 };
 
-export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file)
-  .then((raw) => {
-    commit(types.SET_FILE_RAW_DATA, { file, raw });
-  })
-  .catch(() => flash('Error loading file content. Please try again.', 'alert', document, null, false, true));
+export const setFileMrChange = ({ state, commit }, { file, mrChange }) => {
+  commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange });
+};
 
-export const changeFileContent = ({ commit }, { file, content }) => {
-  commit(types.UPDATE_FILE_CONTENT, { file, content });
+export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => {
+  const file = state.entries[path];
+  return new Promise((resolve, reject) => {
+    service
+      .getRawFileData(file)
+      .then(raw => {
+        commit(types.SET_FILE_RAW_DATA, { file, raw });
+        if (file.mrChange && file.mrChange.new_file === false) {
+          service
+            .getBaseRawFileData(file, baseSha)
+            .then(baseRaw => {
+              commit(types.SET_FILE_BASE_RAW_DATA, {
+                file,
+                baseRaw,
+              });
+              resolve(raw);
+            })
+            .catch(e => {
+              reject(e);
+            });
+        } else {
+          resolve(raw);
+        }
+      })
+      .catch(() => {
+        flash('Error loading file content. Please try again.');
+        reject();
+      });
+  });
 };
 
-export const setFileLanguage = ({ state, commit }, { fileLanguage }) => {
-  if (state.selectedFile) {
-    commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage });
+export const changeFileContent = ({ state, commit }, { path, content }) => {
+  const file = state.entries[path];
+  commit(types.UPDATE_FILE_CONTENT, { path, content });
+
+  const indexOfChangedFile = state.changedFiles.findIndex(f => f.path === path);
+
+  if (file.changed && indexOfChangedFile === -1) {
+    commit(types.ADD_FILE_TO_CHANGED, path);
+  } else if (!file.changed && indexOfChangedFile !== -1) {
+    commit(types.REMOVE_FILE_FROM_CHANGED, path);
   }
 };
 
-export const setFileEOL = ({ state, commit }, { eol }) => {
-  if (state.selectedFile) {
-    commit(types.SET_FILE_EOL, { file: state.selectedFile, eol });
+export const setFileLanguage = ({ getters, commit }, { fileLanguage }) => {
+  if (getters.activeFile) {
+    commit(types.SET_FILE_LANGUAGE, { file: getters.activeFile, fileLanguage });
   }
 };
 
-export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => {
-  if (state.selectedFile) {
-    commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn });
+export const setFileEOL = ({ getters, commit }, { eol }) => {
+  if (getters.activeFile) {
+    commit(types.SET_FILE_EOL, { file: getters.activeFile, eol });
   }
 };
 
-export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => {
-  const path = parent.path !== undefined ? parent.path : '';
-  // We need to do the replacement otherwise the web_url + file.url duplicate
-  const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`;
-  const file = createTemp({
-    projectId,
-    branchId,
-    name: name.replace(`${path}/`, ''),
-    path,
-    type: 'blob',
-    level: parent.level !== undefined ? parent.level + 1 : 0,
-    changed: true,
-    content,
-    base64,
-    url: newUrl,
-  });
+export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn }) => {
+  if (getters.activeFile) {
+    commit(types.SET_FILE_POSITION, {
+      file: getters.activeFile,
+      editorRow,
+      editorColumn,
+    });
+  }
+};
 
-  if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`, 'alert', document, null, false, true);
+export const setFileViewMode = ({ state, commit }, { file, viewMode }) => {
+  commit(types.SET_FILE_VIEWMODE, { file, viewMode });
+};
 
-  commit(types.CREATE_TMP_FILE, {
-    parent,
-    file,
-  });
-  commit(types.TOGGLE_FILE_OPEN, file);
-  dispatch('setFileActive', file);
+export const discardFileChanges = ({ dispatch, state, commit, getters }, path) => {
+  const file = state.entries[path];
+
+  commit(types.DISCARD_FILE_CHANGES, path);
+  commit(types.REMOVE_FILE_FROM_CHANGED, path);
+
+  if (file.tempFile && file.opened) {
+    commit(types.TOGGLE_FILE_OPEN, path);
+  } else if (getters.activeFile && file.path === getters.activeFile.path) {
+    dispatch('updateDelayViewerUpdated', true)
+      .then(() => {
+        router.push(`/project${file.url}`);
+      })
+      .catch(e => {
+        throw e;
+      });
+  }
+
+  eventHub.$emit(`editor.update.model.new.content.${file.key}`, file.content);
+  eventHub.$emit(`editor.update.model.dispose.unstaged-${file.key}`, file.content);
+};
+
+export const stageChange = ({ commit, state }, path) => {
+  const stagedFile = state.stagedFiles.find(f => f.path === path);
+
+  commit(types.STAGE_CHANGE, path);
+
+  if (stagedFile) {
+    eventHub.$emit(`editor.update.model.new.content.staged-${stagedFile.key}`, stagedFile.content);
+  }
+};
 
-  if (!state.editMode && !file.base64) {
-    dispatch('toggleEditMode', true);
+export const unstageChange = ({ commit }, path) => {
+  commit(types.UNSTAGE_CHANGE, path);
+};
+
+export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => {
+  if (getters.activeFile && getters.activeFile === file && state.viewer === 'diff') {
+    return false;
   }
 
-  router.push(`/project${file.url}`);
+  commit(types.ADD_PENDING_TAB, { file, keyPrefix });
+
+  dispatch('scrollToTab');
+
+  router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
+
+  return true;
+};
+
+export const removePendingTab = ({ commit }, file) => {
+  commit(types.REMOVE_PENDING_TAB, file);
 
-  return Promise.resolve(file);
+  eventHub.$emit(`editor.update.model.dispose.${file.key}`);
 };
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
new file mode 100644
index 0000000000000000000000000000000000000000..da73034fd7d3a70e91d0a25247e05cb1d788fc1e
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -0,0 +1,84 @@
+import flash from '~/flash';
+import service from '../../services';
+import * as types from '../mutation_types';
+
+export const getMergeRequestData = (
+  { commit, state, dispatch },
+  { projectId, mergeRequestId, force = false } = {},
+) =>
+  new Promise((resolve, reject) => {
+    if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) {
+      service
+        .getProjectMergeRequestData(projectId, mergeRequestId)
+        .then(res => res.data)
+        .then(data => {
+          commit(types.SET_MERGE_REQUEST, {
+            projectPath: projectId,
+            mergeRequestId,
+            mergeRequest: data,
+          });
+          if (!state.currentMergeRequestId) {
+            commit(types.SET_CURRENT_MERGE_REQUEST, mergeRequestId);
+          }
+          resolve(data);
+        })
+        .catch(() => {
+          flash('Error loading merge request data. Please try again.');
+          reject(new Error(`Merge Request not loaded ${projectId}`));
+        });
+    } else {
+      resolve(state.projects[projectId].mergeRequests[mergeRequestId]);
+    }
+  });
+
+export const getMergeRequestChanges = (
+  { commit, state, dispatch },
+  { projectId, mergeRequestId, force = false } = {},
+) =>
+  new Promise((resolve, reject) => {
+    if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) {
+      service
+        .getProjectMergeRequestChanges(projectId, mergeRequestId)
+        .then(res => res.data)
+        .then(data => {
+          commit(types.SET_MERGE_REQUEST_CHANGES, {
+            projectPath: projectId,
+            mergeRequestId,
+            changes: data,
+          });
+          resolve(data);
+        })
+        .catch(() => {
+          flash('Error loading merge request changes. Please try again.');
+          reject(new Error(`Merge Request Changes not loaded ${projectId}`));
+        });
+    } else {
+      resolve(state.projects[projectId].mergeRequests[mergeRequestId].changes);
+    }
+  });
+
+export const getMergeRequestVersions = (
+  { commit, state, dispatch },
+  { projectId, mergeRequestId, force = false } = {},
+) =>
+  new Promise((resolve, reject) => {
+    if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) {
+      service
+        .getProjectMergeRequestVersions(projectId, mergeRequestId)
+        .then(res => res.data)
+        .then(data => {
+          commit(types.SET_MERGE_REQUEST_VERSIONS, {
+            projectPath: projectId,
+            mergeRequestId,
+            versions: data,
+          });
+          resolve(data);
+        })
+        .catch(() => {
+          flash('Error loading merge request versions. Please try again.');
+          reject(new Error(`Merge Request Versions not loaded ${projectId}`));
+        });
+    } else {
+      resolve(state.projects[projectId].mergeRequests[mergeRequestId].versions);
+    }
+  });
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index faeceb430a2debfd98249c02499ef7fa78a18c53..4eb23b2ee0fae6882de2dcc81f2b00c5c19838f0 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -1,27 +1,75 @@
+import flash from '~/flash';
 import service from '../../services';
-import flash from '../../../flash';
 import * as types from '../mutation_types';
 
-// eslint-disable-next-line import/prefer-default-export
 export const getProjectData = (
   { commit, state, dispatch },
   { namespace, projectId, force = false } = {},
-) => new Promise((resolve, reject) => {
-  if (!state.projects[`${namespace}/${projectId}`] || force) {
-    commit(types.TOGGLE_LOADING, state);
-    service.getProjectData(namespace, projectId)
-    .then(res => res.data)
-    .then((data) => {
-      commit(types.TOGGLE_LOADING, state);
-      commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
-      if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
-      resolve(data);
-    })
-    .catch(() => {
-      flash('Error loading project data. Please try again.', 'alert', document, null, false, true);
-      reject(new Error(`Project not loaded ${namespace}/${projectId}`));
-    });
-  } else {
-    resolve(state.projects[`${namespace}/${projectId}`]);
-  }
-});
+) =>
+  new Promise((resolve, reject) => {
+    if (!state.projects[`${namespace}/${projectId}`] || force) {
+      commit(types.TOGGLE_LOADING, { entry: state });
+      service
+        .getProjectData(namespace, projectId)
+        .then(res => res.data)
+        .then(data => {
+          commit(types.TOGGLE_LOADING, { entry: state });
+          commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
+          if (!state.currentProjectId)
+            commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
+          resolve(data);
+        })
+        .catch(() => {
+          flash(
+            'Error loading project data. Please try again.',
+            'alert',
+            document,
+            null,
+            false,
+            true,
+          );
+          reject(new Error(`Project not loaded ${namespace}/${projectId}`));
+        });
+    } else {
+      resolve(state.projects[`${namespace}/${projectId}`]);
+    }
+  });
+
+export const getBranchData = (
+  { commit, state, dispatch },
+  { projectId, branchId, force = false } = {},
+) =>
+  new Promise((resolve, reject) => {
+    if (
+      typeof state.projects[`${projectId}`] === 'undefined' ||
+      !state.projects[`${projectId}`].branches[branchId] ||
+      force
+    ) {
+      service
+        .getBranchData(`${projectId}`, branchId)
+        .then(({ data }) => {
+          const { id } = data.commit;
+          commit(types.SET_BRANCH, {
+            projectPath: `${projectId}`,
+            branchName: branchId,
+            branch: data,
+          });
+          commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
+          commit(types.SET_CURRENT_BRANCH, branchId);
+          resolve(data);
+        })
+        .catch(() => {
+          flash(
+            'Error loading branch data. Please try again.',
+            'alert',
+            document,
+            null,
+            false,
+            true,
+          );
+          reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
+        });
+    } else {
+      resolve(state.projects[`${projectId}`].branches[branchId]);
+    }
+  });
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index 302ba45edee39ef164e1d1dca811a1cda3ee5776..6536be04f0a1d1a47a5b8a50f238759acd3c5b94 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -1,147 +1,42 @@
-import { visitUrl } from '../../../lib/utils/url_utility';
-import { normalizeHeaders } from '../../../lib/utils/common_utils';
-import flash from '../../../flash';
+import { normalizeHeaders } from '~/lib/utils/common_utils';
+import flash from '~/flash';
 import service from '../../services';
 import * as types from '../mutation_types';
-import router from '../../ide_router';
-import {
-  setPageTitle,
-  findEntry,
-  createTemp,
-  createOrMergeEntry,
-} from '../utils';
+import { findEntry } from '../utils';
+import FilesDecoratorWorker from '../workers/files_decorator_worker';
 
-export const getTreeData = (
-  { commit, state, dispatch },
-  { endpoint, tree = null, projectId, branch, force = false } = {},
-) => new Promise((resolve, reject) => {
-  // We already have the base tree so we resolve immediately
-  if (!tree && state.trees[`${projectId}/${branch}`] && !force) {
-    resolve();
-  } else {
-    if (tree) commit(types.TOGGLE_LOADING, tree);
-    const selectedProject = state.projects[projectId];
-    // We are merging the web_url that we got on the project info with the endpoint
-    // we got on the tree entry, as both contain the projectId, we replace it in the tree endpoint
-    const completeEndpoint = selectedProject.web_url + (endpoint).replace(projectId, '');
-    if (completeEndpoint && (!tree || !tree.tempFile)) {
-      service.getTreeData(completeEndpoint)
-      .then((res) => {
-        const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
-
-        setPageTitle(pageTitle);
-
-        return res.json();
-      })
-      .then((data) => {
-        if (!state.isInitialRoot) {
-          commit(types.SET_ROOT, data.path === '/');
-        }
-
-        dispatch('updateDirectoryData', { data, tree, projectId, branch });
-        const selectedTree = tree || state.trees[`${projectId}/${branch}`];
-
-        commit(types.SET_PARENT_TREE_URL, data.parent_tree_url);
-        commit(types.SET_LAST_COMMIT_URL, { tree: selectedTree, url: data.last_commit_path });
-        if (tree) commit(types.TOGGLE_LOADING, selectedTree);
-
-        const prevLastCommitPath = selectedTree.lastCommitPath;
-        if (prevLastCommitPath !== null) {
-          dispatch('getLastCommitData', selectedTree);
-        }
-        resolve(data);
-      })
-      .catch((e) => {
-        flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
-        if (tree) commit(types.TOGGLE_LOADING, tree);
-        reject(e);
-      });
-    } else {
-      resolve();
-    }
-  }
-});
-
-export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => {
-  if (tree.opened) {
-    // send empty data to clear the tree
-    const data = { trees: [], blobs: [], submodules: [] };
-
-    dispatch('updateDirectoryData', { data, tree, projectId: tree.projectId, branchId: tree.branchId });
-  } else {
-    dispatch('getTreeData', { endpoint, tree, projectId: tree.projectId, branch: tree.branchId });
-  }
-
-  commit(types.TOGGLE_TREE_OPEN, tree);
+export const toggleTreeOpen = ({ commit, dispatch }, path) => {
+  commit(types.TOGGLE_TREE_OPEN, path);
 };
 
 export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
   if (row.type === 'tree') {
-    dispatch('toggleTreeOpen', {
-      endpoint: row.url,
-      tree: row,
-    });
-  } else if (row.type === 'submodule') {
-    commit(types.TOGGLE_LOADING, row);
-    visitUrl(row.url);
-  } else if (row.type === 'blob' && row.opened) {
-    dispatch('setFileActive', row);
+    dispatch('toggleTreeOpen', row.path);
+  } else if (row.type === 'blob' && (row.opened || row.changed)) {
+    if (row.changed && !row.opened) {
+      commit(types.TOGGLE_FILE_OPEN, row.path);
+    }
+
+    dispatch('setFileActive', row.path);
   } else {
-    dispatch('getFileData', row);
+    dispatch('getFileData', { path: row.path });
   }
 };
 
-export const createTempTree = (
-  { state, commit, dispatch },
-  { projectId, branchId, parent, name },
-) => {
-  let selectedTree = parent;
-  const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
-
-  dirNames.forEach((dirName) => {
-    const foundEntry = findEntry(selectedTree.tree, 'tree', dirName);
-
-    if (!foundEntry) {
-      const path = selectedTree.path !== undefined ? selectedTree.path : '';
-      const tmpEntry = createTemp({
-        projectId,
-        branchId,
-        name: dirName,
-        path,
-        type: 'tree',
-        level: selectedTree.level !== undefined ? selectedTree.level + 1 : 0,
-        tree: [],
-        url: `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${dirName}`,
-      });
-
-      commit(types.CREATE_TMP_TREE, {
-        parent: selectedTree,
-        tmpEntry,
-      });
-      commit(types.TOGGLE_TREE_OPEN, tmpEntry);
-
-      router.push(`/project${tmpEntry.url}`);
-
-      selectedTree = tmpEntry;
-    } else {
-      selectedTree = foundEntry;
-    }
-  });
-};
-
 export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
   if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
 
-  service.getTreeLastCommit(tree.lastCommitPath)
-    .then((res) => {
+  service
+    .getTreeLastCommit(tree.lastCommitPath)
+    .then(res => {
       const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
 
       commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
 
       return res.json();
     })
-    .then((data) => {
-      data.forEach((lastCommit) => {
+    .then(data => {
+      data.forEach(lastCommit => {
         const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name);
 
         if (entry) {
@@ -154,35 +49,47 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s
     .catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
 };
 
-export const updateDirectoryData = (
-  { commit, state },
-  { data, tree, projectId, branch },
-) => {
-  if (!tree) {
-    const existingTree = state.trees[`${projectId}/${branch}`];
-    if (!existingTree) {
-      commit(types.CREATE_TREE, { treePath: `${projectId}/${branch}` });
+export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) =>
+  new Promise((resolve, reject) => {
+    if (!state.trees[`${projectId}/${branchId}`]) {
+      const selectedProject = state.projects[projectId];
+      commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
+
+      service
+        .getFiles(selectedProject.web_url, branchId)
+        .then(res => res.json())
+        .then(data => {
+          const worker = new FilesDecoratorWorker();
+          worker.addEventListener('message', e => {
+            const { entries, treeList } = e.data;
+            const selectedTree = state.trees[`${projectId}/${branchId}`];
+
+            commit(types.SET_ENTRIES, entries);
+            commit(types.SET_DIRECTORY_DATA, {
+              treePath: `${projectId}/${branchId}`,
+              data: treeList,
+            });
+            commit(types.TOGGLE_LOADING, {
+              entry: selectedTree,
+              forceValue: false,
+            });
+
+            worker.terminate();
+
+            resolve();
+          });
+
+          worker.postMessage({
+            data,
+            projectId,
+            branchId,
+          });
+        })
+        .catch(e => {
+          flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
+          reject(e);
+        });
+    } else {
+      resolve();
     }
-  }
-
-  const selectedTree = tree || state.trees[`${projectId}/${branch}`];
-  const level = selectedTree.level !== undefined ? selectedTree.level + 1 : 0;
-  const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl;
-  const createEntry = (entry, type) => createOrMergeEntry({
-    tree: selectedTree,
-    projectId: `${projectId}`,
-    branchId: branch,
-    entry,
-    level,
-    type,
-    parentTreeUrl,
   });
-
-  const formattedData = [
-    ...data.trees.map(t => createEntry(t, 'tree')),
-    ...data.submodules.map(m => createEntry(m, 'submodule')),
-    ...data.blobs.map(b => createEntry(b, 'blob')),
-  ];
-
-  commit(types.SET_DIRECTORY_DATA, { tree: selectedTree, data: formattedData });
-};
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 6b51ccff8178b7a58074d37b9f5f811745c43116..8518d2f6f0690c228b46c5d2d2f66626c6709fdf 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -1,19 +1,45 @@
-export const changedFiles = state => state.openFiles.filter(file => file.changed);
+import { __ } from '~/locale';
 
 export const activeFile = state => state.openFiles.find(file => file.active) || null;
 
-export const activeFileExtension = (state) => {
-  const file = activeFile(state);
-  return file ? `.${file.path.split('.').pop()}` : '';
-};
+export const addedFiles = state => state.changedFiles.filter(f => f.tempFile);
+
+export const modifiedFiles = state => state.changedFiles.filter(f => !f.tempFile);
+
+export const projectsWithTrees = state =>
+  Object.keys(state.projects).map(projectId => {
+    const project = state.projects[projectId];
+
+    return {
+      ...project,
+      branches: Object.keys(project.branches).map(branchId => {
+        const branch = project.branches[branchId];
 
-export const canEditFile = (state) => {
-  const currentActiveFile = activeFile(state);
+        return {
+          ...branch,
+          tree: state.trees[branch.treeId],
+        };
+      }),
+    };
+  });
 
-  return state.canCommit &&
-         (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
+export const currentMergeRequest = state => {
+  if (state.projects[state.currentProjectId]) {
+    return state.projects[state.currentProjectId].mergeRequests[state.currentMergeRequestId];
+  }
+  return null;
 };
 
-export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
+// eslint-disable-next-line no-confusing-arrow
+export const collapseButtonIcon = state =>
+  state.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
+
+export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length;
+
+// eslint-disable-next-line no-confusing-arrow
+export const collapseButtonTooltip = state =>
+  state.rightPanelCollapsed ? __('Expand sidebar') : __('Collapse sidebar');
+
+export const hasMergeRequest = state => !!state.currentMergeRequestId;
 
-export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
+export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path);
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index 6ac9bfd8189c356b61405a134869cc319d1b4556..7c82ce7976b6f1a006ba2c857e37ad81fad8fe98 100644
--- a/app/assets/javascripts/ide/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
@@ -4,6 +4,7 @@ import state from './state';
 import * as actions from './actions';
 import * as getters from './getters';
 import mutations from './mutations';
+import commitModule from './modules/commit';
 
 Vue.use(Vuex);
 
@@ -12,4 +13,7 @@ export default new Vuex.Store({
   actions,
   mutations,
   getters,
+  modules: {
+    commit: commitModule,
+  },
 });
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
new file mode 100644
index 0000000000000000000000000000000000000000..b26512e213a91b422e903f53a0805a5b9f3bc6c7
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -0,0 +1,187 @@
+import $ from 'jquery';
+import { sprintf, __ } from '~/locale';
+import flash from '~/flash';
+import { stripHtml } from '~/lib/utils/text_utility';
+import * as rootTypes from '../../mutation_types';
+import { createCommitPayload, createNewMergeRequestUrl } from '../../utils';
+import router from '../../../ide_router';
+import service from '../../../services';
+import * as types from './mutation_types';
+import * as consts from './constants';
+import eventHub from '../../../eventhub';
+
+export const updateCommitMessage = ({ commit }, message) => {
+  commit(types.UPDATE_COMMIT_MESSAGE, message);
+};
+
+export const discardDraft = ({ commit }) => {
+  commit(types.UPDATE_COMMIT_MESSAGE, '');
+};
+
+export const updateCommitAction = ({ commit }, commitAction) => {
+  commit(types.UPDATE_COMMIT_ACTION, commitAction);
+};
+
+export const updateBranchName = ({ commit }, branchName) => {
+  commit(types.UPDATE_NEW_BRANCH_NAME, branchName);
+};
+
+export const setLastCommitMessage = ({ rootState, commit }, data) => {
+  const currentProject = rootState.projects[rootState.currentProjectId];
+  const commitStats = data.stats
+    ? sprintf(__('with %{additions} additions, %{deletions} deletions.'), {
+        additions: data.stats.additions, // eslint-disable-line indent
+        deletions: data.stats.deletions, // eslint-disable-line indent
+      }) // eslint-disable-line indent
+    : '';
+  const commitMsg = sprintf(
+    __('Your changes have been committed. Commit %{commitId} %{commitStats}'),
+    {
+      commitId: `<a href="${currentProject.web_url}/commit/${data.short_id}" class="commit-sha">${
+        data.short_id
+      }</a>`,
+      commitStats,
+    },
+    false,
+  );
+
+  commit(rootTypes.SET_LAST_COMMIT_MSG, commitMsg, { root: true });
+};
+
+export const checkCommitStatus = ({ rootState }) =>
+  service
+    .getBranchData(rootState.currentProjectId, rootState.currentBranchId)
+    .then(({ data }) => {
+      const { id } = data.commit;
+      const selectedBranch =
+        rootState.projects[rootState.currentProjectId].branches[rootState.currentBranchId];
+
+      if (selectedBranch.workingReference !== id) {
+        return true;
+      }
+
+      return false;
+    })
+    .catch(() =>
+      flash(
+        __('Error checking branch data. Please try again.'),
+        'alert',
+        document,
+        null,
+        false,
+        true,
+      ),
+    );
+
+export const updateFilesAfterCommit = (
+  { commit, dispatch, state, rootState, rootGetters },
+  { data, branch },
+) => {
+  const selectedProject = rootState.projects[rootState.currentProjectId];
+  const lastCommit = {
+    commit_path: `${selectedProject.web_url}/commit/${data.id}`,
+    commit: {
+      id: data.id,
+      message: data.message,
+      authored_date: data.committed_date,
+      author_name: data.committer_name,
+    },
+  };
+
+  commit(
+    rootTypes.SET_BRANCH_WORKING_REFERENCE,
+    {
+      projectId: rootState.currentProjectId,
+      branchId: rootState.currentBranchId,
+      reference: data.id,
+    },
+    { root: true },
+  );
+
+  rootState.stagedFiles.forEach(file => {
+    const changedFile = rootState.changedFiles.find(f => f.path === file.path);
+
+    commit(
+      rootTypes.UPDATE_FILE_AFTER_COMMIT,
+      {
+        file,
+        lastCommit,
+      },
+      { root: true },
+    );
+
+    eventHub.$emit(`editor.update.model.content.${file.key}`, {
+      content: file.content,
+      changed: !!changedFile,
+    });
+  });
+
+  if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH && rootGetters.activeFile) {
+    router.push(
+      `/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`,
+    );
+  }
+};
+
+export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => {
+  const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
+  const payload = createCommitPayload(getters.branchName, newBranch, state, rootState);
+  const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus');
+
+  commit(types.UPDATE_LOADING, true);
+
+  return getCommitStatus
+    .then(
+      branchChanged =>
+        new Promise(resolve => {
+          if (branchChanged) {
+            // show the modal with a Bootstrap call
+            $('#ide-create-branch-modal').modal('show');
+          } else {
+            resolve();
+          }
+        }),
+    )
+    .then(() => service.commit(rootState.currentProjectId, payload))
+    .then(({ data }) => {
+      commit(types.UPDATE_LOADING, false);
+
+      if (!data.short_id) {
+        flash(data.message, 'alert', document, null, false, true);
+        return null;
+      }
+
+      dispatch('setLastCommitMessage', data);
+      dispatch('updateCommitMessage', '');
+      return dispatch('updateFilesAfterCommit', {
+        data,
+        branch: getters.branchName,
+      })
+        .then(() => {
+          if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
+            dispatch(
+              'redirectToUrl',
+              createNewMergeRequestUrl(
+                rootState.projects[rootState.currentProjectId].web_url,
+                getters.branchName,
+                rootState.currentBranchId,
+              ),
+              { root: true },
+            );
+          }
+
+          commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true });
+        })
+        .then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH));
+    })
+    .catch(err => {
+      let errMsg = __('Error committing changes. Please try again.');
+      if (err.response.data && err.response.data.message) {
+        errMsg += ` (${stripHtml(err.response.data.message)})`;
+      }
+      flash(errMsg, 'alert', document, null, false, true);
+      window.dispatchEvent(new Event('resize'));
+
+      commit(types.UPDATE_LOADING, false);
+    });
+};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/constants.js b/app/assets/javascripts/ide/stores/modules/commit/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..230b0a3d9b51025592d6c5f780395ec1ceae083b
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/commit/constants.js
@@ -0,0 +1,3 @@
+export const COMMIT_TO_CURRENT_BRANCH = '1';
+export const COMMIT_TO_NEW_BRANCH = '2';
+export const COMMIT_TO_NEW_BRANCH_MR = '3';
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
new file mode 100644
index 0000000000000000000000000000000000000000..9c3905a0b0d7db9315f975d33de357b852912829
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -0,0 +1,29 @@
+import * as consts from './constants';
+
+const BRANCH_SUFFIX_COUNT = 5;
+
+export const discardDraftButtonDisabled = state =>
+  state.commitMessage === '' || state.submitCommitLoading;
+
+export const commitButtonDisabled = (state, getters, rootState) =>
+  getters.discardDraftButtonDisabled || !rootState.stagedFiles.length;
+
+export const newBranchName = (state, _, rootState) =>
+  `${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(
+    -BRANCH_SUFFIX_COUNT,
+  )}`;
+
+export const branchName = (state, getters, rootState) => {
+  if (
+    state.commitAction === consts.COMMIT_TO_NEW_BRANCH ||
+    state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR
+  ) {
+    if (state.newBranchName === '') {
+      return getters.newBranchName;
+    }
+
+    return state.newBranchName;
+  }
+
+  return rootState.currentBranchId;
+};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/index.js b/app/assets/javascripts/ide/stores/modules/commit/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..3bf65b0284700902952fe0c9813f342db7840447
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/commit/index.js
@@ -0,0 +1,12 @@
+import state from './state';
+import mutations from './mutations';
+import * as actions from './actions';
+import * as getters from './getters';
+
+export default {
+  namespaced: true,
+  state: state(),
+  mutations,
+  actions,
+  getters,
+};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
new file mode 100644
index 0000000000000000000000000000000000000000..9221f054e9f40e3927a5e1a13afeebc704c1be66
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
@@ -0,0 +1,4 @@
+export const UPDATE_COMMIT_MESSAGE = 'UPDATE_COMMIT_MESSAGE';
+export const UPDATE_COMMIT_ACTION = 'UPDATE_COMMIT_ACTION';
+export const UPDATE_NEW_BRANCH_NAME = 'UPDATE_NEW_BRANCH_NAME';
+export const UPDATE_LOADING = 'UPDATE_LOADING';
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutations.js b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
new file mode 100644
index 0000000000000000000000000000000000000000..797357e3df9140ed0d4966e0ba542da3954586f6
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
@@ -0,0 +1,24 @@
+import * as types from './mutation_types';
+
+export default {
+  [types.UPDATE_COMMIT_MESSAGE](state, commitMessage) {
+    Object.assign(state, {
+      commitMessage,
+    });
+  },
+  [types.UPDATE_COMMIT_ACTION](state, commitAction) {
+    Object.assign(state, {
+      commitAction,
+    });
+  },
+  [types.UPDATE_NEW_BRANCH_NAME](state, newBranchName) {
+    Object.assign(state, {
+      newBranchName,
+    });
+  },
+  [types.UPDATE_LOADING](state, submitCommitLoading) {
+    Object.assign(state, {
+      submitCommitLoading,
+    });
+  },
+};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/state.js b/app/assets/javascripts/ide/stores/modules/commit/state.js
new file mode 100644
index 0000000000000000000000000000000000000000..8dae50961b09dfd91a54ccbe33791d0da2a0e4d7
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/commit/state.js
@@ -0,0 +1,6 @@
+export default () => ({
+  commitMessage: '',
+  commitAction: '1',
+  newBranchName: '',
+  submitCommitLoading: false,
+});
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index 69b218a5e7d6cf419e2f6a02d5423c13445e09e5..f5f95b755c8494f1a59bdc66d1936d7c3b83fbc0 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -1,8 +1,7 @@
 export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
 export const TOGGLE_LOADING = 'TOGGLE_LOADING';
-export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL';
-export const SET_ROOT = 'SET_ROOT';
 export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
+export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG';
 export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
 export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
 export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
@@ -12,6 +11,12 @@ export const SET_PROJECT = 'SET_PROJECT';
 export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
 export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
 
+// Merge Request Mutation Types
+export const SET_MERGE_REQUEST = 'SET_MERGE_REQUEST';
+export const SET_CURRENT_MERGE_REQUEST = 'SET_CURRENT_MERGE_REQUEST';
+export const SET_MERGE_REQUEST_CHANGES = 'SET_MERGE_REQUEST_CHANGES';
+export const SET_MERGE_REQUEST_VERSIONS = 'SET_MERGE_REQUEST_VERSIONS';
+
 // Branch Mutation Types
 export const SET_BRANCH = 'SET_BRANCH';
 export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
@@ -20,27 +25,36 @@ export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
 // Tree mutation types
 export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA';
 export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN';
-export const CREATE_TMP_TREE = 'CREATE_TMP_TREE';
 export const SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL';
 export const CREATE_TREE = 'CREATE_TREE';
+export const REMOVE_ALL_CHANGES_FILES = 'REMOVE_ALL_CHANGES_FILES';
 
 // File mutation types
 export const SET_FILE_DATA = 'SET_FILE_DATA';
 export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN';
 export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE';
 export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA';
+export const SET_FILE_BASE_RAW_DATA = 'SET_FILE_BASE_RAW_DATA';
 export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
 export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
 export const SET_FILE_POSITION = 'SET_FILE_POSITION';
+export const SET_FILE_VIEWMODE = 'SET_FILE_VIEWMODE';
 export const SET_FILE_EOL = 'SET_FILE_EOL';
 export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
-export const CREATE_TMP_FILE = 'CREATE_TMP_FILE';
-
-// Viewer mutation types
-export const SET_PREVIEW_MODE = 'SET_PREVIEW_MODE';
-export const SET_EDIT_MODE = 'SET_EDIT_MODE';
-export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
-export const TOGGLE_DISCARD_POPUP = 'TOGGLE_DISCARD_POPUP';
-
+export const ADD_FILE_TO_CHANGED = 'ADD_FILE_TO_CHANGED';
+export const REMOVE_FILE_FROM_CHANGED = 'REMOVE_FILE_FROM_CHANGED';
+export const TOGGLE_FILE_CHANGED = 'TOGGLE_FILE_CHANGED';
 export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH';
+export const SET_ENTRIES = 'SET_ENTRIES';
+export const CREATE_TMP_ENTRY = 'CREATE_TMP_ENTRY';
+export const SET_FILE_MERGE_REQUEST_CHANGE = 'SET_FILE_MERGE_REQUEST_CHANGE';
+export const UPDATE_VIEWER = 'UPDATE_VIEWER';
+export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
+
+export const CLEAR_STAGED_CHANGES = 'CLEAR_STAGED_CHANGES';
+export const STAGE_CHANGE = 'STAGE_CHANGE';
+export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE';
 
+export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
+export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
+export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 03d81be10a1b4abacc6d74a5bb145278cdc88fe7..fbe342f91267e968e9ecbfd9bb10bf3cea242296 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -1,5 +1,6 @@
 import * as types from './mutation_types';
 import projectMutations from './mutations/project';
+import mergeRequestMutation from './mutations/merge_request';
 import fileMutations from './mutations/file';
 import treeMutations from './mutations/tree';
 import branchMutations from './mutations/branch';
@@ -8,62 +9,115 @@ export default {
   [types.SET_INITIAL_DATA](state, data) {
     Object.assign(state, data);
   },
-  [types.SET_PREVIEW_MODE](state) {
+  [types.TOGGLE_LOADING](state, { entry, forceValue = undefined }) {
+    if (entry.path) {
+      Object.assign(state.entries[entry.path], {
+        loading: forceValue !== undefined ? forceValue : !state.entries[entry.path].loading,
+      });
+    } else {
+      Object.assign(entry, {
+        loading: forceValue !== undefined ? forceValue : !entry.loading,
+      });
+    }
+  },
+  [types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) {
     Object.assign(state, {
-      currentBlobView: 'repo-preview',
+      leftPanelCollapsed: collapsed,
     });
   },
-  [types.SET_EDIT_MODE](state) {
+  [types.SET_RIGHT_PANEL_COLLAPSED](state, collapsed) {
     Object.assign(state, {
-      currentBlobView: 'repo-editor',
+      rightPanelCollapsed: collapsed,
     });
   },
-  [types.TOGGLE_LOADING](state, entry) {
-    Object.assign(entry, {
-      loading: !entry.loading,
+  [types.SET_RESIZING_STATUS](state, resizing) {
+    Object.assign(state, {
+      panelResizing: resizing,
     });
   },
-  [types.TOGGLE_EDIT_MODE](state) {
-    Object.assign(state, {
-      editMode: !state.editMode,
+  [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
+    Object.assign(entry.lastCommit, {
+      id: lastCommit.commit.id,
+      url: lastCommit.commit_path,
+      message: lastCommit.commit.message,
+      author: lastCommit.commit.author_name,
+      updatedAt: lastCommit.commit.authored_date,
     });
   },
-  [types.TOGGLE_DISCARD_POPUP](state, discardPopupOpen) {
+  [types.SET_LAST_COMMIT_MSG](state, lastCommitMsg) {
     Object.assign(state, {
-      discardPopupOpen,
+      lastCommitMsg,
     });
   },
-  [types.SET_ROOT](state, isRoot) {
+  [types.CLEAR_STAGED_CHANGES](state) {
     Object.assign(state, {
-      isRoot,
-      isInitialRoot: isRoot,
+      stagedFiles: [],
     });
   },
-  [types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) {
+  [types.SET_ENTRIES](state, entries) {
     Object.assign(state, {
-      leftPanelCollapsed: collapsed,
+      entries,
     });
   },
-  [types.SET_RIGHT_PANEL_COLLAPSED](state, collapsed) {
+  [types.CREATE_TMP_ENTRY](state, { data, projectId, branchId }) {
+    Object.keys(data.entries).reduce((acc, key) => {
+      const entry = data.entries[key];
+      const foundEntry = state.entries[key];
+
+      if (!foundEntry) {
+        Object.assign(state.entries, {
+          [key]: entry,
+        });
+      } else {
+        const tree = entry.tree.filter(
+          f => foundEntry.tree.find(e => e.path === f.path) === undefined,
+        );
+        Object.assign(foundEntry, {
+          tree: foundEntry.tree.concat(tree),
+        });
+      }
+
+      return acc.concat(key);
+    }, []);
+
+    const foundEntry = state.trees[`${projectId}/${branchId}`].tree.find(
+      e => e.path === data.treeList[0].path,
+    );
+
+    if (!foundEntry) {
+      Object.assign(state.trees[`${projectId}/${branchId}`], {
+        tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList),
+      });
+    }
+  },
+  [types.UPDATE_VIEWER](state, viewer) {
     Object.assign(state, {
-      rightPanelCollapsed: collapsed,
+      viewer,
     });
   },
-  [types.SET_RESIZING_STATUS](state, resizing) {
+  [types.UPDATE_DELAY_VIEWER_CHANGE](state, delayViewerUpdated) {
     Object.assign(state, {
-      panelResizing: resizing,
+      delayViewerUpdated,
     });
   },
-  [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
-    Object.assign(entry.lastCommit, {
-      id: lastCommit.commit.id,
-      url: lastCommit.commit_path,
-      message: lastCommit.commit.message,
-      author: lastCommit.commit.author_name,
-      updatedAt: lastCommit.commit.authored_date,
+  [types.UPDATE_FILE_AFTER_COMMIT](state, { file, lastCommit }) {
+    const changedFile = state.changedFiles.find(f => f.path === file.path);
+
+    Object.assign(state.entries[file.path], {
+      raw: file.content,
+      changed: !!changedFile,
+      staged: false,
+      lastCommit: Object.assign(state.entries[file.path].lastCommit, {
+        id: lastCommit.commit.id,
+        url: lastCommit.commit_path,
+        message: lastCommit.commit.message,
+        author: lastCommit.commit.author_name,
+        updatedAt: lastCommit.commit.authored_date,
+      }),
     });
   },
   ...projectMutations,
+  ...mergeRequestMutation,
   ...fileMutations,
   ...treeMutations,
   ...branchMutations,
diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js
index 04b9582c5bb35a5bb6b8310593887a4b72c3e1b8..2972ba5e38e2b7f7eebbfbccff93d80c9d484d25 100644
--- a/app/assets/javascripts/ide/stores/mutations/branch.js
+++ b/app/assets/javascripts/ide/stores/mutations/branch.js
@@ -7,16 +7,14 @@ export default {
     });
   },
   [types.SET_BRANCH](state, { projectPath, branchName, branch }) {
-    // Add client side properties
-    Object.assign(branch, {
-      treeId: `${projectPath}/${branchName}`,
-      active: true,
-      workingReference: '',
-    });
-
     Object.assign(state.projects[projectPath], {
       branches: {
-        [branchName]: branch,
+        [branchName]: {
+          ...branch,
+          treeId: `${projectPath}/${branchName}`,
+          active: true,
+          workingReference: '',
+        },
       },
     });
   },
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 72db1c180c99ad9ce2e98733267000aa4e1769c7..dd7dcba8ac70b31a3f0e5fe8762431790fcbab99 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -1,74 +1,203 @@
 import * as types from '../mutation_types';
-import { findIndexOfFile } from '../utils';
 
 export default {
-  [types.SET_FILE_ACTIVE](state, { file, active }) {
-    Object.assign(file, {
+  [types.SET_FILE_ACTIVE](state, { path, active }) {
+    Object.assign(state.entries[path], {
       active,
     });
 
-    Object.assign(state, {
-      selectedFile: file,
-    });
+    if (active && !state.entries[path].pending) {
+      Object.assign(state, {
+        openFiles: state.openFiles.map(f =>
+          Object.assign(f, { active: f.pending ? false : f.active }),
+        ),
+      });
+    }
   },
-  [types.TOGGLE_FILE_OPEN](state, file) {
-    Object.assign(file, {
-      opened: !file.opened,
+  [types.TOGGLE_FILE_OPEN](state, path) {
+    Object.assign(state.entries[path], {
+      opened: !state.entries[path].opened,
     });
 
-    if (file.opened) {
-      state.openFiles.push(file);
+    if (state.entries[path].opened) {
+      Object.assign(state, {
+        openFiles: state.openFiles.filter(f => f.path !== path).concat(state.entries[path]),
+      });
     } else {
-      state.openFiles.splice(findIndexOfFile(state.openFiles, file), 1);
+      const file = state.entries[path];
+
+      Object.assign(state, {
+        openFiles: state.openFiles.filter(f => f.key !== file.key),
+      });
     }
   },
   [types.SET_FILE_DATA](state, { data, file }) {
-    Object.assign(file, {
+    Object.assign(state.entries[file.path], {
+      id: data.id,
       blamePath: data.blame_path,
       commitsPath: data.commits_path,
       permalink: data.permalink,
       rawPath: data.raw_path,
       binary: data.binary,
-      html: data.html,
       renderError: data.render_error,
+      raw: null,
+      baseRaw: null,
+      html: data.html,
+      size: data.size,
     });
   },
   [types.SET_FILE_RAW_DATA](state, { file, raw }) {
-    Object.assign(file, {
+    Object.assign(state.entries[file.path], {
       raw,
     });
   },
-  [types.UPDATE_FILE_CONTENT](state, { file, content }) {
-    const changed = content !== file.raw;
+  [types.SET_FILE_BASE_RAW_DATA](state, { file, baseRaw }) {
+    Object.assign(state.entries[file.path], {
+      baseRaw,
+    });
+  },
+  [types.UPDATE_FILE_CONTENT](state, { path, content }) {
+    const stagedFile = state.stagedFiles.find(f => f.path === path);
+    const rawContent = stagedFile ? stagedFile.content : state.entries[path].raw;
+    const changed = content !== rawContent;
 
-    Object.assign(file, {
+    Object.assign(state.entries[path], {
       content,
       changed,
     });
   },
   [types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) {
-    Object.assign(file, {
+    Object.assign(state.entries[file.path], {
       fileLanguage,
     });
   },
   [types.SET_FILE_EOL](state, { file, eol }) {
-    Object.assign(file, {
+    Object.assign(state.entries[file.path], {
       eol,
     });
   },
   [types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) {
-    Object.assign(file, {
+    Object.assign(state.entries[file.path], {
       editorRow,
       editorColumn,
     });
   },
-  [types.DISCARD_FILE_CHANGES](state, file) {
-    Object.assign(file, {
-      content: file.raw,
+  [types.SET_FILE_MERGE_REQUEST_CHANGE](state, { file, mrChange }) {
+    Object.assign(state.entries[file.path], {
+      mrChange,
+    });
+  },
+  [types.SET_FILE_VIEWMODE](state, { file, viewMode }) {
+    Object.assign(state.entries[file.path], {
+      viewMode,
+    });
+  },
+  [types.DISCARD_FILE_CHANGES](state, path) {
+    const stagedFile = state.stagedFiles.find(f => f.path === path);
+
+    Object.assign(state.entries[path], {
+      content: stagedFile ? stagedFile.content : state.entries[path].raw,
       changed: false,
     });
   },
-  [types.CREATE_TMP_FILE](state, { file, parent }) {
-    parent.tree.push(file);
+  [types.ADD_FILE_TO_CHANGED](state, path) {
+    Object.assign(state, {
+      changedFiles: state.changedFiles.concat(state.entries[path]),
+    });
+  },
+  [types.REMOVE_FILE_FROM_CHANGED](state, path) {
+    Object.assign(state, {
+      changedFiles: state.changedFiles.filter(f => f.path !== path),
+    });
+  },
+  [types.STAGE_CHANGE](state, path) {
+    const stagedFile = state.stagedFiles.find(f => f.path === path);
+
+    Object.assign(state, {
+      changedFiles: state.changedFiles.filter(f => f.path !== path),
+      entries: Object.assign(state.entries, {
+        [path]: Object.assign(state.entries[path], {
+          staged: true,
+          changed: false,
+        }),
+      }),
+    });
+
+    if (stagedFile) {
+      Object.assign(stagedFile, {
+        ...state.entries[path],
+      });
+    } else {
+      Object.assign(state, {
+        stagedFiles: state.stagedFiles.concat({
+          ...state.entries[path],
+        }),
+      });
+    }
+  },
+  [types.UNSTAGE_CHANGE](state, path) {
+    const changedFile = state.changedFiles.find(f => f.path === path);
+    const stagedFile = state.stagedFiles.find(f => f.path === path);
+
+    if (!changedFile && stagedFile) {
+      Object.assign(state.entries[path], {
+        ...stagedFile,
+        key: state.entries[path].key,
+        active: state.entries[path].active,
+        opened: state.entries[path].opened,
+        changed: true,
+      });
+
+      Object.assign(state, {
+        changedFiles: state.changedFiles.concat(state.entries[path]),
+      });
+    }
+
+    Object.assign(state, {
+      stagedFiles: state.stagedFiles.filter(f => f.path !== path),
+      entries: Object.assign(state.entries, {
+        [path]: Object.assign(state.entries[path], {
+          staged: false,
+        }),
+      }),
+    });
+  },
+  [types.TOGGLE_FILE_CHANGED](state, { file, changed }) {
+    Object.assign(state.entries[file.path], {
+      changed,
+    });
+  },
+  [types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) {
+    const key = `${keyPrefix}-${file.key}`;
+    const pendingTab = state.openFiles.find(f => f.key === key && f.pending);
+    let openFiles = state.openFiles.map(f => Object.assign(f, { active: false, opened: false }));
+
+    if (!pendingTab) {
+      const openFile = openFiles.find(f => f.path === file.path);
+
+      openFiles = openFiles.concat(openFile ? null : file).reduce((acc, f) => {
+        if (!f) return acc;
+
+        if (f.path === file.path) {
+          return acc.concat({
+            ...f,
+            content: file.content,
+            active: true,
+            pending: true,
+            opened: true,
+            key,
+          });
+        }
+
+        return acc.concat(f);
+      }, []);
+    }
+
+    Object.assign(state, { openFiles });
+  },
+  [types.REMOVE_PENDING_TAB](state, file) {
+    Object.assign(state, {
+      openFiles: state.openFiles.filter(f => f.key !== file.key),
+    });
   },
 };
diff --git a/app/assets/javascripts/ide/stores/mutations/merge_request.js b/app/assets/javascripts/ide/stores/mutations/merge_request.js
new file mode 100644
index 0000000000000000000000000000000000000000..334819fe702e4e1c5aeb4c25b21e672ad37ad901
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/mutations/merge_request.js
@@ -0,0 +1,33 @@
+import * as types from '../mutation_types';
+
+export default {
+  [types.SET_CURRENT_MERGE_REQUEST](state, currentMergeRequestId) {
+    Object.assign(state, {
+      currentMergeRequestId,
+    });
+  },
+  [types.SET_MERGE_REQUEST](state, { projectPath, mergeRequestId, mergeRequest }) {
+    Object.assign(state.projects[projectPath], {
+      mergeRequests: {
+        [mergeRequestId]: {
+          ...mergeRequest,
+          active: true,
+          changes: [],
+          versions: [],
+          baseCommitSha: null,
+        },
+      },
+    });
+  },
+  [types.SET_MERGE_REQUEST_CHANGES](state, { projectPath, mergeRequestId, changes }) {
+    Object.assign(state.projects[projectPath].mergeRequests[mergeRequestId], {
+      changes,
+    });
+  },
+  [types.SET_MERGE_REQUEST_VERSIONS](state, { projectPath, mergeRequestId, versions }) {
+    Object.assign(state.projects[projectPath].mergeRequests[mergeRequestId], {
+      versions,
+      baseCommitSha: versions.length ? versions[0].base_commit_sha : null,
+    });
+  },
+};
diff --git a/app/assets/javascripts/ide/stores/mutations/project.js b/app/assets/javascripts/ide/stores/mutations/project.js
index 2816562a91968985a64559bce570abf75dd882a0..284b39a2c72a81278937f1a95d782af9dcebd580 100644
--- a/app/assets/javascripts/ide/stores/mutations/project.js
+++ b/app/assets/javascripts/ide/stores/mutations/project.js
@@ -11,6 +11,7 @@ export default {
     Object.assign(project, {
       tree: [],
       branches: {},
+      mergeRequests: {},
       active: true,
     });
 
diff --git a/app/assets/javascripts/ide/stores/mutations/tree.js b/app/assets/javascripts/ide/stores/mutations/tree.js
index 4fe438ab465558229e6429897f51af366d281d9f..1176c040fb9f7c4069620014851d464fd59f00ab 100644
--- a/app/assets/javascripts/ide/stores/mutations/tree.js
+++ b/app/assets/javascripts/ide/stores/mutations/tree.js
@@ -1,9 +1,9 @@
 import * as types from '../mutation_types';
 
 export default {
-  [types.TOGGLE_TREE_OPEN](state, tree) {
-    Object.assign(tree, {
-      opened: !tree.opened,
+  [types.TOGGLE_TREE_OPEN](state, path) {
+    Object.assign(state.entries[path], {
+      opened: !state.entries[path].opened,
     });
   },
   [types.CREATE_TREE](state, { treePath }) {
@@ -11,26 +11,24 @@ export default {
       trees: Object.assign({}, state.trees, {
         [treePath]: {
           tree: [],
+          loading: true,
         },
       }),
     });
   },
-  [types.SET_DIRECTORY_DATA](state, { data, tree }) {
-    Object.assign(tree, {
+  [types.SET_DIRECTORY_DATA](state, { data, treePath }) {
+    Object.assign(state.trees[treePath], {
       tree: data,
     });
   },
-  [types.SET_PARENT_TREE_URL](state, url) {
-    Object.assign(state, {
-      parentTreeUrl: url,
-    });
-  },
   [types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
     Object.assign(tree, {
       lastCommitPath: url,
     });
   },
-  [types.CREATE_TMP_TREE](state, { parent, tmpEntry }) {
-    parent.tree.push(tmpEntry);
+  [types.REMOVE_ALL_CHANGES_FILES](state) {
+    Object.assign(state, {
+      changedFiles: [],
+    });
   },
 };
diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js
index 61d120969462e757d3e2a0019cd21d0cc01ab68f..34975ac3144dc08bd4252aa493a6a03773d5bbdd 100644
--- a/app/assets/javascripts/ide/stores/state.js
+++ b/app/assets/javascripts/ide/stores/state.js
@@ -1,23 +1,21 @@
 export default () => ({
-  canCommit: false,
   currentProjectId: '',
   currentBranchId: '',
-  currentBlobView: 'repo-editor',
-  discardPopupOpen: false,
-  editMode: true,
+  currentMergeRequestId: '',
+  changedFiles: [],
+  stagedFiles: [],
   endpoints: {},
-  isRoot: false,
-  isInitialRoot: false,
+  lastCommitMsg: '',
   lastCommitPath: '',
   loading: false,
-  onTopOfBranch: false,
   openFiles: [],
-  selectedFile: null,
-  path: '',
   parentTreeUrl: '',
   trees: {},
   projects: {},
   leftPanelCollapsed: false,
-  rightPanelCollapsed: true,
+  rightPanelCollapsed: false,
   panelResizing: false,
+  entries: {},
+  viewer: 'editor',
+  delayViewerUpdated: false,
 });
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index d556404faa54fdaf894ab15ee47954aa944b2179..8a222da14c085786f40e77106ea5b550f5017beb 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,7 +1,7 @@
-import _ from 'underscore';
-
 export const dataStructure = () => ({
   id: '',
+  // Key will contain a mixture of ID and path
+  // it can also contain a prefix `pending-` for files opened in review mode
   key: '',
   type: '',
   projectId: '',
@@ -9,14 +9,13 @@ export const dataStructure = () => ({
   name: '',
   url: '',
   path: '',
-  level: 0,
   tempFile: false,
-  icon: '',
   tree: [],
   loading: false,
   opened: false,
   active: false,
   changed: false,
+  staged: false,
   lastCommitPath: '',
   lastCommit: {
     id: '',
@@ -25,7 +24,6 @@ export const dataStructure = () => ({
     updatedAt: '',
     author: '',
   },
-  tree_url: '',
   blamePath: '',
   commitsPath: '',
   permalink: '',
@@ -41,9 +39,12 @@ export const dataStructure = () => ({
   editorColumn: 1,
   fileLanguage: '',
   eol: '',
+  viewMode: 'edit',
+  previewMode: null,
+  size: 0,
 });
 
-export const decorateData = (entity) => {
+export const decorateData = entity => {
   const {
     id,
     projectId,
@@ -51,8 +52,6 @@ export const decorateData = (entity) => {
     type,
     url,
     name,
-    icon,
-    tree_url,
     path,
     renderError,
     content = '',
@@ -61,8 +60,10 @@ export const decorateData = (entity) => {
     opened = false,
     changed = false,
     parentTreeUrl = '',
-    level = 0,
     base64 = false,
+    previewMode,
+    file_lock,
+    html,
   } = entity;
 
   return {
@@ -74,11 +75,8 @@ export const decorateData = (entity) => {
     type,
     name,
     url,
-    tree_url,
     path,
-    level,
     tempFile,
-    icon: `fa-${icon}`,
     opened,
     active,
     parentTreeUrl,
@@ -86,92 +84,52 @@ export const decorateData = (entity) => {
     renderError,
     content,
     base64,
+    previewMode,
+    file_lock,
+    html,
   };
 };
 
-/*
-  Takes the multi-dimensional tree and returns a flattened array.
-  This allows for the table to recursively render the table rows but keeps the data
-  structure nested to make it easier to add new files/directories.
-*/
-export const treeList = (state, treeId) => {
-  const baseTree = state.trees[treeId];
-  if (baseTree) {
-    const mapTree = arr => (!arr.tree || !arr.tree.length ?
-                            [] : _.map(arr.tree, a => [a, mapTree(a)]));
-
-    return _.chain(baseTree.tree)
-      .map(arr => [arr, mapTree(arr)])
-      .flatten()
-      .value();
-  }
-  return [];
-};
-
-export const getTree = state => (namespace, projectId, branch) => state.trees[`${namespace}/${projectId}/${branch}`];
-
-export const getTreeEntry = (store, treeId, path) => {
-  const fileList = treeList(store.state, treeId);
-  return fileList ? fileList.find(file => file.path === path) : null;
-};
-
-export const findEntry = (tree, type, name) => tree.find(
-  f => f.type === type && f.name === name,
-);
+export const findEntry = (tree, type, name, prop = 'name') =>
+  tree.find(f => f.type === type && f[prop] === name);
 
 export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
 
-export const setPageTitle = (title) => {
+export const setPageTitle = title => {
   document.title = title;
 };
 
-export const createTemp = ({
-  projectId, branchId, name, path, type, level, changed, content, base64, url,
-}) => {
-  const treePath = path ? `${path}/${name}` : name;
-
-  return decorateData({
-    id: new Date().getTime().toString(),
-    projectId,
-    branchId,
-    name,
-    type,
-    tempFile: true,
-    path: treePath,
-    icon: type === 'tree' ? 'folder' : 'file-text-o',
-    changed,
-    content,
-    parentTreeUrl: '',
-    level,
-    base64,
-    renderError: base64,
-    url,
-  });
-};
+export const createCommitPayload = (branch, newBranch, state, rootState) => ({
+  branch,
+  commit_message: state.commitMessage,
+  actions: rootState.stagedFiles.map(f => ({
+    action: f.tempFile ? 'create' : 'update',
+    file_path: f.path,
+    content: f.content,
+    encoding: f.base64 ? 'base64' : 'text',
+  })),
+  start_branch: newBranch ? rootState.currentBranchId : undefined,
+});
 
-export const createOrMergeEntry = ({ tree,
-                                     projectId,
-                                     branchId,
-                                     entry,
-                                     type,
-                                     parentTreeUrl,
-                                     level }) => {
-  const found = findEntry(tree.tree || tree, type, entry.name);
+export const createNewMergeRequestUrl = (projectUrl, source, target) =>
+  `${projectUrl}/merge_requests/new?merge_request[source_branch]=${source}&merge_request[target_branch]=${target}`;
 
-  if (found) {
-    return Object.assign({}, found, {
-      id: entry.id,
-      url: entry.url,
-      tempFile: false,
-    });
+const sortTreesByTypeAndName = (a, b) => {
+  if (a.type === 'tree' && b.type === 'blob') {
+    return -1;
+  } else if (a.type === 'blob' && b.type === 'tree') {
+    return 1;
   }
-
-  return decorateData({
-    ...entry,
-    projectId,
-    branchId,
-    type,
-    parentTreeUrl,
-    level,
-  });
+  if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
+  if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
+  return 0;
 };
+
+export const sortTree = sortedTree =>
+  sortedTree
+    .map(entity =>
+      Object.assign(entity, {
+        tree: entity.tree.length ? sortTree(entity.tree) : [],
+      }),
+    )
+    .sort(sortTreesByTypeAndName);
diff --git a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js
new file mode 100644
index 0000000000000000000000000000000000000000..a16732769000efe1894db8c35eff834469937ae2
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js
@@ -0,0 +1,90 @@
+import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
+import { decorateData, sortTree } from '../utils';
+
+self.addEventListener('message', e => {
+  const { data, projectId, branchId, tempFile = false, content = '', base64 = false } = e.data;
+
+  const treeList = [];
+  let file;
+  const entries = data.reduce((acc, path) => {
+    const pathSplit = path.split('/');
+    const blobName = pathSplit.pop().trim();
+
+    if (pathSplit.length > 0) {
+      pathSplit.reduce((pathAcc, folderName) => {
+        const parentFolder = acc[pathAcc[pathAcc.length - 1]];
+        const folderPath = `${parentFolder ? `${parentFolder.path}/` : ''}${folderName}`;
+        const foundEntry = acc[folderPath];
+
+        if (!foundEntry) {
+          const tree = decorateData({
+            projectId,
+            branchId,
+            id: folderPath,
+            name: folderName,
+            path: folderPath,
+            url: `/${projectId}/tree/${branchId}/${folderPath}/`,
+            type: 'tree',
+            parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
+            tempFile,
+            changed: tempFile,
+            opened: tempFile,
+          });
+
+          Object.assign(acc, {
+            [folderPath]: tree,
+          });
+
+          if (parentFolder) {
+            parentFolder.tree.push(tree);
+          } else {
+            treeList.push(tree);
+          }
+
+          pathAcc.push(tree.path);
+        } else {
+          pathAcc.push(foundEntry.path);
+        }
+
+        return pathAcc;
+      }, []);
+    }
+
+    if (blobName !== '') {
+      const fileFolder = acc[pathSplit.join('/')];
+      file = decorateData({
+        projectId,
+        branchId,
+        id: path,
+        name: blobName,
+        path,
+        url: `/${projectId}/blob/${branchId}/${path}`,
+        type: 'blob',
+        parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
+        tempFile,
+        changed: tempFile,
+        content,
+        base64,
+        previewMode: viewerInformationForPath(blobName),
+      });
+
+      Object.assign(acc, {
+        [path]: file,
+      });
+
+      if (fileFolder) {
+        fileFolder.tree.push(file);
+      } else {
+        treeList.push(file);
+      }
+    }
+
+    return acc;
+  }, {});
+
+  self.postMessage({
+    entries,
+    treeList: sortTree(treeList),
+    file,
+  });
+});
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
index f3af92cf2b06627f972da09b2003d0e0b9044e37..fab0255c37888910ac7c0e4750c8fcfb71fb21c7 100644
--- a/app/assets/javascripts/image_diff/image_diff.js
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import imageDiffHelper from './helpers/index';
 import ImageBadge from './image_badge';
 import { isImageLoaded } from '../lib/utils/image_utility';
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 35094f8e73b09a292b342f90b2638eb445ff7ea9..b469e1e2adc14c1d19cc8d2f1bd7fbe959e7eb6d 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -1,11 +1,15 @@
-import { __ } from './locale';
+import $ from 'jquery';
+import _ from 'underscore';
+import { __, sprintf } from './locale';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
+import { convertPermissionToBoolean } from './lib/utils/common_utils';
 
 class ImporterStatus {
-  constructor(jobsUrl, importUrl) {
+  constructor({ jobsUrl, importUrl, ciCdOnly }) {
     this.jobsUrl = jobsUrl;
     this.importUrl = importUrl;
+    this.ciCdOnly = ciCdOnly;
     this.initStatusPage();
     this.setAutoUpdate();
   }
@@ -45,6 +49,7 @@ class ImporterStatus {
       repo_id: id,
       target_namespace: targetNamespace,
       new_name: newName,
+      ci_cd_only: this.ciCdOnly,
     })
     .then(({ data }) => {
       const job = $(`tr#repo_${id}`);
@@ -54,7 +59,13 @@ class ImporterStatus {
       $('table.import-jobs tbody').prepend(job);
 
       job.addClass('active');
-      job.find('.import-actions').html('<i class="fa fa-spinner fa-spin" aria-label="importing"></i> started');
+      const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing');
+      job.find('.import-actions').html(sprintf(
+        _.escape(__('%{loadingIcon} Started')), {
+          loadingIcon: `<i class="fa fa-spinner fa-spin" aria-label="${_.escape(connectingVerb)}"></i>`,
+        },
+        false,
+      ));
     })
     .catch(() => flash(__('An error occurred while importing project')));
   }
@@ -71,13 +82,16 @@ class ImporterStatus {
           switch (job.import_status) {
             case 'finished':
               jobItem.removeClass('active').addClass('success');
-              statusField.html('<span><i class="fa fa-check"></i> done</span>');
+              statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`);
               break;
             case 'scheduled':
-              statusField.html(`${spinner} scheduled`);
+              statusField.html(`${spinner} ${__('Scheduled')}`);
               break;
             case 'started':
-              statusField.html(`${spinner} started`);
+              statusField.html(`${spinner} ${__('Started')}`);
+              break;
+            case 'failed':
+              statusField.html(__('Failed'));
               break;
             default:
               statusField.html(job.import_status);
@@ -98,7 +112,11 @@ function initImporterStatus() {
 
   if (importerStatus) {
     const data = importerStatus.dataset;
-    return new ImporterStatus(data.jobsImportPath, data.importPath);
+    return new ImporterStatus({
+      jobsUrl: data.jobsImportPath,
+      importUrl: data.importPath,
+      ciCdOnly: convertPermissionToBoolean(data.ciCdOnly),
+    });
   }
 }
 
diff --git a/app/assets/javascripts/init_changes_dropdown.js b/app/assets/javascripts/init_changes_dropdown.js
index 1bab7965c191e114a361cce0e42e6e8832c3d0f9..09cca1dc7d99d363363cbaed6fe45ae0e92dfe63 100644
--- a/app/assets/javascripts/init_changes_dropdown.js
+++ b/app/assets/javascripts/init_changes_dropdown.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import stickyMonitor from './lib/utils/sticky';
 
 export default (stickyTop) => {
diff --git a/app/assets/javascripts/init_labels.js b/app/assets/javascripts/init_labels.js
index 5f20055510f34286ee2a53841c3853b790dd04d9..15da5d5cceb048fd7098072f515caf08efb898ff 100644
--- a/app/assets/javascripts/init_labels.js
+++ b/app/assets/javascripts/init_labels.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import LabelManager from './label_manager';
 import GroupLabelSubscription from './group_label_subscription';
 import ProjectLabelSubscription from './project_label_subscription';
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index 2848fe003cb0040e53403a003ee72f14126c7e65..741894b5e6c1b11083e6ad95f7153db0882bd8c2 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import axios from '../lib/utils/axios_utils';
 import flash from '../flash';
 
diff --git a/app/assets/javascripts/issuable/auto_width_dropdown_select.js b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
index 14a2bfbe4e0f2f18e6a25620700413c4861f31b5..b2c2de9e5de788fe2d9d184ca8f755f3ea5714db 100644
--- a/app/assets/javascripts/issuable/auto_width_dropdown_select.js
+++ b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 let instanceCount = 0;
 
 class AutoWidthDropdownSelect {
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index 8c1b2e78ca457e2ae7801edac6b3779a07d804a0..e003fb1d1278838ee392896cc530838e5c86f3f8 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,4 +1,6 @@
 /* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
+
+import $ from 'jquery';
 import _ from 'underscore';
 import axios from './lib/utils/axios_utils';
 import Flash from './flash';
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 2056efe701be16210e336f936de6561588691728..2307c8e0d85520c37e6e888df97ee9746d473af2 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -1,5 +1,6 @@
 /* eslint-disable class-methods-use-this, no-new */
 
+import $ from 'jquery';
 import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
 import MilestoneSelect from './milestone_select';
 import issueStatusSelect from './issue_status_select';
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index da99394ff908f90d753a23ff04fa5fc2aadbd7c7..f3d722409b0081e7d79337547a008ac33c8115c3 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 import bp from './breakpoints';
 import UsersSelect from './users_select';
@@ -29,10 +30,10 @@ export default class IssuableContext {
         const $selectbox = $block.find('.selectbox');
         if ($selectbox.is(':visible')) {
           $selectbox.hide();
-          $block.find('.value').show();
+          $block.find('.value:not(.dont-hide)').show();
         } else {
           $selectbox.show();
-          $block.find('.value').hide();
+          $block.find('.value:not(.dont-hide)').hide();
         }
 
         if ($selectbox.is(':visible')) {
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index fdfad0b6a4fe67cc2742c21a1d4bf24bf00cb31f..bb8b3d91e40e60b6c2756f167f5f05a00c6531c0 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -1,6 +1,7 @@
 /* eslint-disable func-names, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
 /* global GitLab */
 
+import $ from 'jquery';
 import Pikaday from 'pikaday';
 import Autosave from './autosave';
 import UsersSelect from './users_select';
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index 0683ca82a3842bacefdfb963b489318f36832531..06ec45461645148310b6ac5f9e763f2d4ac73cd6 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
 import { __ } from './locale';
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 333bbd9e0bab9bea88344a26303244c07fc7c87c..5113ac6775d5c73e6647b722ac8d5117b858fe41 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
+
+import $ from 'jquery';
 import axios from './lib/utils/axios_utils';
 import { addDelimiter } from './lib/utils/text_utility';
 import flash from './flash';
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 1338be0ec4b033cd9742f717c26e6db81ad003c2..ae577e04a56b2bc16f3a9269018adea603683c7c 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,4 +1,5 @@
 <script>
+  import $ from 'jquery';
   import animateMixin from '../mixins/animate';
   import TaskList from '../../task_list';
   import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue
index 1ad0e59287ece94bdb5dc30fcbdc6f4000f86cca..7db0488e30664b70905611adaf51a10da3820fc5 100644
--- a/app/assets/javascripts/issue_show/components/fields/description_template.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue
@@ -1,4 +1,5 @@
 <script>
+  import $ from 'jquery';
   import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
 
   export default {
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
index 71c0f894389a08827e25c636f847a58416b40f1f..c14803c80e73377d41485d5bb86b4b6c43021355 100644
--- a/app/assets/javascripts/issue_status_select.js
+++ b/app/assets/javascripts/issue_status_select.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default function issueStatusSelect() {
   $('.js-issue-status').each((i, el) => {
     const fieldName = $(el).data('fieldName');
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index f39ae764d3c5c922e388225bc7412baad6965787..ace45e9dd291ef5687cdcf735f0f3ad707c828ac 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import axios from './lib/utils/axios_utils';
 import { visitUrl } from './lib/utils/url_utility';
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index 357bc9aab176b77205c6a107fe5206515b562fde..21b545d6cabd4b1a0b8fbdff34ae43dd2d51ace3 100644
--- a/app/assets/javascripts/jobs/components/header.vue
+++ b/app/assets/javascripts/jobs/components/header.vue
@@ -1,82 +1,94 @@
 <script>
-  import ciHeader from '../../vue_shared/components/header_ci_component.vue';
-  import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import ciHeader from '../../vue_shared/components/header_ci_component.vue';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import callout from '../../vue_shared/components/callout.vue';
 
-  export default {
-    name: 'JobHeaderSection',
-    components: {
-      ciHeader,
-      loadingIcon,
+export default {
+  name: 'JobHeaderSection',
+  components: {
+    ciHeader,
+    loadingIcon,
+    callout,
+  },
+  props: {
+    job: {
+      type: Object,
+      required: true,
     },
-    props: {
-      job: {
-        type: Object,
-        required: true,
-      },
-      isLoading: {
-        type: Boolean,
-        required: true,
-      },
+    isLoading: {
+      type: Boolean,
+      required: true,
     },
-    data() {
-      return {
-        actions: this.getActions(),
-      };
+  },
+  data() {
+    return {
+      actions: this.getActions(),
+    };
+  },
+  computed: {
+    status() {
+      return this.job && this.job.status;
     },
-    computed: {
-      status() {
-        return this.job && this.job.status;
-      },
-      shouldRenderContent() {
-        return !this.isLoading && Object.keys(this.job).length;
-      },
-      /**
-       * When job has not started the key will be `false`
-       * When job started the key will be a string with a date.
-       */
-      jobStarted() {
-        return !this.job.started === false;
-      },
+    shouldRenderContent() {
+      return !this.isLoading && Object.keys(this.job).length;
     },
-    watch: {
-      job() {
-        this.actions = this.getActions();
-      },
+    shouldRenderReason() {
+      return !!(this.job.status && this.job.callout_message);
     },
-    methods: {
-      getActions() {
-        const actions = [];
+    /**
+     * When job has not started the key will be `false`
+     * When job started the key will be a string with a date.
+     */
+    jobStarted() {
+      return !this.job.started === false;
+    },
+  },
+  watch: {
+    job() {
+      this.actions = this.getActions();
+    },
+  },
+  methods: {
+    getActions() {
+      const actions = [];
 
-        if (this.job.new_issue_path) {
-          actions.push({
-            label: 'New issue',
-            path: this.job.new_issue_path,
-            cssClass: 'js-new-issue btn btn-new btn-inverted visible-md-block visible-lg-block',
-            type: 'link',
-          });
-        }
-        return actions;
-      },
+      if (this.job.new_issue_path) {
+        actions.push({
+          label: 'New issue',
+          path: this.job.new_issue_path,
+          cssClass: 'js-new-issue btn btn-new btn-inverted visible-md-block visible-lg-block',
+          type: 'link',
+        });
+      }
+      return actions;
     },
-  };
+  },
+};
 </script>
 <template>
-  <div class="js-build-header build-header top-area">
-    <ci-header
-      v-if="shouldRenderContent"
-      :status="status"
-      item-name="Job"
-      :item-id="job.id"
-      :time="job.created_at"
-      :user="job.user"
-      :actions="actions"
-      :has-sidebar-button="true"
-      :should-render-triggered-label="jobStarted"
-    />
-    <loading-icon
-      v-if="isLoading"
-      size="2"
-      class="prepend-top-default append-bottom-default"
+  <header>
+    <div class="js-build-header build-header top-area">
+      <ci-header
+        v-if="shouldRenderContent"
+        :status="status"
+        item-name="Job"
+        :item-id="job.id"
+        :time="job.created_at"
+        :user="job.user"
+        :actions="actions"
+        :has-sidebar-button="true"
+        :should-render-triggered-label="jobStarted"
+      />
+      <loading-icon
+        v-if="isLoading"
+        size="2"
+        class="prepend-top-default append-bottom-default"
+      />
+    </div>
+
+    <callout
+      v-if="shouldRenderReason"
+      :message="job.callout_message"
     />
-  </div>
+  </header>
 </template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
index a6819aaeb123c828fd59153f38aa5ec80c778094..dfe87d89a3990dd065e7ce8ef77ad797cde276d3 100644
--- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
@@ -11,11 +11,19 @@
         type: String,
         required: true,
       },
+      helpUrl: {
+        type: String,
+        required: false,
+        default: '',
+      },
     },
     computed: {
       hasTitle() {
         return this.title.length > 0;
       },
+      hasHelpURL() {
+        return this.helpUrl.length > 0;
+      },
     },
   };
 </script>
@@ -28,5 +36,21 @@
       {{ title }}:
     </span>
     {{ value }}
+
+    <span
+      v-if="hasHelpURL"
+      class="help-button pull-right"
+    >
+      <a
+        :href="helpUrl"
+        target="_blank"
+        rel="noopener noreferrer nofollow"
+      >
+        <i
+          class="fa fa-question-circle"
+          aria-hidden="true"
+        ></i>
+      </a>
+    </span>
   </p>
 </template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index 56814a525255223ee8d6b5c663c7bb3e2bf0faca..4cd44bf7a76951f8a570bf180f200011c05a703a 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -1,60 +1,119 @@
 <script>
-  import detailRow from './sidebar_detail_row.vue';
-  import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-  import timeagoMixin from '../../vue_shared/mixins/timeago';
-  import { timeIntervalInWords } from '../../lib/utils/datetime_utility';
+import detailRow from './sidebar_detail_row.vue';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import timeagoMixin from '../../vue_shared/mixins/timeago';
+import { timeIntervalInWords } from '../../lib/utils/datetime_utility';
 
-  export default {
-    name: 'SidebarDetailsBlock',
-    components: {
-      detailRow,
-      loadingIcon,
+export default {
+  name: 'SidebarDetailsBlock',
+  components: {
+    detailRow,
+    loadingIcon,
+  },
+  mixins: [timeagoMixin],
+  props: {
+    job: {
+      type: Object,
+      required: true,
     },
-    mixins: [
-      timeagoMixin,
-    ],
-    props: {
-      job: {
-        type: Object,
-        required: true,
-      },
-      isLoading: {
-        type: Boolean,
-        required: true,
-      },
+    isLoading: {
+      type: Boolean,
+      required: true,
     },
-    computed: {
-      shouldRenderContent() {
-        return !this.isLoading && Object.keys(this.job).length > 0;
-      },
-      coverage() {
-        return `${this.job.coverage}%`;
-      },
-      duration() {
-        return timeIntervalInWords(this.job.duration);
-      },
-      queued() {
-        return timeIntervalInWords(this.job.queued);
-      },
-      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;
-      },
+    canUserRetry: {
+      type: Boolean,
+      required: false,
+      default: false,
     },
-  };
+    runnerHelpUrl: {
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
+  computed: {
+    shouldRenderContent() {
+      return !this.isLoading && Object.keys(this.job).length > 0;
+    },
+    coverage() {
+      return `${this.job.coverage}%`;
+    },
+    duration() {
+      return timeIntervalInWords(this.job.duration);
+    },
+    queued() {
+      return timeIntervalInWords(this.job.queued);
+    },
+    runnerId() {
+      return `#${this.job.runner.id}`;
+    },
+    retryButtonClass() {
+      let className = 'js-retry-button pull-right btn btn-retry visible-md-block visible-lg-block';
+      className +=
+        this.job.status && this.job.recoverable
+          ? ' btn-primary'
+          : ' btn-inverted-secondary';
+      return className;
+    },
+    hasTimeout() {
+      return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
+    },
+    timeout() {
+      if (this.job.metadata == null) {
+        return '';
+      }
+
+      let t = this.job.metadata.timeout_human_readable;
+      if (this.job.metadata.timeout_source !== '') {
+        t += ` (from ${this.job.metadata.timeout_source})`;
+      }
+
+      return t;
+    },
+    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>
 <template>
   <div>
+    <div class="block">
+      <strong class="inline prepend-top-8">
+        {{ job.name }}
+      </strong>
+      <a
+        v-if="canUserRetry"
+        :class="retryButtonClass"
+        :href="job.retry_path"
+        data-method="post"
+        rel="nofollow"
+      >
+        {{ __('Retry') }}
+      </a>
+      <button
+        type="button"
+        :aria-label="__('Toggle Sidebar')"
+        class="btn btn-blank gutter-toggle pull-right
+          visible-xs-block visible-sm-block js-sidebar-build-toggle"
+      >
+        <i
+          aria-hidden="true"
+          data-hidden="true"
+          class="fa fa-angle-double-right"
+        ></i>
+      </button>
+    </div>
     <template v-if="shouldRenderContent">
       <div
         class="block retry-link"
@@ -65,16 +124,16 @@
           class="js-new-issue btn btn-new btn-inverted"
           :href="job.new_issue_path"
         >
-          New issue
+          {{ __('New issue') }}
         </a>
         <a
-          v-if="job.retry_path"
+          v-if="canUserRetry"
           class="js-retry-job btn btn-inverted-secondary"
           :href="job.retry_path"
           data-method="post"
           rel="nofollow"
         >
-          Retry
+          {{ __('Retry') }}
         </a>
       </div>
       <div :class="{block : renderBlock }">
@@ -83,7 +142,7 @@
           v-if="job.merge_request"
         >
           <span class="build-light-text">
-            Merge Request:
+            {{ __('Merge Request:') }}
           </span>
           <a :href="job.merge_request.path">
             !{{ job.merge_request.iid }}
@@ -114,6 +173,13 @@
           title="Queued"
           :value="queued"
         />
+        <detail-row
+          class="js-job-timeout"
+          v-if="hasTimeout"
+          title="Timeout"
+          :help-url="runnerHelpUrl"
+          :value="timeout"
+        />
         <detail-row
           class="js-job-runner"
           v-if="job.runner"
@@ -131,7 +197,7 @@
           v-if="job.tags.length"
         >
           <span class="build-light-text">
-            Tags:
+            {{ __('Tags:') }}
           </span>
           <span
             v-for="(tag, i) in job.tags"
@@ -151,7 +217,7 @@
             data-method="post"
             rel="nofollow"
           >
-            Cancel
+            {{ __('Cancel') }}
           </a>
         </div>
       </div>
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 85a88ae409b463291a3269a7e2b1e004f40c49b4..f2939ad4dbef606fdc266d6729970512d0b47ebf 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -35,9 +35,11 @@ export default () => {
   });
 
   // Sidebar information block
+  const detailsBlockElement = document.getElementById('js-details-block-vue');
+  const detailsBlockDataset = detailsBlockElement.dataset;
   // eslint-disable-next-line
   new Vue({
-    el: '#js-details-block-vue',
+    el: detailsBlockElement,
     components: {
       detailsBlock,
     },
@@ -50,7 +52,9 @@ export default () => {
       return createElement('details-block', {
         props: {
           isLoading: this.mediator.state.isLoading,
+          canUserRetry: !!('canUserRetry' in detailsBlockDataset),
           job: this.mediator.store.state.job,
+          runnerHelpUrl: dataset.runnerHelpUrl,
         },
       });
     },
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index 61b40f79db16f2ba64bab801931815fe3b8b722f..e230dbbd4acbf0706a6105d016a5a5c7f6adcabf 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -1,4 +1,6 @@
 /* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
+
+import $ from 'jquery';
 import Sortable from 'vendor/Sortable';
 
 import flash from './flash';
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
index 7aab13ed9c6c200120d2219788da35e20cd02e8f..d85ae851706b0947eae497ce89c7c0492863a110 100644
--- a/app/assets/javascripts/labels.js
+++ b/app/assets/javascripts/labels.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default class Labels {
   constructor() {
     this.setSuggestedColor = this.setSuggestedColor.bind(this);
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 9b46bbf83da1722caa413a22a69f3ec8cb7f03dd..9b62cfb82064643538a8055b3d556f7e9df44061 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,6 +1,8 @@
 /* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread */
 /* global Issuable */
 /* global ListLabel */
+
+import $ from 'jquery';
 import _ from 'underscore';
 import { __ } from './locale';
 import axios from './lib/utils/axios_utils';
@@ -8,6 +10,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
 import DropdownUtils from './filtered_search/dropdown_utils';
 import CreateLabelDropdown from './create_label';
 import flash from './flash';
+import ModalStore from './boards/stores/modal_store';
 
 export default class LabelsSelect {
   constructor(els, options = {}) {
@@ -80,7 +83,7 @@ export default class LabelsSelect {
         $dropdown.trigger('loading.gl.dropdown');
         axios.put(issueUpdateURL, data)
           .then(({ data }) => {
-            var labelCount, template, labelTooltipTitle, labelTitles;
+            var labelCount, template, labelTooltipTitle, labelTitles, formattedLabels;
             $loading.fadeOut();
             $dropdown.trigger('loaded.gl.dropdown');
             $selectbox.hide();
@@ -112,8 +115,7 @@ export default class LabelsSelect {
               labelTooltipTitle = labelTitles.join(', ');
             }
             else {
-              labelTooltipTitle = '';
-              $sidebarLabelTooltip.tooltip('destroy');
+              labelTooltipTitle = __('Labels');
             }
 
             $sidebarLabelTooltip
@@ -348,7 +350,7 @@ export default class LabelsSelect {
           }
 
           if ($dropdown.closest('.add-issues-modal').length) {
-            boardsModel = gl.issueBoards.ModalStore.store.filter;
+            boardsModel = ModalStore.store.filter;
           }
 
           if (boardsModel) {
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 1b4900827b892487f915973bde64ff5109c13a3d..e3177188772ce84f33a4861177e20b22892d669b 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import ContextualSidebar from './contextual_sidebar';
 import initFlyOutNav from './fly_out_nav';
 
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index 0bf2ba6acc268059750eff2b5ee584cdc6d8ed13..3873f4528ceef4a7cfe0addebf8e12cad38a4745 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 /**
  * Linked Tabs
  *
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index ed90db317df6aeeddc1abe567ea9b3a2eba037b3..9ff2042475bc0f4485c8326fe0d8aca53dd3858f 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,4 +1,4 @@
-import jQuery from 'jquery';
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 import axios from './axios_utils';
 import { getLocationHash } from './url_utility';
@@ -33,6 +33,7 @@ export const checkPageAndAction = (page, action) => {
 
 export const isInIssuePage = () => checkPageAndAction('issues', 'show');
 export const isInMRPage = () => checkPageAndAction('merge_requests', 'show');
+export const isInEpicPage = () => checkPageAndAction('epics', 'show');
 export const isInNoteablePage = () => isInIssuePage() || isInMRPage();
 export const hasVueMRDiscussionsCookie = () => Cookies.get('vue_mr_discussions');
 
@@ -142,7 +143,7 @@ export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2;
 
 export const scrollToElement = (element) => {
   let $el = element;
-  if (!(element instanceof jQuery)) {
+  if (!(element instanceof $)) {
     $el = $(element);
   }
   const top = $el.offset().top;
diff --git a/app/assets/javascripts/lib/utils/csrf.js b/app/assets/javascripts/lib/utils/csrf.js
index 0bdb547d31ae2fffe6539b63ba8d7e38e3a3f184..ca9828c46824f40a780db7856719d4e0a99d0031 100644
--- a/app/assets/javascripts/lib/utils/csrf.js
+++ b/app/assets/javascripts/lib/utils/csrf.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 /*
 This module provides easy access to the CSRF token and caches
 it for re-use. It also exposes some values commonly used in relation
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index d6cccbef42b2ed56bbc2efd26a1a22dbf6d0f1b9..c3d94d63c13330ae076ed8dd3a18567841c41bcd 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import timeago from 'timeago.js';
 import dateFormat from 'vendor/date.format';
 import { pluralize } from './text_utility';
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index de65ea15a6034185d5f7b1a9ccee1ea69b7c60d2..914de9de9409bfb1dcd6c1a3afc1650d82158790 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -1,7 +1,12 @@
-/* eslint-disable import/prefer-default-export */
+import $ from 'jquery';
+import { isInIssuePage, isInMRPage, isInEpicPage, hasVueMRDiscussionsCookie } from './common_utils';
+
+const isVueMRDiscussions = () => isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible');
 
 export const addClassIfElementExists = (element, className) => {
   if (element) {
     element.classList.add(className);
   }
 };
+
+export const isInVueNoteablePage = () => isInIssuePage() || isInEpicPage() || isVueMRDiscussions();
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 5dc98b4a920caa502d43f97fe34d8c486dfc2397..5a16adea4dcd0f1f585ec6ee4fc875d7b2d4f913 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -1,26 +1,25 @@
 /* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
+import $ from 'jquery';
+import { insertText } from '~/lib/utils/common_utils';
 
-const textUtils = {};
-
-textUtils.selectedText = function(text, textarea) {
+function selectedText(text, textarea) {
   return text.substring(textarea.selectionStart, textarea.selectionEnd);
-};
+}
 
-textUtils.lineBefore = function(text, textarea) {
+function lineBefore(text, textarea) {
   var split;
   split = text.substring(0, textarea.selectionStart).trim().split('\n');
   return split[split.length - 1];
-};
+}
 
-textUtils.lineAfter = function(text, textarea) {
+function lineAfter(text, textarea) {
   return text.substring(textarea.selectionEnd).trim().split('\n')[0];
-};
+}
 
-textUtils.blockTagText = function(text, textArea, blockTag, selected) {
-  var lineAfter, lineBefore;
-  lineBefore = this.lineBefore(text, textArea);
-  lineAfter = this.lineAfter(text, textArea);
-  if (lineBefore === blockTag && lineAfter === blockTag) {
+function blockTagText(text, textArea, blockTag, selected) {
+  const before = lineBefore(text, textArea);
+  const after = lineAfter(text, textArea);
+  if (before === blockTag && after === blockTag) {
     // To remove the block tag we have to select the line before & after
     if (blockTag != null) {
       textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
@@ -30,10 +29,30 @@ textUtils.blockTagText = function(text, textArea, blockTag, selected) {
   } else {
     return blockTag + "\n" + selected + "\n" + blockTag;
   }
-};
+}
+
+function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
+  var pos;
+  if (!textArea.setSelectionRange) {
+    return;
+  }
+  if (textArea.selectionStart === textArea.selectionEnd) {
+    if (wrapped) {
+      pos = textArea.selectionStart - tag.length;
+    } else {
+      pos = textArea.selectionStart;
+    }
+
+    if (removedLastNewLine) {
+      pos -= 1;
+    }
+
+    return textArea.setSelectionRange(pos, pos);
+  }
+}
 
-textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
-  var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
+export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap) {
+  var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
   removedLastNewLine = false;
   removedFirstNewLine = false;
   currentLineEmpty = false;
@@ -65,9 +84,9 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
 
   if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
     if (blockTag != null && blockTag !== '') {
-      insertText = this.blockTagText(text, textArea, blockTag, selected);
+      textToInsert = blockTagText(text, textArea, blockTag, selected);
     } else {
-      insertText = selectedSplit.map(function(val) {
+      textToInsert = selectedSplit.map(function(val) {
         if (val.indexOf(tag) === 0) {
           return "" + (val.replace(tag, ''));
         } else {
@@ -76,78 +95,42 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
       }).join('\n');
     }
   } else {
-    insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
+    textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
   }
 
   if (removedFirstNewLine) {
-    insertText = '\n' + insertText;
+    textToInsert = '\n' + textToInsert;
   }
 
   if (removedLastNewLine) {
-    insertText += '\n';
-  }
-
-  if (document.queryCommandSupported('insertText')) {
-    inserted = document.execCommand('insertText', false, insertText);
-  }
-  if (!inserted) {
-    try {
-      document.execCommand("ms-beginUndoUnit");
-    } catch (error) {}
-    textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
-    try {
-      document.execCommand("ms-endUndoUnit");
-    } catch (error) {}
+    textToInsert += '\n';
   }
-  return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
-};
 
-textUtils.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
-  var pos;
-  if (!textArea.setSelectionRange) {
-    return;
-  }
-  if (textArea.selectionStart === textArea.selectionEnd) {
-    if (wrapped) {
-      pos = textArea.selectionStart - tag.length;
-    } else {
-      pos = textArea.selectionStart;
-    }
-
-    if (removedLastNewLine) {
-      pos -= 1;
-    }
-
-    return textArea.setSelectionRange(pos, pos);
-  }
-};
+  insertText(textArea, textToInsert);
+  return moveCursor(textArea, tag, wrap, removedLastNewLine);
+}
 
-textUtils.updateText = function(textArea, tag, blockTag, wrap) {
+function updateText(textArea, tag, blockTag, wrap) {
   var $textArea, selected, text;
   $textArea = $(textArea);
   textArea = $textArea.get(0);
   text = $textArea.val();
-  selected = this.selectedText(text, textArea);
+  selected = selectedText(text, textArea);
   $textArea.focus();
-  return this.insertText(textArea, text, tag, blockTag, selected, wrap);
-};
+  return insertMarkdownText(textArea, text, tag, blockTag, selected, wrap);
+}
 
-textUtils.init = function(form) {
-  var self;
-  self = this;
+function replaceRange(s, start, end, substitute) {
+  return s.substring(0, start) + substitute + s.substring(end);
+}
+
+export function addMarkdownListeners(form) {
   return $('.js-md', form).off('click').on('click', function() {
-    var $this;
-    $this = $(this);
-    return self.updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
+    const $this = $(this);
+    return updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
   });
-};
+}
 
-textUtils.removeListeners = function(form) {
+export function removeMarkdownListeners(form) {
   return $('.js-md', form).off('click');
-};
-
-textUtils.replaceRange = function(s, start, end, substitute) {
-  return s.substring(0, start) + substitute + s.substring(end);
-};
-
-export default textUtils;
+}
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index c0ce078651890a238c51937f8040a55955ba84f2..b54ecd2d54389eeca85bb214acf31565bbed51e6 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -7,7 +7,8 @@
  * @param {String} text
  * @returns {String}
  */
-export const addDelimiter = text => (text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text);
+export const addDelimiter = text =>
+  (text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text);
 
 /**
  * Returns '99+' for numbers bigger than 99.
@@ -22,7 +23,8 @@ export const highCountTrim = count => (count > 99 ? '99+' : count);
  * @param {String} string
  * @requires {String}
  */
-export const humanize = string => string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
+export const humanize = string =>
+  string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
 
 /**
  * Adds an 's' to the end of the string when count is bigger than 0
@@ -53,7 +55,7 @@ export const slugify = str => str.trim().toLowerCase();
  * @param {Number} maxLength
  * @returns {String}
  */
-export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - 3))}...`;
+export const truncate = (string, maxLength) => `${string.substr(0, maxLength - 3)}...`;
 
 /**
  * Capitalizes first character
@@ -65,20 +67,6 @@ export function capitalizeFirstCharacter(text) {
   return `${text[0].toUpperCase()}${text.slice(1)}`;
 }
 
-export function camelCase(str) {
-  return str.replace(/_+([a-z])/gi, ($1, $2) => $2.toUpperCase());
-}
-
-export function camelCaseKeys(obj = {}) {
-  return Object.keys(obj).reduce((acc, key) => {
-    const camelKey = camelCase(key);
-    return {
-      ...acc,
-      [camelKey]: obj[key],
-    };
-  }, {});
-}
-
 /**
  * Replaces all html tags from a string with the given replacement.
  *
@@ -94,3 +82,15 @@ export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, re
  * @param {*} string
  */
 export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase());
+
+/**
+ * Converts a sentence to lower case from the second word onwards
+ * e.g. Hello World => Hello world
+ *
+ * @param {*} string
+ */
+export const convertToSentenceCase = string => {
+  const splitWord = string.split(' ').map((word, index) => (index > 0 ? word.toLowerCase() : word));
+
+  return splitWord.join(' ');
+};
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index a266bb6771fa6c5ab3faeda6ffe12ea1404876fc..dd17544b656be8f7b046e15fde63d54d91580641 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -51,7 +51,7 @@ export function removeParams(params) {
   const url = document.createElement('a');
   url.href = window.location.href;
 
-  params.forEach((param) => {
+  params.forEach(param => {
     url.search = removeParamQueryString(url.search, param);
   });
 
@@ -83,3 +83,11 @@ export function refreshCurrentPage() {
 export function redirectTo(url) {
   return window.location.assign(url);
 }
+
+export function webIDEUrl(route = undefined) {
+  let returnUrl = `${gon.relative_url_root}/-/ide/`;
+  if (route) {
+    returnUrl += `project${route}`;
+  }
+  return returnUrl;
+}
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index e5c1fce3db94a740b0e8a1da654613fc82cc0288..f2323f57455be7756f8aa9a2c4b384d3a9fa16a0 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -1,5 +1,7 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-param-reassign, prefer-template, quotes, comma-dangle, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-else-return, max-len */
 
+import $ from 'jquery';
+
 // LineHighlighter
 //
 // Handles single- and multi-line selection and highlight for blob views.
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
index 3688a57937e0c0b221524575ea0489c33b5de63c..403e216e70f43b23790fab812799b832c3d3473d 100644
--- a/app/assets/javascripts/logo.js
+++ b/app/assets/javascripts/logo.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default function initLogoAnimation() {
   window.addEventListener('beforeunload', () => {
     $('.tanuki-logo').addClass('animate');
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 53b01cca1d3bfc79f6761beb261930f67614b918..2c80baba10b18c7468e65fb8c9e6c581dfe17b9a 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,5 +1,5 @@
 /* eslint-disable import/first */
-/* global ConfirmDangerModal */
+/* global $ */
 
 import jQuery from 'jquery';
 import Cookies from 'js-cookie';
@@ -20,7 +20,6 @@ import './behaviors/';
 // everything else
 import loadAwardsHandler from './awards_handler';
 import bp from './breakpoints';
-import './confirm_danger_modal';
 import Flash, { removeFlashClickListener } from './flash';
 import './gl_dropdown';
 import initTodoToggle from './header';
@@ -31,7 +30,6 @@ import LazyLoader from './lazy_loader';
 import initLogoAnimation from './logo';
 import './milestone_select';
 import './projects_dropdown';
-import './render_gfm';
 import initBreadcrumbs from './breadcrumb';
 
 import initDispatcher from './dispatcher';
@@ -214,16 +212,6 @@ document.addEventListener('DOMContentLoaded', () => {
     $(document).trigger('toggle.comments');
   });
 
-  $document.on('click', '.js-confirm-danger', (e) => {
-    const btn = $(e.target);
-    const form = btn.closest('form');
-    const text = btn.data('confirmDangerMessage');
-    e.preventDefault();
-
-    // eslint-disable-next-line no-new
-    new ConfirmDangerModal(form, text);
-  });
-
   $document.on('breakpoint:change', (e, breakpoint) => {
     if (breakpoint === 'sm' || breakpoint === 'xs') {
       const $gutterIcon = $sidebarGutterToggle.find('i');
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index 84e70e35bad0971de5ec38ae9c4c0af6e35aaee7..d27922a2099a0ce2c964b93acf35fdb0aab2f0aa 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Pikaday from 'pikaday';
 import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
 
diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js
index 330ebed5f739636e74fbbec78e3b650b499dc80e..7d0c701fd703e9cd8f804dec13c96f288068b0da 100644
--- a/app/assets/javascripts/members.js
+++ b/app/assets/javascripts/members.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default class Members {
   constructor() {
     this.addListeners();
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index 8be7314ded80bb0cb45e188489b0da8150306dc8..db1d09eb2f22c45884612387a58b6c04b0bb47e3 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -1,5 +1,6 @@
 /* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */
 
+import $ from 'jquery';
 import Vue from 'vue';
 import Cookies from 'js-cookie';
 
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 66b258839aef6c32a55474aed5609be501b31489..4abd5433bb5f4bd5606f485681a004ee817625a8 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -1,5 +1,6 @@
 /* eslint-disable new-cap, comma-dangle, no-new */
 
+import $ from 'jquery';
 import Vue from 'vue';
 import Flash from '../flash';
 import initIssuableSidebar from '../init_issuable_sidebar';
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index a64093afcf4a9cf00c042ba8d719bc02ba3a471a..d8222ebec63243a0d1c888e93f090dd2d907237d 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
+
+import $ from 'jquery';
 import { __ } from '~/locale';
 import TaskList from './task_list';
 import MergeRequestTabs from './merge_request_tabs';
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 46789e324c2376c304ff1480a57530b7cf208f6a..3f84f4b9499bbdbdb5b8335eb86004c4fe1970f6 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,16 +1,13 @@
 /* eslint-disable no-new, class-methods-use-this */
 
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
 import BlobForkSuggestion from './blob/blob_fork_suggestion';
 import initChangesDropdown from './init_changes_dropdown';
 import bp from './breakpoints';
-import {
-  parseUrlPathname,
-  handleLocationHash,
-  isMetaClick,
-} from './lib/utils/common_utils';
+import { parseUrlPathname, handleLocationHash, isMetaClick } from './lib/utils/common_utils';
 import { getLocationHash } from './lib/utils/url_utility';
 import initDiscussionTab from './image_diff/init_discussion_tab';
 import Diff from './diff';
@@ -68,10 +65,10 @@ import Notes from './notes';
 let location = window.location;
 
 export default class MergeRequestTabs {
-
   constructor({ action, setUrl, stubLocation } = {}) {
     const mergeRequestTabs = document.querySelector('.js-tabs-affix');
     const navbar = document.querySelector('.navbar-gitlab');
+    const peek = document.getElementById('js-peek');
     const paddingTop = 16;
 
     this.diffsLoaded = false;
@@ -85,6 +82,10 @@ export default class MergeRequestTabs {
     this.showTab = this.showTab.bind(this);
     this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
 
+    if (peek) {
+      this.stickyTop += peek.offsetHeight;
+    }
+
     if (mergeRequestTabs) {
       this.stickyTop += mergeRequestTabs.offsetHeight;
     }
@@ -103,8 +104,7 @@ export default class MergeRequestTabs {
       .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
       .on('click', '.js-show-tab', this.showTab);
 
-    $('.merge-request-tabs a[data-toggle="tab"]')
-      .on('click', this.clickTab);
+    $('.merge-request-tabs a[data-toggle="tab"]').on('click', this.clickTab);
   }
 
   // Used in tests
@@ -113,8 +113,7 @@ export default class MergeRequestTabs {
       .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
       .off('click', '.js-show-tab', this.showTab);
 
-    $('.merge-request-tabs a[data-toggle="tab"]')
-      .off('click', this.clickTab);
+    $('.merge-request-tabs a[data-toggle="tab"]').off('click', this.clickTab);
   }
 
   destroyPipelinesView() {
@@ -177,10 +176,7 @@ export default class MergeRequestTabs {
 
   scrollToElement(container) {
     if (location.hash) {
-      const offset = 0 - (
-        $('.navbar-gitlab').outerHeight() +
-        $('.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 });
@@ -234,9 +230,13 @@ export default class MergeRequestTabs {
     // Turbolinks' history.
     //
     // See https://github.com/rails/turbolinks/issues/363
-    window.history.replaceState({
-      url: newState,
-    }, document.title, newState);
+    window.history.replaceState(
+      {
+        url: newState,
+      },
+      document.title,
+      newState,
+    );
 
     return newState;
   }
@@ -252,7 +252,8 @@ export default class MergeRequestTabs {
 
     this.toggleLoading(true);
 
-    axios.get(`${source}.json`)
+    axios
+      .get(`${source}.json`)
       .then(({ data }) => {
         document.querySelector('div#commits').innerHTML = data.html;
         localTimeAgo($('.js-timeago', 'div#commits'));
@@ -297,7 +298,8 @@ export default class MergeRequestTabs {
 
     this.toggleLoading(true);
 
-    axios.get(`${urlPathname}.json${location.search}`)
+    axios
+      .get(`${urlPathname}.json${location.search}`)
       .then(({ data }) => {
         const $container = $('#diffs');
         $container.html(data.html);
@@ -326,8 +328,7 @@ export default class MergeRequestTabs {
             cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
             suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
             actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
-          })
-            .init();
+          }).init();
         });
 
         // Scroll any linked note into view
@@ -382,8 +383,7 @@ export default class MergeRequestTabs {
 
   resetViewContainer() {
     if (this.fixedLayoutPref !== null) {
-      $('.content-wrapper .container-fluid')
-        .toggleClass('container-limited', this.fixedLayoutPref);
+      $('.content-wrapper .container-fluid').toggleClass('container-limited', this.fixedLayoutPref);
     }
   }
 
@@ -432,12 +432,11 @@ export default class MergeRequestTabs {
 
     const $diffTabs = $('#diff-notes-app');
 
-    $tabs.off('affix.bs.affix affix-top.bs.affix')
+    $tabs
+      .off('affix.bs.affix affix-top.bs.affix')
       .affix({
         offset: {
-          top: () => (
-            $diffTabs.offset().top - $tabs.height() - $fixedNav.height()
-          ),
+          top: () => $diffTabs.offset().top - $tabs.height() - $fixedNav.height(),
         },
       })
       .on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index b1d74250dfdea9e2cf78e775ef25020a8faa2cf9..325fa570f37d8703a41870d81b3b3d9710566eea 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,5 +1,7 @@
+import $ from 'jquery';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
+import { mouseenter, debouncedMouseleave, togglePopover } from './shared/popover';
 
 export default class Milestone {
   constructor() {
@@ -42,4 +44,25 @@ export default class Milestone {
         .catch(() => flash('Error loading milestone tab'));
     }
   }
+
+  static initDeprecationMessage() {
+    const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message');
+
+    if (!deprecationMesssageContainer) return;
+
+    const deprecationMessage = deprecationMesssageContainer.querySelector('.js-milestone-deprecation-message-template').innerHTML;
+    const $popover = $('.js-popover-link', deprecationMesssageContainer);
+    const hideOnScroll = togglePopover.bind($popover, false);
+
+    $popover.popover({
+      content: deprecationMessage,
+      html: true,
+      placement: 'bottom',
+    })
+    .on('mouseenter', mouseenter)
+    .on('mouseleave', debouncedMouseleave())
+    .on('show.bs.popover', () => {
+      window.addEventListener('scroll', hideOnScroll, { once: true });
+    });
+  }
 }
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 2841ecb558b0322ca37e5c00ea2764bd36588ce1..7e9a50a885d34b873da69702d83a40c946a7f11b 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -1,9 +1,13 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
 /* global Issuable */
 /* global ListMilestone */
+
+import $ from 'jquery';
 import _ from 'underscore';
+import { __ } from '~/locale';
 import axios from './lib/utils/axios_utils';
 import { timeFor } from './lib/utils/datetime_utility';
+import ModalStore from './boards/stores/modal_store';
 
 export default class MilestoneSelect {
   constructor(currentProject, els, options = {}) {
@@ -22,7 +26,7 @@ export default class MilestoneSelect {
     }
 
     $els.each((i, dropdown) => {
-      let collapsedSidebarLabelTemplate, milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault;
+      let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault;
       const $dropdown = $(dropdown);
       const projectId = $dropdown.data('projectId');
       const milestonesUrl = $dropdown.data('milestones');
@@ -49,7 +53,6 @@ export default class MilestoneSelect {
       if (issueUpdateURL) {
         milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
         milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
-        collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
       }
       return $dropdown.glDropdown({
         showMenuAbove: showMenuAbove,
@@ -92,10 +95,10 @@ export default class MilestoneSelect {
             if (showMenuAbove) {
               $dropdown.data('glDropdown').positionMenuAbove();
             }
-            $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
+            $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`).addClass('is-active');
           }),
         renderRow: milestone => `
-          <li data-milestone-id="${milestone.name}">
+          <li data-milestone-id="${_.escape(milestone.name)}">
             <a href='#' class='dropdown-menu-milestone-link'>
               ${_.escape(milestone.title)}
             </a>
@@ -123,7 +126,6 @@ export default class MilestoneSelect {
             return milestone.id;
           }
         },
-        isSelected: milestone => milestone.name === selectedMilestone,
         hidden: () => {
           $selectBox.hide();
           // display:block overrides the hide-collapse rule
@@ -135,7 +137,7 @@ export default class MilestoneSelect {
             selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
           }
           $('a.is-active', $el).removeClass('is-active');
-          $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
+          $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active');
         },
         vue: $dropdown.hasClass('js-issue-board-sidebar'),
         clicked: (clickEvent) => {
@@ -156,13 +158,14 @@ export default class MilestoneSelect {
           const isMRIndex = (page === page && page === 'projects:merge_requests:index');
           const isSelecting = (selected.name !== selectedMilestone);
           selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
+
           if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
             e.preventDefault();
             return;
           }
 
           if ($dropdown.closest('.add-issues-modal').length) {
-            boardsStore = gl.issueBoards.ModalStore.store.filter;
+            boardsStore = ModalStore.store.filter;
           }
 
           if (boardsStore) {
@@ -211,11 +214,20 @@ export default class MilestoneSelect {
                   data.milestone.remaining = timeFor(data.milestone.due_date);
                   data.milestone.name = data.milestone.title;
                   $value.html(milestoneLinkTemplate(data.milestone));
-                  return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
+                  return $sidebarCollapsedValue
+                    .attr('data-original-title', `${data.milestone.name}<br />${data.milestone.remaining}`)
+                    .find('span')
+                    .text(data.milestone.title);
                 } else {
                   $value.html(milestoneLinkNoneTemplate);
-                  return $sidebarCollapsedValue.find('span').text('No');
+                  return $sidebarCollapsedValue
+                    .attr('data-original-title', __('Milestone'))
+                    .find('span')
+                    .text(__('None'));
                 }
+              })
+              .catch(() => {
+                $loading.fadeOut();
               });
           }
         }
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index c7bccd483acbe97d4ef68c53f12915ec59d90d6f..01399de4c62b8c70563e68df15f18ef1ba7dc4c6 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-new */
+
+import $ from 'jquery';
 import flash from './flash';
 import axios from './lib/utils/axios_utils';
 
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 031badc702695d0c0c72909845f0e88b6ef2f38b..f5572be5fbf8783b3643d18d70ab6af6fd9acdc1 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -1,109 +1,155 @@
 <script>
-  import _ from 'underscore';
-  import Flash from '../../flash';
-  import MonitoringService from '../services/monitoring_service';
-  import GraphGroup from './graph_group.vue';
-  import Graph from './graph.vue';
-  import EmptyState from './empty_state.vue';
-  import MonitoringStore from '../stores/monitoring_store';
-  import eventHub from '../event_hub';
-  import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
+import _ from 'underscore';
+import Flash from '../../flash';
+import MonitoringService from '../services/monitoring_service';
+import GraphGroup from './graph_group.vue';
+import Graph from './graph.vue';
+import EmptyState from './empty_state.vue';
+import MonitoringStore from '../stores/monitoring_store';
+import eventHub from '../event_hub';
 
-  export default {
-
-    components: {
-      Graph,
-      GraphGroup,
-      EmptyState,
+export default {
+  components: {
+    Graph,
+    GraphGroup,
+    EmptyState,
+  },
+  props: {
+    hasMetrics: {
+      type: Boolean,
+      required: false,
+      default: true,
     },
-
-    data() {
-      const metricsData = document.querySelector('#prometheus-graphs').dataset;
-      const store = new MonitoringStore();
-
-      return {
-        store,
-        state: 'gettingStarted',
-        hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
-        documentationPath: metricsData.documentationPath,
-        settingsPath: metricsData.settingsPath,
-        clustersPath: metricsData.clustersPath,
-        tagsPath: metricsData.tagsPath,
-        projectPath: metricsData.projectPath,
-        metricsEndpoint: metricsData.additionalMetrics,
-        deploymentEndpoint: metricsData.deploymentEndpoint,
-        emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath,
-        emptyLoadingSvgPath: metricsData.emptyLoadingSvgPath,
-        emptyUnableToConnectSvgPath: metricsData.emptyUnableToConnectSvgPath,
-        showEmptyState: true,
-        updateAspectRatio: false,
-        updatedAspectRatios: 0,
-        hoverData: {},
-        resizeThrottled: {},
-      };
+    showLegend: {
+      type: Boolean,
+      required: false,
+      default: true,
     },
-
-    created() {
-      this.service = new MonitoringService({
-        metricsEndpoint: this.metricsEndpoint,
-        deploymentEndpoint: this.deploymentEndpoint,
-      });
-      eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
-      eventHub.$on('hoverChanged', this.hoverChanged);
+    showPanels: {
+      type: Boolean,
+      required: false,
+      default: true,
     },
-
-    beforeDestroy() {
-      eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
-      eventHub.$off('hoverChanged', this.hoverChanged);
-      window.removeEventListener('resize', this.resizeThrottled, false);
+    forceSmallGraph: {
+      type: Boolean,
+      required: false,
+      default: false,
     },
-
-    mounted() {
-      this.resizeThrottled = _.throttle(this.resize, 600);
-      if (!this.hasMetrics) {
-        this.state = 'gettingStarted';
-      } else {
-        this.getGraphsData();
-        window.addEventListener('resize', this.resizeThrottled, false);
+    documentationPath: {
+      type: String,
+      required: true,
+    },
+    settingsPath: {
+      type: String,
+      required: true,
+    },
+    clustersPath: {
+      type: String,
+      required: true,
+    },
+    tagsPath: {
+      type: String,
+      required: true,
+    },
+    projectPath: {
+      type: String,
+      required: true,
+    },
+    metricsEndpoint: {
+      type: String,
+      required: true,
+    },
+    deploymentEndpoint: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    emptyGettingStartedSvgPath: {
+      type: String,
+      required: true,
+    },
+    emptyLoadingSvgPath: {
+      type: String,
+      required: true,
+    },
+    emptyNoDataSvgPath: {
+      type: String,
+      required: true,
+    },
+    emptyUnableToConnectSvgPath: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      store: new MonitoringStore(),
+      state: 'gettingStarted',
+      showEmptyState: true,
+      updateAspectRatio: false,
+      updatedAspectRatios: 0,
+      hoverData: {},
+      resizeThrottled: {},
+    };
+  },
+  created() {
+    this.service = new MonitoringService({
+      metricsEndpoint: this.metricsEndpoint,
+      deploymentEndpoint: this.deploymentEndpoint,
+    });
+    eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
+    eventHub.$on('hoverChanged', this.hoverChanged);
+  },
+  beforeDestroy() {
+    eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
+    eventHub.$off('hoverChanged', this.hoverChanged);
+    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);
+    }
+  },
+  methods: {
+    getGraphsData() {
+      this.state = 'loading';
+      Promise.all([
+        this.service.getGraphsData().then(data => this.store.storeMetrics(data)),
+        this.service
+          .getDeploymentData()
+          .then(data => this.store.storeDeploymentData(data))
+          .catch(() => new Flash('Error getting deployment information.')),
+      ])
+        .then(() => {
+          if (this.store.groups.length < 1) {
+            this.state = 'noData';
+            return;
+          }
+          this.showEmptyState = false;
+        })
+        .catch(() => {
+          this.state = 'unableToConnect';
+        });
+    },
+    resize() {
+      this.updateAspectRatio = true;
+    },
+    toggleAspectRatio() {
+      this.updatedAspectRatios = this.updatedAspectRatios += 1;
+      if (this.store.getMetricsCount() === this.updatedAspectRatios) {
+        this.updateAspectRatio = !this.updateAspectRatio;
+        this.updatedAspectRatios = 0;
       }
     },
-    methods: {
-      getGraphsData() {
-        this.state = 'loading';
-        Promise.all([
-          this.service.getGraphsData()
-            .then(data => this.store.storeMetrics(data)),
-          this.service.getDeploymentData()
-            .then(data => this.store.storeDeploymentData(data))
-            .catch(() => new Flash('Error getting deployment information.')),
-        ])
-          .then(() => {
-            if (this.store.groups.length < 1) {
-              this.state = 'noData';
-              return;
-            }
-            this.showEmptyState = false;
-          })
-          .catch(() => { this.state = 'unableToConnect'; });
-      },
-
-      resize() {
-        this.updateAspectRatio = true;
-      },
-
-      toggleAspectRatio() {
-        this.updatedAspectRatios = this.updatedAspectRatios += 1;
-        if (this.store.getMetricsCount() === this.updatedAspectRatios) {
-          this.updateAspectRatio = !this.updateAspectRatio;
-          this.updatedAspectRatios = 0;
-        }
-      },
-
-      hoverChanged(data) {
-        this.hoverData = data;
-      },
+    hoverChanged(data) {
+      this.hoverData = data;
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -115,6 +161,7 @@
       v-for="(groupData, index) in store.groups"
       :key="index"
       :name="groupData.group"
+      :show-panels="showPanels"
     >
       <graph
         v-for="(graphData, index) in groupData.metrics"
@@ -125,6 +172,8 @@
         :deployment-data="store.deploymentData"
         :project-path="projectPath"
         :tags-path="tagsPath"
+        :show-legend="showLegend"
+        :small-graph="forceSmallGraph"
       />
     </graph-group>
   </div>
@@ -136,6 +185,7 @@
     :clusters-path="clustersPath"
     :empty-getting-started-svg-path="emptyGettingStartedSvgPath"
     :empty-loading-svg-path="emptyLoadingSvgPath"
+    :empty-no-data-svg-path="emptyNoDataSvgPath"
     :empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
   />
 </template>
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index 9517b8ccb6706d19160b5f48d26371fb89399009..c77f451c2d30364f08960923541ce2122eb4b27d 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -1,87 +1,90 @@
 <script>
-  export default {
-    props: {
-      documentationPath: {
-        type: String,
-        required: true,
-      },
-      settingsPath: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      clustersPath: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      selectedState: {
-        type: String,
-        required: true,
-      },
-      emptyGettingStartedSvgPath: {
-        type: String,
-        required: true,
-      },
-      emptyLoadingSvgPath: {
-        type: String,
-        required: true,
-      },
-      emptyUnableToConnectSvgPath: {
-        type: String,
-        required: true,
-      },
+export default {
+  props: {
+    documentationPath: {
+      type: String,
+      required: true,
+    },
+    settingsPath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    clustersPath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    selectedState: {
+      type: String,
+      required: true,
+    },
+    emptyGettingStartedSvgPath: {
+      type: String,
+      required: true,
     },
-    data() {
-      return {
-        states: {
-          gettingStarted: {
-            svgUrl: this.emptyGettingStartedSvgPath,
-            title: 'Get started with performance monitoring',
-            description: `Stay updated about the performance and health
+    emptyLoadingSvgPath: {
+      type: String,
+      required: true,
+    },
+    emptyNoDataSvgPath: {
+      type: String,
+      required: true,
+    },
+    emptyUnableToConnectSvgPath: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      states: {
+        gettingStarted: {
+          svgUrl: this.emptyGettingStartedSvgPath,
+          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: 'Install Prometheus on clusters',
-            buttonPath: this.clustersPath,
-            secondaryButtonText: 'Configure existing Prometheus',
-            secondaryButtonPath: this.settingsPath,
-          },
-          loading: {
-            svgUrl: this.emptyLoadingSvgPath,
-            title: 'Waiting for performance data',
-            description: `Creating graphs uses the data from the Prometheus server.
+          buttonText: 'Install Prometheus on clusters',
+          buttonPath: this.clustersPath,
+          secondaryButtonText: 'Configure existing Prometheus',
+          secondaryButtonPath: this.settingsPath,
+        },
+        loading: {
+          svgUrl: this.emptyLoadingSvgPath,
+          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',
-            buttonPath: this.documentationPath,
-          },
-          noData: {
-            svgUrl: this.emptyUnableToConnectSvgPath,
-            title: 'No data found',
-            description: `You are connected to the Prometheus server, but there is currently
+          buttonText: 'View documentation',
+          buttonPath: this.documentationPath,
+        },
+        noData: {
+          svgUrl: this.emptyNoDataSvgPath,
+          title: 'No data found',
+          description: `You are connected to the Prometheus server, but there is currently
               no data to display.`,
-            buttonText: 'Configure Prometheus',
-            buttonPath: this.settingsPath,
-          },
-          unableToConnect: {
-            svgUrl: this.emptyUnableToConnectSvgPath,
-            title: 'Unable to connect to Prometheus server',
-            description: 'Ensure connectivity is available from the GitLab server to the ',
-            buttonText: 'View documentation',
-            buttonPath: this.documentationPath,
-          },
+          buttonText: 'Configure Prometheus',
+          buttonPath: this.settingsPath,
+        },
+        unableToConnect: {
+          svgUrl: this.emptyUnableToConnectSvgPath,
+          title: 'Unable to connect to Prometheus server',
+          description: 'Ensure connectivity is available from the GitLab server to the ',
+          buttonText: 'View documentation',
+          buttonPath: this.documentationPath,
         },
-      };
-    },
-    computed: {
-      currentState() {
-        return this.states[this.selectedState];
-      },
-
-      showButtonDescription() {
-        if (this.selectedState === 'unableToConnect') return true;
-        return false;
       },
+    };
+  },
+  computed: {
+    currentState() {
+      return this.states[this.selectedState];
+    },
+    showButtonDescription() {
+      if (this.selectedState === 'unableToConnect') return true;
+      return false;
     },
-  };
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index ea5c24efaf9a91dc4649935332f41fed1223462a..f93b1da4f58e639beb32869853ed40bf50bd4f0c 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -1,223 +1,230 @@
 <script>
-  import { scaleLinear, scaleTime } from 'd3-scale';
-  import { axisLeft, axisBottom } from 'd3-axis';
-  import { max, extent } from 'd3-array';
-  import { select } from 'd3-selection';
-  import GraphLegend from './graph/legend.vue';
-  import GraphFlag from './graph/flag.vue';
-  import GraphDeployment from './graph/deployment.vue';
-  import GraphPath from './graph/path.vue';
-  import MonitoringMixin from '../mixins/monitoring_mixins';
-  import eventHub from '../event_hub';
-  import measurements from '../utils/measurements';
-  import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters';
-  import createTimeSeries from '../utils/multiple_time_series';
-  import bp from '../../breakpoints';
+import { scaleLinear, scaleTime } from 'd3-scale';
+import { axisLeft, axisBottom } from 'd3-axis';
+import _ from 'underscore';
+import { max, extent } from 'd3-array';
+import { select } from 'd3-selection';
+import GraphAxis from './graph/axis.vue';
+import GraphLegend from './graph/legend.vue';
+import GraphFlag from './graph/flag.vue';
+import GraphDeployment from './graph/deployment.vue';
+import GraphPath from './graph/path.vue';
+import MonitoringMixin from '../mixins/monitoring_mixins';
+import eventHub from '../event_hub';
+import measurements from '../utils/measurements';
+import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters';
+import createTimeSeries from '../utils/multiple_time_series';
+import bp from '../../breakpoints';
 
-  const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
+const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
 
-  export default {
-    components: {
-      GraphLegend,
-      GraphFlag,
-      GraphDeployment,
-      GraphPath,
+export default {
+  components: {
+    GraphAxis,
+    GraphFlag,
+    GraphDeployment,
+    GraphPath,
+    GraphLegend,
+  },
+  mixins: [MonitoringMixin],
+  props: {
+    graphData: {
+      type: Object,
+      required: true,
     },
-
-    mixins: [MonitoringMixin],
-
-    props: {
-      graphData: {
-        type: Object,
-        required: true,
-      },
-      updateAspectRatio: {
-        type: Boolean,
-        required: true,
-      },
-      deploymentData: {
-        type: Array,
-        required: true,
-      },
-      hoverData: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
-      projectPath: {
-        type: String,
-        required: true,
-      },
-      tagsPath: {
-        type: String,
-        required: true,
+    updateAspectRatio: {
+      type: Boolean,
+      required: true,
+    },
+    deploymentData: {
+      type: Array,
+      required: true,
+    },
+    hoverData: {
+      type: Object,
+      required: false,
+      default: () => ({}),
+    },
+    projectPath: {
+      type: String,
+      required: true,
+    },
+    tagsPath: {
+      type: String,
+      required: true,
+    },
+    showLegend: {
+      type: Boolean,
+      required: false,
+      default: true,
+    },
+    smallGraph: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      baseGraphHeight: 450,
+      baseGraphWidth: 600,
+      graphHeight: 450,
+      graphWidth: 600,
+      graphHeightOffset: 120,
+      margin: {},
+      unitOfDisplay: '',
+      yAxisLabel: '',
+      legendTitle: '',
+      reducedDeploymentData: [],
+      measurements: measurements.large,
+      currentData: {
+        time: new Date(),
+        value: 0,
       },
+      currentDataIndex: 0,
+      currentXCoordinate: 0,
+      currentFlagPosition: 0,
+      showFlag: false,
+      showFlagContent: false,
+      timeSeries: [],
+      realPixelRatio: 1,
+    };
+  },
+  computed: {
+    outerViewBox() {
+      return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
     },
-
-    data() {
+    innerViewBox() {
+      return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
+    },
+    axisTransform() {
+      return `translate(70, ${this.graphHeight - 100})`;
+    },
+    paddingBottomRootSvg() {
       return {
-        baseGraphHeight: 450,
-        baseGraphWidth: 600,
-        graphHeight: 450,
-        graphWidth: 600,
-        graphHeightOffset: 120,
-        margin: {},
-        unitOfDisplay: '',
-        yAxisLabel: '',
-        legendTitle: '',
-        reducedDeploymentData: [],
-        measurements: measurements.large,
-        currentData: {
-          time: new Date(),
-          value: 0,
-        },
-        currentDataIndex: 0,
-        currentXCoordinate: 0,
-        currentFlagPosition: 0,
-        showFlag: false,
-        showFlagContent: false,
-        timeSeries: [],
-        realPixelRatio: 1,
+        paddingBottom: `${Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth || 0}%`,
       };
     },
-
-    computed: {
-      outerViewBox() {
-        return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
-      },
-
-      innerViewBox() {
-        return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
-      },
-
-      axisTransform() {
-        return `translate(70, ${this.graphHeight - 100})`;
-      },
-
-      paddingBottomRootSvg() {
-        return {
-          paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
-        };
-      },
-
-      deploymentFlagData() {
-        return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
-      },
+    deploymentFlagData() {
+      return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
     },
-
-    watch: {
-      updateAspectRatio() {
-        if (this.updateAspectRatio) {
-          this.graphHeight = 450;
-          this.graphWidth = 600;
-          this.measurements = measurements.large;
-          this.draw();
-          eventHub.$emit('toggleAspectRatio');
-        }
-      },
-
-      hoverData() {
-        this.positionFlag();
-      },
+  },
+  watch: {
+    updateAspectRatio() {
+      if (this.updateAspectRatio) {
+        this.graphHeight = 450;
+        this.graphWidth = 600;
+        this.measurements = measurements.large;
+        this.draw();
+        eventHub.$emit('toggleAspectRatio');
+      }
     },
-
-    mounted() {
-      this.draw();
+    hoverData() {
+      this.positionFlag();
     },
+  },
+  mounted() {
+    this.draw();
+  },
+  methods: {
+    draw() {
+      const breakpointSize = bp.getBreakpointSize();
+      const query = this.graphData.queries[0];
+      this.margin = measurements.large.margin;
+      if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') {
+        this.graphHeight = 300;
+        this.margin = measurements.small.margin;
+        this.measurements = measurements.small;
+      }
+      this.unitOfDisplay = query.unit || '';
+      this.yAxisLabel = this.graphData.y_label || 'Values';
+      this.legendTitle = query.label || 'Average';
+      this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right;
+      this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
+      this.baseGraphHeight = this.graphHeight - 50;
+      this.baseGraphWidth = this.graphWidth;
 
-    methods: {
-      draw() {
-        const breakpointSize = bp.getBreakpointSize();
-        const query = this.graphData.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.unitOfDisplay = query.unit || '';
-        this.yAxisLabel = this.graphData.y_label || 'Values';
-        this.legendTitle = query.label || 'Average';
-        this.graphWidth = this.$refs.baseSvg.clientWidth -
-                     this.margin.left - this.margin.right;
-        this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
-        this.baseGraphHeight = this.graphHeight;
-        this.baseGraphWidth = this.graphWidth;
-
-        // pixel offsets inside the svg and outside are not 1:1
-        this.realPixelRatio = (this.$refs.baseSvg.clientWidth / this.baseGraphWidth);
-
-        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 firstTimeSeries = this.timeSeries[0];
-        const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
-        const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
-        const d0 = firstTimeSeries.values[overlayIndex - 1];
-        const d1 = firstTimeSeries.values[overlayIndex];
-        if (d0 === undefined || d1 === undefined) return;
-        const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
-        const hoveredDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
-        const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
-        const currentDeployXPos = this.mouseOverDeployInfo(point.x);
+      // pixel offsets inside the svg and outside are not 1:1
+      this.realPixelRatio = this.$refs.baseSvg.clientWidth / this.baseGraphWidth;
 
-        eventHub.$emit('hoverChanged', {
-          hoveredDate,
-          currentDeployXPos,
-        });
-      },
+      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 firstTimeSeries = this.timeSeries[0];
+      const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
+      const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
+      const d0 = firstTimeSeries.values[overlayIndex - 1];
+      const d1 = firstTimeSeries.values[overlayIndex];
+      if (d0 === undefined || d1 === undefined) return;
+      const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
+      const hoveredDataIndex = evalTime ? overlayIndex : overlayIndex - 1;
+      const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
+      const currentDeployXPos = this.mouseOverDeployInfo(point.x);
 
-      renderAxesPaths() {
-        this.timeSeries = createTimeSeries(
-          this.graphData.queries,
-          this.graphWidth,
-          this.graphHeight,
-          this.graphHeightOffset,
-        );
+      eventHub.$emit('hoverChanged', {
+        hoveredDate,
+        currentDeployXPos,
+      });
+    },
+    renderAxesPaths() {
+      this.timeSeries = createTimeSeries(
+        this.graphData.queries,
+        this.graphWidth,
+        this.graphHeight,
+        this.graphHeightOffset,
+      );
 
-        if (this.timeSeries.length > 3) {
-          this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
-        }
+      if (_.findWhere(this.timeSeries, { renderCanary: true })) {
+        this.timeSeries = this.timeSeries.map(series => ({ ...series, renderCanary: true }));
+      }
 
-        const axisXScale = d3.scaleTime()
-          .range([0, this.graphWidth - 70]);
-        const axisYScale = d3.scaleLinear()
-          .range([this.graphHeight - this.graphHeightOffset, 0]);
+      const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]);
+      const axisYScale = d3.scaleLinear().range([this.graphHeight - this.graphHeightOffset, 0]);
 
-        const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
-        axisXScale.domain(d3.extent(allValues, d => d.time));
-        axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
+      const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
+      axisXScale.domain(d3.extent(allValues, d => d.time));
+      axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
 
-        const xAxis = d3.axisBottom()
-          .scale(axisXScale)
-          .tickFormat(timeScaleFormat);
+      const xAxis = d3
+        .axisBottom()
+        .scale(axisXScale)
+        .ticks(this.graphWidth / 120)
+        .tickFormat(timeScaleFormat);
 
-        const yAxis = d3.axisLeft()
-          .scale(axisYScale)
-          .ticks(measurements.yTicks);
+      const yAxis = d3
+        .axisLeft()
+        .scale(axisYScale)
+        .ticks(measurements.yTicks);
 
-        d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
+      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(d, i) {
-            if (i > 0) {
-              d3.select(this).select('line')
-                .attr('x2', width)
-                .attr('class', 'axis-tick');
-            } // Avoid adding the class to the first tick, to prevent coloring
-          }); // This will select all of the ticks once they're rendered
-      },
+      const width = this.graphWidth;
+      d3
+        .select(this.$refs.baseSvg)
+        .select('.y-axis')
+        .call(yAxis)
+        .selectAll('.tick')
+        .each(function createTickLines(d, i) {
+          if (i > 0) {
+            d3
+              .select(this)
+              .select('line')
+              .attr('x2', width)
+              .attr('class', 'axis-tick');
+          } // Avoid adding the class to the first tick, to prevent coloring
+        }); // This will select all of the ticks once they're rendered
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -245,16 +252,13 @@
           class="y-axis"
           transform="translate(70, 20)"
         />
-        <graph-legend
+        <graph-axis
           :graph-width="graphWidth"
           :graph-height="graphHeight"
           :margin="margin"
           :measurements="measurements"
-          :legend-title="legendTitle"
           :y-axis-label="yAxisLabel"
-          :time-series="timeSeries"
           :unit-of-display="unitOfDisplay"
-          :current-data-index="currentDataIndex"
         />
         <svg
           class="graph-data"
@@ -299,5 +303,10 @@
         :deployment-flag-data="deploymentFlagData"
       />
     </div>
+    <graph-legend
+      v-if="showLegend"
+      :legend-title="legendTitle"
+      :time-series="timeSeries"
+    />
   </div>
 </template>
diff --git a/app/assets/javascripts/monitoring/components/graph/axis.vue b/app/assets/javascripts/monitoring/components/graph/axis.vue
new file mode 100644
index 0000000000000000000000000000000000000000..fc4b3689dfd653027a0ef42f19a8bd6f8d7da70d
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/axis.vue
@@ -0,0 +1,142 @@
+<script>
+import { convertToSentenceCase } from '~/lib/utils/text_utility';
+import { s__ } from '~/locale';
+
+export default {
+  props: {
+    graphWidth: {
+      type: Number,
+      required: true,
+    },
+    graphHeight: {
+      type: Number,
+      required: true,
+    },
+    margin: {
+      type: Object,
+      required: true,
+    },
+    measurements: {
+      type: Object,
+      required: true,
+    },
+    yAxisLabel: {
+      type: String,
+      required: true,
+    },
+    unitOfDisplay: {
+      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 +
+          this.measurements.axisLabelLineOffset) /
+          2 +
+          this.yLabelWidth / 2 || 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
+      );
+    },
+
+    yAxisLabelSentenceCase() {
+      return `${convertToSentenceCase(this.yAxisLabel)} (${this.unitOfDisplay})`;
+    },
+
+    timeString() {
+      return s__('PrometheusDashboard|Time');
+    },
+  },
+  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
+      class="label-y-axis-line"
+      stroke="#000000"
+      stroke-width="1"
+      x1="10"
+      y1="0"
+      :x2="10"
+      :y2="yPosition"
+    />
+    <rect
+      class="rect-axis-text"
+      :transform="rectTransform"
+      :width="yLabelWidth"
+      :height="yLabelHeight"
+    />
+    <text
+      class="label-axis-text y-label-text"
+      text-anchor="middle"
+      :transform="textTransform"
+      ref="ylabel"
+    >
+      {{ yAxisLabelSentenceCase }}
+    </text>
+    <rect
+      class="rect-axis-text"
+      :x="xPosition + 60"
+      :y="graphHeight - 80"
+      width="35"
+      height="50"
+    />
+    <text
+      class="label-axis-text x-label-text"
+      :x="xPosition + 60"
+      :y="yPosition"
+      dy=".35em"
+    >
+      {{ timeString }}
+    </text>
+  </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue
index 98c25307b74f08d4e07faa3c156e26016f85f1a6..4012191ceb9ecd770b2ddb9b408739ead49043c9 100644
--- a/app/assets/javascripts/monitoring/components/graph/deployment.vue
+++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue
@@ -1,32 +1,30 @@
 <script>
-  export default {
-    props: {
-      deploymentData: {
-        type: Array,
-        required: true,
-      },
-      graphHeight: {
-        type: Number,
-        required: true,
-      },
-      graphHeightOffset: {
-        type: Number,
-        required: true,
-      },
+export default {
+  props: {
+    deploymentData: {
+      type: Array,
+      required: true,
     },
-
-    computed: {
-      calculatedHeight() {
-        return this.graphHeight - this.graphHeightOffset;
-      },
+    graphHeight: {
+      type: Number,
+      required: true,
     },
-
-    methods: {
-      transformDeploymentGroup(deployment) {
-        return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
-      },
+    graphHeightOffset: {
+      type: Number,
+      required: true,
     },
-  };
+  },
+  computed: {
+    calculatedHeight() {
+      return this.graphHeight - this.graphHeightOffset;
+    },
+  },
+  methods: {
+    transformDeploymentGroup(deployment) {
+      return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
+    },
+  },
+};
 </script>
 <template>
   <g class="deploy-info">
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index 07aa6a3e5de046b729ca721102f2f39c996594a6..b8202e25685a7c3e2e312bab5c97f0022337d59a 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -1,127 +1,116 @@
 <script>
-  import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
-  import { formatRelevantDigits } from '../../../lib/utils/number_utils';
-  import icon from '../../../vue_shared/components/icon.vue';
+import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
+import { formatRelevantDigits } from '../../../lib/utils/number_utils';
+import Icon from '../../../vue_shared/components/icon.vue';
+import TrackLine from './track_line.vue';
 
-  export default {
-    components: {
-      icon,
-    },
-    props: {
-      currentXCoordinate: {
-        type: Number,
-        required: true,
-      },
-      currentData: {
-        type: Object,
-        required: true,
-      },
-      deploymentFlagData: {
-        type: Object,
-        required: false,
-        default: null,
-      },
-      graphHeight: {
-        type: Number,
-        required: true,
-      },
-      graphHeightOffset: {
-        type: Number,
-        required: true,
-      },
-      realPixelRatio: {
-        type: Number,
-        required: true,
-      },
-      showFlagContent: {
-        type: Boolean,
-        required: true,
-      },
-      timeSeries: {
-        type: Array,
-        required: true,
-      },
-      unitOfDisplay: {
-        type: String,
-        required: true,
-      },
-      currentDataIndex: {
-        type: Number,
-        required: true,
-      },
-      legendTitle: {
-        type: String,
-        required: true,
-      },
+export default {
+  components: {
+    Icon,
+    TrackLine,
+  },
+  props: {
+    currentXCoordinate: {
+      type: Number,
+      required: true,
     },
-
-    computed: {
-      formatTime() {
-        return this.deploymentFlagData ?
-          timeFormat(this.deploymentFlagData.time) :
-          timeFormat(this.currentData.time);
-      },
-
-      formatDate() {
-        return this.deploymentFlagData ?
-          dateFormat(this.deploymentFlagData.time) :
-          dateFormat(this.currentData.time);
-      },
-
-      cursorStyle() {
-        const xCoordinate = this.deploymentFlagData ?
-          this.deploymentFlagData.xPos :
-          this.currentXCoordinate;
-
-        const offsetTop = 20 * this.realPixelRatio;
-        const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
-        const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
-
-        return {
-          top: `${offsetTop}px`,
-          left: `${offsetLeft}px`,
-          height: `${height}px`,
-        };
-      },
-
-      flagOrientation() {
-        if (this.currentXCoordinate * this.realPixelRatio > 120) {
-          return 'left';
-        }
-        return 'right';
-      },
+    currentData: {
+      type: Object,
+      required: true,
     },
+    deploymentFlagData: {
+      type: Object,
+      required: false,
+      default: null,
+    },
+    graphHeight: {
+      type: Number,
+      required: true,
+    },
+    graphHeightOffset: {
+      type: Number,
+      required: true,
+    },
+    realPixelRatio: {
+      type: Number,
+      required: true,
+    },
+    showFlagContent: {
+      type: Boolean,
+      required: true,
+    },
+    timeSeries: {
+      type: Array,
+      required: true,
+    },
+    unitOfDisplay: {
+      type: String,
+      required: true,
+    },
+    currentDataIndex: {
+      type: Number,
+      required: true,
+    },
+    legendTitle: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    formatTime() {
+      return this.deploymentFlagData
+        ? timeFormat(this.deploymentFlagData.time)
+        : timeFormat(this.currentData.time);
+    },
+    formatDate() {
+      return this.deploymentFlagData
+        ? dateFormat(this.deploymentFlagData.time)
+        : dateFormat(this.currentData.time);
+    },
+    cursorStyle() {
+      const xCoordinate = this.deploymentFlagData
+        ? this.deploymentFlagData.xPos
+        : this.currentXCoordinate;
 
-    methods: {
-      seriesMetricValue(series) {
-        const index = this.deploymentFlagData ?
-          this.deploymentFlagData.seriesIndex :
-          this.currentDataIndex;
-        const value = series.values[index] &&
-          series.values[index].value;
-        if (isNaN(value)) {
-          return '-';
-        }
-        return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
-      },
-
-      seriesMetricLabel(index, series) {
-        if (this.timeSeries.length < 2) {
-          return this.legendTitle;
-        }
-        if (series.metricTag) {
-          return series.metricTag;
-        }
-        return `series ${index + 1}`;
-      },
+      const offsetTop = 20 * this.realPixelRatio;
+      const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
+      const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
 
-      strokeDashArray(type) {
-        if (type === 'dashed') return '6, 3';
-        if (type === 'dotted') return '3, 3';
-        return null;
-      },
+      return {
+        top: `${offsetTop}px`,
+        left: `${offsetLeft}px`,
+        height: `${height}px`,
+      };
     },
-  };
+    flagOrientation() {
+      if (this.currentXCoordinate * this.realPixelRatio > 120) {
+        return 'left';
+      }
+      return 'right';
+    },
+  },
+  methods: {
+    seriesMetricValue(series) {
+      const index = this.deploymentFlagData
+        ? this.deploymentFlagData.seriesIndex
+        : this.currentDataIndex;
+      const value = series.values[index] && series.values[index].value;
+      if (isNaN(value)) {
+        return '-';
+      }
+      return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
+    },
+    seriesMetricLabel(index, series) {
+      if (this.timeSeries.length < 2) {
+        return this.legendTitle;
+      }
+      if (series.metricTag) {
+        return series.metricTag;
+      }
+      return `series ${index + 1}`;
+    },
+  },
+};
 </script>
 
 <template>
@@ -168,28 +157,13 @@
         </div>
       </div>
       <div class="popover-content">
-        <table>
+        <table class="prometheus-table">
           <tr
             v-for="(series, index) in timeSeries"
             :key="index"
           >
-            <td>
-              <svg
-                width="15"
-                height="6"
-              >
-                <line
-                  :stroke="series.lineColor"
-                  :stroke-dasharray="strokeDashArray(series.lineStyle)"
-                  stroke-width="4"
-                  x1="0"
-                  x2="15"
-                  y1="2"
-                  y2="2"
-                />
-              </svg>
-            </td>
-            <td>{{ seriesMetricLabel(index, series) }}</td>
+            <track-line :track="series"/>
+            <td>{{ series.track }} {{ seriesMetricLabel(index, series) }}</td>
             <td>
               <strong>{{ seriesMetricValue(series) }}</strong>
             </td>
diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue
index c6e8d726ffcd6ca86ace0d1144424230010067e6..da9280cf1f19e0885554eaf406a9da6169b8361c 100644
--- a/app/assets/javascripts/monitoring/components/graph/legend.vue
+++ b/app/assets/javascripts/monitoring/components/graph/legend.vue
@@ -1,204 +1,72 @@
 <script>
-  import { formatRelevantDigits } from '../../../lib/utils/number_utils';
+import TrackLine from './track_line.vue';
+import TrackInfo from './track_info.vue';
 
-  export default {
-    props: {
-      graphWidth: {
-        type: Number,
-        required: true,
-      },
-      graphHeight: {
-        type: Number,
-        required: true,
-      },
-      margin: {
-        type: Object,
-        required: true,
-      },
-      measurements: {
-        type: Object,
-        required: true,
-      },
-      legendTitle: {
-        type: String,
-        required: true,
-      },
-      yAxisLabel: {
-        type: String,
-        required: true,
-      },
-      timeSeries: {
-        type: Array,
-        required: true,
-      },
-      unitOfDisplay: {
-        type: String,
-        required: true,
-      },
-      currentDataIndex: {
-        type: Number,
-        required: true,
-      },
+export default {
+  components: {
+    TrackLine,
+    TrackInfo,
+  },
+  props: {
+    legendTitle: {
+      type: String,
+      required: true,
     },
-    data() {
+    timeSeries: {
+      type: Array,
+      required: true,
+    },
+  },
+  methods: {
+    isStable(track) {
       return {
-        yLabelWidth: 0,
-        yLabelHeight: 0,
-        seriesXPosition: 0,
-        metricUsageXPosition: 0,
+        'prometheus-table-row-highlight': track.trackName !== 'Canary' && track.renderCanary,
       };
     },
-    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.metricUsageXPosition = 0;
-        this.seriesXPosition = 0;
-        if (this.$refs.legendTitleSvg != null) {
-          this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
-        }
-        if (this.$refs.seriesTitleSvg != null) {
-          this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
-        }
-        this.yLabelWidth = bbox.width + 10; // Added some padding
-        this.yLabelHeight = bbox.height + 5;
-      });
-    },
-    methods: {
-      translateLegendGroup(index) {
-        return `translate(0, ${12 * (index)})`;
-      },
-
-      formatMetricUsage(series) {
-        const value = series.values[this.currentDataIndex] &&
-          series.values[this.currentDataIndex].value;
-        if (isNaN(value)) {
-          return '-';
-        }
-        return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
-      },
-
-      createSeriesString(index, series) {
-        if (series.metricTag) {
-          return `${series.metricTag} ${this.formatMetricUsage(series)}`;
-        }
-        return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
-      },
-
-      strokeDashArray(type) {
-        if (type === 'dashed') return '6, 3';
-        if (type === 'dotted') return '3, 3';
-        return null;
-      },
-    },
-  };
+  },
+};
 </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
-      class="label-y-axis-line"
-      stroke="#000000"
-      stroke-width="1"
-      x1="10"
-      y1="0"
-      :x2="10"
-      :y2="yPosition"
-    />
-    <rect
-      class="rect-axis-text"
-      :transform="rectTransform"
-      :width="yLabelWidth"
-      :height="yLabelHeight"
-    />
-    <text
-      class="label-axis-text y-label-text"
-      text-anchor="middle"
-      :transform="textTransform"
-      ref="ylabel"
-    >
-      {{ yAxisLabel }}
-    </text>
-    <rect
-      class="rect-axis-text"
-      :x="xPosition + 60"
-      :y="graphHeight - 80"
-      width="35"
-      height="50"
-    />
-    <text
-      class="label-axis-text x-label-text"
-      :x="xPosition + 60"
-      :y="yPosition"
-      dy=".35em"
-    >
-      Time
-    </text>
-    <g
-      class="legend-group"
-      v-for="(series, index) in timeSeries"
-      :key="index"
-      :transform="translateLegendGroup(index)"
-    >
-      <line
-        :stroke="series.lineColor"
-        :stroke-width="measurements.legends.height"
-        :stroke-dasharray="strokeDashArray(series.lineStyle)"
-        :x1="measurements.legends.offsetX"
-        :x2="measurements.legends.offsetX + measurements.legends.width"
-        :y1="graphHeight - measurements.legends.offsetY"
-        :y2="graphHeight - measurements.legends.offsetY"
-      />
-      <text
-        v-if="timeSeries.length > 1"
-        class="legend-metric-title"
-        ref="legendTitleSvg"
-        x="38"
-        :y="graphHeight - 30"
-      >
-        {{ createSeriesString(index, series) }}
-      </text>
-      <text
-        v-else
-        class="legend-metric-title"
-        ref="legendTitleSvg"
-        x="38"
-        :y="graphHeight - 30"
+  <div class="prometheus-graph-legends prepend-left-10">
+    <table class="prometheus-table">
+      <tr
+        v-for="(series, index) in timeSeries"
+        :key="index"
+        v-if="series.shouldRenderLegend"
+        :class="isStable(series)"
       >
-        {{ legendTitle }} {{ formatMetricUsage(series) }}
-      </text>
-    </g>
-  </g>
+        <td>
+          <strong v-if="series.renderCanary">{{ series.trackName }}</strong>
+        </td>
+        <track-line :track="series" />
+        <td
+          class="legend-metric-title"
+          v-if="timeSeries.length > 1">
+          <track-info
+            :track="series"
+            v-if="series.metricTag" />
+          <track-info
+            v-else
+            :track="series">
+            <strong>{{ legendTitle }}</strong> series {{ index + 1 }}
+          </track-info>
+        </td>
+        <td v-else>
+          <track-info :track="series">
+            <strong>{{ legendTitle }}</strong>
+          </track-info>
+        </td>
+        <template v-for="(track, trackIndex) in series.tracksLegend">
+          <track-line
+            :track="track"
+            :key="`track-line-${trackIndex}`"/>
+          <td :key="`track-info-${trackIndex}`">
+            <track-info
+              class="legend-metric-title"
+              :track="track" />
+          </td>
+        </template>
+      </tr>
+    </table>
+  </div>
 </template>
diff --git a/app/assets/javascripts/monitoring/components/graph/path.vue b/app/assets/javascripts/monitoring/components/graph/path.vue
index c9721c4cb018692dd5c0b50c178215032e7584dd..881560124a597ec36b84f2f6295bf5a17cd507e9 100644
--- a/app/assets/javascripts/monitoring/components/graph/path.vue
+++ b/app/assets/javascripts/monitoring/components/graph/path.vue
@@ -1,36 +1,36 @@
 <script>
-  export default {
-    props: {
-      generatedLinePath: {
-        type: String,
-        required: true,
-      },
-      generatedAreaPath: {
-        type: String,
-        required: true,
-      },
-      lineStyle: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      lineColor: {
-        type: String,
-        required: true,
-      },
-      areaColor: {
-        type: String,
-        required: true,
-      },
+export default {
+  props: {
+    generatedLinePath: {
+      type: String,
+      required: true,
     },
-    computed: {
-      strokeDashArray() {
-        if (this.lineStyle === 'dashed') return '3, 1';
-        if (this.lineStyle === 'dotted') return '1, 1';
-        return null;
-      },
+    generatedAreaPath: {
+      type: String,
+      required: true,
     },
-  };
+    lineStyle: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    lineColor: {
+      type: String,
+      required: true,
+    },
+    areaColor: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    strokeDashArray() {
+      if (this.lineStyle === 'dashed') return '3, 1';
+      if (this.lineStyle === 'dotted') return '1, 1';
+      return null;
+    },
+  },
+};
 </script>
 <template>
   <g>
diff --git a/app/assets/javascripts/monitoring/components/graph/track_info.vue b/app/assets/javascripts/monitoring/components/graph/track_info.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ec1c2222af95f82284f11c09c19ceafab377f3ca
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/track_info.vue
@@ -0,0 +1,29 @@
+<script>
+import { formatRelevantDigits } from '~/lib/utils/number_utils';
+
+export default {
+  name: 'TrackInfo',
+  props: {
+    track: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    summaryMetrics() {
+      return `Avg: ${formatRelevantDigits(this.track.average)} 路 Max: ${formatRelevantDigits(
+        this.track.max,
+      )}`;
+    },
+  },
+};
+</script>
+<template>
+  <span>
+    <slot>
+      <strong> {{ track.metricTag }} </strong>
+    </slot>
+    {{ summaryMetrics }}
+  </span>
+</template>
+
diff --git a/app/assets/javascripts/monitoring/components/graph/track_line.vue b/app/assets/javascripts/monitoring/components/graph/track_line.vue
new file mode 100644
index 0000000000000000000000000000000000000000..79b322e2e4272c83b09ea2dd85adaef9fb3d42c4
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/track_line.vue
@@ -0,0 +1,36 @@
+<script>
+export default {
+  name: 'TrackLine',
+  props: {
+    track: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    stylizedLine() {
+      if (this.track.lineStyle === 'dashed') return '6, 3';
+      if (this.track.lineStyle === 'dotted') return '3, 3';
+      return null;
+    },
+  },
+};
+</script>
+<template>
+  <td>
+    <svg
+      width="15"
+      height="6">
+      <line
+        :stroke-dasharray="stylizedLine"
+        :stroke="track.lineColor"
+        stroke-width="4"
+        :x1="0"
+        :x2="15"
+        :y1="2"
+        :y2="2"
+      />
+    </svg>
+  </td>
+</template>
+
diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue
index 079351a69af6ea20799c2afc13433f99bf45872e..a6dbe42a8f05928695197670d61a0dcbfb3194e8 100644
--- a/app/assets/javascripts/monitoring/components/graph_group.vue
+++ b/app/assets/javascripts/monitoring/components/graph_group.vue
@@ -1,16 +1,24 @@
 <script>
-  export default {
-    props: {
-      name: {
-        type: String,
-        required: true,
-      },
+export default {
+  props: {
+    name: {
+      type: String,
+      required: true,
     },
-  };
+    showPanels: {
+      type: Boolean,
+      required: false,
+      default: true,
+    },
+  },
+};
 </script>
 
 <template>
-  <div class="panel panel-default prometheus-panel">
+  <div
+    v-if="showPanels"
+    class="panel panel-default prometheus-panel"
+  >
     <div class="panel-heading">
       <h4>{{ name }}</h4>
     </div>
@@ -18,4 +26,10 @@
       <slot></slot>
     </div>
   </div>
+  <div
+    v-else
+    class="prometheus-graph-group"
+  >
+    <slot></slot>
+  </div>
 </template>
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index c3b0ef7e9ca1ff1258c9ab1a6a84719e22c8dd29..41270e015d49390e96af8c18974e382207999419 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,7 +1,22 @@
 import Vue from 'vue';
+import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
 import Dashboard from './components/dashboard.vue';
 
-export default () => new Vue({
-  el: '#prometheus-graphs',
-  render: createElement => createElement(Dashboard),
-});
+export default () => {
+  const el = document.getElementById('prometheus-graphs');
+
+  if (el && el.dataset) {
+    // eslint-disable-next-line no-new
+    new Vue({
+      el,
+      render(createElement) {
+        return createElement(Dashboard, {
+          props: {
+            ...el.dataset,
+            hasMetrics: convertPermissionToBoolean(el.dataset.hasMetrics),
+          },
+        });
+      },
+    });
+  }
+};
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
index e230a06cd8c540f988af33ad51499227d9e23f89..6fcca36d2fad2d77228f5f70c491b4d459d11182 100644
--- a/app/assets/javascripts/monitoring/services/monitoring_service.js
+++ b/app/assets/javascripts/monitoring/services/monitoring_service.js
@@ -40,6 +40,9 @@ export default class MonitoringService {
   }
 
   getDeploymentData() {
+    if (!this.deploymentEndpoint) {
+      return Promise.resolve([]);
+    }
     return backOffRequest(() => axios.get(this.deploymentEndpoint))
       .then(resp => resp.data)
       .then((response) => {
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
index 854636e9a89c8cba4e77a39a64afa97e588b0a09..535c415cd6d057940698197b5366b770f1682cb2 100644
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -1,7 +1,7 @@
 import _ from 'underscore';
 
 function sortMetrics(metrics) {
-  return _.chain(metrics).sortBy('weight').sortBy('title').value();
+  return _.chain(metrics).sortBy('title').sortBy('weight').value();
 }
 
 function normalizeMetrics(metrics) {
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index 4ce3dad440c0a2d118743dfaff09a535e0d32746..8a93c7e6bae8f552afed4b2f4da26047ad53c7f8 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -1,10 +1,21 @@
 import _ from 'underscore';
 import { scaleLinear, scaleTime } from 'd3-scale';
 import { line, area, curveLinear } from 'd3-shape';
-import { extent, max } from 'd3-array';
+import { extent, max, sum } from 'd3-array';
 import { timeMinute } from 'd3-time';
-
-const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute };
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+
+const d3 = {
+  scaleLinear,
+  scaleTime,
+  line,
+  area,
+  curveLinear,
+  extent,
+  max,
+  timeMinute,
+  sum,
+};
 
 const defaultColorPalette = {
   blue: ['#1f78d1', '#8fbce8'],
@@ -20,6 +31,8 @@ const defaultStyleOrder = ['solid', 'dashed', 'dotted'];
 
 function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) {
   let usedColors = [];
+  let renderCanary = false;
+  const timeSeriesParsed = [];
 
   function pickColor(name) {
     let pick;
@@ -38,16 +51,23 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
     return defaultColorPalette[pick];
   }
 
-  return query.result.map((timeSeries, timeSeriesNumber) => {
+  query.result.forEach((timeSeries, timeSeriesNumber) => {
     let metricTag = '';
     let lineColor = '';
     let areaColor = '';
+    let shouldRenderLegend = true;
+    const timeSeriesValues = timeSeries.values.map(d => d.value);
+    const maximumValue = d3.max(timeSeriesValues);
+    const accum = d3.sum(timeSeriesValues);
+    const trackName = capitalizeFirstCharacter(query.track ? query.track : 'Stable');
+
+    if (trackName === 'Canary') {
+      renderCanary = true;
+    }
 
-    const timeSeriesScaleX = d3.scaleTime()
-      .range([0, graphWidth - 70]);
+    const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]);
 
-    const timeSeriesScaleY = d3.scaleLinear()
-      .range([graphHeight - graphHeightOffset, 0]);
+    const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]);
 
     timeSeriesScaleX.domain(xDom);
     timeSeriesScaleX.ticks(d3.timeMinute, 60);
@@ -55,13 +75,15 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
 
     const defined = d => !isNaN(d.value) && d.value != null;
 
-    const lineFunction = d3.line()
+    const lineFunction = d3
+      .line()
       .defined(defined)
       .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate
       .x(d => timeSeriesScaleX(d.time))
       .y(d => timeSeriesScaleY(d.value));
 
-    const areaFunction = d3.area()
+    const areaFunction = d3
+      .area()
       .defined(defined)
       .curve(d3.curveLinear)
       .x(d => timeSeriesScaleX(d.time))
@@ -69,38 +91,62 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
       .y1(d => timeSeriesScaleY(d.value));
 
     const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
-    const seriesCustomizationData = query.series != null &&
-      _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
+    const seriesCustomizationData =
+      query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
 
     if (seriesCustomizationData) {
       metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
       [lineColor, areaColor] = pickColor(seriesCustomizationData.color);
+      shouldRenderLegend = false;
     } else {
-      metricTag = timeSeriesMetricLabel || `series ${timeSeriesNumber + 1}`;
+      metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`;
       [lineColor, areaColor] = pickColor();
+      if (timeSeriesParsed.length > 1) {
+        shouldRenderLegend = false;
+      }
     }
 
-    if (query.track) {
-      metricTag += ` - ${query.track}`;
+    if (!shouldRenderLegend) {
+      if (!timeSeriesParsed[0].tracksLegend) {
+        timeSeriesParsed[0].tracksLegend = [];
+      }
+      timeSeriesParsed[0].tracksLegend.push({
+        max: maximumValue,
+        average: accum / timeSeries.values.length,
+        lineStyle,
+        lineColor,
+        metricTag,
+      });
     }
 
-    return {
+    timeSeriesParsed.push({
       linePath: lineFunction(timeSeries.values),
       areaPath: areaFunction(timeSeries.values),
       timeSeriesScaleX,
       values: timeSeries.values,
+      max: maximumValue,
+      average: accum / timeSeries.values.length,
       lineStyle,
       lineColor,
       areaColor,
       metricTag,
-    };
+      trackName,
+      shouldRenderLegend,
+      renderCanary,
+    });
   });
+
+  return timeSeriesParsed;
 }
 
 export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) {
-  const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat(
-    query.result.reduce((allResults, result) => allResults.concat(result.values), []),
-  ), []);
+  const allValues = queries.reduce(
+    (allQueryResults, query) =>
+      allQueryResults.concat(
+        query.result.reduce((allResults, result) => allResults.concat(result.values), []),
+      ),
+    [],
+  );
 
   const xDom = d3.extent(allValues, d => d.time);
   const yDom = [0, d3.max(allValues.map(d => d.value))];
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 972fdb2b791ea2479dbdac21da3174ee48d81bf3..e3c5bf06b3d8b25ee68ed682f4f1e1bdc5a9066f 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -4,15 +4,20 @@ import discussionCounter from '../notes/components/discussion_counter.vue';
 import store from '../notes/stores';
 
 export default function initMrNotes() {
-  new Vue({ // eslint-disable-line
+  // eslint-disable-next-line no-new
+  new Vue({
     el: '#js-vue-mr-discussions',
     components: {
       notesApp,
     },
     data() {
-      const notesDataset = document.getElementById('js-vue-mr-discussions').dataset;
+      const notesDataset = document.getElementById('js-vue-mr-discussions')
+        .dataset;
+      const noteableData = JSON.parse(notesDataset.noteableData);
+      noteableData.noteableType = notesDataset.noteableType;
+
       return {
-        noteableData: JSON.parse(notesDataset.noteableData),
+        noteableData,
         currentUserData: JSON.parse(notesDataset.currentUserData),
         notesData: JSON.parse(notesDataset.notesData),
       };
@@ -28,7 +33,8 @@ export default function initMrNotes() {
     },
   });
 
-  new Vue({ // eslint-disable-line
+  // eslint-disable-next-line no-new
+  new Vue({
     el: '#js-vue-discussion-counter',
     components: {
       discussionCounter,
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index aa377327107b555bd57a3215857b9b5bd832bda4..c7a8aac79df42a25fc3078c61b9466be5b88fd44 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
+
+import $ from 'jquery';
 import Api from './api';
 import { mergeUrlParams } from './lib/utils/url_utility';
 
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index d3edcb724f19d2d8811df396fc06c77a21cea71d..bd007c707f2694aca8eacc5c4eda691a6cbb4402 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,5 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
 
+import $ from 'jquery';
 import { __ } from '../locale';
 import axios from '../lib/utils/axios_utils';
 import flash from '../flash';
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 77733b67c4df729c7b952b25a34809171a047bf6..40c08ee0ace1f0e556ae9c3af781e3369a0028c5 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
+
+import $ from 'jquery';
 import RefSelectDropdown from './ref_select_dropdown';
 
 export default class NewBranchForm {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index c640003d95892a1cd64b182667f3a78159dc3bc5..2121907dff0afa59b76f22971f52edfdf3c3f462 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -16,6 +16,9 @@ import Autosize from 'autosize';
 import 'vendor/jquery.caret'; // required by jquery.atwho
 import 'vendor/jquery.atwho';
 import AjaxCache from '~/lib/utils/ajax_cache';
+import Vue from 'vue';
+import syntaxHighlight from '~/syntax_highlight';
+import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
 import axios from './lib/utils/axios_utils';
 import { getLocationHash } from './lib/utils/url_utility';
 import Flash from './flash';
@@ -24,7 +27,13 @@ import GLForm from './gl_form';
 import loadAwardsHandler from './awards_handler';
 import Autosave from './autosave';
 import TaskList from './task_list';
-import { isInViewport, getPagePath, scrollToElement, isMetaKey, hasVueMRDiscussionsCookie } from './lib/utils/common_utils';
+import {
+  isInViewport,
+  getPagePath,
+  scrollToElement,
+  isMetaKey,
+  hasVueMRDiscussionsCookie,
+} from './lib/utils/common_utils';
 import imageDiffHelper from './image_diff/helpers/index';
 import { localTimeAgo } from './lib/utils/datetime_utility';
 
@@ -38,9 +47,21 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
 const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
 
 export default class Notes {
-  static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
+  static initialize(
+    notes_url,
+    note_ids,
+    last_fetched_at,
+    view,
+    enableGFM = true,
+  ) {
     if (!this.instance) {
-      this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
+      this.instance = new Notes(
+        notes_url,
+        note_ids,
+        last_fetched_at,
+        view,
+        enableGFM,
+      );
     }
   }
 
@@ -78,10 +99,14 @@ export default class Notes {
     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.notesCountBadge ||
+      (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
     this.basePollingInterval = 15000;
     this.maxPollingSteps = 4;
 
+    this.$wrapperEl = hasVueMRDiscussionsCookie()
+      ? $(document).find('.diffs')
+      : $(document);
     this.cleanBinding();
     this.addBinding();
     this.setPollingInterval();
@@ -89,15 +114,24 @@ export default class Notes {
     this.taskList = new TaskList({
       dataType: 'note',
       fieldName: 'note',
-      selector: '.notes'
+      selector: '.notes',
     });
     this.collapseLongCommitList();
     this.setViewType(view);
 
     // We are in the Merge Requests page so we need another edit form for Changes tab
     if (getPagePath(1) === 'merge_requests') {
-      $('.note-edit-form').clone()
-        .addClass('mr-note-edit-form').insertAfter('.note-edit-form');
+      $('.note-edit-form')
+        .clone()
+        .addClass('mr-note-edit-form')
+        .insertAfter('.note-edit-form');
+    }
+
+    const hash = getLocationHash();
+    const $anchor = hash && document.getElementById(hash);
+
+    if ($anchor) {
+      this.loadLazyDiff({ currentTarget: $anchor });
     }
   }
 
@@ -106,58 +140,95 @@ export default class Notes {
   }
 
   addBinding() {
-    this.$wrapperEl = hasVueMRDiscussionsCookie() ? $(document).find('.diffs') : $(document);
-
     // Edit note link
     this.$wrapperEl.on('click', '.js-note-edit', this.showEditForm.bind(this));
     this.$wrapperEl.on('click', '.note-edit-cancel', this.cancelEdit);
     // Reopen and close actions for Issue/MR combined with note form submit
     this.$wrapperEl.on('click', '.js-comment-submit-button', this.postComment);
     this.$wrapperEl.on('click', '.js-comment-save-button', this.updateComment);
-    this.$wrapperEl.on('keyup input', '.js-note-text', this.updateTargetButtons);
+    this.$wrapperEl.on(
+      'keyup input',
+      '.js-note-text',
+      this.updateTargetButtons,
+    );
     // resolve a discussion
     this.$wrapperEl.on('click', '.js-comment-resolve-button', this.postComment);
     // remove a note (in general)
     this.$wrapperEl.on('click', '.js-note-delete', this.removeNote);
     // delete note attachment
-    this.$wrapperEl.on('click', '.js-note-attachment-delete', this.removeAttachment);
+    this.$wrapperEl.on(
+      'click',
+      '.js-note-attachment-delete',
+      this.removeAttachment,
+    );
     // reset main target form when clicking discard
     this.$wrapperEl.on('click', '.js-note-discard', this.resetMainTargetForm);
     // update the file name when an attachment is selected
-    this.$wrapperEl.on('change', '.js-note-attachment-input', this.updateFormAttachment);
+    this.$wrapperEl.on(
+      'change',
+      '.js-note-attachment-input',
+      this.updateFormAttachment,
+    );
     // reply to diff/discussion notes
-    this.$wrapperEl.on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
+    this.$wrapperEl.on(
+      'click',
+      '.js-discussion-reply-button',
+      this.onReplyToDiscussionNote,
+    );
     // add diff note
     this.$wrapperEl.on('click', '.js-add-diff-note-button', this.onAddDiffNote);
     // add diff note for images
-    this.$wrapperEl.on('click', '.js-add-image-diff-note-button', this.onAddImageDiffNote);
+    this.$wrapperEl.on(
+      'click',
+      '.js-add-image-diff-note-button',
+      this.onAddImageDiffNote,
+    );
     // hide diff note form
-    this.$wrapperEl.on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
+    this.$wrapperEl.on(
+      'click',
+      '.js-close-discussion-note-form',
+      this.cancelDiscussionForm,
+    );
     // toggle commit list
-    this.$wrapperEl.on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
+    this.$wrapperEl.on(
+      'click',
+      '.system-note-commit-list-toggler',
+      this.toggleCommitList,
+    );
+
+    this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff);
+    this.$wrapperEl.on('click', '.js-toggle-lazy-diff-retry-button', this.onClickRetryLazyLoad.bind(this));
+
     // fetch notes when tab becomes visible
     this.$wrapperEl.on('visibilitychange', this.visibilityChange);
     // when issue status changes, we need to refresh data
     this.$wrapperEl.on('issuable:change', this.refresh);
     // ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
     this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.addNote);
-    this.$wrapperEl.on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
-    this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
-    this.$wrapperEl.on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
+    this.$wrapperEl.on(
+      'ajax:success',
+      '.js-discussion-note-form',
+      this.addDiscussionNote,
+    );
+    this.$wrapperEl.on(
+      'ajax:success',
+      '.js-main-target-form',
+      this.resetMainTargetForm,
+    );
+    this.$wrapperEl.on(
+      'ajax:complete',
+      '.js-main-target-form',
+      this.reenableTargetFormSubmitButton,
+    );
     // when a key is clicked on the notes
     this.$wrapperEl.on('keydown', '.js-note-text', this.keydownNoteText);
     // When the URL fragment/hash has changed, `#note_xxx`
     $(window).on('hashchange', this.onHashChange);
     this.boundGetContent = this.getContent.bind(this);
     document.addEventListener('refreshLegacyNotes', this.boundGetContent);
-    this.eventsBound = true;
   }
 
   cleanBinding() {
-    if (!this.eventsBound) {
-      return;
-    }
-
     this.$wrapperEl.off('click', '.js-note-edit');
     this.$wrapperEl.off('click', '.note-edit-cancel');
     this.$wrapperEl.off('click', '.js-note-delete');
@@ -173,6 +244,8 @@ export default class Notes {
     this.$wrapperEl.off('keydown', '.js-note-text');
     this.$wrapperEl.off('click', '.js-comment-resolve-button');
     this.$wrapperEl.off('click', '.system-note-commit-list-toggler');
+    this.$wrapperEl.off('click', '.js-toggle-lazy-diff');
+    this.$wrapperEl.off('click', '.js-toggle-lazy-diff-retry-button');
     this.$wrapperEl.off('ajax:success', '.js-main-target-form');
     this.$wrapperEl.off('ajax:success', '.js-discussion-note-form');
     this.$wrapperEl.off('ajax:complete', '.js-main-target-form');
@@ -181,10 +254,16 @@ export default class Notes {
   }
 
   static initCommentTypeToggle(form) {
-    const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
-    const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
+    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 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');
 
@@ -201,7 +280,13 @@ export default class Notes {
   }
 
   keydownNoteText(e) {
-    var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
+    var $textarea,
+      discussionNoteForm,
+      editNote,
+      myLastNote,
+      myLastNoteEditBtn,
+      newText,
+      originalText;
     if (isMetaKey(e)) {
       return;
     }
@@ -213,7 +298,12 @@ export default class Notes {
         if ($textarea.val() !== '') {
           return;
         }
-        myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, .notes_holder, #notes'));
+        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]);
@@ -224,7 +314,9 @@ export default class Notes {
         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?')) {
+            if (
+              !confirm('Are you sure you want to cancel creating this comment?')
+            ) {
               return;
             }
           }
@@ -236,7 +328,9 @@ export default class Notes {
           originalText = $textarea.closest('form').data('originalNote');
           newText = $textarea.val();
           if (originalText !== newText) {
-            if (!confirm('Are you sure you want to cancel editing this comment?')) {
+            if (
+              !confirm('Are you sure you want to cancel editing this comment?')
+            ) {
               return;
             }
           }
@@ -249,11 +343,14 @@ export default class Notes {
     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,
+    ));
   }
 
   refresh() {
@@ -269,20 +366,23 @@ export default class Notes {
 
     this.refreshing = true;
 
-    axios.get(`${this.notes_url}?html=true`, {
-      headers: {
-        'X-Last-Fetched-At': this.last_fetched_at,
-      },
-    }).then(({ data }) => {
-      const notes = data.notes;
-      this.last_fetched_at = data.last_fetched_at;
-      this.setPollingInterval(data.notes.length);
-      $.each(notes, (i, note) => this.renderNote(note));
-
-      this.refreshing = false;
-    }).catch(() => {
-      this.refreshing = false;
-    });
+    axios
+      .get(`${this.notes_url}?html=true`, {
+        headers: {
+          'X-Last-Fetched-At': this.last_fetched_at,
+        },
+      })
+      .then(({ data }) => {
+        const notes = data.notes;
+        this.last_fetched_at = data.last_fetched_at;
+        this.setPollingInterval(data.notes.length);
+        $.each(notes, (i, note) => this.renderNote(note));
+
+        this.refreshing = false;
+      })
+      .catch(() => {
+        this.refreshing = false;
+      });
   }
 
   /**
@@ -298,7 +398,8 @@ export default class Notes {
     if (shouldReset == null) {
       shouldReset = true;
     }
-    nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
+    nthInterval =
+      this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
     if (shouldReset) {
       this.pollingInterval = this.basePollingInterval;
     } else if (this.pollingInterval < nthInterval) {
@@ -317,12 +418,17 @@ export default class Notes {
       if ('emoji_award' in noteEntity.commands_changes) {
         votesBlock = $('.js-awards-block').eq(0);
 
-        loadAwardsHandler().then((awardsHandler) => {
-          awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
-          awardsHandler.scrollToAwards();
-        }).catch(() => {
-          // ignore
-        });
+        loadAwardsHandler()
+          .then(awardsHandler => {
+            awardsHandler.addAwardToEmojiBar(
+              votesBlock,
+              noteEntity.commands_changes.emoji_award,
+            );
+            awardsHandler.scrollToAwards();
+          })
+          .catch(() => {
+            // ignore
+          });
       }
     }
   }
@@ -367,11 +473,17 @@ export default class Notes {
 
     if (!noteEntity.valid) {
       if (noteEntity.errors && noteEntity.errors.commands_only) {
-        if (noteEntity.commands_changes &&
-            Object.keys(noteEntity.commands_changes).length > 0) {
+        if (
+          noteEntity.commands_changes &&
+          Object.keys(noteEntity.commands_changes).length > 0
+        ) {
           $notesList.find('.system-note.being-posted').remove();
         }
-        this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline.get(0));
+        this.addFlash(
+          noteEntity.errors.commands_only,
+          'notice',
+          this.parentTimeline.get(0),
+        );
         this.refresh();
       }
       return;
@@ -393,28 +505,30 @@ export default class Notes {
       this.setupNewNote($newNote);
       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)) {
+    } else if (Notes.isUpdatedNote(noteEntity, $note)) {
+      // The server can send the same update multiple times so we need to make sure to only update once per actual update.
       const isEditing = $note.hasClass('is-editing');
       const initialContent = normalizeNewlines(
-        $note.find('.original-note-content').text().trim()
+        $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;
+      const isTextareaUntouched =
+        currentContent === initialContent ||
+        currentContent === sanitizedNoteNote;
 
       if (isEditing && isTextareaUntouched) {
         $textarea.val(noteEntity.note);
         this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
-      }
-      else if (isEditing && !isTextareaUntouched) {
+      } else if (isEditing && !isTextareaUntouched) {
         this.putConflictEditWarningInPlace(noteEntity, $note);
         this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
-      }
-      else {
+      } else {
         const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
         this.setupNewNote($updatedNote);
       }
@@ -438,17 +552,31 @@ export default class Notes {
     }
     this.note_ids.push(noteEntity.id);
 
-    form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
-    row = (form.length || !noteEntity.discussion_line_code) ? form.closest('tr') : $(`#${noteEntity.discussion_line_code}`);
+    form =
+      $form ||
+      $(
+        `.js-discussion-note-form[data-discussion-id="${
+          noteEntity.discussion_id
+        }"]`,
+      );
+    row =
+      form.length || !noteEntity.discussion_line_code
+        ? form.closest('tr')
+        : $(`#${noteEntity.discussion_line_code}`);
 
     if (noteEntity.on_image) {
       row = form;
     }
 
     lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
-    diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
+    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}"]`);
+    discussionContainer = $(
+      `.notes[data-discussion-id="${noteEntity.discussion_id}"]`,
+    );
     if (!discussionContainer.length) {
       discussionContainer = form.closest('.discussion').find('.notes');
     }
@@ -456,25 +584,42 @@ export default class Notes {
       if (noteEntity.diff_discussion_html) {
         var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
 
-        if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) {
+        if (
+          !this.isParallelView() ||
+          row.hasClass('js-temp-notes-holder') ||
+          noteEntity.on_image
+        ) {
           // 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());
+          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) {
+      if (
+        (page && page.indexOf('projects:merge_request') !== -1) ||
+        !noteEntity.diff_discussion_html
+      ) {
         if (!hasVueMRDiscussionsCookie()) {
-          Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
+          Notes.animateAppendNote(
+            noteEntity.discussion_html,
+            $('.main-notes-list'),
+          );
         }
       }
     } else {
@@ -482,7 +627,10 @@ export default class Notes {
       Notes.animateAppendNote(noteEntity.html, discussionContainer);
     }
 
-    if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
+    if (
+      typeof gl.diffNotesCompileComponents !== 'undefined' &&
+      noteEntity.discussion_resolvable
+    ) {
       gl.diffNotesCompileComponents();
 
       this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
@@ -494,7 +642,8 @@ export default class Notes {
   }
 
   getLineHolder(changesDiscussionContainer) {
-    return $(changesDiscussionContainer).closest('.notes_holder')
+    return $(changesDiscussionContainer)
+      .closest('.notes_holder')
       .prevAll('.line_holder')
       .first()
       .get(0);
@@ -527,8 +676,14 @@ export default class Notes {
     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();
+    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);
@@ -564,7 +719,10 @@ export default class Notes {
     form.find('#note_type').val('');
     form.find('#note_project_id').remove();
     form.find('#in_reply_to_discussion_id').remove();
-    form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
+    form
+      .find('.js-comment-resolve-button')
+      .closest('comment-and-resolve-btn')
+      .remove();
     this.parentTimeline = form.parents('.timeline');
 
     if (form.length) {
@@ -618,11 +776,17 @@ export default class Notes {
     } 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.get(0));
+    return this.addFlash(
+      'Your comment could not be submitted! Please check your network connection and try again.',
+      'alert',
+      formParentTimeline.get(0),
+    );
   }
 
   updateNoteError($parentTimeline) {
-    new Flash('Your comment could not be updated! Please check your network connection and try again.');
+    new Flash(
+      'Your comment could not be updated! Please check your network connection and try again.',
+    );
   }
 
   /**
@@ -671,14 +835,16 @@ export default class Notes {
   }
 
   checkContentToAllowEditing($el) {
-    var initialContent = $el.find('.original-note-content').text().trim();
+    var initialContent = $el
+      .find('.original-note-content')
+      .text()
+      .trim();
     var currentContent = $el.find('.js-note-text').val();
     var isAllowed = true;
 
     if (currentContent === initialContent) {
       this.removeNoteEditForm($el);
-    }
-    else {
+    } else {
       var $buttons = $el.find('.note-form-actions');
       var isWidgetVisible = isInViewport($el.get(0));
 
@@ -740,8 +906,7 @@ export default class Notes {
       this.setupNewNote($newNote);
       // Now that we have taken care of the update, clear it out
       delete this.updatedNotesTrackingMap[noteId];
-    }
-    else {
+    } else {
       $note.find('.js-finish-edit-warning').hide();
       this.removeNoteEditForm($note);
     }
@@ -774,7 +939,9 @@ export default class Notes {
     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('originalNote'));
+    return form
+      .find('.js-note-text')
+      .val(form.find('form.edit-note').data('originalNote'));
   }
 
   /**
@@ -788,58 +955,67 @@ export default class Notes {
     $note = $(e.currentTarget).closest('.note');
     noteElId = $note.attr('id');
     noteId = $note.attr('data-note-id');
-    lineHolder = $(e.currentTarget).closest('.notes[data-discussion-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');
-        const discussionId = $('.notes', $notes).data('discussionId');
-
-        if (typeof gl.diffNotesCompileComponents !== 'undefined') {
-          if (gl.diffNoteApps[noteElId]) {
-            gl.diffNoteApps[noteElId].$destroy();
+    $(`.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');
+          const discussionId = $('.notes', $notes).data('discussionId');
+
+          if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+            if (gl.diffNoteApps[noteElId]) {
+              gl.diffNoteApps[noteElId].$destroy();
+            }
           }
-        }
-
-        $note.remove();
-
-        // 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();
-
-          $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
-
-          // The notes tr can contain multiple lists of notes, like on the parallel diff
-          // notesTr does not exist for image diffs
-          if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
-            const $diffFile = $notes.closest('.diff-file');
-            if ($diffFile.length > 0) {
-              const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
-                detail: {
-                  // badgeNumber's start with 1 and index starts with 0
-                  badgeNumber: $notes.index() + 1,
-                },
-              });
 
-              $diffFile[0].dispatchEvent(removeBadgeEvent);
+          $note.remove();
+
+          // 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();
+
+            $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
+
+            // The notes tr can contain multiple lists of notes, like on the parallel diff
+            // notesTr does not exist for image diffs
+            if (
+              notesTr.find('.discussion-notes').length > 1 ||
+              notesTr.length === 0
+            ) {
+              const $diffFile = $notes.closest('.diff-file');
+              if ($diffFile.length > 0) {
+                const removeBadgeEvent = new CustomEvent(
+                  'removeBadge.imageDiff',
+                  {
+                    detail: {
+                      // badgeNumber's start with 1 and index starts with 0
+                      badgeNumber: $notes.index() + 1,
+                    },
+                  },
+                );
+
+                $diffFile[0].dispatchEvent(removeBadgeEvent);
+              }
+
+              $notes.remove();
+            } else if (notesTr.length > 0) {
+              notesTr.remove();
             }
-
-            $notes.remove();
-          } else if (notesTr.length > 0) {
-            notesTr.remove();
           }
-        }
-      };
-    })(this));
+        };
+      })(this),
+    );
 
     Notes.refreshVueNotes();
     Notes.checkMergeRequestStatus();
@@ -921,7 +1097,12 @@ export default class Notes {
     // 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('cancelText'));
+    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('cancelText'));
     form.find('.js-note-target-close').remove();
     form.find('.js-note-new-discussion').remove();
     this.setupNoteForm(form);
@@ -957,7 +1138,7 @@ export default class Notes {
     this.toggleDiffNote({
       target: $link,
       lineType: link.dataset.lineType,
-      showReplyInput
+      showReplyInput,
     });
   }
 
@@ -973,7 +1154,9 @@ export default class Notes {
 
     // Setup comment form
     let newForm;
-    const $noteContainer = $link.closest('.diff-viewer').find('.note-container');
+    const $noteContainer = $link
+      .closest('.diff-viewer')
+      .find('.note-container');
     const $form = $noteContainer.find('> .discussion-form');
 
     if ($form.length === 0) {
@@ -986,13 +1169,17 @@ export default class Notes {
     this.setupDiscussionNoteForm($link, newForm);
   }
 
-  toggleDiffNote({
-    target,
-    lineType,
-    forceShow,
-    showReplyInput = false,
-  }) {
-    var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
+  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();
@@ -1004,11 +1191,13 @@ export default class Notes {
     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>';
+    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>';
+      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);
@@ -1036,7 +1225,9 @@ export default class Notes {
       notesContent = targetRow.find(notesContentSelector);
       addForm = true;
     } else {
-      const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
+      const isCurrentlyShown = targetRow
+        .find('.content:not(:empty)')
+        .is(':visible');
       const isForced = forceShow === true || forceShow === false;
       const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
 
@@ -1063,11 +1254,12 @@ export default class Notes {
     row = form.closest('tr');
     glForm = form.data('glForm');
     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();
+      .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();
@@ -1108,7 +1300,9 @@ export default class Notes {
     var filename, form;
     form = $(this).closest('form');
     // get only the basename
-    filename = $(this).val().replace(/^.*[\\\/]/, '');
+    filename = $(this)
+      .val()
+      .replace(/^.*[\\\/]/, '');
     return form.find('.js-attachment-filename').text(filename);
   }
 
@@ -1180,12 +1374,16 @@ export default class Notes {
 
     this.glForm = new GLForm($editForm.find('form'), this.enableGFM);
 
-    $editForm.find('form')
+    $editForm
+      .find('form')
       .attr('action', `${postUrl}?html=true`)
       .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-note-text')
+      .focus()
+      .val(originalContent);
     $editForm.find('.js-md-write-button').trigger('click');
     $editForm.find('.referenced-users').hide();
   }
@@ -1194,7 +1392,9 @@ export default class Notes {
     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">
+        <a href="#note_${
+          noteEntity.id
+        }" target="_blank" rel="noopener noreferrer">
           updated comment
         </a>
         to ensure information is not lost
@@ -1204,14 +1404,93 @@ export default class Notes {
   }
 
   updateNotesCount(updateCount) {
-    return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
+    return this.notesCountBadge.text(
+      parseInt(this.notesCountBadge.text(), 10) + updateCount,
+    );
+  }
+
+  static renderPlaceholderComponent($container) {
+    const el = $container.find('.js-code-placeholder').get(0);
+    new Vue({
+      // eslint-disable-line no-new
+      el,
+      components: {
+        SkeletonLoadingContainer,
+      },
+      render(createElement) {
+        return createElement('skeleton-loading-container');
+      },
+    });
+  }
+
+  static renderDiffContent($container, data) {
+    const { discussion_html } = data;
+    const lines = $(discussion_html).find('.line_holder');
+    lines.addClass('fade-in');
+    $container.find('tbody').prepend(lines);
+    const fileHolder = $container.find('.file-holder');
+    $container.find('.line-holder-placeholder').remove();
+    syntaxHighlight(fileHolder);
+  }
+
+  onClickRetryLazyLoad(e) {
+    const $retryButton = $(e.currentTarget);
+
+    $retryButton.prop('disabled', true);
+
+    return this.loadLazyDiff(e)
+    .then(() => {
+      $retryButton.prop('disabled', false);
+    });
+  }
+
+  loadLazyDiff(e) {
+    const $container = $(e.currentTarget).closest('.js-toggle-container');
+    Notes.renderPlaceholderComponent($container);
+
+    $container.find('.js-toggle-lazy-diff').removeClass('js-toggle-lazy-diff');
+
+    const $tableEl = $container.find('tbody');
+    if ($tableEl.length === 0) return;
+
+    const fileHolder = $container.find('.file-holder');
+    const url = fileHolder.data('linesPath');
+
+    const $errorContainer = $container.find('.js-error-lazy-load-diff');
+    const $successContainer = $container.find('.js-success-lazy-load');
+
+    /**
+     * We only fetch resolved discussions.
+     * Unresolved discussions don't have an endpoint being provided.
+     */
+    if (url) {
+      return axios
+      .get(url)
+      .then(({ data }) => {
+        // Reset state in case last request returned error
+        $successContainer.removeClass('hidden');
+        $errorContainer.addClass('hidden');
+
+        Notes.renderDiffContent($container, data);
+      })
+      .catch(() => {
+        $successContainer.addClass('hidden');
+        $errorContainer.removeClass('hidden');
+      });
+    }
+    return Promise.resolve();
   }
 
   toggleCommitList(e) {
     const $element = $(e.currentTarget);
-    const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
+    const $closestSystemCommitList = $element.siblings(
+      '.system-note-commit-list',
+    );
 
-    $element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
+    $element
+      .find('.fa')
+      .toggleClass('fa-angle-down')
+      .toggleClass('fa-angle-up');
     $closestSystemCommitList.toggleClass('hide-shade');
   }
 
@@ -1221,11 +1500,17 @@ export default class Notes {
    * intrusive.
    */
   collapseLongCommitList() {
-    const systemNotes = $('#notes-list').find('li.system-note').has('ul');
+    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(':', '');
+      const headerMessage = $systemNote
+        .find('.note-text')
+        .find('p:first')
+        .text()
+        .replace(':', '');
 
       $systemNote.find('.note-header .system-note-message').html(headerMessage);
 
@@ -1233,7 +1518,9 @@ export default class Notes {
         $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');
+        $systemNote
+          .find('.note-text')
+          .addClass('system-note-commit-list hide-shade');
       }
     });
   }
@@ -1251,14 +1538,10 @@ export default class Notes {
 
   cleanForm($form) {
     // Remove JS classes that are not needed here
-    $form
-      .find('.js-comment-type-dropdown')
-      .removeClass('btn-group');
+    $form.find('.js-comment-type-dropdown').removeClass('btn-group');
 
     // Remove dropdown
-    $form
-      .find('.dropdown-menu')
-      .remove();
+    $form.find('.dropdown-menu').remove();
 
     return $form;
   }
@@ -1277,7 +1560,11 @@ export default class Notes {
     // 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()
+      $note
+        .find('.original-note-content')
+        .first()
+        .text()
+        .trim(),
     );
     return sanitizedNoteEntityText !== currentNoteText;
   }
@@ -1367,7 +1654,14 @@ export default class Notes {
    * 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 }) {
+  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">
@@ -1381,8 +1675,12 @@ export default class Notes {
                <div class="note-header">
                   <div class="note-header-info">
                      <a href="/${_.escape(currentUsername)}">
-                       <span class="hidden-xs">${_.escape(currentUsername)}</span>
-                       <span class="note-headline-light">${_.escape(currentUsername)}</span>
+                       <span class="hidden-xs">${_.escape(
+                         currentUsername,
+                       )}</span>
+                       <span class="note-headline-light">${_.escape(
+                         currentUsername,
+                       )}</span>
                      </a>
                   </div>
                </div>
@@ -1393,11 +1691,13 @@ export default class Notes {
                </div>
             </div>
          </div>
-      </li>`
+      </li>`,
     );
 
     $tempNote.find('.hidden-xs').text(_.escape(currentUserFullname));
-    $tempNote.find('.note-headline-light').text(`@${_.escape(currentUsername)}`);
+    $tempNote
+      .find('.note-headline-light')
+      .text(`@${_.escape(currentUsername)}`);
 
     return $tempNote;
   }
@@ -1413,7 +1713,7 @@ export default class Notes {
              <i>${formContent}</i>
            </div>
          </div>
-       </li>`
+       </li>`,
     );
 
     return $tempNote;
@@ -1443,13 +1743,25 @@ export default class Notes {
 
     // Get Form metadata
     const $submitBtn = $(e.target);
+    $submitBtn.prop('disabled', true);
     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 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, formContentOriginal } = this.getFormData($form);
+    const isDiscussionResolve = $submitBtn.hasClass(
+      'js-comment-resolve-button',
+    );
+    const {
+      formData,
+      formContent,
+      formAction,
+      formContentOriginal,
+    } = this.getFormData($form);
     let noteUniqueId;
     let systemNoteUniqueId;
     let hasQuickActions = false;
@@ -1466,7 +1778,6 @@ export default class Notes {
     // 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();
     }
 
@@ -1479,23 +1790,30 @@ export default class Notes {
     // 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,
-      }));
+      $notesContainer.append(
+        this.createPlaceholderNote({
+          formContent: tempFormContent,
+          uniqueId: noteUniqueId,
+          isDiscussionNote,
+          currentUsername: gon.current_username,
+          currentUserFullname: gon.current_user_fullname,
+          currentUserAvatar: gon.current_user_avatar_url,
+        }),
+      );
     }
 
     // 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,
-      }));
+      $notesContainer.append(
+        this.createPlaceholderSystemNote({
+          formContent: this.getQuickActionDescription(
+            formContent,
+            AjaxCache.get(gl.GfmAutoComplete.dataSources.commands),
+          ),
+          uniqueId: systemNoteUniqueId,
+        }),
+      );
     }
 
     // Clear the form textarea
@@ -1507,12 +1825,16 @@ export default class Notes {
       }
     }
 
+    $closeBtn.text($closeBtn.data('originalText'));
+
     /* eslint-disable promise/catch-or-return */
     // Make request to submit comment on server
-    axios.post(`${formAction}?html=true`, formData)
-      .then((res) => {
+    return axios
+      .post(`${formAction}?html=true`, formData)
+      .then(res => {
         const note = res.data;
 
+        $submitBtn.prop('disabled', false);
         // Submission successful! remove placeholder
         $notesContainer.find(`#${noteUniqueId}`).remove();
 
@@ -1527,7 +1849,9 @@ export default class Notes {
 
         // Reset cached commands list when command is applied
         if (hasQuickActions) {
-          $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
+          $form
+            .find('textarea.js-note-text')
+            .trigger('clear-commands-cache.atwho');
         }
 
         // Clear previous form errors
@@ -1572,11 +1896,14 @@ export default class Notes {
 
           // append flash-container to the Notes list
           if ($notesContainer.length) {
-            $notesContainer.append('<div class="flash-container" style="display: none;"></div>');
+            $notesContainer.append(
+              '<div class="flash-container" style="display: none;"></div>',
+            );
           }
 
           Notes.refreshVueNotes();
-        } else if (isMainForm) { // Check if this was main thread comment
+        } 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);
@@ -1587,10 +1914,11 @@ export default class Notes {
         }
 
         $form.trigger('ajax:success', [note]);
-      }).catch(() => {
+      })
+      .catch(() => {
         // Submission failed, remove placeholder note and show Flash error message
         $notesContainer.find(`#${noteUniqueId}`).remove();
-
+        $submitBtn.prop('disabled', false);
         const blurEvent = new CustomEvent('blur.imageDiff', {
           detail: e,
         });
@@ -1607,7 +1935,9 @@ export default class Notes {
 
         // Show form again on UI on failure
         if (isDiscussionForm && $notesContainer.length) {
-          const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
+          const replyButton = $notesContainer
+            .parent()
+            .find('.js-discussion-reply-button');
           this.replyToDiscussionNote(replyButton[0]);
           $form = $notesContainer.parent().find('form');
         }
@@ -1616,8 +1946,6 @@ export default class Notes {
         this.reenableTargetFormSubmitButton(e);
         this.addNoteError($form);
       });
-
-    return $closeBtn.text($closeBtn.data('originalText'));
   }
 
   /**
@@ -1652,12 +1980,19 @@ export default class Notes {
 
     // 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>');
+    $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
-    axios.post(`${formAction}?html=true`, formData)
+    axios
+      .post(`${formAction}?html=true`, formData)
       .then(({ data }) => {
         // Submission successful! render final note element
         this.updateNote(data, $editingNote);
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index b85c1a6ad72ef74954dfdecce609db26a2e6f96c..396a675b4ac5f3be749f8ef1c42284656a8df8c2 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -1,297 +1,315 @@
 <script>
-  import { mapActions, mapGetters } from 'vuex';
-  import _ from 'underscore';
-  import Autosize from 'autosize';
-  import { __, sprintf } from '~/locale';
-  import Flash from '../../flash';
-  import Autosave from '../../autosave';
-  import TaskList from '../../task_list';
-  import { capitalizeFirstCharacter, convertToCamelCase } from '../../lib/utils/text_utility';
-  import * as constants from '../constants';
-  import eventHub from '../event_hub';
-  import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
-  import markdownField from '../../vue_shared/components/markdown/field.vue';
-  import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-  import loadingButton from '../../vue_shared/components/loading_button.vue';
-  import noteSignedOutWidget from './note_signed_out_widget.vue';
-  import discussionLockedWidget from './discussion_locked_widget.vue';
-  import issuableStateMixin from '../mixins/issuable_state';
+import $ from 'jquery';
+import { mapActions, mapGetters, mapState } from 'vuex';
+import _ from 'underscore';
+import Autosize from 'autosize';
+import { __, sprintf } from '~/locale';
+import Flash from '../../flash';
+import Autosave from '../../autosave';
+import TaskList from '../../task_list';
+import {
+  capitalizeFirstCharacter,
+  convertToCamelCase,
+} from '../../lib/utils/text_utility';
+import * as constants from '../constants';
+import eventHub from '../event_hub';
+import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
+import markdownField from '../../vue_shared/components/markdown/field.vue';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import loadingButton from '../../vue_shared/components/loading_button.vue';
+import noteSignedOutWidget from './note_signed_out_widget.vue';
+import discussionLockedWidget from './discussion_locked_widget.vue';
+import issuableStateMixin from '../mixins/issuable_state';
 
-  export default {
-    name: 'CommentForm',
-    components: {
-      issueWarning,
-      noteSignedOutWidget,
-      discussionLockedWidget,
-      markdownField,
-      userAvatarLink,
-      loadingButton,
+export default {
+  name: 'CommentForm',
+  components: {
+    issueWarning,
+    noteSignedOutWidget,
+    discussionLockedWidget,
+    markdownField,
+    userAvatarLink,
+    loadingButton,
+  },
+  mixins: [issuableStateMixin],
+  props: {
+    noteableType: {
+      type: String,
+      required: true,
     },
-    mixins: [
-      issuableStateMixin,
-    ],
-    props: {
-      noteableType: {
-        type: String,
-        required: true,
-      },
+  },
+  data() {
+    return {
+      note: '',
+      noteType: constants.COMMENT,
+      isSubmitting: false,
+      isSubmitButtonDisabled: true,
+    };
+  },
+  computed: {
+    ...mapGetters([
+      'getCurrentUserLastNote',
+      'getUserData',
+      'getNoteableData',
+      'getNotesData',
+      'openState',
+    ]),
+    ...mapState(['isToggleStateButtonLoading']),
+    noteableDisplayName() {
+      return this.noteableType.replace(/_/g, ' ');
     },
-    data() {
-      return {
-        note: '',
-        noteType: constants.COMMENT,
-        isSubmitting: false,
-        isSubmitButtonDisabled: true,
-      };
+    isLoggedIn() {
+      return this.getUserData.id;
+    },
+    commentButtonTitle() {
+      return this.noteType === constants.COMMENT
+        ? 'Comment'
+        : 'Start discussion';
+    },
+    isOpen() {
+      return (
+        this.openState === constants.OPENED ||
+        this.openState === constants.REOPENED
+      );
     },
-    computed: {
-      ...mapGetters([
-        'getCurrentUserLastNote',
-        'getUserData',
-        'getNoteableData',
-        'getNotesData',
-        'openState',
-      ]),
-      noteableDisplayName() {
-        return this.noteableType.replace(/_/g, ' ');
-      },
-      isLoggedIn() {
-        return this.getUserData.id;
-      },
-      commentButtonTitle() {
-        return this.noteType === constants.COMMENT ? 'Comment' : 'Start discussion';
-      },
-      isOpen() {
-        return this.openState === constants.OPENED || this.openState === constants.REOPENED;
-      },
-      canCreateNote() {
-        return this.getNoteableData.current_user.can_create_note;
-      },
-      issueActionButtonTitle() {
-        const openOrClose = this.isOpen ? 'close' : 'reopen';
+    canCreateNote() {
+      return this.getNoteableData.current_user.can_create_note;
+    },
+    issueActionButtonTitle() {
+      const openOrClose = this.isOpen ? 'close' : 'reopen';
 
-        if (this.note.length) {
-          return sprintf(
-            __('%{actionText} & %{openOrClose} %{noteable}'),
-            {
-              actionText: this.commentButtonTitle,
-              openOrClose,
-              noteable: this.noteableDisplayName,
-            },
-          );
-        }
+      if (this.note.length) {
+        return sprintf(__('%{actionText} & %{openOrClose} %{noteable}'), {
+          actionText: this.commentButtonTitle,
+          openOrClose,
+          noteable: this.noteableDisplayName,
+        });
+      }
 
-        return sprintf(
-          __('%{openOrClose} %{noteable}'),
-          {
-            openOrClose: capitalizeFirstCharacter(openOrClose),
-            noteable: this.noteableDisplayName,
-          },
-        );
-      },
-      actionButtonClassNames() {
-        return {
-          'btn-reopen': !this.isOpen,
-          'btn-close': this.isOpen,
-          'js-note-target-close': this.isOpen,
-          'js-note-target-reopen': !this.isOpen,
-        };
-      },
-      markdownDocsPath() {
-        return this.getNotesData.markdownDocsPath;
-      },
-      quickActionsDocsPath() {
-        return this.getNotesData.quickActionsDocsPath;
-      },
-      markdownPreviewPath() {
-        return this.getNoteableData.preview_note_path;
-      },
-      author() {
-        return this.getUserData;
-      },
-      canUpdateIssue() {
-        return this.getNoteableData.current_user.can_update;
-      },
-      endpoint() {
-        return this.getNoteableData.create_note_path;
-      },
+      return sprintf(__('%{openOrClose} %{noteable}'), {
+        openOrClose: capitalizeFirstCharacter(openOrClose),
+        noteable: this.noteableDisplayName,
+      });
     },
-    watch: {
-      note(newNote) {
-        this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
-      },
-      isSubmitting(newValue) {
-        this.setIsSubmitButtonDisabled(this.note, newValue);
-      },
+    actionButtonClassNames() {
+      return {
+        'btn-reopen': !this.isOpen,
+        'btn-close': this.isOpen,
+        'js-note-target-close': this.isOpen,
+        'js-note-target-reopen': !this.isOpen,
+      };
     },
-    mounted() {
-      // jQuery is needed here because it is a custom event being dispatched with jQuery.
-      $(document).on('issuable:change', (e, isClosed) => {
-        this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED);
-      });
+    supportQuickActions() {
+      // Disable quick actions support for Epics
+      return this.noteableType !== constants.EPIC_NOTEABLE_TYPE;
+    },
+    markdownDocsPath() {
+      return this.getNotesData.markdownDocsPath;
+    },
+    quickActionsDocsPath() {
+      return this.getNotesData.quickActionsDocsPath;
+    },
+    markdownPreviewPath() {
+      return this.getNoteableData.preview_note_path;
+    },
+    author() {
+      return this.getUserData;
+    },
+    canUpdateIssue() {
+      return this.getNoteableData.current_user.can_update;
+    },
+    endpoint() {
+      return this.getNoteableData.create_note_path;
+    },
+  },
+  watch: {
+    note(newNote) {
+      this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
+    },
+    isSubmitting(newValue) {
+      this.setIsSubmitButtonDisabled(this.note, newValue);
+    },
+  },
+  mounted() {
+    // jQuery is needed here because it is a custom event being dispatched with jQuery.
+    $(document).on('issuable:change', (e, isClosed) => {
+      this.toggleIssueLocalState(
+        isClosed ? constants.CLOSED : constants.REOPENED,
+      );
+    });
 
-      this.initAutoSave();
-      this.initTaskList();
+    this.initAutoSave();
+    this.initTaskList();
+  },
+  methods: {
+    ...mapActions([
+      'saveNote',
+      'stopPolling',
+      'restartPolling',
+      'removePlaceholderNotes',
+      'closeIssue',
+      'reopenIssue',
+      'toggleIssueLocalState',
+      'toggleStateButtonLoading',
+    ]),
+    setIsSubmitButtonDisabled(note, isSubmitting) {
+      if (!_.isEmpty(note) && !isSubmitting) {
+        this.isSubmitButtonDisabled = false;
+      } else {
+        this.isSubmitButtonDisabled = true;
+      }
     },
-    methods: {
-      ...mapActions([
-        'saveNote',
-        'stopPolling',
-        'restartPolling',
-        'removePlaceholderNotes',
-        'closeIssue',
-        'reopenIssue',
-        'toggleIssueLocalState',
-      ]),
-      setIsSubmitButtonDisabled(note, isSubmitting) {
-        if (!_.isEmpty(note) && !isSubmitting) {
-          this.isSubmitButtonDisabled = false;
-        } else {
-          this.isSubmitButtonDisabled = true;
-        }
-      },
-      handleSave(withIssueAction) {
-        this.isSubmitting = true;
+    handleSave(withIssueAction) {
+      this.isSubmitting = true;
 
-        if (this.note.length) {
-          const noteData = {
-            endpoint: this.endpoint,
-            flashContainer: this.$el,
-            data: {
-              note: {
-                noteable_type: this.noteableType,
-                noteable_id: this.getNoteableData.id,
-                note: this.note,
-              },
+      if (this.note.length) {
+        const noteData = {
+          endpoint: this.endpoint,
+          flashContainer: this.$el,
+          data: {
+            note: {
+              noteable_type: this.noteableType,
+              noteable_id: this.getNoteableData.id,
+              note: this.note,
             },
-          };
+          },
+        };
 
-          if (this.noteType === constants.DISCUSSION) {
-            noteData.data.note.type = constants.DISCUSSION_NOTE;
-          }
-          this.note = ''; // Empty textarea while being requested. Repopulate in catch
-          this.resizeTextarea();
-          this.stopPolling();
+        if (this.noteType === constants.DISCUSSION) {
+          noteData.data.note.type = constants.DISCUSSION_NOTE;
+        }
 
-          this.saveNote(noteData)
-            .then((res) => {
-              this.isSubmitting = false;
-              this.restartPolling();
+        this.note = ''; // Empty textarea while being requested. Repopulate in catch
+        this.resizeTextarea();
+        this.stopPolling();
 
-              if (res.errors) {
-                if (res.errors.commands_only) {
-                  this.discard();
-                } else {
-                  Flash(
-                    'Something went wrong while adding your comment. Please try again.',
-                    'alert',
-                    this.$refs.commentForm,
-                  );
-                }
-              } else {
+        this.saveNote(noteData)
+          .then(res => {
+            this.enableButton();
+            this.restartPolling();
+
+            if (res.errors) {
+              if (res.errors.commands_only) {
                 this.discard();
+              } else {
+                Flash(
+                  'Something went wrong while adding your comment. Please try again.',
+                  'alert',
+                  this.$refs.commentForm,
+                );
               }
+            } else {
+              this.discard();
+            }
 
-              if (withIssueAction) {
-                this.toggleIssueState();
-              }
-            })
-            .catch(() => {
-              this.isSubmitting = false;
-              this.discard(false);
-              const msg =
-                `Your comment could not be submitted!
+            if (withIssueAction) {
+              this.toggleIssueState();
+            }
+          })
+          .catch(() => {
+            this.enableButton();
+            this.discard(false);
+            const msg = `Your comment could not be submitted!
 Please check your network connection and try again.`;
-              Flash(msg, 'alert', this.$el);
-              this.note = noteData.data.note.note; // Restore textarea content.
-              this.removePlaceholderNotes();
-            });
-        } else {
-          this.toggleIssueState();
-        }
-      },
-      enableButton() {
-        this.isSubmitting = false;
-      },
-      toggleIssueState() {
-        if (this.isOpen) {
-          this.closeIssue()
-            .then(() => this.enableButton())
-            .catch(() => {
-              this.enableButton();
-              Flash(
-                sprintf(
-                  __('Something went wrong while closing the %{issuable}. Please try again later'),
-                  { issuable: this.noteableDisplayName },
+            Flash(msg, 'alert', this.$el);
+            this.note = noteData.data.note.note; // Restore textarea content.
+            this.removePlaceholderNotes();
+          });
+      } else {
+        this.toggleIssueState();
+      }
+    },
+    enableButton() {
+      this.isSubmitting = false;
+    },
+    toggleIssueState() {
+      if (this.isOpen) {
+        this.closeIssue()
+          .then(() => this.enableButton())
+          .catch(() => {
+            this.enableButton();
+            this.toggleStateButtonLoading(false);
+            Flash(
+              sprintf(
+                __(
+                  'Something went wrong while closing the %{issuable}. Please try again later',
                 ),
-              );
-            });
-        } else {
-          this.reopenIssue()
-            .then(() => this.enableButton())
-            .catch(() => {
-              this.enableButton();
-              Flash(
-                sprintf(
-                  __('Something went wrong while reopening the %{issuable}. Please try again later'),
-                  { issuable: this.noteableDisplayName },
+                { issuable: this.noteableDisplayName },
+              ),
+            );
+          });
+      } else {
+        this.reopenIssue()
+          .then(() => this.enableButton())
+          .catch(() => {
+            this.enableButton();
+            this.toggleStateButtonLoading(false);
+            Flash(
+              sprintf(
+                __(
+                  'Something went wrong while reopening the %{issuable}. Please try again later',
                 ),
-              );
-            });
-        }
-      },
-      discard(shouldClear = true) {
-        // `blur` is needed to clear slash commands autocomplete cache if event fired.
-        // `focus` is needed to remain cursor in the textarea.
-        this.$refs.textarea.blur();
-        this.$refs.textarea.focus();
+                { issuable: this.noteableDisplayName },
+              ),
+            );
+          });
+      }
+    },
+    discard(shouldClear = true) {
+      // `blur` is needed to clear slash commands autocomplete cache if event fired.
+      // `focus` is needed to remain cursor in the textarea.
+      this.$refs.textarea.blur();
+      this.$refs.textarea.focus();
 
-        if (shouldClear) {
-          this.note = '';
-          this.resizeTextarea();
-          this.$refs.markdownField.previewMarkdown = false;
-        }
+      if (shouldClear) {
+        this.note = '';
+        this.resizeTextarea();
+        this.$refs.markdownField.previewMarkdown = false;
+      }
 
-        this.autosave.reset();
-      },
-      setNoteType(type) {
-        this.noteType = type;
-      },
-      editCurrentUserLastNote() {
-        if (this.note === '') {
-          const lastNote = this.getCurrentUserLastNote;
+      this.autosave.reset();
+    },
+    setNoteType(type) {
+      this.noteType = type;
+    },
+    editCurrentUserLastNote() {
+      if (this.note === '') {
+        const lastNote = this.getCurrentUserLastNote;
 
-          if (lastNote) {
-            eventHub.$emit('enterEditMode', {
-              noteId: lastNote.id,
-            });
-          }
+        if (lastNote) {
+          eventHub.$emit('enterEditMode', {
+            noteId: lastNote.id,
+          });
         }
-      },
-      initAutoSave() {
-        if (this.isLoggedIn) {
-          const noteableType = capitalizeFirstCharacter(convertToCamelCase(this.noteableType));
+      }
+    },
+    initAutoSave() {
+      if (this.isLoggedIn) {
+        const noteableType = capitalizeFirstCharacter(
+          convertToCamelCase(this.noteableType),
+        );
 
-          this.autosave = new Autosave(
-            $(this.$refs.textarea),
-            ['Note', noteableType, this.getNoteableData.id],
-          );
-        }
-      },
-      initTaskList() {
-        return new TaskList({
-          dataType: 'note',
-          fieldName: 'note',
-          selector: '.notes',
-        });
-      },
-      resizeTextarea() {
-        this.$nextTick(() => {
-          Autosize.update(this.$refs.textarea);
-        });
-      },
+        this.autosave = new Autosave($(this.$refs.textarea), [
+          'Note',
+          noteableType,
+          this.getNoteableData.id,
+        ]);
+      }
+    },
+    initTaskList() {
+      return new TaskList({
+        dataType: 'note',
+        fieldName: 'note',
+        selector: '.notes',
+      });
     },
-  };
+    resizeTextarea() {
+      this.$nextTick(() => {
+        Autosize.update(this.$refs.textarea);
+      });
+    },
+  },
+};
 </script>
 
 <template>
@@ -299,10 +317,10 @@ Please check your network connection and try again.`;
     <note-signed-out-widget v-if="!isLoggedIn" />
     <discussion-locked-widget
       issuable-type="issue"
-      v-else-if="!canCreateNote"
+      v-else-if="isLocked(getNoteableData) && !canCreateNote"
     />
     <ul
-      v-else
+      v-else-if="canCreateNote"
       class="notes notes-form timeline">
       <li class="timeline-entry">
         <div class="timeline-entry-inner">
@@ -341,7 +359,7 @@ Please check your network connection and try again.`;
                   name="note[note]"
                   class="note-textarea js-vue-comment-form
 js-gfm-input js-autosize markdown-area js-vue-textarea"
-                  data-supports-quick-actions="true"
+                  :data-supports-quick-actions="supportQuickActions"
                   aria-label="Description"
                   v-model="note"
                   ref="textarea"
@@ -418,13 +436,13 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
 
                 <loading-button
                   v-if="canUpdateIssue"
-                  :loading="isSubmitting"
+                  :loading="isToggleStateButtonLoading"
                   @click="handleSave(true)"
                   :container-class="[
                     actionButtonClassNames,
                     'btn btn-comment btn-comment-and-close js-action-button'
                   ]"
-                  :disabled="isSubmitting"
+                  :disabled="isToggleStateButtonLoading || isSubmitting"
                   :label="issueActionButtonTitle"
                 />
 
diff --git a/app/assets/javascripts/notes/components/diff_file_header.vue b/app/assets/javascripts/notes/components/diff_file_header.vue
index fe5baa3537f867f78617c34d2f43d4d0f0f2b747..94d9dc699640e9e75d4c56745087b9f367bcd20f 100644
--- a/app/assets/javascripts/notes/components/diff_file_header.vue
+++ b/app/assets/javascripts/notes/components/diff_file_header.vue
@@ -1,24 +1,24 @@
 <script>
-  import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-  import Icon from '~/vue_shared/components/icon.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
 
-  export default {
-    components: {
-      ClipboardButton,
-      Icon,
+export default {
+  components: {
+    ClipboardButton,
+    Icon,
+  },
+  props: {
+    diffFile: {
+      type: Object,
+      required: true,
     },
-    props: {
-      diffFile: {
-        type: Object,
-        required: true,
-      },
+  },
+  computed: {
+    titleTag() {
+      return this.diffFile.discussionPath ? 'a' : 'span';
     },
-    computed: {
-      titleTag() {
-        return this.diffFile.discussionPath ? 'a' : 'span';
-      },
-    },
-  };
+  },
+};
 </script>
 
 <template>
@@ -35,6 +35,7 @@
         <clipboard-button
           title="Copy file path to clipboard"
           :text="diffFile.submoduleLink"
+          css-class="btn-default btn-transparent btn-clipboard"
         />
       </span>
     </div>
@@ -79,6 +80,7 @@
       <clipboard-button
         title="Copy file path to clipboard"
         :text="diffFile.filePath"
+        css-class="btn-default btn-transparent btn-clipboard"
       />
 
       <small
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 75a32709ad5953310d99075e71c886980c1fe5fa..ee01ec85bbbf9c075c99bcec7611145e8990310e 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -1,55 +1,60 @@
 <script>
-  import syntaxHighlight from '~/syntax_highlight';
-  import imageDiffHelper from '~/image_diff/helpers/index';
-  import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-  import DiffFileHeader from './diff_file_header.vue';
+import $ from 'jquery';
+import syntaxHighlight from '~/syntax_highlight';
+import imageDiffHelper from '~/image_diff/helpers/index';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import DiffFileHeader from './diff_file_header.vue';
 
-  export default {
-    components: {
-      DiffFileHeader,
+export default {
+  components: {
+    DiffFileHeader,
+  },
+  props: {
+    discussion: {
+      type: Object,
+      required: true,
     },
-    props: {
-      discussion: {
-        type: Object,
-        required: true,
-      },
+  },
+  computed: {
+    isImageDiff() {
+      return !this.diffFile.text;
     },
-    computed: {
-      isImageDiff() {
-        return !this.diffFile.text;
-      },
-      diffFileClass() {
-        const { text } = this.diffFile;
-        return text ? 'text-file' : 'js-image-file';
-      },
-      diffRows() {
-        return $(this.discussion.truncatedDiffLines);
-      },
-      diffFile() {
-        return convertObjectPropsToCamelCase(this.discussion.diffFile);
-      },
-      imageDiffHtml() {
-        return this.discussion.imageDiffHtml;
-      },
+    diffFileClass() {
+      const { text } = this.diffFile;
+      return text ? 'text-file' : 'js-image-file';
     },
-    mounted() {
-      if (this.isImageDiff) {
-        const canCreateNote = false;
-        const renderCommentBadge = true;
-        imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
-      } else {
-        const fileHolder = $(this.$refs.fileHolder);
-        this.$nextTick(() => {
-          syntaxHighlight(fileHolder);
-        });
-      }
+    diffRows() {
+      return $(this.discussion.truncatedDiffLines);
     },
-    methods: {
-      rowTag(html) {
-        return html.outerHTML ? 'tr' : 'template';
-      },
+    diffFile() {
+      return convertObjectPropsToCamelCase(this.discussion.diffFile);
     },
-  };
+    imageDiffHtml() {
+      return this.discussion.imageDiffHtml;
+    },
+  },
+  mounted() {
+    if (this.isImageDiff) {
+      const canCreateNote = false;
+      const renderCommentBadge = true;
+      imageDiffHelper.initImageDiff(
+        this.$refs.fileHolder,
+        canCreateNote,
+        renderCommentBadge,
+      );
+    } else {
+      const fileHolder = $(this.$refs.fileHolder);
+      this.$nextTick(() => {
+        syntaxHighlight(fileHolder);
+      });
+    }
+  },
+  methods: {
+    rowTag(html) {
+      return html.outerHTML ? 'tr' : 'template';
+    },
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 0158f58b56996f160e7a2a13a47671dad71c0194..d492d1cd001a5b78c226fe44caec0486877a6eee 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,67 +1,69 @@
 <script>
-  import { mapGetters } from 'vuex';
-  import resolveSvg from 'icons/_icon_resolve_discussion.svg';
-  import resolvedSvg from 'icons/_icon_status_success_solid.svg';
-  import mrIssueSvg from 'icons/_icon_mr_issue.svg';
-  import nextDiscussionSvg from 'icons/_next_discussion.svg';
-  import { pluralize } from '../../lib/utils/text_utility';
-  import { scrollToElement } from '../../lib/utils/common_utils';
-  import tooltip from '../../vue_shared/directives/tooltip';
+import { mapGetters } from 'vuex';
+import resolveSvg from 'icons/_icon_resolve_discussion.svg';
+import resolvedSvg from 'icons/_icon_status_success_solid.svg';
+import mrIssueSvg from 'icons/_icon_mr_issue.svg';
+import nextDiscussionSvg from 'icons/_next_discussion.svg';
+import { pluralize } from '../../lib/utils/text_utility';
+import { scrollToElement } from '../../lib/utils/common_utils';
+import tooltip from '../../vue_shared/directives/tooltip';
 
-  export default {
-    directives: {
-      tooltip,
+export default {
+  directives: {
+    tooltip,
+  },
+  computed: {
+    ...mapGetters([
+      'getUserData',
+      'getNoteableData',
+      'discussionCount',
+      'unresolvedDiscussions',
+      'resolvedDiscussionCount',
+    ]),
+    isLoggedIn() {
+      return this.getUserData.id;
     },
-    computed: {
-      ...mapGetters([
-        'getUserData',
-        'getNoteableData',
-        'discussionCount',
-        'unresolvedDiscussions',
-        'resolvedDiscussionCount',
-      ]),
-      isLoggedIn() {
-        return this.getUserData.id;
-      },
-      hasNextButton() {
-        return this.isLoggedIn && !this.allResolved;
-      },
-      countText() {
-        return pluralize('discussion', this.discussionCount);
-      },
-      allResolved() {
-        return this.resolvedDiscussionCount === this.discussionCount;
-      },
-      resolveAllDiscussionsIssuePath() {
-        return this.getNoteableData.create_issue_to_resolve_discussions_path;
-      },
-      firstUnresolvedDiscussionId() {
-        const item = this.unresolvedDiscussions[0] || {};
-
-        return item.id;
-      },
+    hasNextButton() {
+      return this.isLoggedIn && !this.allResolved;
+    },
+    countText() {
+      return pluralize('discussion', this.discussionCount);
+    },
+    allResolved() {
+      return this.resolvedDiscussionCount === this.discussionCount;
     },
-    created() {
-      this.resolveSvg = resolveSvg;
-      this.resolvedSvg = resolvedSvg;
-      this.mrIssueSvg = mrIssueSvg;
-      this.nextDiscussionSvg = nextDiscussionSvg;
+    resolveAllDiscussionsIssuePath() {
+      return this.getNoteableData.create_issue_to_resolve_discussions_path;
+    },
+    firstUnresolvedDiscussionId() {
+      const item = this.unresolvedDiscussions[0] || {};
+
+      return item.id;
     },
-    methods: {
-      jumpToFirstDiscussion() {
-        const el = document.querySelector(`[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`);
-        const activeTab = window.mrTabs.currentAction;
+  },
+  created() {
+    this.resolveSvg = resolveSvg;
+    this.resolvedSvg = resolvedSvg;
+    this.mrIssueSvg = mrIssueSvg;
+    this.nextDiscussionSvg = nextDiscussionSvg;
+  },
+  methods: {
+    jumpToFirstDiscussion() {
+      const el = document.querySelector(
+        `[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`,
+      );
+      const activeTab = window.mrTabs.currentAction;
 
-        if (activeTab === 'commits' || activeTab === 'pipelines') {
-          window.mrTabs.activateTab('show');
-        }
+      if (activeTab === 'commits' || activeTab === 'pipelines') {
+        window.mrTabs.activateTab('show');
+      }
 
-        if (el) {
-          scrollToElement(el);
-        }
-      },
+      if (el) {
+        scrollToElement(el);
+      }
     },
-  };
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/discussion_locked_widget.vue b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
index fc0722042ccd826eeaa2fe8e68f03330db68c8d0..13283b187d1801f325333b02de7466810c9b4160 100644
--- a/app/assets/javascripts/notes/components/discussion_locked_widget.vue
+++ b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
@@ -1,15 +1,13 @@
 <script>
-  import Icon from '~/vue_shared/components/icon.vue';
-  import Issuable from '~/vue_shared/mixins/issuable';
+import Icon from '~/vue_shared/components/icon.vue';
+import Issuable from '~/vue_shared/mixins/issuable';
 
-  export default {
-    components: {
-      Icon,
-    },
-    mixins: [
-      Issuable,
-    ],
-  };
+export default {
+  components: {
+    Icon,
+  },
+  mixins: [Issuable],
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index c26aa6fa15db7136f003135a33813af2732fe72c..626b0799581b1ee4c4120dafc057de7109664fe8 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,121 +1,120 @@
 <script>
-  import { mapGetters } from 'vuex';
-  import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
-  import emojiSmile from 'icons/_emoji_smile.svg';
-  import emojiSmiley from 'icons/_emoji_smiley.svg';
-  import editSvg from 'icons/_icon_pencil.svg';
-  import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
-  import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
-  import ellipsisSvg from 'icons/_ellipsis_v.svg';
-  import loadingIcon from '~/vue_shared/components/loading_icon.vue';
-  import tooltip from '~/vue_shared/directives/tooltip';
+import { mapGetters } from 'vuex';
+import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
+import emojiSmile from 'icons/_emoji_smile.svg';
+import emojiSmiley from 'icons/_emoji_smiley.svg';
+import editSvg from 'icons/_icon_pencil.svg';
+import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
+import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
+import ellipsisSvg from 'icons/_ellipsis_v.svg';
+import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
 
-  export default {
-    name: 'NoteActions',
-    directives: {
-      tooltip,
-    },
-    components: {
-      loadingIcon,
-    },
-    props: {
-      authorId: {
-        type: Number,
-        required: true,
-      },
-      noteId: {
-        type: Number,
-        required: true,
-      },
-      accessLevel: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      reportAbusePath: {
-        type: String,
-        required: true,
-      },
-      canEdit: {
-        type: Boolean,
-        required: true,
-      },
-      canDelete: {
-        type: Boolean,
-        required: true,
-      },
-      resolvable: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
-      isResolved: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
-      isResolving: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
-      resolvedBy: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
-      canReportAsAbuse: {
-        type: Boolean,
-        required: true,
-      },
-    },
-    computed: {
-      ...mapGetters([
-        'getUserDataByProp',
-      ]),
-      shouldShowActionsDropdown() {
-        return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
-      },
-      canAddAwardEmoji() {
-        return this.currentUserId;
-      },
-      isAuthoredByCurrentUser() {
-        return this.authorId === this.currentUserId;
-      },
-      currentUserId() {
-        return this.getUserDataByProp('id');
-      },
-      resolveButtonTitle() {
-        let title = 'Mark as resolved';
+export default {
+  name: 'NoteActions',
+  directives: {
+    tooltip,
+  },
+  components: {
+    loadingIcon,
+  },
+  props: {
+    authorId: {
+      type: Number,
+      required: true,
+    },
+    noteId: {
+      type: Number,
+      required: true,
+    },
+    accessLevel: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    reportAbusePath: {
+      type: String,
+      required: true,
+    },
+    canEdit: {
+      type: Boolean,
+      required: true,
+    },
+    canAwardEmoji: {
+      type: Boolean,
+      required: true,
+    },
+    canDelete: {
+      type: Boolean,
+      required: true,
+    },
+    resolvable: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    isResolved: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    isResolving: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    resolvedBy: {
+      type: Object,
+      required: false,
+      default: () => ({}),
+    },
+    canReportAsAbuse: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  computed: {
+    ...mapGetters(['getUserDataByProp']),
+    shouldShowActionsDropdown() {
+      return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
+    },
+    isAuthoredByCurrentUser() {
+      return this.authorId === this.currentUserId;
+    },
+    currentUserId() {
+      return this.getUserDataByProp('id');
+    },
+    resolveButtonTitle() {
+      let title = 'Mark as resolved';
 
-        if (this.resolvedBy) {
-          title = `Resolved by ${this.resolvedBy.name}`;
-        }
+      if (this.resolvedBy) {
+        title = `Resolved by ${this.resolvedBy.name}`;
+      }
 
-        return title;
-      },
-    },
-    created() {
-      this.emojiSmiling = emojiSmiling;
-      this.emojiSmile = emojiSmile;
-      this.emojiSmiley = emojiSmiley;
-      this.editSvg = editSvg;
-      this.ellipsisSvg = ellipsisSvg;
-      this.resolveDiscussionSvg = resolveDiscussionSvg;
-      this.resolvedDiscussionSvg = resolvedDiscussionSvg;
-    },
-    methods: {
-      onEdit() {
-        this.$emit('handleEdit');
-      },
-      onDelete() {
-        this.$emit('handleDelete');
-      },
-      onResolve() {
-        this.$emit('handleResolve');
-      },
-    },
-  };
+      return title;
+    },
+  },
+  created() {
+    this.emojiSmiling = emojiSmiling;
+    this.emojiSmile = emojiSmile;
+    this.emojiSmiley = emojiSmiley;
+    this.editSvg = editSvg;
+    this.ellipsisSvg = ellipsisSvg;
+    this.resolveDiscussionSvg = resolveDiscussionSvg;
+    this.resolvedDiscussionSvg = resolvedDiscussionSvg;
+  },
+  methods: {
+    onEdit() {
+      this.$emit('handleEdit');
+    },
+    onDelete() {
+      this.$emit('handleDelete');
+    },
+    onResolve() {
+      this.$emit('handleResolve');
+    },
+  },
+};
 </script>
 
 <template>
@@ -151,7 +150,7 @@
       </button>
     </div>
     <div
-      v-if="canAddAwardEmoji"
+      v-if="canAwardEmoji"
       class="note-actions-item">
       <a
         v-tooltip
diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue
index 618b807b9cc540b92798ce3d7edb51b428a97492..34ecbd00c63a88b3f480968f4eb5011e2f5e5037 100644
--- a/app/assets/javascripts/notes/components/note_attachment.vue
+++ b/app/assets/javascripts/notes/components/note_attachment.vue
@@ -1,13 +1,13 @@
 <script>
-  export default {
-    name: 'NoteAttachment',
-    props: {
-      attachment: {
-        type: Object,
-        required: true,
-      },
+export default {
+  name: 'NoteAttachment',
+  props: {
+    attachment: {
+      type: Object,
+      required: true,
     },
-  };
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index caa9701e03f4b859c01d2b266ec433ff7f9a3e61..e8fd155a1ee18136fc3b8a7f4c80c44aa7b304fc 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -1,179 +1,193 @@
 <script>
-  import { mapActions, mapGetters } from 'vuex';
-  import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
-  import emojiSmile from 'icons/_emoji_smile.svg';
-  import emojiSmiley from 'icons/_emoji_smiley.svg';
-  import Flash from '../../flash';
-  import { glEmojiTag } from '../../emoji';
-  import tooltip from '../../vue_shared/directives/tooltip';
-
-  export default {
-    directives: {
-      tooltip,
+import { mapActions, mapGetters } from 'vuex';
+import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
+import emojiSmile from 'icons/_emoji_smile.svg';
+import emojiSmiley from 'icons/_emoji_smiley.svg';
+import Flash from '../../flash';
+import { glEmojiTag } from '../../emoji';
+import tooltip from '../../vue_shared/directives/tooltip';
+
+export default {
+  directives: {
+    tooltip,
+  },
+  props: {
+    awards: {
+      type: Array,
+      required: true,
     },
-    props: {
-      awards: {
-        type: Array,
-        required: true,
-      },
-      toggleAwardPath: {
-        type: String,
-        required: true,
-      },
-      noteAuthorId: {
-        type: Number,
-        required: true,
-      },
-      noteId: {
-        type: Number,
-        required: true,
-      },
+    toggleAwardPath: {
+      type: String,
+      required: true,
     },
-    computed: {
-      ...mapGetters([
-        'getUserData',
-      ]),
-      // `this.awards` is an array with emojis but they are not grouped by emoji name. See below.
-      // [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ]
-      // This method will group emojis by their name as an Object. See below.
-      // {
-      //   foo: [ { name: foo, user: user1 }, { name: foo, user: user2 } ],
-      //   bar: [ { name: bar, user: user1 } ]
-      // }
-      // We need to do this otherwise we will render the same emoji over and over again.
-      groupedAwards() {
-        const awards = this.awards.reduce((acc, award) => {
-          if (Object.prototype.hasOwnProperty.call(acc, award.name)) {
-            acc[award.name].push(award);
-          } else {
-            Object.assign(acc, { [award.name]: [award] });
-          }
-
-          return acc;
-        }, {});
-
-        const orderedAwards = {};
-        const { thumbsdown, thumbsup } = awards;
-        // Always show thumbsup and thumbsdown first
-        if (thumbsup) {
-          orderedAwards.thumbsup = thumbsup;
-          delete awards.thumbsup;
-        }
-        if (thumbsdown) {
-          orderedAwards.thumbsdown = thumbsdown;
-          delete awards.thumbsdown;
-        }
-
-        return Object.assign({}, orderedAwards, awards);
-      },
-      isAuthoredByMe() {
-        return this.noteAuthorId === this.getUserData.id;
-      },
-      isLoggedIn() {
-        return this.getUserData.id;
-      },
+    noteAuthorId: {
+      type: Number,
+      required: true,
     },
-    created() {
-      this.emojiSmiling = emojiSmiling;
-      this.emojiSmile = emojiSmile;
-      this.emojiSmiley = emojiSmiley;
+    noteId: {
+      type: Number,
+      required: true,
     },
-    methods: {
-      ...mapActions([
-        'toggleAwardRequest',
-      ]),
-      getAwardHTML(name) {
-        return glEmojiTag(name);
-      },
-      getAwardClassBindings(awardList, awardName) {
-        return {
-          active: this.hasReactionByCurrentUser(awardList),
-          disabled: !this.canInteractWithEmoji(awardList, awardName),
-        };
-      },
-      canInteractWithEmoji(awardList, awardName) {
-        let isAllowed = true;
-        const restrictedEmojis = ['thumbsup', 'thumbsdown'];
-
-        // Users can not add :+1: and :-1: to their own notes
-        if (this.getUserData.id === this.noteAuthorId && restrictedEmojis.indexOf(awardName) > -1) {
-          isAllowed = false;
-        }
-
-        return this.getUserData.id && isAllowed;
-      },
-      hasReactionByCurrentUser(awardList) {
-        return awardList.filter(award => award.user.id === this.getUserData.id).length;
-      },
-      awardTitle(awardsList) {
-        const hasReactionByCurrentUser = this.hasReactionByCurrentUser(awardsList);
-        const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10;
-        let awardList = awardsList;
-
-        // Filter myself from list if I am awarded.
-        if (hasReactionByCurrentUser) {
-          awardList = awardList.filter(award => award.user.id !== this.getUserData.id);
-        }
-
-        // Get only 9-10 usernames to show in tooltip text.
-        const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map(award => award.user.name);
-
-        // Get the remaining list to use in `and x more` text.
-        const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length);
-
-        // Add myself to the begining of the list so title will start with You.
-        if (hasReactionByCurrentUser) {
-          namesToShow.unshift('You');
-        }
-
-        let title = '';
-
-        // We have 10+ awarded user, join them with comma and add `and x more`.
-        if (remainingAwardList.length) {
-          title = `${namesToShow.join(', ')}, and ${remainingAwardList.length} more.`;
-        } else if (namesToShow.length > 1) {
-          // Join all names with comma but not the last one, it will be added with and text.
-          title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
-          // If we have more than 2 users we need an extra comma before and text.
-          title += namesToShow.length > 2 ? ',' : '';
-          title += ` and ${namesToShow.slice(-1)}`; // Append and text
-        } else { // We have only 2 users so join them with and.
-          title = namesToShow.join(' and ');
-        }
-
-        return title;
-      },
-      handleAward(awardName) {
-        if (!this.isLoggedIn) {
-          return;
-        }
-
-        let parsedName;
-
-        // 100 and 1234 emoji are a number. Callback for v-for click sends it as a string
-        switch (awardName) {
-          case '100':
-            parsedName = 100;
-            break;
-          case '1234':
-            parsedName = 1234;
-            break;
-          default:
-            parsedName = awardName;
-            break;
+    canAwardEmoji: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  computed: {
+    ...mapGetters(['getUserData']),
+    // `this.awards` is an array with emojis but they are not grouped by emoji name. See below.
+    // [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ]
+    // This method will group emojis by their name as an Object. See below.
+    // {
+    //   foo: [ { name: foo, user: user1 }, { name: foo, user: user2 } ],
+    //   bar: [ { name: bar, user: user1 } ]
+    // }
+    // We need to do this otherwise we will render the same emoji over and over again.
+    groupedAwards() {
+      const awards = this.awards.reduce((acc, award) => {
+        if (Object.prototype.hasOwnProperty.call(acc, award.name)) {
+          acc[award.name].push(award);
+        } else {
+          Object.assign(acc, { [award.name]: [award] });
         }
 
-        const data = {
-          endpoint: this.toggleAwardPath,
-          noteId: this.noteId,
-          awardName: parsedName,
-        };
-
-        this.toggleAwardRequest(data)
-          .catch(() => Flash('Something went wrong on our end.'));
-      },
+        return acc;
+      }, {});
+
+      const orderedAwards = {};
+      const { thumbsdown, thumbsup } = awards;
+      // Always show thumbsup and thumbsdown first
+      if (thumbsup) {
+        orderedAwards.thumbsup = thumbsup;
+        delete awards.thumbsup;
+      }
+      if (thumbsdown) {
+        orderedAwards.thumbsdown = thumbsdown;
+        delete awards.thumbsdown;
+      }
+
+      return Object.assign({}, orderedAwards, awards);
+    },
+    isAuthoredByMe() {
+      return this.noteAuthorId === this.getUserData.id;
+    },
+  },
+  created() {
+    this.emojiSmiling = emojiSmiling;
+    this.emojiSmile = emojiSmile;
+    this.emojiSmiley = emojiSmiley;
+  },
+  methods: {
+    ...mapActions(['toggleAwardRequest']),
+    getAwardHTML(name) {
+      return glEmojiTag(name);
+    },
+    getAwardClassBindings(awardList, awardName) {
+      return {
+        active: this.hasReactionByCurrentUser(awardList),
+        disabled: !this.canInteractWithEmoji(awardList, awardName),
+      };
+    },
+    canInteractWithEmoji(awardList, awardName) {
+      let isAllowed = true;
+      const restrictedEmojis = ['thumbsup', 'thumbsdown'];
+
+      // Users can not add :+1: and :-1: to their own notes
+      if (
+        this.getUserData.id === this.noteAuthorId &&
+        restrictedEmojis.indexOf(awardName) > -1
+      ) {
+        isAllowed = false;
+      }
+
+      return this.getUserData.id && isAllowed;
+    },
+    hasReactionByCurrentUser(awardList) {
+      return awardList.filter(award => award.user.id === this.getUserData.id)
+        .length;
+    },
+    awardTitle(awardsList) {
+      const hasReactionByCurrentUser = this.hasReactionByCurrentUser(
+        awardsList,
+      );
+      const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10;
+      let awardList = awardsList;
+
+      // Filter myself from list if I am awarded.
+      if (hasReactionByCurrentUser) {
+        awardList = awardList.filter(
+          award => award.user.id !== this.getUserData.id,
+        );
+      }
+
+      // Get only 9-10 usernames to show in tooltip text.
+      const namesToShow = awardList
+        .slice(0, TOOLTIP_NAME_COUNT)
+        .map(award => award.user.name);
+
+      // Get the remaining list to use in `and x more` text.
+      const remainingAwardList = awardList.slice(
+        TOOLTIP_NAME_COUNT,
+        awardList.length,
+      );
+
+      // Add myself to the begining of the list so title will start with You.
+      if (hasReactionByCurrentUser) {
+        namesToShow.unshift('You');
+      }
+
+      let title = '';
+
+      // We have 10+ awarded user, join them with comma and add `and x more`.
+      if (remainingAwardList.length) {
+        title = `${namesToShow.join(', ')}, and ${
+          remainingAwardList.length
+        } more.`;
+      } else if (namesToShow.length > 1) {
+        // Join all names with comma but not the last one, it will be added with and text.
+        title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
+        // If we have more than 2 users we need an extra comma before and text.
+        title += namesToShow.length > 2 ? ',' : '';
+        title += ` and ${namesToShow.slice(-1)}`; // Append and text
+      } else {
+        // We have only 2 users so join them with and.
+        title = namesToShow.join(' and ');
+      }
+
+      return title;
+    },
+    handleAward(awardName) {
+      if (!this.canAwardEmoji) {
+        return;
+      }
+
+      let parsedName;
+
+      // 100 and 1234 emoji are a number. Callback for v-for click sends it as a string
+      switch (awardName) {
+        case '100':
+          parsedName = 100;
+          break;
+        case '1234':
+          parsedName = 1234;
+          break;
+        default:
+          parsedName = awardName;
+          break;
+      }
+
+      const data = {
+        endpoint: this.toggleAwardPath,
+        noteId: this.noteId,
+        awardName: parsedName,
+      };
+
+      this.toggleAwardRequest(data).catch(() =>
+        Flash('Something went wrong on our end.'),
+      );
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -195,7 +209,7 @@
         </span>
       </button>
       <div
-        v-if="isLoggedIn"
+        v-if="canAwardEmoji"
         class="award-menu-holder">
         <button
           v-tooltip
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index ca12df9db64bfae785e7251c2e95f5391f5d343a..0cb626c14f48a7ee1a49b24646936012c06994f1 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -1,82 +1,81 @@
 <script>
-  import noteEditedText from './note_edited_text.vue';
-  import noteAwardsList from './note_awards_list.vue';
-  import noteAttachment from './note_attachment.vue';
-  import noteForm from './note_form.vue';
-  import TaskList from '../../task_list';
-  import autosave from '../mixins/autosave';
+import $ from 'jquery';
+import noteEditedText from './note_edited_text.vue';
+import noteAwardsList from './note_awards_list.vue';
+import noteAttachment from './note_attachment.vue';
+import noteForm from './note_form.vue';
+import TaskList from '../../task_list';
+import autosave from '../mixins/autosave';
 
-  export default {
-    components: {
-      noteEditedText,
-      noteAwardsList,
-      noteAttachment,
-      noteForm,
+export default {
+  components: {
+    noteEditedText,
+    noteAwardsList,
+    noteAttachment,
+    noteForm,
+  },
+  mixins: [autosave],
+  props: {
+    note: {
+      type: Object,
+      required: true,
     },
-    mixins: [
-      autosave,
-    ],
-    props: {
-      note: {
-        type: Object,
-        required: true,
-      },
-      canEdit: {
-        type: Boolean,
-        required: true,
-      },
-      isEditing: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
+    canEdit: {
+      type: Boolean,
+      required: true,
     },
-    computed: {
-      noteBody() {
-        return this.note.note;
-      },
+    isEditing: {
+      type: Boolean,
+      required: false,
+      default: false,
     },
-    mounted() {
-      this.renderGFM();
-      this.initTaskList();
+  },
+  computed: {
+    noteBody() {
+      return this.note.note;
+    },
+  },
+  mounted() {
+    this.renderGFM();
+    this.initTaskList();
+
+    if (this.isEditing) {
+      this.initAutoSave(this.note.noteable_type);
+    }
+  },
+  updated() {
+    this.initTaskList();
+    this.renderGFM();
 
-      if (this.isEditing) {
+    if (this.isEditing) {
+      if (!this.autosave) {
         this.initAutoSave(this.note.noteable_type);
+      } else {
+        this.setAutoSave();
       }
+    }
+  },
+  methods: {
+    renderGFM() {
+      $(this.$refs['note-body']).renderGFM();
     },
-    updated() {
-      this.initTaskList();
-      this.renderGFM();
-
-      if (this.isEditing) {
-        if (!this.autosave) {
-          this.initAutoSave(this.note.noteable_type);
-        } else {
-          this.setAutoSave();
-        }
+    initTaskList() {
+      if (this.canEdit) {
+        this.taskList = new TaskList({
+          dataType: 'note',
+          fieldName: 'note',
+          selector: '.notes',
+        });
       }
     },
-    methods: {
-      renderGFM() {
-        $(this.$refs['note-body']).renderGFM();
-      },
-      initTaskList() {
-        if (this.canEdit) {
-          this.taskList = new TaskList({
-            dataType: 'note',
-            fieldName: 'note',
-            selector: '.notes',
-          });
-        }
-      },
-      handleFormUpdate(note, parentElement, callback) {
-        this.$emit('handleFormUpdate', note, parentElement, callback);
-      },
-      formCancelHandler(shouldConfirm, isDirty) {
-        this.$emit('cancelFormEdition', shouldConfirm, isDirty);
-      },
+    handleFormUpdate(note, parentElement, callback) {
+      this.$emit('handleFormUpdate', note, parentElement, callback);
+    },
+    formCancelHandler(shouldConfirm, isDirty) {
+      this.$emit('cancelFormEdition', shouldConfirm, isDirty);
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -113,6 +112,7 @@
       :note-author-id="note.author.id"
       :awards="note.award_emoji"
       :toggle-award-path="note.toggle_award_path"
+      :can-award-emoji="note.current_user.can_award_emoji"
     />
     <note-attachment
       v-if="note.attachment"
diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue
index ae2e52554d23412d82645c42f8f6f59a024412b7..4ddca918495f3cf176d26cdc1d8e63e0d1b2ffbc 100644
--- a/app/assets/javascripts/notes/components/note_edited_text.vue
+++ b/app/assets/javascripts/notes/components/note_edited_text.vue
@@ -1,32 +1,32 @@
 <script>
-  import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
+import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
 
-  export default {
-    name: 'EditedNoteText',
-    components: {
-      timeAgoTooltip,
+export default {
+  name: 'EditedNoteText',
+  components: {
+    timeAgoTooltip,
+  },
+  props: {
+    actionText: {
+      type: String,
+      required: true,
     },
-    props: {
-      actionText: {
-        type: String,
-        required: true,
-      },
-      editedAt: {
-        type: String,
-        required: true,
-      },
-      editedBy: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
-      className: {
-        type: String,
-        required: false,
-        default: 'edited-text',
-      },
+    editedAt: {
+      type: String,
+      required: true,
     },
-  };
+    editedBy: {
+      type: Object,
+      required: false,
+      default: () => ({}),
+    },
+    className: {
+      type: String,
+      required: false,
+      default: 'edited-text',
+    },
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 1a13fdbeb7c6d74a2910de759057411d651dfad3..c59a2e7a40627c122c1c77929e1ecd6d0e768fcc 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -1,128 +1,136 @@
 <script>
-  import { mapGetters, mapActions } from 'vuex';
-  import eventHub from '../event_hub';
-  import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
-  import markdownField from '../../vue_shared/components/markdown/field.vue';
-  import issuableStateMixin from '../mixins/issuable_state';
-  import resolvable from '../mixins/resolvable';
+import { mapGetters, mapActions } from 'vuex';
+import eventHub from '../event_hub';
+import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
+import markdownField from '../../vue_shared/components/markdown/field.vue';
+import issuableStateMixin from '../mixins/issuable_state';
+import resolvable from '../mixins/resolvable';
 
-  export default {
-    name: 'IssueNoteForm',
-    components: {
-      issueWarning,
-      markdownField,
+export default {
+  name: 'IssueNoteForm',
+  components: {
+    issueWarning,
+    markdownField,
+  },
+  mixins: [issuableStateMixin, resolvable],
+  props: {
+    noteBody: {
+      type: String,
+      required: false,
+      default: '',
     },
-    mixins: [
-      issuableStateMixin,
-      resolvable,
-    ],
-    props: {
-      noteBody: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      noteId: {
-        type: Number,
-        required: false,
-        default: 0,
-      },
-      saveButtonTitle: {
-        type: String,
-        required: false,
-        default: 'Save comment',
-      },
-      note: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
-      isEditing: {
-        type: Boolean,
-        required: true,
-      },
+    noteId: {
+      type: Number,
+      required: false,
+      default: 0,
     },
-    data() {
-      return {
-        updatedNoteBody: this.noteBody,
-        conflictWhileEditing: false,
-        isSubmitting: false,
-        isResolving: false,
-        resolveAsThread: true,
-      };
+    saveButtonTitle: {
+      type: String,
+      required: false,
+      default: 'Save comment',
     },
-    computed: {
-      ...mapGetters([
-        'getDiscussionLastNote',
-        'getNoteableData',
-        'getNoteableDataByProp',
-        'getNotesDataByProp',
-        'getUserDataByProp',
-      ]),
-      noteHash() {
-        return `#note_${this.noteId}`;
-      },
-      markdownPreviewPath() {
-        return this.getNoteableDataByProp('preview_note_path');
-      },
-      markdownDocsPath() {
-        return this.getNotesDataByProp('markdownDocsPath');
-      },
-      quickActionsDocsPath() {
-        return !this.isEditing ? this.getNotesDataByProp('quickActionsDocsPath') : undefined;
-      },
-      currentUserId() {
-        return this.getUserDataByProp('id');
-      },
-      isDisabled() {
-        return !this.updatedNoteBody.length || this.isSubmitting;
-      },
+    note: {
+      type: Object,
+      required: false,
+      default: () => ({}),
     },
-    watch: {
-      noteBody() {
-        if (this.updatedNoteBody === this.noteBody) {
-          this.updatedNoteBody = this.noteBody;
-        } else {
-          this.conflictWhileEditing = true;
-        }
-      },
+    isEditing: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      updatedNoteBody: this.noteBody,
+      conflictWhileEditing: false,
+      isSubmitting: false,
+      isResolving: false,
+      resolveAsThread: true,
+    };
+  },
+  computed: {
+    ...mapGetters([
+      'getDiscussionLastNote',
+      'getNoteableData',
+      'getNoteableDataByProp',
+      'getNotesDataByProp',
+      'getUserDataByProp',
+    ]),
+    noteHash() {
+      return `#note_${this.noteId}`;
+    },
+    markdownPreviewPath() {
+      return this.getNoteableDataByProp('preview_note_path');
+    },
+    markdownDocsPath() {
+      return this.getNotesDataByProp('markdownDocsPath');
+    },
+    quickActionsDocsPath() {
+      return !this.isEditing
+        ? this.getNotesDataByProp('quickActionsDocsPath')
+        : undefined;
     },
-    mounted() {
-      this.$refs.textarea.focus();
+    currentUserId() {
+      return this.getUserDataByProp('id');
     },
-    methods: {
-      ...mapActions([
-        'toggleResolveNote',
-      ]),
-      handleUpdate(shouldResolve) {
-        const beforeSubmitDiscussionState = this.discussionResolved;
-        this.isSubmitting = true;
+    isDisabled() {
+      return !this.updatedNoteBody.length || this.isSubmitting;
+    },
+  },
+  watch: {
+    noteBody() {
+      if (this.updatedNoteBody === this.noteBody) {
+        this.updatedNoteBody = this.noteBody;
+      } else {
+        this.conflictWhileEditing = true;
+      }
+    },
+  },
+  mounted() {
+    this.$refs.textarea.focus();
+  },
+  methods: {
+    ...mapActions(['toggleResolveNote']),
+    handleUpdate(shouldResolve) {
+      const beforeSubmitDiscussionState = this.discussionResolved;
+      this.isSubmitting = true;
 
-        this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
+      this.$emit(
+        'handleFormUpdate',
+        this.updatedNoteBody,
+        this.$refs.editNoteForm,
+        () => {
           this.isSubmitting = false;
 
           if (shouldResolve) {
             this.resolveHandler(beforeSubmitDiscussionState);
           }
-        });
-      },
-      editMyLastNote() {
-        if (this.updatedNoteBody === '') {
-          const lastNoteInDiscussion = this.getDiscussionLastNote(this.updatedNoteBody);
+        },
+      );
+    },
+    editMyLastNote() {
+      if (this.updatedNoteBody === '') {
+        const lastNoteInDiscussion = this.getDiscussionLastNote(
+          this.updatedNoteBody,
+        );
 
-          if (lastNoteInDiscussion) {
-            eventHub.$emit('enterEditMode', {
-              noteId: lastNoteInDiscussion.id,
-            });
-          }
+        if (lastNoteInDiscussion) {
+          eventHub.$emit('enterEditMode', {
+            noteId: lastNoteInDiscussion.id,
+          });
         }
-      },
-      cancelHandler(shouldConfirm = false) {
-        // Sends information about confirm message and if the textarea has changed
-        this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.updatedNoteBody);
-      },
+      }
+    },
+    cancelHandler(shouldConfirm = false) {
+      // Sends information about confirm message and if the textarea has changed
+      this.$emit(
+        'cancelFormEdition',
+        shouldConfirm,
+        this.noteBody !== this.updatedNoteBody,
+      );
     },
-  };
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 4743d95b95148a5723883f4cb131d146104e5e5e..c3d1ef1fcc635a3f24183ed0430d1403b0701dc4 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,65 +1,63 @@
 <script>
-  import { mapActions } from 'vuex';
-  import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
+import { mapActions } from 'vuex';
+import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
 
-  export default {
-    components: {
-      timeAgoTooltip,
+export default {
+  components: {
+    timeAgoTooltip,
+  },
+  props: {
+    author: {
+      type: Object,
+      required: true,
     },
-    props: {
-      author: {
-        type: Object,
-        required: true,
-      },
-      createdAt: {
-        type: String,
-        required: true,
-      },
-      actionText: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      actionTextHtml: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      noteId: {
-        type: Number,
-        required: true,
-      },
-      includeToggle: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
-      expanded: {
-        type: Boolean,
-        required: false,
-        default: true,
-      },
+    createdAt: {
+      type: String,
+      required: true,
     },
-    computed: {
-      toggleChevronClass() {
-        return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
-      },
-      noteTimestampLink() {
-        return `#note_${this.noteId}`;
-      },
+    actionText: {
+      type: String,
+      required: false,
+      default: '',
     },
-    methods: {
-      ...mapActions([
-        'setTargetNoteHash',
-      ]),
-      handleToggle() {
-        this.$emit('toggleHandler');
-      },
-      updateTargetNoteHash() {
-        this.setTargetNoteHash(this.noteTimestampLink);
-      },
+    actionTextHtml: {
+      type: String,
+      required: false,
+      default: '',
     },
-  };
+    noteId: {
+      type: Number,
+      required: true,
+    },
+    includeToggle: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    expanded: {
+      type: Boolean,
+      required: false,
+      default: true,
+    },
+  },
+  computed: {
+    toggleChevronClass() {
+      return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
+    },
+    noteTimestampLink() {
+      return `#note_${this.noteId}`;
+    },
+  },
+  methods: {
+    ...mapActions(['setTargetNoteHash']),
+    handleToggle() {
+      this.$emit('toggleHandler');
+    },
+    updateTargetNoteHash() {
+      this.setTargetNoteHash(this.noteTimestampLink);
+    },
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/note_signed_out_widget.vue b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
index 45d3c2de355d32b94812a560575ce64bb90f2357..91f7c269757dfcec1025fa1bab66066591f2cb18 100644
--- a/app/assets/javascripts/notes/components/note_signed_out_widget.vue
+++ b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
@@ -1,19 +1,17 @@
 <script>
-  import { mapGetters } from 'vuex';
+import { mapGetters } from 'vuex';
 
-  export default {
-    computed: {
-      ...mapGetters([
-        'getNotesDataByProp',
-      ]),
-      registerLink() {
-        return this.getNotesDataByProp('registerPath');
-      },
-      signInLink() {
-        return this.getNotesDataByProp('newSessionPath');
-      },
+export default {
+  computed: {
+    ...mapGetters(['getNotesDataByProp']),
+    registerLink() {
+      return this.getNotesDataByProp('registerPath');
     },
-  };
+    signInLink() {
+      return this.getNotesDataByProp('newSessionPath');
+    },
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 76bb53eaf2f926ac23e977f624465501b7c712a6..e0f883a8e08b095740d4109ab735c6611d653f6b 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,210 +1,210 @@
 <script>
-  import { mapActions, mapGetters } from 'vuex';
-  import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
-  import nextDiscussionsSvg from 'icons/_next_discussion.svg';
-  import Flash from '../../flash';
-  import { SYSTEM_NOTE } from '../constants';
-  import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-  import noteableNote from './noteable_note.vue';
-  import noteHeader from './note_header.vue';
-  import noteSignedOutWidget from './note_signed_out_widget.vue';
-  import noteEditedText from './note_edited_text.vue';
-  import noteForm from './note_form.vue';
-  import diffWithNote from './diff_with_note.vue';
-  import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
-  import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
-  import autosave from '../mixins/autosave';
-  import noteable from '../mixins/noteable';
-  import resolvable from '../mixins/resolvable';
-  import tooltip from '../../vue_shared/directives/tooltip';
-  import { scrollToElement } from '../../lib/utils/common_utils';
+import { mapActions, mapGetters } from 'vuex';
+import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
+import nextDiscussionsSvg from 'icons/_next_discussion.svg';
+import Flash from '../../flash';
+import { SYSTEM_NOTE } from '../constants';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import noteableNote from './noteable_note.vue';
+import noteHeader from './note_header.vue';
+import noteSignedOutWidget from './note_signed_out_widget.vue';
+import noteEditedText from './note_edited_text.vue';
+import noteForm from './note_form.vue';
+import diffWithNote from './diff_with_note.vue';
+import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
+import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
+import autosave from '../mixins/autosave';
+import noteable from '../mixins/noteable';
+import resolvable from '../mixins/resolvable';
+import tooltip from '../../vue_shared/directives/tooltip';
+import { scrollToElement } from '../../lib/utils/common_utils';
 
-  export default {
-    components: {
-      noteableNote,
-      diffWithNote,
-      userAvatarLink,
-      noteHeader,
-      noteSignedOutWidget,
-      noteEditedText,
-      noteForm,
-      placeholderNote,
-      placeholderSystemNote,
+export default {
+  components: {
+    noteableNote,
+    diffWithNote,
+    userAvatarLink,
+    noteHeader,
+    noteSignedOutWidget,
+    noteEditedText,
+    noteForm,
+    placeholderNote,
+    placeholderSystemNote,
+  },
+  directives: {
+    tooltip,
+  },
+  mixins: [autosave, noteable, resolvable],
+  props: {
+    note: {
+      type: Object,
+      required: true,
     },
-    directives: {
-      tooltip,
-    },
-    mixins: [
-      autosave,
-      noteable,
-      resolvable,
-    ],
-    props: {
-      note: {
-        type: Object,
-        required: true,
-      },
-    },
-    data() {
+  },
+  data() {
+    return {
+      isReplying: false,
+      isResolving: false,
+      resolveAsThread: true,
+    };
+  },
+  computed: {
+    ...mapGetters([
+      'getNoteableData',
+      'discussionCount',
+      'resolvedDiscussionCount',
+      'unresolvedDiscussions',
+    ]),
+    discussion() {
       return {
-        isReplying: false,
-        isResolving: false,
-        resolveAsThread: true,
+        ...this.note.notes[0],
+        truncatedDiffLines: this.note.truncated_diff_lines,
+        diffFile: this.note.diff_file,
+        diffDiscussion: this.note.diff_discussion,
+        imageDiffHtml: this.note.image_diff_html,
       };
     },
-    computed: {
-      ...mapGetters([
-        'getNoteableData',
-        'discussionCount',
-        'resolvedDiscussionCount',
-        'unresolvedDiscussions',
-      ]),
-      discussion() {
-        return {
-          ...this.note.notes[0],
-          truncatedDiffLines: this.note.truncated_diff_lines,
-          diffFile: this.note.diff_file,
-          diffDiscussion: this.note.diff_discussion,
-          imageDiffHtml: this.note.image_diff_html,
-        };
-      },
-      author() {
-        return this.discussion.author;
-      },
-      canReply() {
-        return this.getNoteableData.current_user.can_create_note;
-      },
-      newNotePath() {
-        return this.getNoteableData.create_note_path;
-      },
-      lastUpdatedBy() {
-        const { notes } = this.note;
+    author() {
+      return this.discussion.author;
+    },
+    canReply() {
+      return this.getNoteableData.current_user.can_create_note;
+    },
+    newNotePath() {
+      return this.getNoteableData.create_note_path;
+    },
+    lastUpdatedBy() {
+      const { notes } = this.note;
 
-        if (notes.length > 1) {
-          return notes[notes.length - 1].author;
-        }
+      if (notes.length > 1) {
+        return notes[notes.length - 1].author;
+      }
 
-        return null;
-      },
-      lastUpdatedAt() {
-        const { notes } = this.note;
+      return null;
+    },
+    lastUpdatedAt() {
+      const { notes } = this.note;
 
-        if (notes.length > 1) {
-          return notes[notes.length - 1].created_at;
-        }
+      if (notes.length > 1) {
+        return notes[notes.length - 1].created_at;
+      }
 
-        return null;
-      },
-      hasUnresolvedDiscussion() {
-        return this.unresolvedDiscussions.length > 0;
-      },
-      wrapperComponent() {
-        return (this.discussion.diffDiscussion && this.discussion.diffFile) ? diffWithNote : 'div';
-      },
-      wrapperClass() {
-        return this.isDiffDiscussion ? '' : 'panel panel-default';
-      },
+      return null;
+    },
+    hasUnresolvedDiscussion() {
+      return this.unresolvedDiscussions.length > 0;
+    },
+    wrapperComponent() {
+      return this.discussion.diffDiscussion && this.discussion.diffFile
+        ? diffWithNote
+        : 'div';
+    },
+    wrapperClass() {
+      return this.isDiffDiscussion ? '' : 'panel panel-default';
     },
-    mounted() {
-      if (this.isReplying) {
+  },
+  mounted() {
+    if (this.isReplying) {
+      this.initAutoSave(this.discussion.noteable_type);
+    }
+  },
+  updated() {
+    if (this.isReplying) {
+      if (!this.autosave) {
         this.initAutoSave(this.discussion.noteable_type);
+      } else {
+        this.setAutoSave();
       }
-    },
-    updated() {
-      if (this.isReplying) {
-        if (!this.autosave) {
-          this.initAutoSave(this.discussion.noteable_type);
-        } else {
-          this.setAutoSave();
+    }
+  },
+  created() {
+    this.resolveDiscussionsSvg = resolveDiscussionsSvg;
+    this.nextDiscussionsSvg = nextDiscussionsSvg;
+  },
+  methods: {
+    ...mapActions([
+      'saveNote',
+      'toggleDiscussion',
+      'removePlaceholderNotes',
+      'toggleResolveNote',
+    ]),
+    componentName(note) {
+      if (note.isPlaceholderNote) {
+        if (note.placeholderType === SYSTEM_NOTE) {
+          return placeholderSystemNote;
         }
+        return placeholderNote;
       }
+
+      return noteableNote;
     },
-    created() {
-      this.resolveDiscussionsSvg = resolveDiscussionsSvg;
-      this.nextDiscussionsSvg = nextDiscussionsSvg;
+    componentData(note) {
+      return note.isPlaceholderNote ? this.note.notes[0] : note;
     },
-    methods: {
-      ...mapActions([
-        'saveNote',
-        'toggleDiscussion',
-        'removePlaceholderNotes',
-        'toggleResolveNote',
-      ]),
-      componentName(note) {
-        if (note.isPlaceholderNote) {
-          if (note.placeholderType === SYSTEM_NOTE) {
-            return placeholderSystemNote;
-          }
-          return placeholderNote;
-        }
+    toggleDiscussionHandler() {
+      this.toggleDiscussion({ discussionId: this.note.id });
+    },
+    showReplyForm() {
+      this.isReplying = true;
+    },
+    cancelReplyForm(shouldConfirm) {
+      if (shouldConfirm && this.$refs.noteForm.isDirty) {
+        const msg = 'Are you sure you want to cancel creating this comment?';
 
-        return noteableNote;
-      },
-      componentData(note) {
-        return note.isPlaceholderNote ? this.note.notes[0] : note;
-      },
-      toggleDiscussionHandler() {
-        this.toggleDiscussion({ discussionId: this.note.id });
-      },
-      showReplyForm() {
-        this.isReplying = true;
-      },
-      cancelReplyForm(shouldConfirm) {
-        if (shouldConfirm && this.$refs.noteForm.isDirty) {
-          // eslint-disable-next-line no-alert
-          if (!confirm('Are you sure you want to cancel creating this comment?')) {
-            return;
-          }
+        // eslint-disable-next-line no-alert
+        if (!confirm(msg)) {
+          return;
         }
+      }
 
-        this.resetAutoSave();
-        this.isReplying = false;
-      },
-      saveReply(noteText, form, callback) {
-        const replyData = {
-          endpoint: this.newNotePath,
-          flashContainer: this.$el,
-          data: {
-            in_reply_to_discussion_id: this.note.reply_id,
-            target_type: this.noteableType,
-            target_id: this.discussion.noteable_id,
-            note: { note: noteText },
-          },
-        };
-        this.isReplying = false;
+      this.resetAutoSave();
+      this.isReplying = false;
+    },
+    saveReply(noteText, form, callback) {
+      const replyData = {
+        endpoint: this.newNotePath,
+        flashContainer: this.$el,
+        data: {
+          in_reply_to_discussion_id: this.note.reply_id,
+          target_type: this.noteableType,
+          target_id: this.discussion.noteable_id,
+          note: { note: noteText },
+        },
+      };
+      this.isReplying = false;
 
-        this.saveNote(replyData)
-          .then(() => {
-            this.resetAutoSave();
-            callback();
-          })
-          .catch((err) => {
-            this.removePlaceholderNotes();
-            this.isReplying = true;
-            this.$nextTick(() => {
-              const msg = `Your comment could not be submitted!
+      this.saveNote(replyData)
+        .then(() => {
+          this.resetAutoSave();
+          callback();
+        })
+        .catch(err => {
+          this.removePlaceholderNotes();
+          this.isReplying = true;
+          this.$nextTick(() => {
+            const msg = `Your comment could not be submitted!
 Please check your network connection and try again.`;
-              Flash(msg, 'alert', this.$el);
-              this.$refs.noteForm.note = noteText;
-              callback(err);
-            });
+            Flash(msg, 'alert', this.$el);
+            this.$refs.noteForm.note = noteText;
+            callback(err);
           });
-      },
-      jumpToDiscussion() {
-        const unresolvedIds = this.unresolvedDiscussions.map(d => d.id);
-        const index = unresolvedIds.indexOf(this.note.id);
+        });
+    },
+    jumpToDiscussion() {
+      const unresolvedIds = this.unresolvedDiscussions.map(d => d.id);
+      const index = unresolvedIds.indexOf(this.note.id);
 
-        if (index >= 0 && index !== unresolvedIds.length) {
-          const nextId = unresolvedIds[index + 1];
-          const el = document.querySelector(`[data-discussion-id="${nextId}"]`);
+      if (index >= 0 && index !== unresolvedIds.length) {
+        const nextId = unresolvedIds[index + 1];
+        const el = document.querySelector(`[data-discussion-id="${nextId}"]`);
 
-          if (el) {
-            scrollToElement(el);
-          }
+        if (el) {
+          scrollToElement(el);
         }
-      },
+      }
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -292,10 +292,12 @@ Please check your network connection and try again.`;
                         </button>
                       </div>
                       <div
+                        v-if="note.resolvable"
                         class="btn-group discussion-actions"
-                        role="group">
+                        role="group"
+                      >
                         <div
-                          v-if="note.resolvable && !discussionResolved"
+                          v-if="!discussionResolved"
                           class="btn-group"
                           role="group">
                           <a
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 4d17bd5acc23d3e8b5a56cf6bc9a093bd744eb93..566f5c68e66f85ed61c0bfd89b2558f9332f9928 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -1,151 +1,152 @@
 <script>
-  import { mapGetters, mapActions } from 'vuex';
-  import { escape } from 'underscore';
-  import Flash from '../../flash';
-  import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-  import noteHeader from './note_header.vue';
-  import noteActions from './note_actions.vue';
-  import noteBody from './note_body.vue';
-  import eventHub from '../event_hub';
-  import noteable from '../mixins/noteable';
-  import resolvable from '../mixins/resolvable';
+import $ from 'jquery';
+import { mapGetters, mapActions } from 'vuex';
+import { escape } from 'underscore';
+import Flash from '../../flash';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import noteHeader from './note_header.vue';
+import noteActions from './note_actions.vue';
+import noteBody from './note_body.vue';
+import eventHub from '../event_hub';
+import noteable from '../mixins/noteable';
+import resolvable from '../mixins/resolvable';
 
-  export default {
-    components: {
-      userAvatarLink,
-      noteHeader,
-      noteActions,
-      noteBody,
+export default {
+  components: {
+    userAvatarLink,
+    noteHeader,
+    noteActions,
+    noteBody,
+  },
+  mixins: [noteable, resolvable],
+  props: {
+    note: {
+      type: Object,
+      required: true,
     },
-    mixins: [
-      noteable,
-      resolvable,
-    ],
-    props: {
-      note: {
-        type: Object,
-        required: true,
-      },
+  },
+  data() {
+    return {
+      isEditing: false,
+      isDeleting: false,
+      isRequesting: false,
+      isResolving: false,
+    };
+  },
+  computed: {
+    ...mapGetters(['targetNoteHash', 'getUserData']),
+    author() {
+      return this.note.author;
     },
-    data() {
+    classNameBindings() {
       return {
-        isEditing: false,
-        isDeleting: false,
-        isRequesting: false,
-        isResolving: false,
+        'is-editing': this.isEditing && !this.isRequesting,
+        'is-requesting being-posted': this.isRequesting,
+        'disabled-content': this.isDeleting,
+        target: this.targetNoteHash === this.noteAnchorId,
       };
     },
-    computed: {
-      ...mapGetters([
-        'targetNoteHash',
-        'getUserData',
-      ]),
-      author() {
-        return this.note.author;
-      },
-      classNameBindings() {
-        return {
-          'is-editing': this.isEditing && !this.isRequesting,
-          'is-requesting being-posted': this.isRequesting,
-          'disabled-content': this.isDeleting,
-          target: this.targetNoteHash === this.noteAnchorId,
-        };
-      },
-      canReportAsAbuse() {
-        return this.note.report_abuse_path && this.author.id !== this.getUserData.id;
-      },
-      noteAnchorId() {
-        return `note_${this.note.id}`;
-      },
+    canReportAsAbuse() {
+      return (
+        this.note.report_abuse_path && this.author.id !== this.getUserData.id
+      );
     },
-
-    created() {
-      eventHub.$on('enterEditMode', ({ noteId }) => {
-        if (noteId === this.note.id) {
-          this.isEditing = true;
-          this.scrollToNoteIfNeeded($(this.$el));
-        }
-      });
+    noteAnchorId() {
+      return `note_${this.note.id}`;
     },
+  },
 
-    methods: {
-      ...mapActions([
-        'deleteNote',
-        'updateNote',
-        'toggleResolveNote',
-        'scrollToNoteIfNeeded',
-      ]),
-      editHandler() {
+  created() {
+    eventHub.$on('enterEditMode', ({ noteId }) => {
+      if (noteId === this.note.id) {
         this.isEditing = true;
-      },
-      deleteHandler() {
-        // eslint-disable-next-line no-alert
-        if (confirm('Are you sure you want to delete this comment?')) {
-          this.isDeleting = true;
+        this.scrollToNoteIfNeeded($(this.$el));
+      }
+    });
+  },
 
-          this.deleteNote(this.note)
-            .then(() => {
-              this.isDeleting = false;
-            })
-            .catch(() => {
-              Flash('Something went wrong while deleting your note. Please try again.');
-              this.isDeleting = false;
-            });
-        }
-      },
-      formUpdateHandler(noteText, parentElement, callback) {
-        const data = {
-          endpoint: this.note.path,
-          note: {
-            target_type: this.noteableType,
-            target_id: this.note.noteable_id,
-            note: { note: noteText },
-          },
-        };
-        this.isRequesting = true;
-        this.oldContent = this.note.note_html;
-        this.note.note_html = escape(noteText);
+  methods: {
+    ...mapActions([
+      'deleteNote',
+      'updateNote',
+      'toggleResolveNote',
+      'scrollToNoteIfNeeded',
+    ]),
+    editHandler() {
+      this.isEditing = true;
+    },
+    deleteHandler() {
+      // eslint-disable-next-line no-alert
+      if (confirm('Are you sure you want to delete this comment?')) {
+        this.isDeleting = true;
 
-        this.updateNote(data)
+        this.deleteNote(this.note)
           .then(() => {
-            this.isEditing = false;
-            this.isRequesting = false;
-            this.oldContent = null;
-            $(this.$refs.noteBody.$el).renderGFM();
-            this.$refs.noteBody.resetAutoSave();
-            callback();
+            this.isDeleting = false;
           })
           .catch(() => {
-            this.isRequesting = false;
-            this.isEditing = true;
-            this.$nextTick(() => {
-              const msg = 'Something went wrong while editing your comment. Please try again.';
-              Flash(msg, 'alert', this.$el);
-              this.recoverNoteContent(noteText);
-              callback();
-            });
+            Flash(
+              'Something went wrong while deleting your note. Please try again.',
+            );
+            this.isDeleting = false;
           });
-      },
-      formCancelHandler(shouldConfirm, isDirty) {
-        if (shouldConfirm && isDirty) {
-          // eslint-disable-next-line no-alert
-          if (!confirm('Are you sure you want to cancel editing this comment?')) return;
-        }
-        this.$refs.noteBody.resetAutoSave();
-        if (this.oldContent) {
-          this.note.note_html = this.oldContent;
+      }
+    },
+    formUpdateHandler(noteText, parentElement, callback) {
+      const data = {
+        endpoint: this.note.path,
+        note: {
+          target_type: this.noteableType,
+          target_id: this.note.noteable_id,
+          note: { note: noteText },
+        },
+      };
+      this.isRequesting = true;
+      this.oldContent = this.note.note_html;
+      this.note.note_html = escape(noteText);
+
+      this.updateNote(data)
+        .then(() => {
+          this.isEditing = false;
+          this.isRequesting = false;
           this.oldContent = null;
-        }
-        this.isEditing = false;
-      },
-      recoverNoteContent(noteText) {
-        // we need to do this to prevent noteForm inconsistent content warning
-        // this is something we intentionally do so we need to recover the content
-        this.note.note = noteText;
-        this.$refs.noteBody.$refs.noteForm.note.note = noteText;
-      },
+          $(this.$refs.noteBody.$el).renderGFM();
+          this.$refs.noteBody.resetAutoSave();
+          callback();
+        })
+        .catch(() => {
+          this.isRequesting = false;
+          this.isEditing = true;
+          this.$nextTick(() => {
+            const msg =
+              'Something went wrong while editing your comment. Please try again.';
+            Flash(msg, 'alert', this.$el);
+            this.recoverNoteContent(noteText);
+            callback();
+          });
+        });
+    },
+    formCancelHandler(shouldConfirm, isDirty) {
+      if (shouldConfirm && isDirty) {
+        // eslint-disable-next-line no-alert
+        if (!confirm('Are you sure you want to cancel editing this comment?'))
+          return;
+      }
+      this.$refs.noteBody.resetAutoSave();
+      if (this.oldContent) {
+        this.note.note_html = this.oldContent;
+        this.oldContent = null;
+      }
+      this.isEditing = false;
+    },
+    recoverNoteContent(noteText) {
+      // we need to do this to prevent noteForm inconsistent content warning
+      // this is something we intentionally do so we need to recover the content
+      this.note.note = noteText;
+      this.$refs.noteBody.$refs.noteForm.note.note = noteText;
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -176,6 +177,7 @@
             :note-id="note.id"
             :access-level="note.human_access"
             :can-edit="note.current_user.can_edit"
+            :can-award-emoji="note.current_user.can_award_emoji"
             :can-delete="note.current_user.can_edit"
             :can-report-as-abuse="canReportAsAbuse"
             :report-abuse-path="note.report_abuse_path"
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 74afed5560b7dae86044039c962066781bccb9cb..ebfc827ac576764d93d25203d0e2e3ba4833d4ff 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -1,159 +1,157 @@
 <script>
-  import { mapGetters, mapActions } from 'vuex';
-  import { getLocationHash } from '../../lib/utils/url_utility';
-  import Flash from '../../flash';
-  import store from '../stores/';
-  import * as constants from '../constants';
-  import noteableNote from './noteable_note.vue';
-  import noteableDiscussion from './noteable_discussion.vue';
-  import systemNote from '../../vue_shared/components/notes/system_note.vue';
-  import commentForm from './comment_form.vue';
-  import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
-  import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
-  import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-  import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
+import $ from 'jquery';
+import { mapGetters, mapActions } from 'vuex';
+import { getLocationHash } from '../../lib/utils/url_utility';
+import Flash from '../../flash';
+import store from '../stores/';
+import * as constants from '../constants';
+import noteableNote from './noteable_note.vue';
+import noteableDiscussion from './noteable_discussion.vue';
+import systemNote from '../../vue_shared/components/notes/system_note.vue';
+import commentForm from './comment_form.vue';
+import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
+import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
 
-  export default {
-    name: 'NotesApp',
-    components: {
-      noteableNote,
-      noteableDiscussion,
-      systemNote,
-      commentForm,
-      loadingIcon,
-      placeholderNote,
-      placeholderSystemNote,
+export default {
+  name: 'NotesApp',
+  components: {
+    noteableNote,
+    noteableDiscussion,
+    systemNote,
+    commentForm,
+    loadingIcon,
+    placeholderNote,
+    placeholderSystemNote,
+  },
+  props: {
+    noteableData: {
+      type: Object,
+      required: true,
     },
-    props: {
-      noteableData: {
-        type: Object,
-        required: true,
-      },
-      notesData: {
-        type: Object,
-        required: true,
-      },
-      userData: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
+    notesData: {
+      type: Object,
+      required: true,
     },
-    store,
-    data() {
-      return {
-        isLoading: true,
-      };
+    userData: {
+      type: Object,
+      required: false,
+      default: () => ({}),
     },
-    computed: {
-      ...mapGetters([
-        'notes',
-        'getNotesDataByProp',
-        'discussionCount',
-      ]),
-      noteableType() {
-        // FIXME -- @fatihacet Get this from JSON data.
-        const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE } = constants;
-
-        return this.noteableData.merge_params ? MERGE_REQUEST_NOTEABLE_TYPE : ISSUE_NOTEABLE_TYPE;
-      },
-      allNotes() {
-        if (this.isLoading) {
-          const totalNotes = parseInt(this.notesData.totalNotes, 10) || 0;
-
-          return new Array(totalNotes).fill({
-            isSkeletonNote: true,
-          });
-        }
-        return this.notes;
-      },
+  },
+  store,
+  data() {
+    return {
+      isLoading: true,
+    };
+  },
+  computed: {
+    ...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']),
+    noteableType() {
+      return this.noteableData.noteableType;
     },
-    created() {
-      this.setNotesData(this.notesData);
-      this.setNoteableData(this.noteableData);
-      this.setUserData(this.userData);
-    },
-    mounted() {
-      this.fetchNotes();
-
-      const parentElement = this.$el.parentElement;
+    allNotes() {
+      if (this.isLoading) {
+        const totalNotes = parseInt(this.notesData.totalNotes, 10) || 0;
 
-      if (parentElement &&
-        parentElement.classList.contains('js-vue-notes-event')) {
-        parentElement.addEventListener('toggleAward', (event) => {
-          const { awardName, noteId } = event.detail;
-          this.actionToggleAward({ awardName, noteId });
+        return new Array(totalNotes).fill({
+          isSkeletonNote: true,
         });
       }
-      document.addEventListener('refreshVueNotes', this.fetchNotes);
+      return this.notes;
     },
-    beforeDestroy() {
-      document.removeEventListener('refreshVueNotes', this.fetchNotes);
-    },
-    methods: {
-      ...mapActions({
-        actionFetchNotes: 'fetchNotes',
-        poll: 'poll',
-        actionToggleAward: 'toggleAward',
-        scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
-        setNotesData: 'setNotesData',
-        setNoteableData: 'setNoteableData',
-        setUserData: 'setUserData',
-        setLastFetchedAt: 'setLastFetchedAt',
-        setTargetNoteHash: 'setTargetNoteHash',
-      }),
-      getComponentName(note) {
-        if (note.isSkeletonNote) {
-          return skeletonLoadingContainer;
-        }
-        if (note.isPlaceholderNote) {
-          if (note.placeholderType === constants.SYSTEM_NOTE) {
-            return placeholderSystemNote;
-          }
-          return placeholderNote;
-        } else if (note.individual_note) {
-          return note.notes[0].system ? systemNote : noteableNote;
-        }
+  },
+  created() {
+    this.setNotesData(this.notesData);
+    this.setNoteableData(this.noteableData);
+    this.setUserData(this.userData);
+  },
+  mounted() {
+    this.fetchNotes();
 
-        return noteableDiscussion;
-      },
-      getComponentData(note) {
-        return note.individual_note ? note.notes[0] : note;
-      },
-      fetchNotes() {
-        return this.actionFetchNotes(this.getNotesDataByProp('discussionsPath'))
-          .then(() => this.initPolling())
-          .then(() => {
-            this.isLoading = false;
-          })
-          .then(() => this.$nextTick())
-          .then(() => this.checkLocationHash())
-          .catch(() => {
-            this.isLoading = false;
-            Flash('Something went wrong while fetching comments. Please try again.');
-          });
-      },
-      initPolling() {
-        if (this.isPollingInitialized) {
-          return;
+    const parentElement = this.$el.parentElement;
+
+    if (
+      parentElement &&
+      parentElement.classList.contains('js-vue-notes-event')
+    ) {
+      parentElement.addEventListener('toggleAward', event => {
+        const { awardName, noteId } = event.detail;
+        this.actionToggleAward({ awardName, noteId });
+      });
+    }
+    document.addEventListener('refreshVueNotes', this.fetchNotes);
+  },
+  beforeDestroy() {
+    document.removeEventListener('refreshVueNotes', this.fetchNotes);
+  },
+  methods: {
+    ...mapActions({
+      actionFetchNotes: 'fetchNotes',
+      poll: 'poll',
+      actionToggleAward: 'toggleAward',
+      scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
+      setNotesData: 'setNotesData',
+      setNoteableData: 'setNoteableData',
+      setUserData: 'setUserData',
+      setLastFetchedAt: 'setLastFetchedAt',
+      setTargetNoteHash: 'setTargetNoteHash',
+    }),
+    getComponentName(note) {
+      if (note.isSkeletonNote) {
+        return skeletonLoadingContainer;
+      }
+      if (note.isPlaceholderNote) {
+        if (note.placeholderType === constants.SYSTEM_NOTE) {
+          return placeholderSystemNote;
         }
+        return placeholderNote;
+      } else if (note.individual_note) {
+        return note.notes[0].system ? systemNote : noteableNote;
+      }
+
+      return noteableDiscussion;
+    },
+    getComponentData(note) {
+      return note.individual_note ? note.notes[0] : note;
+    },
+    fetchNotes() {
+      return this.actionFetchNotes(this.getNotesDataByProp('discussionsPath'))
+        .then(() => this.initPolling())
+        .then(() => {
+          this.isLoading = false;
+        })
+        .then(() => this.$nextTick())
+        .then(() => this.checkLocationHash())
+        .catch(() => {
+          this.isLoading = false;
+          Flash(
+            'Something went wrong while fetching comments. Please try again.',
+          );
+        });
+    },
+    initPolling() {
+      if (this.isPollingInitialized) {
+        return;
+      }
 
-        this.setLastFetchedAt(this.getNotesDataByProp('lastFetchedAt'));
+      this.setLastFetchedAt(this.getNotesDataByProp('lastFetchedAt'));
 
-        this.poll();
-        this.isPollingInitialized = true;
-      },
-      checkLocationHash() {
-        const hash = getLocationHash();
-        const element = document.getElementById(hash);
+      this.poll();
+      this.isPollingInitialized = true;
+    },
+    checkLocationHash() {
+      const hash = getLocationHash();
+      const element = document.getElementById(hash);
 
-        if (hash && element) {
-          this.setTargetNoteHash(hash);
-          this.scrollToNoteIfNeeded($(element));
-        }
-      },
+      if (hash && element) {
+        this.setTargetNoteHash(hash);
+        this.scrollToNoteIfNeeded($(element));
+      }
     },
-  };
+  },
+};
 </script>
 
 <template>
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index f4f407ffd8adbbdcb92cd46dd3a08f701836a76a..c4de4826eda43ad213530c583e5352bdb9a88977 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -10,6 +10,13 @@ export const CLOSED = 'closed';
 export const EMOJI_THUMBSUP = 'thumbsup';
 export const EMOJI_THUMBSDOWN = 'thumbsdown';
 export const ISSUE_NOTEABLE_TYPE = 'issue';
+export const EPIC_NOTEABLE_TYPE = 'epic';
 export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request';
 export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
 export const RESOLVE_NOTE_METHOD_NAME = 'post';
+
+export const NOTEABLE_TYPE_MAPPING = {
+  Issue: ISSUE_NOTEABLE_TYPE,
+  MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE,
+  Epic: EPIC_NOTEABLE_TYPE,
+};
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 545bf2c99a72101a2397c6a34df636d980ea96ef..e4121f151dbb1e5007a995d745c09a4d04b75c36 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,35 +1,46 @@
 import Vue from 'vue';
 import notesApp from './components/notes_app.vue';
 
-document.addEventListener('DOMContentLoaded', () => new Vue({
-  el: '#js-vue-notes',
-  components: {
-    notesApp,
-  },
-  data() {
-    const notesDataset = document.getElementById('js-vue-notes').dataset;
-    const parsedUserData = JSON.parse(notesDataset.currentUserData);
-    const currentUserData = parsedUserData ? {
-      id: parsedUserData.id,
-      name: parsedUserData.name,
-      username: parsedUserData.username,
-      avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
-      path: parsedUserData.path,
-    } : {};
+document.addEventListener(
+  'DOMContentLoaded',
+  () =>
+    new Vue({
+      el: '#js-vue-notes',
+      components: {
+        notesApp,
+      },
+      data() {
+        const notesDataset = document.getElementById('js-vue-notes').dataset;
+        const parsedUserData = JSON.parse(notesDataset.currentUserData);
+        const noteableData = JSON.parse(notesDataset.noteableData);
+        let currentUserData = {};
+
+        noteableData.noteableType = notesDataset.noteableType;
 
-    return {
-      noteableData: JSON.parse(notesDataset.noteableData),
-      currentUserData,
-      notesData: JSON.parse(notesDataset.notesData),
-    };
-  },
-  render(createElement) {
-    return createElement('notes-app', {
-      props: {
-        noteableData: this.noteableData,
-        notesData: this.notesData,
-        userData: this.currentUserData,
+        if (parsedUserData) {
+          currentUserData = {
+            id: parsedUserData.id,
+            name: parsedUserData.name,
+            username: parsedUserData.username,
+            avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
+            path: parsedUserData.path,
+          };
+        }
+
+        return {
+          noteableData,
+          currentUserData,
+          notesData: JSON.parse(notesDataset.notesData),
+        };
+      },
+      render(createElement) {
+        return createElement('notes-app', {
+          props: {
+            noteableData: this.noteableData,
+            notesData: this.notesData,
+            userData: this.currentUserData,
+          },
+        });
       },
-    });
-  },
-}));
+    }),
+);
diff --git a/app/assets/javascripts/notes/mixins/autosave.js b/app/assets/javascripts/notes/mixins/autosave.js
index a3d897f2f121969141f44a90ce5ef09e4dc913e8..3dff715905f7c4a666eaa8be918c628f66270054 100644
--- a/app/assets/javascripts/notes/mixins/autosave.js
+++ b/app/assets/javascripts/notes/mixins/autosave.js
@@ -1,10 +1,15 @@
+import $ from 'jquery';
 import Autosave from '../../autosave';
 import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
 
 export default {
   methods: {
     initAutoSave(noteableType) {
-      this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), ['Note', capitalizeFirstCharacter(noteableType), this.note.id]);
+      this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), [
+        'Note',
+        capitalizeFirstCharacter(noteableType),
+        this.note.id,
+      ]);
     },
     resetAutoSave() {
       this.autosave.reset();
diff --git a/app/assets/javascripts/notes/mixins/noteable.js b/app/assets/javascripts/notes/mixins/noteable.js
index 0da4ff49f0862136687a9209957d45c7f01e64ad..b68543d71c8e7b74ee612e62dce5662f535389d2 100644
--- a/app/assets/javascripts/notes/mixins/noteable.js
+++ b/app/assets/javascripts/notes/mixins/noteable.js
@@ -9,14 +9,7 @@ export default {
   },
   computed: {
     noteableType() {
-      switch (this.note.noteable_type) {
-        case 'MergeRequest':
-          return constants.MERGE_REQUEST_NOTEABLE_TYPE;
-        case 'Issue':
-          return constants.ISSUE_NOTEABLE_TYPE;
-        default:
-          return '';
-      }
+      return constants.NOTEABLE_TYPE_MAPPING[this.note.noteable_type];
     },
   },
 };
diff --git a/app/assets/javascripts/notes/mixins/resolvable.js b/app/assets/javascripts/notes/mixins/resolvable.js
index ab1ae115e527edc39bbb3e3ae112ccf1f7517fea..f79049b85f6968b5da9c0abfd5272b180ddf74cd 100644
--- a/app/assets/javascripts/notes/mixins/resolvable.js
+++ b/app/assets/javascripts/notes/mixins/resolvable.js
@@ -12,7 +12,8 @@ export default {
     discussionResolved() {
       const { notes, resolved } = this.note;
 
-      if (notes) { // Decide resolved state using store. Only valid for discussions.
+      if (notes) {
+        // Decide resolved state using store. Only valid for discussions.
         return notes.every(note => note.resolved && !note.system);
       }
 
@@ -26,7 +27,9 @@ export default {
 
         return __('Comment and resolve discussion');
       }
-      return this.discussionResolved ? __('Unresolve discussion') : __('Resolve discussion');
+      return this.discussionResolved
+        ? __('Unresolve discussion')
+        : __('Resolve discussion');
     },
   },
   methods: {
@@ -42,7 +45,9 @@ export default {
         })
         .catch(() => {
           this.isResolving = false;
-          const msg = __('Something went wrong while resolving this discussion. Please try again.');
+          const msg = __(
+            'Something went wrong while resolving this discussion. Please try again.',
+          );
           Flash(msg, 'alert', this.$el);
         });
     },
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 4766351dfc584ee19274920453ab0ccfe3cadd1e..7c623aac6edc335f6025fe79e08acaf4d811f44e 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -22,15 +22,18 @@ export default {
   },
   toggleResolveNote(endpoint, isResolved) {
     const { RESOLVE_NOTE_METHOD_NAME, UNRESOLVE_NOTE_METHOD_NAME } = constants;
-    const method = isResolved ? UNRESOLVE_NOTE_METHOD_NAME : RESOLVE_NOTE_METHOD_NAME;
+    const method = isResolved
+      ? UNRESOLVE_NOTE_METHOD_NAME
+      : RESOLVE_NOTE_METHOD_NAME;
 
     return Vue.http[method](endpoint);
   },
   poll(data = {}) {
-    const { endpoint, lastFetchedAt } = data;
+    const endpoint = data.notesData.notesPath;
+    const lastFetchedAt = data.lastFetchedAt;
     const options = {
       headers: {
-        'X-Last-Fetched-At': lastFetchedAt,
+        'X-Last-Fetched-At': lastFetchedAt ? `${lastFetchedAt}` : undefined,
       },
     };
 
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 42fc2a131b861c9f9baec57b97c09ce289138a93..244a6980b5afdfa970aec44367866ec3a2de43aa 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Visibility from 'visibilityjs';
 import Flash from '../../flash';
 import Poll from '../../lib/utils/poll';
@@ -11,86 +12,115 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
 
 let eTagPoll;
 
-export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
-export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
-export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
-export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
-export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data);
-export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
-export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
-
-export const fetchNotes = ({ commit }, path) => service
-  .fetchNotes(path)
-  .then(res => res.json())
-  .then((res) => {
-    commit(types.SET_INITIAL_NOTES, res);
-  });
+export const setNotesData = ({ commit }, data) =>
+  commit(types.SET_NOTES_DATA, data);
+export const setNoteableData = ({ commit }, data) =>
+  commit(types.SET_NOTEABLE_DATA, data);
+export const setUserData = ({ commit }, data) =>
+  commit(types.SET_USER_DATA, data);
+export const setLastFetchedAt = ({ commit }, data) =>
+  commit(types.SET_LAST_FETCHED_AT, data);
+export const setInitialNotes = ({ commit }, data) =>
+  commit(types.SET_INITIAL_NOTES, data);
+export const setTargetNoteHash = ({ commit }, data) =>
+  commit(types.SET_TARGET_NOTE_HASH, data);
+export const toggleDiscussion = ({ commit }, data) =>
+  commit(types.TOGGLE_DISCUSSION, data);
+
+export const fetchNotes = ({ commit }, path) =>
+  service
+    .fetchNotes(path)
+    .then(res => res.json())
+    .then(res => {
+      commit(types.SET_INITIAL_NOTES, res);
+    });
 
-export const deleteNote = ({ commit }, note) => service
-  .deleteNote(note.path)
-  .then(() => {
+export const deleteNote = ({ commit }, note) =>
+  service.deleteNote(note.path).then(() => {
     commit(types.DELETE_NOTE, note);
   });
 
-export const updateNote = ({ commit }, { endpoint, note }) => service
-  .updateNote(endpoint, note)
-  .then(res => res.json())
-  .then((res) => {
-    commit(types.UPDATE_NOTE, res);
-  });
+export const updateNote = ({ commit }, { endpoint, note }) =>
+  service
+    .updateNote(endpoint, note)
+    .then(res => res.json())
+    .then(res => {
+      commit(types.UPDATE_NOTE, res);
+    });
 
-export const replyToDiscussion = ({ commit }, { endpoint, data }) => service
-  .replyToDiscussion(endpoint, data)
-  .then(res => res.json())
-  .then((res) => {
-    commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
+export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
+  service
+    .replyToDiscussion(endpoint, data)
+    .then(res => res.json())
+    .then(res => {
+      commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
 
-    return res;
-  });
+      return res;
+    });
 
-export const createNewNote = ({ commit }, { endpoint, data }) => service
-  .createNewNote(endpoint, data)
-  .then(res => res.json())
-  .then((res) => {
-    if (!res.errors) {
-      commit(types.ADD_NEW_NOTE, res);
-    }
-    return res;
-  });
+export const createNewNote = ({ commit }, { endpoint, data }) =>
+  service
+    .createNewNote(endpoint, data)
+    .then(res => res.json())
+    .then(res => {
+      if (!res.errors) {
+        commit(types.ADD_NEW_NOTE, res);
+      }
+      return res;
+    });
 
 export const removePlaceholderNotes = ({ commit }) =>
   commit(types.REMOVE_PLACEHOLDER_NOTES);
 
-export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) => service
-  .toggleResolveNote(endpoint, isResolved)
-  .then(res => res.json())
-  .then((res) => {
-    const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
+export const toggleResolveNote = (
+  { commit },
+  { endpoint, isResolved, discussion },
+) =>
+  service
+    .toggleResolveNote(endpoint, isResolved)
+    .then(res => res.json())
+    .then(res => {
+      const mutationType = discussion
+        ? types.UPDATE_DISCUSSION
+        : types.UPDATE_NOTE;
 
-    commit(mutationType, res);
-  });
+      commit(mutationType, res);
+    });
 
-export const closeIssue = ({ commit, dispatch, state }) => service
-  .toggleIssueState(state.notesData.closePath)
-  .then(res => res.json())
-  .then((data) => {
-    commit(types.CLOSE_ISSUE);
-    dispatch('emitStateChangedEvent', data);
-  });
+export const closeIssue = ({ commit, dispatch, state }) => {
+  dispatch('toggleStateButtonLoading', true);
+  return service
+    .toggleIssueState(state.notesData.closePath)
+    .then(res => res.json())
+    .then(data => {
+      commit(types.CLOSE_ISSUE);
+      dispatch('emitStateChangedEvent', data);
+      dispatch('toggleStateButtonLoading', false);
+    });
+};
 
-export const reopenIssue = ({ commit, dispatch, state }) => service
-  .toggleIssueState(state.notesData.reopenPath)
-  .then(res => res.json())
-  .then((data) => {
-    commit(types.REOPEN_ISSUE);
-    dispatch('emitStateChangedEvent', data);
-  });
+export const reopenIssue = ({ commit, dispatch, state }) => {
+  dispatch('toggleStateButtonLoading', true);
+  return service
+    .toggleIssueState(state.notesData.reopenPath)
+    .then(res => res.json())
+    .then(data => {
+      commit(types.REOPEN_ISSUE);
+      dispatch('emitStateChangedEvent', data);
+      dispatch('toggleStateButtonLoading', false);
+    });
+};
+
+export const toggleStateButtonLoading = ({ commit }, value) =>
+  commit(types.TOGGLE_STATE_BUTTON_LOADING, value);
 
 export const emitStateChangedEvent = ({ commit, getters }, data) => {
-  const event = new CustomEvent('issuable_vue_app:change', { detail: {
-    data,
-    isClosed: getters.openState === constants.CLOSED,
-  } });
+  const event = new CustomEvent('issuable_vue_app:change', {
+    detail: {
+      data,
+      isClosed: getters.openState === constants.CLOSED,
+    },
+  });
 
   document.dispatchEvent(event);
 };
@@ -132,59 +162,70 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
     });
   }
 
-  return dispatch(methodToDispatch, noteData)
-    .then((res) => {
-      const { errors } = res;
-      const commandsChanges = res.commands_changes;
-
-      if (hasQuickActions && errors && Object.keys(errors).length) {
-        eTagPoll.makeRequest();
+  return dispatch(methodToDispatch, noteData).then(res => {
+    const { errors } = res;
+    const commandsChanges = res.commands_changes;
 
-        $('.js-gfm-input').trigger('clear-commands-cache.atwho');
-        Flash('Commands applied', 'notice', noteData.flashContainer);
-      }
+    if (hasQuickActions && errors && Object.keys(errors).length) {
+      eTagPoll.makeRequest();
 
-      if (commandsChanges) {
-        if (commandsChanges.emoji_award) {
-          const votesBlock = $('.js-awards-block').eq(0);
-
-          loadAwardsHandler()
-            .then((awardsHandler) => {
-              awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award);
-              awardsHandler.scrollToAwards();
-            })
-            .catch(() => {
-              Flash(
-                'Something went wrong while adding your award. Please try again.',
-                'alert',
-                noteData.flashContainer,
-              );
-            });
-        }
+      $('.js-gfm-input').trigger('clear-commands-cache.atwho');
+      Flash('Commands applied', 'notice', noteData.flashContainer);
+    }
 
-        if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) {
-          sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
-        }
+    if (commandsChanges) {
+      if (commandsChanges.emoji_award) {
+        const votesBlock = $('.js-awards-block').eq(0);
+
+        loadAwardsHandler()
+          .then(awardsHandler => {
+            awardsHandler.addAwardToEmojiBar(
+              votesBlock,
+              commandsChanges.emoji_award,
+            );
+            awardsHandler.scrollToAwards();
+          })
+          .catch(() => {
+            Flash(
+              'Something went wrong while adding your award. Please try again.',
+              'alert',
+              noteData.flashContainer,
+            );
+          });
       }
 
-      if (errors && errors.commands_only) {
-        Flash(errors.commands_only, 'notice', noteData.flashContainer);
+      if (
+        commandsChanges.spend_time != null ||
+        commandsChanges.time_estimate != null
+      ) {
+        sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
       }
-      commit(types.REMOVE_PLACEHOLDER_NOTES);
+    }
 
-      return res;
-    });
+    if (errors && errors.commands_only) {
+      Flash(errors.commands_only, 'notice', noteData.flashContainer);
+    }
+    commit(types.REMOVE_PLACEHOLDER_NOTES);
+
+    return res;
+  });
 };
 
 const pollSuccessCallBack = (resp, commit, state, getters) => {
   if (resp.notes && resp.notes.length) {
     const { notesById } = getters;
 
-    resp.notes.forEach((note) => {
+    resp.notes.forEach(note => {
       if (notesById[note.id]) {
         commit(types.UPDATE_NOTE, note);
-      } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
-        const discussion = utils.findNoteObjectById(state.notes, note.discussion_id);
+      } else if (
+        note.type === constants.DISCUSSION_NOTE ||
+        note.type === constants.DIFF_NOTE
+      ) {
+        const discussion = utils.findNoteObjectById(
+          state.notes,
+          note.discussion_id,
+        );
 
         if (discussion) {
           commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
@@ -197,27 +238,28 @@ const pollSuccessCallBack = (resp, commit, state, getters) => {
     });
   }
 
-  commit(types.SET_LAST_FETCHED_AT, resp.lastFetchedAt);
+  commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
 
   return resp;
 };
 
 export const poll = ({ commit, state, getters }) => {
-  const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
-
   eTagPoll = new Poll({
     resource: service,
     method: 'poll',
-    data: requestData,
-    successCallback: resp => resp.json()
-      .then(data => pollSuccessCallBack(data, commit, state, getters)),
-    errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
+    data: state,
+    successCallback: resp =>
+      resp
+        .json()
+        .then(data => pollSuccessCallBack(data, commit, state, getters)),
+    errorCallback: () =>
+      Flash('Something went wrong while fetching latest comments.'),
   });
 
   if (!Visibility.hidden()) {
     eTagPoll.makeRequest();
   } else {
-    service.poll(requestData);
+    service.poll(state);
   }
 
   Visibility.change(() => {
@@ -238,15 +280,22 @@ export const restartPolling = () => {
 };
 
 export const fetchData = ({ commit, state, getters }) => {
-  const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
+  const requestData = {
+    endpoint: state.notesData.notesPath,
+    lastFetchedAt: state.lastFetchedAt,
+  };
 
-  service.poll(requestData)
+  service
+    .poll(requestData)
     .then(resp => resp.json)
     .then(data => pollSuccessCallBack(data, commit, state, getters))
     .catch(() => Flash('Something went wrong while fetching latest comments.'));
 };
 
-export const toggleAward = ({ commit, state, getters, dispatch }, { awardName, noteId }) => {
+export const toggleAward = (
+  { commit, state, getters, dispatch },
+  { awardName, noteId },
+) => {
   commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] });
 };
 
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index e6180101c589c4a2ff0b11e96831cc90cb933219..f89591a54d6b7dc5b8fb7be259d5cf915f292984 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -11,27 +11,31 @@ export const getNoteableDataByProp = state => prop => state.noteableData[prop];
 export const openState = state => state.noteableData.state;
 
 export const getUserData = state => state.userData || {};
-export const getUserDataByProp = state => prop => state.userData && state.userData[prop];
+export const getUserDataByProp = state => prop =>
+  state.userData && state.userData[prop];
 
-export const notesById = state => state.notes.reduce((acc, note) => {
-  note.notes.every(n => Object.assign(acc, { [n.id]: n }));
-  return acc;
-}, {});
+export const notesById = state =>
+  state.notes.reduce((acc, note) => {
+    note.notes.every(n => Object.assign(acc, { [n.id]: n }));
+    return acc;
+  }, {});
 
 const reverseNotes = array => array.slice(0).reverse();
-const isLastNote = (note, state) => !note.system &&
-  state.userData && note.author &&
+const isLastNote = (note, state) =>
+  !note.system &&
+  state.userData &&
+  note.author &&
   note.author.id === state.userData.id;
 
-export const getCurrentUserLastNote = state => _.flatten(
-    reverseNotes(state.notes)
-    .map(note => reverseNotes(note.notes)),
+export const getCurrentUserLastNote = state =>
+  _.flatten(
+    reverseNotes(state.notes).map(note => reverseNotes(note.notes)),
   ).find(el => isLastNote(el, state));
 
-export const getDiscussionLastNote = state => discussion => reverseNotes(discussion.notes)
-  .find(el => isLastNote(el, state));
+export const getDiscussionLastNote = state => discussion =>
+  reverseNotes(discussion.notes).find(el => isLastNote(el, state));
 
-export const discussionCount = (state) => {
+export const discussionCount = state => {
   const discussions = state.notes.filter(n => !n.individual_note);
 
   return discussions.length;
@@ -43,10 +47,10 @@ export const unresolvedDiscussions = (state, getters) => {
   return state.notes.filter(n => !n.individual_note && !resolvedMap[n.id]);
 };
 
-export const resolvedDiscussionsById = (state) => {
+export const resolvedDiscussionsById = state => {
   const map = {};
 
-  state.notes.forEach((n) => {
+  state.notes.forEach(n => {
     if (n.notes) {
       const resolved = n.notes.every(note => note.resolved && !note.system);
 
diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js
index 488a9ca38d37603fb9af12ce7faf9e600ced7840..9ed19bf171e4056338624176a9aa2a4f80e6e9a9 100644
--- a/app/assets/javascripts/notes/stores/index.js
+++ b/app/assets/javascripts/notes/stores/index.js
@@ -12,6 +12,9 @@ export default new Vuex.Store({
     targetNoteHash: null,
     lastFetchedAt: null,
 
+    // View layer
+    isToggleStateButtonLoading: false,
+
     // holds endpoints and permissions provided through haml
     notesData: {},
     userData: {},
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index da1b5a9e51adcb75b3bd95a73f0f2bd21378f5db..b455e23ecdee44db1167ec350791ef5fb9e5a4b0 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -17,3 +17,4 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
 // Issue
 export const CLOSE_ISSUE = 'CLOSE_ISSUE';
 export const REOPEN_ISSUE = 'REOPEN_ISSUE';
+export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 963b40be3fd653a0ba03d302169af12bf533de39..c8edc06349f650ad8982a0b5f5a4a7a55aa49777 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -7,7 +7,7 @@ export default {
   [types.ADD_NEW_NOTE](state, note) {
     const { discussion_id, type } = note;
     const [exists] = state.notes.filter(n => n.id === note.discussion_id);
-    const isDiscussion = (type === constants.DISCUSSION_NOTE);
+    const isDiscussion = type === constants.DISCUSSION_NOTE;
 
     if (!exists) {
       const noteData = {
@@ -63,13 +63,15 @@ export default {
       const note = notes[i];
       const children = note.notes;
 
-      if (children.length && !note.individual_note) { // remove placeholder from discussions
+      if (children.length && !note.individual_note) {
+        // remove placeholder from discussions
         for (let j = children.length - 1; j >= 0; j -= 1) {
           if (children[j].isPlaceholderNote) {
             children.splice(j, 1);
           }
         }
-      } else if (note.isPlaceholderNote) { // remove placeholders from state root
+      } else if (note.isPlaceholderNote) {
+        // remove placeholders from state root
         notes.splice(i, 1);
       }
     }
@@ -89,20 +91,22 @@ export default {
   [types.SET_INITIAL_NOTES](state, notesData) {
     const notes = [];
 
-    notesData.forEach((note) => {
-      const nn = Object.assign({}, note);
-
+    notesData.forEach(note => {
       // To support legacy notes, should be very rare case.
       if (note.individual_note && note.notes.length > 1) {
-        note.notes.forEach((n) => {
-          nn.notes = [n]; // override notes array to only have one item to mimick individual_note
-          notes.push(nn);
+        note.notes.forEach(n => {
+          notes.push({
+            ...note,
+            notes: [n], // override notes array to only have one item to mimick individual_note
+          });
         });
       } else {
         const oldNote = utils.findNoteObjectById(state.notes, note.id);
-        nn.expanded = oldNote ? oldNote.expanded : note.expanded;
 
-        notes.push(nn);
+        notes.push({
+          ...note,
+          expanded: oldNote ? oldNote.expanded : note.expanded,
+        });
       }
     });
 
@@ -126,7 +130,9 @@ export default {
     notesArr.push({
       individual_note: true,
       isPlaceholderNote: true,
-      placeholderType: data.isSystemNote ? constants.SYSTEM_NOTE : constants.NOTE,
+      placeholderType: data.isSystemNote
+        ? constants.SYSTEM_NOTE
+        : constants.NOTE,
       notes: [
         {
           body: data.noteBody,
@@ -139,12 +145,16 @@ export default {
     const { awardName, note } = data;
     const { id, name, username } = state.userData;
 
-    const hasEmojiAwardedByCurrentUser = note.award_emoji
-      .filter(emoji => emoji.name === data.awardName && emoji.user.id === id);
+    const hasEmojiAwardedByCurrentUser = note.award_emoji.filter(
+      emoji => emoji.name === data.awardName && emoji.user.id === id,
+    );
 
     if (hasEmojiAwardedByCurrentUser.length) {
       // If current user has awarded this emoji, remove it.
-      note.award_emoji.splice(note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]), 1);
+      note.award_emoji.splice(
+        note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]),
+        1,
+      );
     } else {
       note.award_emoji.push({
         name: awardName,
@@ -197,4 +207,8 @@ export default {
   [types.REOPEN_ISSUE](state) {
     Object.assign(state.noteableData, { state: constants.REOPENED });
   },
+
+  [types.TOGGLE_STATE_BUTTON_LOADING](state, value) {
+    Object.assign(state, { isToggleStateButtonLoading: value });
+  },
 };
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index 275263a2aaaba6947cccf6738a25f44caa6d06ba..a0e096ebfafa8e5077d9802236204a36cde69be6 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -2,13 +2,15 @@ import AjaxCache from '~/lib/utils/ajax_cache';
 
 const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
 
-export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0];
+export const findNoteObjectById = (notes, id) =>
+  notes.filter(n => n.id === id)[0];
 
-export const getQuickActionText = (note) => {
+export const getQuickActionText = note => {
   let text = 'Applying command';
-  const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
+  const quickActions =
+    AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
 
-  const executedCommands = quickActions.filter((command) => {
+  const executedCommands = quickActions.filter(command => {
     const commandRegex = new RegExp(`/${command.name}`);
     return commandRegex.test(note);
   });
@@ -27,4 +29,5 @@ export const getQuickActionText = (note) => {
 
 export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
 
-export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
+export const stripQuickActions = note =>
+  note.replace(REGEX_QUICK_ACTIONS, '').trim();
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index 479a512ed6515a0b19e50d732edca8cb3e9b4ea6..8ff8bb446ad5a3863d312800e2cc0970e860e85b 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Flash from './flash';
 
 export default function notificationsDropdown() {
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
index 4e0afe13590c9104a86a920dcea36321af24c819..9e6cf67dff01f4cb6d67dddd13fc2edb79cfac51 100644
--- a/app/assets/javascripts/notifications_form.js
+++ b/app/assets/javascripts/notifications_form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { __ } from './locale';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index 7e85bce0d7304bdc8cfd1dbe0cff177630025ab2..86a43b66cc862e96ca9808d5c4aef880dd54b1d0 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { getParameterByName } from '~/lib/utils/common_utils';
 import axios from './lib/utils/axios_utils';
 import { removeParams } from './lib/utils/url_utility';
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index 66702ec4ca07a001bd3d951938677a900ea83854..15e737fff05f54078600787d0909122329ac477e 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { truncate } from '../../../lib/utils/text_utility';
 
 const MAX_MESSAGE_LENGTH = 500;
diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js
index 45e05f111a7f1dc63b4100cdc42f8b7290b6304c..91f154b7ecd07da46fa8ed529dcc6595e6a24f4d 100644
--- a/app/assets/javascripts/pages/admin/admin.js
+++ b/app/assets/javascripts/pages/admin/admin.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { refreshCurrentPage } from '../../lib/utils/url_utility';
 
 function showBlacklistType() {
diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..48d75f5443bdbeaafa32239bf73d83f3e5e887ff
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/application_settings/index.js
@@ -0,0 +1,6 @@
+import initSettingsPanels from '~/settings_panels';
+
+document.addEventListener('DOMContentLoaded', () => {
+  // Initialize expandable settings panels
+  initSettingsPanels();
+});
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index f92450cbaa72256a22edbb0b2acc3d2315721895..e7ceccb6f47b05338bcc3eeed701670c0929ddfd 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import axios from '~/lib/utils/axios_utils';
 import flash from '~/flash';
diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
index 14315d5492ee64c00782c54324230d9a7afa0434..343c65edb3776be8a6f1f44841d38ca96c795ce1 100644
--- a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
+++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
@@ -1,11 +1,11 @@
 <script>
   import _ from 'underscore';
-  import modal from '~/vue_shared/components/modal.vue';
+  import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
   import { s__, sprintf } from '~/locale';
 
   export default {
     components: {
-      modal,
+      DeprecatedModal,
     },
     props: {
       deleteProjectUrl: {
@@ -79,7 +79,7 @@
 </script>
 
 <template>
-  <modal
+  <deprecated-modal
     id="delete-project-modal"
     :title="title"
     :text="text"
@@ -121,5 +121,5 @@
         />
       </form>
     </template>
-  </modal>
+  </deprecated-modal>
 </template>
diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js
index 3c597a1093e8ed48559e294716cc3d1b8bbc11d2..ddbefec87b61851ef4820d5e4352df58cc2dc306 100644
--- a/app/assets/javascripts/pages/admin/projects/index/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 
 import Translate from '~/vue_shared/translate';
diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
index 7b5e333011e132bc152f62511b266952fe1c72a0..0e3ac6366613b3368d0fa93fdf7acde65f697dc7 100644
--- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
+++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
@@ -1,11 +1,11 @@
 <script>
   import _ from 'underscore';
-  import modal from '~/vue_shared/components/modal.vue';
+  import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
   import { s__, sprintf } from '~/locale';
 
   export default {
     components: {
-      modal,
+      DeprecatedModal,
     },
     props: {
       deleteUserUrl: {
@@ -113,7 +113,7 @@
 </script>
 
 <template>
-  <modal
+  <deprecated-modal
     id="delete-user-modal"
     :title="title"
     :text="text"
@@ -170,5 +170,5 @@
         {{ secondaryButtonLabel }}
       </button>
     </template>
-  </modal>
+  </deprecated-modal>
 </template>
diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js
index 4f5d6b5503159e8518fae939d2ec9bef720fa3bb..06599c3fd5f8485f72e14cc3a8dea47f6e9b756a 100644
--- a/app/assets/javascripts/pages/admin/users/index.js
+++ b/app/assets/javascripts/pages/admin/users/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 
 import Translate from '~/vue_shared/translate';
diff --git a/app/assets/javascripts/pages/dashboard/milestones/show/index.js b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
index 397149aaa9e14058f8b71f53a99bdde95b092c71..8b52958589865a1e763ff8dcab72d5bad04666c6 100644
--- a/app/assets/javascripts/pages/dashboard/milestones/show/index.js
+++ b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
@@ -6,4 +6,6 @@ document.addEventListener('DOMContentLoaded', () => {
   new Milestone(); // eslint-disable-line no-new
   new Sidebar(); // eslint-disable-line no-new
   new MountMilestoneSidebar(); // eslint-disable-line no-new
+
+  Milestone.initDeprecationMessage();
 });
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index 42f7460ad553f8c028056030ece7b4cd0570bc06..c334eaa90f8777074ac66bf35ef6cc03a439aa42 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -1,4 +1,6 @@
 /* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
+
+import $ from 'jquery';
 import { visitUrl } from '~/lib/utils/url_utility';
 import UsersSelect from '~/users_select';
 import { isMetaClick } from '~/lib/utils/common_utils';
diff --git a/app/assets/javascripts/pages/groups/boards/index.js b/app/assets/javascripts/pages/groups/boards/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cfe8723204fcdea79b33390d3d3f985a34ba96c
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/boards/index.js
@@ -0,0 +1,9 @@
+import UsersSelect from '~/users_select';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import initBoards from '~/boards';
+
+document.addEventListener('DOMContentLoaded', () => {
+  new UsersSelect(); // eslint-disable-line no-new
+  new ShortcutsNavigation(); // eslint-disable-line no-new
+  initBoards();
+});
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index d44874c8741765ba431a8275b1dd50d0015860aa..bb91ac84ffb787c46365f31ccf6f1399ebcdfeba 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -1,7 +1,9 @@
 import groupAvatar from '~/group_avatar';
 import TransferDropdown from '~/groups/transfer_dropdown';
+import initConfirmDangerModal from '~/confirm_danger_modal';
 
 document.addEventListener('DOMContentLoaded', () => {
   groupAvatar();
   new TransferDropdown(); // eslint-disable-line no-new
+  initConfirmDangerModal();
 });
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index d149b307e7fca450a596cccd99dfa124f84b2cba..914f804fdd3b68a5f0681759e7395f695b4e2b07 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
 document.addEventListener('DOMContentLoaded', () => {
   initFilteredSearch({
     page: FILTERED_SEARCH.ISSUES,
+    isGroupDecendent: true,
   });
   projectSelect();
 });
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index a5cc1f34b63278c9225321fa584839a05dbf390b..1600faa3611e46ceadd36523ccae05b9ed36da1c 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
 document.addEventListener('DOMContentLoaded', () => {
   initFilteredSearch({
     page: FILTERED_SEARCH.MERGE_REQUESTS,
+    isGroupDecendent: true,
   });
   projectSelect();
 });
diff --git a/app/assets/javascripts/pages/groups/milestones/show/index.js b/app/assets/javascripts/pages/groups/milestones/show/index.js
index 88f40b5278e3b708a843b18f9d0a3b9bdf969db4..74cc4ba42c1a9184d22f96e77ad6a2871acd62d6 100644
--- a/app/assets/javascripts/pages/groups/milestones/show/index.js
+++ b/app/assets/javascripts/pages/groups/milestones/show/index.js
@@ -1,3 +1,8 @@
 import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
+import Milestone from '~/milestone';
 
-document.addEventListener('DOMContentLoaded', initMilestonesShow);
+document.addEventListener('DOMContentLoaded', () => {
+  initMilestonesShow();
+
+  Milestone.initDeprecationMessage();
+});
diff --git a/app/assets/javascripts/pages/groups/settings/badges/index.js b/app/assets/javascripts/pages/groups/settings/badges/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..74e96ee4a8f47052b451d41831e5a29b6a9eb850
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/settings/badges/index.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { GROUP_BADGE } from '~/badges/constants';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => {
+  mountBadgeSettings(GROUP_BADGE);
+});
diff --git a/app/assets/javascripts/pages/help/index/index.js b/app/assets/javascripts/pages/help/index/index.js
index 05c81fc618b533f14ec5c084e1ce0748bd2b5b10..1bafe564a37a5c8246d31110ae6ffc0c0d2b9250 100644
--- a/app/assets/javascripts/pages/help/index/index.js
+++ b/app/assets/javascripts/pages/help/index/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import VersionCheckImage from '~/version_check_image';
 import docs from '~/docs/docs_bundle';
 
diff --git a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue
index c43e0a0490f670ea35dc00b7fd8d0a31c41ce551..16f792d635ab4818a7c3d6ac1140e36e9c7e5d6e 100644
--- a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue
+++ b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue
@@ -2,14 +2,14 @@
   import axios from '~/lib/utils/axios_utils';
 
   import Flash from '~/flash';
-  import modal from '~/vue_shared/components/modal.vue';
+  import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
   import { n__, s__, sprintf } from '~/locale';
   import { redirectTo } from '~/lib/utils/url_utility';
   import eventHub from '../event_hub';
 
   export default {
     components: {
-      modal,
+      DeprecatedModal,
     },
     props: {
       issueCount: {
@@ -92,7 +92,7 @@ Once deleted, it cannot be undone or recovered.`),
 </script>
 
 <template>
-  <modal
+  <deprecated-modal
     id="delete-milestone-modal"
     :title="title"
     :text="text"
@@ -106,5 +106,5 @@ Once deleted, it cannot be undone or recovered.`),
       <p v-html="props.text"></p>
     </template>
 
-  </modal>
+  </deprecated-modal>
 </template>
diff --git a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2bda2aeb3a18468e75dd3f6fc1fe2b7eca5816ba
--- /dev/null
+++ b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
@@ -0,0 +1,68 @@
+<script>
+  import axios from '~/lib/utils/axios_utils';
+  import createFlash from '~/flash';
+  import GlModal from '~/vue_shared/components/gl_modal.vue';
+  import { s__, sprintf } from '~/locale';
+  import { visitUrl } from '~/lib/utils/url_utility';
+  import eventHub from '../event_hub';
+
+  export default {
+    components: {
+      GlModal,
+    },
+    props: {
+      milestoneTitle: {
+        type: String,
+        required: true,
+      },
+      url: {
+        type: String,
+        required: true,
+      },
+      groupName: {
+        type: String,
+        required: true,
+      },
+    },
+    computed: {
+      title() {
+        return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { milestoneTitle: this.milestoneTitle });
+      },
+      text() {
+        return sprintf(s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
+        Existing project milestones with the same title will be merged.
+        This action cannot be reversed.`), { milestoneTitle: this.milestoneTitle, groupName: this.groupName });
+      },
+    },
+    methods: {
+      onSubmit() {
+        eventHub.$emit('promoteMilestoneModal.requestStarted', this.url);
+        return axios.post(this.url, { params: { format: 'json' } })
+          .then((response) => {
+            eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: true });
+            visitUrl(response.data.url);
+          })
+          .catch((error) => {
+            eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: false });
+            createFlash(error);
+          });
+      },
+    },
+  };
+</script>
+<template>
+  <gl-modal
+    id="promote-milestone-modal"
+    footer-primary-button-variant="warning"
+    :footer-primary-button-text="s__('Milestones|Promote Milestone')"
+    @submit="onSubmit"
+  >
+    <template
+      slot="title"
+    >
+      {{ title }}
+    </template>
+    {{ text }}
+  </gl-modal>
+</template>
+
diff --git a/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
new file mode 100644
index 0000000000000000000000000000000000000000..d51b5c221e3eee6267a739d8da0fbb57751b7ee6
--- /dev/null
+++ b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import deleteMilestoneModal from './components/delete_milestone_modal.vue';
+import eventHub from './event_hub';
+
+export default () => {
+  Vue.use(Translate);
+
+  const onRequestFinished = ({ milestoneUrl, successful }) => {
+    const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
+
+    if (!successful) {
+      button.removeAttribute('disabled');
+    }
+
+    button.querySelector('.js-loading-icon').classList.add('hidden');
+  };
+
+  const onRequestStarted = (milestoneUrl) => {
+    const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
+    button.setAttribute('disabled', '');
+    button.querySelector('.js-loading-icon').classList.remove('hidden');
+    eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
+  };
+
+  const onDeleteButtonClick = (event) => {
+    const button = event.currentTarget;
+    const modalProps = {
+      milestoneId: parseInt(button.dataset.milestoneId, 10),
+      milestoneTitle: button.dataset.milestoneTitle,
+      milestoneUrl: button.dataset.milestoneUrl,
+      issueCount: parseInt(button.dataset.milestoneIssueCount, 10),
+      mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10),
+    };
+    eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted);
+    eventHub.$emit('deleteMilestoneModal.props', modalProps);
+  };
+
+  const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
+  deleteMilestoneButtons.forEach((button) => {
+    button.addEventListener('click', onDeleteButtonClick);
+  });
+
+  eventHub.$once('deleteMilestoneModal.mounted', () => {
+    deleteMilestoneButtons.forEach((button) => {
+      button.removeAttribute('disabled');
+    });
+  });
+
+  return new Vue({
+    el: '#delete-milestone-modal',
+    components: {
+      deleteMilestoneModal,
+    },
+    data() {
+      return {
+        modalProps: {
+          milestoneId: -1,
+          milestoneTitle: '',
+          milestoneUrl: '',
+          issueCount: -1,
+          mergeRequestCount: -1,
+        },
+      };
+    },
+    mounted() {
+      eventHub.$on('deleteMilestoneModal.props', this.setModalProps);
+      eventHub.$emit('deleteMilestoneModal.mounted');
+    },
+    beforeDestroy() {
+      eventHub.$off('deleteMilestoneModal.props', this.setModalProps);
+    },
+    methods: {
+      setModalProps(modalProps) {
+        this.modalProps = modalProps;
+      },
+    },
+    render(createElement) {
+      return createElement(deleteMilestoneModal, {
+        props: this.modalProps,
+      });
+    },
+  });
+};
diff --git a/app/assets/javascripts/pages/milestones/shared/index.js b/app/assets/javascripts/pages/milestones/shared/index.js
index 327e2cf569c1bd5e5ecba07425038708d598ba8c..dabfe32848b7e0ca9ab6d8d44c9758fc34d81633 100644
--- a/app/assets/javascripts/pages/milestones/shared/index.js
+++ b/app/assets/javascripts/pages/milestones/shared/index.js
@@ -1,88 +1,7 @@
-import Vue from 'vue';
-
-import Translate from '~/vue_shared/translate';
-
-import deleteMilestoneModal from './components/delete_milestone_modal.vue';
-import eventHub from './event_hub';
+import initDeleteMilestoneModal from './delete_milestone_modal_init';
+import initPromoteMilestoneModal from './promote_milestone_modal_init';
 
 export default () => {
-  Vue.use(Translate);
-
-  const onRequestFinished = ({ milestoneUrl, successful }) => {
-    const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
-
-    if (!successful) {
-      button.removeAttribute('disabled');
-    }
-
-    button.querySelector('.js-loading-icon').classList.add('hidden');
-  };
-
-  const onRequestStarted = (milestoneUrl) => {
-    const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
-    button.setAttribute('disabled', '');
-    button.querySelector('.js-loading-icon').classList.remove('hidden');
-    eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
-  };
-
-  const onDeleteButtonClick = (event) => {
-    const button = event.currentTarget;
-    const modalProps = {
-      milestoneId: parseInt(button.dataset.milestoneId, 10),
-      milestoneTitle: button.dataset.milestoneTitle,
-      milestoneUrl: button.dataset.milestoneUrl,
-      issueCount: parseInt(button.dataset.milestoneIssueCount, 10),
-      mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10),
-    };
-    eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted);
-    eventHub.$emit('deleteMilestoneModal.props', modalProps);
-  };
-
-  const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
-  for (let i = 0; i < deleteMilestoneButtons.length; i += 1) {
-    const button = deleteMilestoneButtons[i];
-    button.addEventListener('click', onDeleteButtonClick);
-  }
-
-  eventHub.$once('deleteMilestoneModal.mounted', () => {
-    for (let i = 0; i < deleteMilestoneButtons.length; i += 1) {
-      const button = deleteMilestoneButtons[i];
-      button.removeAttribute('disabled');
-    }
-  });
-
-  return new Vue({
-    el: '#delete-milestone-modal',
-    components: {
-      deleteMilestoneModal,
-    },
-    data() {
-      return {
-        modalProps: {
-          milestoneId: -1,
-          milestoneTitle: '',
-          milestoneUrl: '',
-          issueCount: -1,
-          mergeRequestCount: -1,
-        },
-      };
-    },
-    mounted() {
-      eventHub.$on('deleteMilestoneModal.props', this.setModalProps);
-      eventHub.$emit('deleteMilestoneModal.mounted');
-    },
-    beforeDestroy() {
-      eventHub.$off('deleteMilestoneModal.props', this.setModalProps);
-    },
-    methods: {
-      setModalProps(modalProps) {
-        this.modalProps = modalProps;
-      },
-    },
-    render(createElement) {
-      return createElement(deleteMilestoneModal, {
-        props: this.modalProps,
-      });
-    },
-  });
+  initDeleteMilestoneModal();
+  initPromoteMilestoneModal();
 };
diff --git a/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
new file mode 100644
index 0000000000000000000000000000000000000000..8e79341e96abbca87556b17e3fe070b184aeacf9
--- /dev/null
+++ b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import PromoteMilestoneModal from './components/promote_milestone_modal.vue';
+import eventHub from './event_hub';
+
+Vue.use(Translate);
+
+export default () => {
+  const onRequestFinished = ({ milestoneUrl, successful }) => {
+    const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`);
+
+    if (!successful) {
+      button.removeAttribute('disabled');
+    }
+  };
+
+  const onRequestStarted = (milestoneUrl) => {
+    const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`);
+    button.setAttribute('disabled', '');
+    eventHub.$once('promoteMilestoneModal.requestFinished', onRequestFinished);
+  };
+
+  const onDeleteButtonClick = (event) => {
+    const button = event.currentTarget;
+    const modalProps = {
+      milestoneTitle: button.dataset.milestoneTitle,
+      url: button.dataset.url,
+      groupName: button.dataset.groupName,
+    };
+    eventHub.$once('promoteMilestoneModal.requestStarted', onRequestStarted);
+    eventHub.$emit('promoteMilestoneModal.props', modalProps);
+  };
+
+  const promoteMilestoneButtons = document.querySelectorAll('.js-promote-project-milestone-button');
+  promoteMilestoneButtons.forEach((button) => {
+    button.addEventListener('click', onDeleteButtonClick);
+  });
+
+  eventHub.$once('promoteMilestoneModal.mounted', () => {
+    promoteMilestoneButtons.forEach((button) => {
+      button.removeAttribute('disabled');
+    });
+  });
+
+  const promoteMilestoneModal = document.getElementById('promote-milestone-modal');
+  let promoteMilestoneComponent;
+
+  if (promoteMilestoneModal) {
+    promoteMilestoneComponent = new Vue({
+      el: promoteMilestoneModal,
+      components: {
+        PromoteMilestoneModal,
+      },
+      data() {
+        return {
+          modalProps: {
+            milestoneTitle: '',
+            groupName: '',
+            url: '',
+          },
+        };
+      },
+      mounted() {
+        eventHub.$on('promoteMilestoneModal.props', this.setModalProps);
+        eventHub.$emit('promoteMilestoneModal.mounted');
+      },
+      beforeDestroy() {
+        eventHub.$off('promoteMilestoneModal.props', this.setModalProps);
+      },
+      methods: {
+        setModalProps(modalProps) {
+          this.modalProps = modalProps;
+        },
+      },
+      render(createElement) {
+        return createElement('promote-milestone-modal', {
+          props: this.modalProps,
+        });
+      },
+    });
+  }
+
+  return promoteMilestoneComponent;
+};
diff --git a/app/assets/javascripts/pages/profiles/index.js b/app/assets/javascripts/pages/profiles/index.js
index c52ad7bc33545cff742029a1b6d9cc1fbdd1fa6a..04e50963699ffece28d0f63fab1abd8c17a5fe4d 100644
--- a/app/assets/javascripts/pages/profiles/index.js
+++ b/app/assets/javascripts/pages/profiles/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import '~/profile/gl_crop';
 import Profile from '~/profile/profile';
 
diff --git a/app/assets/javascripts/pages/profiles/notifications/show/index.js b/app/assets/javascripts/pages/profiles/notifications/show/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e24a10fa5c6a8290018cd458c5843c045e12231
--- /dev/null
+++ b/app/assets/javascripts/pages/profiles/notifications/show/index.js
@@ -0,0 +1,7 @@
+import NotificationsForm from '../../../../notifications_form';
+import notificationsDropdown from '../../../../notifications_dropdown';
+
+document.addEventListener('DOMContentLoaded', () => {
+  new NotificationsForm(); // eslint-disable-line no-new
+  notificationsDropdown();
+});
diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
index 5b2473e09898eef3f085bbf4ed423913bd473fe3..fbdef329ab28e0fba52873f3ec463eac7b3034f9 100644
--- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
+++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import U2FRegister from '~/u2f/register';
 
 document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
index 26cbb279d4a9dea2ebd02de3d9c8982fa0804cba..85c6862d629bcbdf053be8bc0898397ea5624412 100644
--- a/app/assets/javascripts/pages/projects/blob/show/index.js
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -1,7 +1,29 @@
+import Vue from 'vue';
+import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
 import BlobViewer from '~/blob/viewer/index';
 import initBlob from '~/pages/projects/init_blob';
 
 document.addEventListener('DOMContentLoaded', () => {
   new BlobViewer(); // eslint-disable-line no-new
   initBlob();
+
+  const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
+  const statusLink = document.querySelector('.commit-actions .ci-status-link');
+  if (statusLink) {
+    statusLink.remove();
+    // eslint-disable-next-line no-new
+    new Vue({
+      el: CommitPipelineStatusEl,
+      components: {
+        commitPipelineStatus,
+      },
+      render(createElement) {
+        return createElement('commit-pipeline-status', {
+          props: {
+            endpoint: CommitPipelineStatusEl.dataset.endpoint,
+          },
+        });
+      },
+    });
+  }
 });
diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js
index d32d5c6cb2918acc7962a5eb3a3a58270530a8e8..a9658fd1eb4411ffa28c216e665b9df6ef23ade3 100644
--- a/app/assets/javascripts/pages/projects/branches/new/index.js
+++ b/app/assets/javascripts/pages/projects/branches/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import NewBranchForm from '~/new_branch_form';
 
 document.addEventListener('DOMContentLoaded', () => (
diff --git a/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js b/app/assets/javascripts/pages/projects/ci/lints/ci_lint_editor.js
similarity index 100%
rename from app/assets/javascripts/pages/ci/lints/ci_lint_editor.js
rename to app/assets/javascripts/pages/projects/ci/lints/ci_lint_editor.js
diff --git a/app/assets/javascripts/pages/ci/lints/create/index.js b/app/assets/javascripts/pages/projects/ci/lints/new/index.js
similarity index 100%
rename from app/assets/javascripts/pages/ci/lints/create/index.js
rename to app/assets/javascripts/pages/projects/ci/lints/new/index.js
diff --git a/app/assets/javascripts/pages/ci/lints/show/index.js b/app/assets/javascripts/pages/projects/ci/lints/show/index.js
similarity index 100%
rename from app/assets/javascripts/pages/ci/lints/show/index.js
rename to app/assets/javascripts/pages/projects/ci/lints/show/index.js
diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
index cd923f13ce854d3a1e568cbd85857cca3bd5ca14..8cc3cb0a57c92ca0038d558b8373261948f73f95 100644
--- a/app/assets/javascripts/pages/projects/commit/pipelines/index.js
+++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
 import initPipelines from '~/commit/pipelines/pipelines_bundle';
 
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index 1aeed197385bb059f2c2fb5a96ed34c7a2dbff68..2e23cce11ce25fa23e554b706cd38a71d4f9dda0 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-new */
+
+import $ from 'jquery';
 import Diff from '~/diff';
 import ZenMode from '~/zen_mode';
 import ShortcutsNavigation from '~/shortcuts_navigation';
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 064de22dfd69f80e101fef49cd0cfa04b97c29de..628913483c603ceb39ef47605bc3aa3e448614bf 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -1,14 +1,16 @@
 import initSettingsPanels from '~/settings_panels';
 import setupProjectEdit from '~/project_edit';
-import ProjectNew from '../shared/project_new';
+import initConfirmDangerModal from '~/confirm_danger_modal';
+import initProjectLoadingSpinner from '../shared/save_project_loader';
 import projectAvatar from '../shared/project_avatar';
 import initProjectPermissionsSettings from '../shared/permissions';
 
 document.addEventListener('DOMContentLoaded', () => {
-  new ProjectNew(); // eslint-disable-line no-new
+  initProjectLoadingSpinner();
   setupProjectEdit();
   // Initialize expandable settings panels
   initSettingsPanels();
   projectAvatar();
   initProjectPermissionsSettings();
+  initConfirmDangerModal();
 });
diff --git a/app/assets/javascripts/pages/projects/environments/index.js b/app/assets/javascripts/pages/projects/environments/index/index.js
similarity index 100%
rename from app/assets/javascripts/pages/projects/environments/index.js
rename to app/assets/javascripts/pages/projects/environments/index/index.js
diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js
index 23d857d69ec57f08de3b432ab0084e6fb218ce7c..24630c2aa05cb38d09fcaac887fb93e67fbbc677 100644
--- a/app/assets/javascripts/pages/projects/find_file/show/index.js
+++ b/app/assets/javascripts/pages/projects/find_file/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import ProjectFindFile from '~/project_find_file';
 import ShortcutsFindFile from '~/shortcuts_find_file';
 
diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js
index 42df19c2968e0f0bf2b8b12944a28b17908f6281..80159a82bd43f47dd366220c955c8a103a59b7cb 100644
--- a/app/assets/javascripts/pages/projects/graphs/charts/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Chart from 'chart.js';
 import _ from 'underscore';
 
diff --git a/app/assets/javascripts/pages/projects/graphs/show/index.js b/app/assets/javascripts/pages/projects/graphs/show/index.js
index f516ff2099534ae6cc80ae143f9e0545de60e1cc..71f629fbc1372c5028ef1e40ccb2b66a928867a9 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import flash from '~/flash';
 import { __ } from '~/locale';
 import axios from '~/lib/utils/axios_utils';
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
index 9ac0b4c07e5ebe412bc0f779bc8d6f8d628b040f..653e2502d01ca7d86a6f13f79a0e9759d9880763 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
@@ -1,5 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
 
+import $ from 'jquery';
 import _ from 'underscore';
 import { n__, s__, createDateTimeFormat, sprintf } from '~/locale';
 import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
index 6ffaa277a0a8a7c0571027be2142c9996384b111..a99ce0f1c363da82065f492e3f10d94615bcb280 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
+
+import $ from 'jquery';
 import _ from 'underscore';
 import { extent, max } from 'd3-array';
 import { select, event as d3Event } from 'd3-selection';
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 5c7daf8473868fc222da53f857f33edaf03d4a4d..14fddbc9a05a19367175c42f265395d6fd7edfb7 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-new */
+
+import $ from 'jquery';
 import GLForm from '~/gl_form';
 import IssuableForm from '~/issuable_form';
 import LabelsSelect from '~/labels_select';
diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ad6df51bb7a44213ae204047d7d7e65657043a83
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
@@ -0,0 +1,88 @@
+<script>
+  import _ from 'underscore';
+  import axios from '~/lib/utils/axios_utils';
+  import createFlash from '~/flash';
+  import GlModal from '~/vue_shared/components/gl_modal.vue';
+  import { s__, sprintf } from '~/locale';
+  import { visitUrl } from '~/lib/utils/url_utility';
+  import eventHub from '../event_hub';
+
+  export default {
+    components: {
+      GlModal,
+    },
+    props: {
+      url: {
+        type: String,
+        required: true,
+      },
+      labelTitle: {
+        type: String,
+        required: true,
+      },
+      labelColor: {
+        type: String,
+        required: true,
+      },
+      labelTextColor: {
+        type: String,
+        required: true,
+      },
+      groupName: {
+        type: String,
+        required: true,
+      },
+    },
+    computed: {
+      text() {
+        return sprintf(s__(`Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. 
+        Existing project labels with the same title will be merged. This action cannot be reversed.`), {
+          labelTitle: this.labelTitle,
+          groupName: this.groupName,
+        });
+      },
+      title() {
+        const label = `<span
+          class="label color-label"
+          style="background-color: ${this.labelColor}; color: ${this.labelTextColor};"
+        >${_.escape(this.labelTitle)}</span>`;
+
+        return sprintf(s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'), {
+          labelTitle: label,
+        }, false);
+      },
+    },
+    methods: {
+      onSubmit() {
+        eventHub.$emit('promoteLabelModal.requestStarted', this.url);
+        return axios.post(this.url, { params: { format: 'json' } })
+          .then((response) => {
+            eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: true });
+            visitUrl(response.data.url);
+          })
+          .catch((error) => {
+            eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: false });
+            createFlash(error);
+          });
+      },
+    },
+  };
+</script>
+<template>
+  <gl-modal
+    id="promote-label-modal"
+    footer-primary-button-variant="warning"
+    :footer-primary-button-text="s__('Labels|Promote Label')"
+    @submit="onSubmit"
+  >
+    <div
+      slot="title"
+      class="modal-title-with-label"
+      v-html="title"
+    >
+      {{ title }}
+    </div>
+
+    {{ text }}
+  </gl-modal>
+</template>
diff --git a/app/assets/javascripts/pages/projects/labels/event_hub.js b/app/assets/javascripts/pages/projects/labels/event_hub.js
new file mode 100644
index 0000000000000000000000000000000000000000..0948c2e53524a736a55c060600868ce89ee7687a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/pages/projects/labels/index/index.js b/app/assets/javascripts/pages/projects/labels/index/index.js
index 6e45de2a724e103d9ed01124f7fefce9996e514a..03cfef61311a22d86b59e18c18fcd4595b54970b 100644
--- a/app/assets/javascripts/pages/projects/labels/index/index.js
+++ b/app/assets/javascripts/pages/projects/labels/index/index.js
@@ -1,3 +1,93 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
 import initLabels from '~/init_labels';
+import eventHub from '../event_hub';
+import PromoteLabelModal from '../components/promote_label_modal.vue';
 
-document.addEventListener('DOMContentLoaded', initLabels);
+Vue.use(Translate);
+
+const initLabelIndex = () => {
+  initLabels();
+
+  const onRequestFinished = ({ labelUrl, successful }) => {
+    const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`);
+
+    if (!successful) {
+      button.removeAttribute('disabled');
+    }
+  };
+
+  const onRequestStarted = (labelUrl) => {
+    const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`);
+    button.setAttribute('disabled', '');
+    eventHub.$once('promoteLabelModal.requestFinished', onRequestFinished);
+  };
+
+  const onDeleteButtonClick = (event) => {
+    const button = event.currentTarget;
+    const modalProps = {
+      labelTitle: button.dataset.labelTitle,
+      labelColor: button.dataset.labelColor,
+      labelTextColor: button.dataset.labelTextColor,
+      url: button.dataset.url,
+      groupName: button.dataset.groupName,
+    };
+    eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted);
+    eventHub.$emit('promoteLabelModal.props', modalProps);
+  };
+
+  const promoteLabelButtons = document.querySelectorAll('.js-promote-project-label-button');
+  promoteLabelButtons.forEach((button) => {
+    button.addEventListener('click', onDeleteButtonClick);
+  });
+
+  eventHub.$once('promoteLabelModal.mounted', () => {
+    promoteLabelButtons.forEach((button) => {
+      button.removeAttribute('disabled');
+    });
+  });
+
+  const promoteLabelModal = document.getElementById('promote-label-modal');
+  let promoteLabelModalComponent;
+
+  if (promoteLabelModal) {
+    promoteLabelModalComponent = new Vue({
+      el: promoteLabelModal,
+      components: {
+        PromoteLabelModal,
+      },
+      data() {
+        return {
+          modalProps: {
+            labelTitle: '',
+            labelColor: '',
+            labelTextColor: '',
+            url: '',
+            groupName: '',
+          },
+        };
+      },
+      mounted() {
+        eventHub.$on('promoteLabelModal.props', this.setModalProps);
+        eventHub.$emit('promoteLabelModal.mounted');
+      },
+      beforeDestroy() {
+        eventHub.$off('promoteLabelModal.props', this.setModalProps);
+      },
+      methods: {
+        setModalProps(modalProps) {
+          this.modalProps = modalProps;
+        },
+      },
+      render(createElement) {
+        return createElement('promote-label-modal', {
+          props: this.modalProps,
+        });
+      },
+    });
+  }
+
+  return promoteLabelModalComponent;
+};
+
+document.addEventListener('DOMContentLoaded', initLabelIndex);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index 8bfac606aab3e41f40e30f2608dd3df39fe52608..406fc32f9a20a9acfd9cc80afd3d34e18751d876 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-new */
 
+import $ from 'jquery';
 import Diff from '~/diff';
 import ShortcutsNavigation from '~/shortcuts_navigation';
 import GLForm from '~/gl_form';
diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js
index 7354243e4c8de8ab26b5ccca2acfd0defa97283d..aa50dd4bb2548bbdce2c66e9d567f2c258204883 100644
--- a/app/assets/javascripts/pages/projects/network/network.js
+++ b/app/assets/javascripts/pages/projects/network/network.js
@@ -1,5 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */
 
+import $ from 'jquery';
 import BranchGraph from '../../../network/branch_graph';
 
 export default (function() {
diff --git a/app/assets/javascripts/pages/projects/network/show/index.js b/app/assets/javascripts/pages/projects/network/show/index.js
index e7dfd2d01280c6901e316f6a181ba7704db8b2bd..a0b14fed10fd37be871f3a71dba045d8adac23a8 100644
--- a/app/assets/javascripts/pages/projects/network/show/index.js
+++ b/app/assets/javascripts/pages/projects/network/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import ShortcutsNetwork from '../../../../shortcuts_network';
 import Network from '../network';
 
diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js
index ea6fd961393a14e4f61a58bddfe32a2951c7d442..7db644e2477cac5812706b9e6551d7c6c0269824 100644
--- a/app/assets/javascripts/pages/projects/new/index.js
+++ b/app/assets/javascripts/pages/projects/new/index.js
@@ -1,9 +1,9 @@
-import ProjectNew from '../shared/project_new';
+import initProjectLoadingSpinner from '../shared/save_project_loader';
 import initProjectVisibilitySelector from '../../../project_visibility';
 import initProjectNew from '../../../projects/project_new';
 
 document.addEventListener('DOMContentLoaded', () => {
-  new ProjectNew(); // eslint-disable-line no-new
+  initProjectLoadingSpinner();
   initProjectVisibilitySelector();
   initProjectNew.bindEvents();
 });
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
index 0c3926d76b5497f2053717a95ff9c4cbc601f422..4ef0d11dd361682978cca46be312f88c2a2b7fcc 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default class TargetBranchDropdown {
   constructor() {
     this.$dropdown = $('.js-target-branch-dropdown');
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
index 95ed9c7dc21d40b27685dea31776890db91d6183..95b57d5e0485b158bed6b1a3b5f7eccf85e1bbad 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
@@ -1,5 +1,7 @@
 /* eslint-disable class-methods-use-this */
 
+import $ from 'jquery';
+
 const defaultTimezone = 'UTC';
 
 export default class TimezoneDropdown {
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index cfd30d6053f715649eb5c935b1736cba50362308..c3ac54733a3c4ad581092dcd5d99ec45d41b6e4f 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import Translate from '../../../../vue_shared/translate';
 import GlFieldErrors from '../../../../gl_field_errors';
diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
index bb92f4e14597482f6583144d5476ecab0171632a..07b6992eba1ed35f475dff82086318f27042ca85 100644
--- a/app/assets/javascripts/pages/projects/pipelines/charts/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Chart from 'chart.js';
 
 const options = {
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index 25dfa99ad9ca8863038a214d0b19ba56900e4772..a84e2790680217be70fd64f6f2a1af9e63918b61 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
 import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
 import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
 import Translate from '../../../../vue_shared/translate';
+import { convertPermissionToBoolean } from '../../../../lib/utils/common_utils';
 
 Vue.use(Translate);
 
@@ -11,16 +12,28 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
     pipelinesComponent,
   },
   data() {
-    const store = new PipelinesStore();
-
     return {
-      store,
+      store: new PipelinesStore(),
     };
   },
+  created() {
+    this.dataset = document.querySelector(this.$options.el).dataset;
+  },
   render(createElement) {
     return createElement('pipelines-component', {
       props: {
         store: this.store,
+        endpoint: this.dataset.endpoint,
+        helpPagePath: this.dataset.helpPagePath,
+        emptyStateSvgPath: this.dataset.emptyStateSvgPath,
+        errorStateSvgPath: this.dataset.errorStateSvgPath,
+        noPipelinesSvgPath: this.dataset.noPipelinesSvgPath,
+        autoDevopsPath: this.dataset.helpAutoDevopsPath,
+        newPipelinePath: this.dataset.newPipelinePath,
+        canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline),
+        hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi),
+        ciLintPath: this.dataset.ciLintPath,
+        resetCachePath: this.dataset.resetCachePath,
       },
     });
   },
diff --git a/app/assets/javascripts/pages/projects/pipelines/new/index.js b/app/assets/javascripts/pages/projects/pipelines/new/index.js
index da20bd995e9aacf463fa5d66a33a8505dd862ba0..9aa8945e268854e4831962e7e0a6be30f2dee82f 100644
--- a/app/assets/javascripts/pages/projects/pipelines/new/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import NewBranchForm from '~/new_branch_form';
 
 document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index d23ad9a92f4604f594b63a8ad00602bbbd5579f8..c1e3425ec75873cdadf473c897496275433f0923 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,5 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
 
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 import { __ } from '~/locale';
 import { visitUrl } from '~/lib/utils/url_utility';
diff --git a/app/assets/javascripts/pages/projects/releases/edit/index.js b/app/assets/javascripts/pages/projects/releases/edit/index.js
index 0bf53a8de094be357e7ef3454ce9113be46671e1..c70271b09c44fd1efd1b3a690a6b0d3c42b87783 100644
--- a/app/assets/javascripts/pages/projects/releases/edit/index.js
+++ b/app/assets/javascripts/pages/projects/releases/edit/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import initForm from '~/pages/projects/init_form';
 
 document.addEventListener('DOMContentLoaded', () => initForm($('.release-form')));
diff --git a/app/assets/javascripts/pages/projects/settings/badges/index/index.js b/app/assets/javascripts/pages/projects/settings/badges/index/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..304695508662f38e665fcfe5bc67c6a3f7df3209
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/badges/index/index.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { PROJECT_BADGE } from '~/badges/constants';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => {
+  mountBadgeSettings(PROJECT_BADGE);
+});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js b/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..ffc84dc106b2116a92e7a1ad71a95fe7e2c1c791
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js
@@ -0,0 +1,3 @@
+import initForm from '../form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/settings/repository/form.js b/app/assets/javascripts/pages/projects/settings/repository/form.js
new file mode 100644
index 0000000000000000000000000000000000000000..a5c17ab322ca9969d039169a6ead200bb9a645af
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/form.js
@@ -0,0 +1,19 @@
+/* eslint-disable no-new */
+
+import ProtectedTagCreate from '~/protected_tags/protected_tag_create';
+import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
+import initSettingsPanels from '~/settings_panels';
+import initDeployKeys from '~/deploy_keys';
+import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
+import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
+import DueDateSelectors from '~/due_date_select';
+
+export default () => {
+  new ProtectedTagCreate();
+  new ProtectedTagEditList();
+  initDeployKeys();
+  initSettingsPanels();
+  new ProtectedBranchCreate(); // eslint-disable-line no-new
+  new ProtectedBranchEditList(); // eslint-disable-line no-new
+  new DueDateSelectors();
+};
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index 788d86d11926ddce62dc5a3976dddc2338f0832a..ffc84dc106b2116a92e7a1ad71a95fe7e2c1c791 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -1,17 +1,3 @@
-/* eslint-disable no-new */
+import initForm from '../form';
 
-import ProtectedTagCreate from '~/protected_tags/protected_tag_create';
-import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
-import initSettingsPanels from '~/settings_panels';
-import initDeployKeys from '~/deploy_keys';
-import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
-import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
-
-document.addEventListener('DOMContentLoaded', () => {
-  new ProtectedTagCreate();
-  new ProtectedTagEditList();
-  initDeployKeys();
-  initSettingsPanels();
-  new ProtectedBranchCreate(); // eslint-disable-line no-new
-  new ProtectedBranchEditList(); // eslint-disable-line no-new
-});
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/shared/project_avatar.js b/app/assets/javascripts/pages/projects/shared/project_avatar.js
index 56627aa155ce5c4c65a52832937e5d39059cc260..447877752fe0eb21be52437d4458c1820a80578e 100644
--- a/app/assets/javascripts/pages/projects/shared/project_avatar.js
+++ b/app/assets/javascripts/pages/projects/shared/project_avatar.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default function projectAvatar() {
   $('.js-choose-project-avatar-button').bind('click', function onClickAvatar() {
     const form = $(this).closest('form');
diff --git a/app/assets/javascripts/pages/projects/shared/project_new.js b/app/assets/javascripts/pages/projects/shared/project_new.js
deleted file mode 100644
index 86faba0b91044afd1e5bcd59b366e673b57d0d03..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/pages/projects/shared/project_new.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
-
-import VisibilitySelect from '../../../visibility_select';
-
-function highlightChanges($elm) {
-  $elm.addClass('highlight-changes');
-  setTimeout(() => $elm.removeClass('highlight-changes'), 10);
-}
-
-export default class ProjectNew {
-  constructor() {
-    this.toggleSettings = this.toggleSettings.bind(this);
-    this.$selects = $('.features select');
-    this.$repoSelects = this.$selects.filter('.js-repo-select');
-    this.$projectSelects = this.$selects.not('.js-repo-select');
-
-    $('.project-edit-container').on('ajax:before', () => {
-      $('.project-edit-container').hide();
-      return $('.save-project-loader').show();
-    });
-
-    this.initVisibilitySelect();
-
-    this.toggleSettings();
-    this.toggleSettingsOnclick();
-    this.toggleRepoVisibility();
-  }
-
-  initVisibilitySelect() {
-    const visibilityContainer = document.querySelector('.js-visibility-select');
-    if (!visibilityContainer) return;
-    const visibilitySelect = new VisibilitySelect(visibilityContainer);
-    visibilitySelect.init();
-
-    const $visibilitySelect = $(visibilityContainer).find('select');
-    let projectVisibility = $visibilitySelect.val();
-    const PROJECT_VISIBILITY_PRIVATE = '0';
-
-    $visibilitySelect.on('change', () => {
-      const newProjectVisibility = $visibilitySelect.val();
-
-      if (projectVisibility !== newProjectVisibility) {
-        this.$projectSelects.each((idx, select) => {
-          const $select = $(select);
-          const $options = $select.find('option');
-          const values = $.map($options, e => e.value);
-
-          // if switched to "private", limit visibility options
-          if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) {
-            if ($select.val() !== values[0] && $select.val() !== values[1]) {
-              $select.val(values[1]).trigger('change');
-              highlightChanges($select);
-            }
-            $options.slice(2).disable();
-          }
-
-          // if switched from "private", increase visibility for non-disabled options
-          if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) {
-            $options.enable();
-            if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) {
-              $select.val(values[values.length - 1]).trigger('change');
-              highlightChanges($select);
-            }
-          }
-        });
-
-        projectVisibility = newProjectVisibility;
-      }
-    });
-  }
-
-  toggleSettings() {
-    this.$selects.each(function () {
-      var $select = $(this);
-      var className = $select.data('field')
-        .replace(/_/g, '-')
-        .replace('access-level', 'feature');
-      ProjectNew._showOrHide($select, '.' + className);
-    });
-  }
-
-  toggleSettingsOnclick() {
-    this.$selects.on('change', this.toggleSettings);
-  }
-
-  static _showOrHide(checkElement, container) {
-    const $container = $(container);
-
-    if ($(checkElement).val() !== '0') {
-      return $container.show();
-    }
-    return $container.hide();
-  }
-
-  toggleRepoVisibility() {
-    var $repoAccessLevel = $('.js-repo-access-level select');
-    var $lfsEnabledOption = $('.js-lfs-enabled select');
-    var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
-    var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
-    var prevSelectedVal = parseInt($repoAccessLevel.val(), 10);
-
-    this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
-      .nextAll()
-      .hide();
-
-    $repoAccessLevel
-      .off('change')
-      .on('change', function () {
-        var selectedVal = parseInt($repoAccessLevel.val(), 10);
-
-        this.$repoSelects.each(function () {
-          var $this = $(this);
-          var repoSelectVal = parseInt($this.val(), 10);
-
-          $this.find('option').enable();
-
-          if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) {
-            $this.val(selectedVal).trigger('change');
-            highlightChanges($this);
-          }
-
-          $this.find("option[value='" + selectedVal + "']").nextAll().disable();
-        });
-
-        if (selectedVal) {
-          this.$repoSelects.removeClass('disabled');
-
-          if ($lfsEnabledOption.length) {
-            $lfsEnabledOption.removeClass('disabled');
-            highlightChanges($lfsEnabledOption);
-          }
-          if (containerRegistry) {
-            containerRegistry.style.display = '';
-          }
-        } else {
-          this.$repoSelects.addClass('disabled');
-
-          if ($lfsEnabledOption.length) {
-            $lfsEnabledOption.val('false').addClass('disabled');
-            highlightChanges($lfsEnabledOption);
-          }
-          if (containerRegistry) {
-            containerRegistry.style.display = 'none';
-            containerRegistryCheckbox.checked = false;
-          }
-        }
-
-        prevSelectedVal = selectedVal;
-      }.bind(this));
-  }
-}
diff --git a/app/assets/javascripts/pages/projects/shared/save_project_loader.js b/app/assets/javascripts/pages/projects/shared/save_project_loader.js
new file mode 100644
index 0000000000000000000000000000000000000000..aa3589ac88d50b60d0851816161d0768e9e006d7
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/shared/save_project_loader.js
@@ -0,0 +1,12 @@
+import $ from 'jquery';
+
+export default function initProjectLoadingSpinner() {
+  const $formContainer = $('.project-edit-container');
+  const $loadingSpinner = $('.save-project-loader');
+
+  // show loading spinner when saving
+  $formContainer.on('ajax:before', () => {
+    $formContainer.hide();
+    $loadingSpinner.show();
+  });
+}
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 9b87f249f09b1a9a018307ce982074b87637a21b..3b0f0f960b82f88c8b00c4d265613ca6220024f5 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+import initBlob from '~/blob_edit/blob_bundle';
 import ShortcutsNavigation from '~/shortcuts_navigation';
 import NotificationsForm from '~/notifications_form';
 import UserCallout from '~/user_callout';
@@ -18,10 +20,22 @@ document.addEventListener('DOMContentLoaded', () => {
     className: 'js-autodevops-banner',
   });
 
-  if ($('#tree-slider').length) new TreeView(); // eslint-disable-line no-new
-  if ($('.blob-viewer').length) new BlobViewer(); // eslint-disable-line no-new
-  if ($('.project-show-activity').length) new Activities(); // eslint-disable-line no-new
-  $('#tree-slider').waitForImages(() => {
+  // Project show page loads different overview content based on user preferences
+  const treeSlider = document.querySelector('#tree-slider');
+  if (treeSlider) {
+    new TreeView(); // eslint-disable-line no-new
+    initBlob();
+  }
+
+  if (document.querySelector('.blob-viewer')) {
+    new BlobViewer(); // eslint-disable-line no-new
+  }
+
+  if (document.querySelector('.project-show-activity')) {
+    new Activities(); // eslint-disable-line no-new
+  }
+
+  $(treeSlider).waitForImages(() => {
     ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
   });
 });
diff --git a/app/assets/javascripts/pages/projects/snippets/edit/index.js b/app/assets/javascripts/pages/projects/snippets/edit/index.js
index c15f798b63005ca5037094a688f4240dd8519e97..53606acc508f0072897eb60a906371b170ab7776 100644
--- a/app/assets/javascripts/pages/projects/snippets/edit/index.js
+++ b/app/assets/javascripts/pages/projects/snippets/edit/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import initSnippet from '~/snippet/snippet_bundle';
 import initForm from '~/pages/projects/init_form';
 
diff --git a/app/assets/javascripts/pages/projects/snippets/new/index.js b/app/assets/javascripts/pages/projects/snippets/new/index.js
index c15f798b63005ca5037094a688f4240dd8519e97..53606acc508f0072897eb60a906371b170ab7776 100644
--- a/app/assets/javascripts/pages/projects/snippets/new/index.js
+++ b/app/assets/javascripts/pages/projects/snippets/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import initSnippet from '~/snippet/snippet_bundle';
 import initForm from '~/pages/projects/init_form';
 
diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js
index a134599cb048f58921f0db693a614fd571860913..c35b9c300581a19abe37a2231a8221648d732a29 100644
--- a/app/assets/javascripts/pages/projects/snippets/show/index.js
+++ b/app/assets/javascripts/pages/projects/snippets/show/index.js
@@ -1,11 +1,13 @@
 import initNotes from '~/init_notes';
 import ZenMode from '~/zen_mode';
-import LineHighlighter from '../../../../line_highlighter';
-import BlobViewer from '../../../../blob/viewer';
+import LineHighlighter from '~/line_highlighter';
+import BlobViewer from '~/blob/viewer';
+import snippetEmbed from '~/snippet/snippet_embed';
 
 document.addEventListener('DOMContentLoaded', () => {
   new LineHighlighter(); // eslint-disable-line no-new
   new BlobViewer(); // eslint-disable-line no-new
   initNotes();
   new ZenMode(); // eslint-disable-line no-new
+  snippetEmbed();
 });
diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js
index 191c98b36bb2f1392446a45e344d80474b1b243f..8d0edf7e06c9317f94e204d5f0713fcab1865de1 100644
--- a/app/assets/javascripts/pages/projects/tags/new/index.js
+++ b/app/assets/javascripts/pages/projects/tags/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import RefSelectDropdown from '../../../../ref_select_dropdown';
 import ZenMode from '../../../../zen_mode';
 import GLForm from '../../../../gl_form';
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index ed7d3f1747cc5a075f5fd6b70ca8e04ff7722276..7ad082a5e6167cc050477c02eb27005af2845979 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import initBlob from '~/blob_edit/blob_bundle';
 import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index b9f8707fd6e5482df43ab1ef657ca14aca3d4826..ec01c66ffda3fe6a04e8d2cbe505a4a2eab28b75 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Wikis from './wikis';
 import ShortcutsWiki from '../../../shortcuts_wiki';
 import ZenMode from '../../../zen_mode';
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index cf44e291199ef2299b1cf7db6fef3ad95ae8a637..2e1fe78b3fa6baecd1864d151c159cfb7f1da865 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Flash from '~/flash';
 import Api from '~/api';
 
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index a0aa04997764dcb7e9803190a47e3e48ffe6cd4d..80a7114f94d5d3195b1c461601b2168891f4e679 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import UsernameValidator from './username_validator';
 import SigninTabsMemoizer from './signin_tabs_memoizer';
 import OAuthRememberMe from './oauth_remember_me';
diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
index ffc2dd6bbca857386e8b78619f6c94f4e9998a84..5303004529272d225a943cf99144ab21882a8857 100644
--- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
+++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 /**
  * OAuth-based login buttons have a separate "remember me" checkbox.
  *
diff --git a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
index 08f0afdcce3f31ccf08e76624396ddd935b84cac..d321892d2d2fbac41414b4577bf601cd2cb2ccfb 100644
--- a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
@@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
  * Does that setting the current selected tab in the localStorage
  */
 export default class SigninTabsMemoizer {
-  constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
+  constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.new-session-tabs' } = {}) {
     this.currentTabKey = currentTabKey;
     this.tabSelector = tabSelector;
     this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index 745543c22daed96a4bdf996085510dd442e7e0fb..825de01b5a2493a96d038e9511983c374a036c59 100644
--- a/app/assets/javascripts/pages/sessions/new/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
@@ -1,5 +1,6 @@
 /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
 
+import $ from 'jquery';
 import _ from 'underscore';
 import axios from '~/lib/utils/axios_utils';
 import flash from '~/flash';
diff --git a/app/assets/javascripts/pages/shared/mount_badge_settings.js b/app/assets/javascripts/pages/shared/mount_badge_settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..1397c0834ff9549f9a79bdfb4351f41db402be43
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/mount_badge_settings.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import BadgeSettings from '~/badges/components/badge_settings.vue';
+import store from '~/badges/store';
+
+export default kind => {
+  const badgeSettingsElement = document.getElementById('badge-settings');
+
+  store.dispatch('loadBadges', {
+    kind,
+    apiEndpointUrl: badgeSettingsElement.dataset.apiEndpointUrl,
+    docsUrl: badgeSettingsElement.dataset.docsUrl,
+  });
+
+  return new Vue({
+    el: badgeSettingsElement,
+    store,
+    components: {
+      BadgeSettings,
+    },
+    render(createElement) {
+      return createElement(BadgeSettings);
+    },
+  });
+};
diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js
index f996d3cd74e8a9ee9813213037ea6bec0961e3c2..72d05da1069dfd163d190e87ea7be1610cb1a8d5 100644
--- a/app/assets/javascripts/pages/snippets/form.js
+++ b/app/assets/javascripts/pages/snippets/form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import GLForm from '~/gl_form';
 import ZenMode from '~/zen_mode';
 
diff --git a/app/assets/javascripts/pages/snippets/show/index.js b/app/assets/javascripts/pages/snippets/show/index.js
index f548b9fad65097dbb4731adf12ddb97f024673a2..269361104026ebfd1144e3f1818604876a943101 100644
--- a/app/assets/javascripts/pages/snippets/show/index.js
+++ b/app/assets/javascripts/pages/snippets/show/index.js
@@ -1,11 +1,13 @@
-import LineHighlighter from '../../../line_highlighter';
-import BlobViewer from '../../../blob/viewer';
-import ZenMode from '../../../zen_mode';
-import initNotes from '../../../init_notes';
+import LineHighlighter from '~/line_highlighter';
+import BlobViewer from '~/blob/viewer';
+import ZenMode from '~/zen_mode';
+import initNotes from '~/init_notes';
+import snippetEmbed from '~/snippet/snippet_embed';
 
 document.addEventListener('DOMContentLoaded', () => {
   new LineHighlighter(); // eslint-disable-line no-new
   new BlobViewer(); // eslint-disable-line no-new
   initNotes();
   new ZenMode(); // eslint-disable-line no-new
+  snippetEmbed();
 });
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 57306322aa47958f6d4ec58a4b09b5b7e45d5b2a..8ce938c958bdebec0f68928779f973fbd672f5dc 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import { scaleLinear, scaleThreshold } from 'd3-scale';
 import { select } from 'd3-selection';
diff --git a/app/assets/javascripts/pages/users/index.js b/app/assets/javascripts/pages/users/index.js
index 899dcd42e377fb3c9868c5e9d4174b629ec77243..6b1626b016143f298faa0ab55a4453c2355464a1 100644
--- a/app/assets/javascripts/pages/users/index.js
+++ b/app/assets/javascripts/pages/users/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import UserCallout from '~/user_callout';
 import Cookies from 'js-cookie';
 import UserTabs from './user_tabs';
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index c12176234676a13c74a4cf953914ae10c0fa50cb..124bc2ba7105ff5618fef7f7cfd2128593b3450b 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import axios from '~/lib/utils/axios_utils';
 import Activities from '~/activities';
 import { localTimeAgo } from '~/lib/utils/datetime_utility';
diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js
deleted file mode 100644
index 0562a681c4bb699f3958e282b7bd23cd1a48b678..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/performance_bar.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import 'vendor/peek';
-import 'vendor/peek.performance_bar';
-import { getParameterValues } from './lib/utils/url_utility';
-
-export default class PerformanceBar {
-  constructor(opts) {
-    if (!PerformanceBar.singleton) {
-      this.init(opts);
-      PerformanceBar.singleton = this;
-    }
-    return PerformanceBar.singleton;
-  }
-
-  init(opts) {
-    const $container = $(opts.container);
-    this.$sqlProfileLink = $container.find('.js-toggle-modal-peek-sql');
-    this.$sqlProfileModal = $container.find('#modal-peek-pg-queries');
-    this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
-    this.$lineProfileModal = $('#modal-peek-line-profile');
-    this.initEventListeners();
-    this.showModalOnLoad();
-  }
-
-  initEventListeners() {
-    this.$sqlProfileLink.on('click', () => this.handleSQLProfileLink());
-    this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
-    $(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
-  }
-
-  showModalOnLoad() {
-    // When a lineprofiler query-string param is present, we show the line
-    // profiler modal upon page load
-    if (/lineprofiler/.test(window.location.search)) {
-      PerformanceBar.toggleModal(this.$lineProfileModal);
-    }
-  }
-
-  handleSQLProfileLink() {
-    PerformanceBar.toggleModal(this.$sqlProfileModal);
-  }
-
-  handleLineProfileLink(e) {
-    const lineProfilerParameter = getParameterValues('lineprofiler');
-    const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
-    const shouldToggleModal = lineProfilerParameter.length > 0 &&
-      lineProfilerParameterRegex.test(e.currentTarget.href);
-
-    if (shouldToggleModal) {
-      e.preventDefault();
-      PerformanceBar.toggleModal(this.$lineProfileModal);
-    }
-  }
-
-  static toggleModal($modal) {
-    if ($modal.length) {
-      $modal.modal('toggle');
-    }
-  }
-
-  static toggleLineProfileFile(e) {
-    $(e.currentTarget).parents('.peek-rblineprof-file').find('.data').toggle();
-  }
-}
diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
new file mode 100644
index 0000000000000000000000000000000000000000..db8a0055acd033fa2978285c7805bcf787324d02
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
@@ -0,0 +1,93 @@
+<script>
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+
+export default {
+  components: {
+    GlModal,
+  },
+  props: {
+    currentRequest: {
+      type: Object,
+      required: true,
+    },
+    metric: {
+      type: String,
+      required: true,
+    },
+    header: {
+      type: String,
+      required: true,
+    },
+    details: {
+      type: String,
+      required: true,
+    },
+    keys: {
+      type: Array,
+      required: true,
+    },
+  },
+  computed: {
+    metricDetails() {
+      return this.currentRequest.details[this.metric];
+    },
+    detailsList() {
+      return this.metricDetails[this.details];
+    },
+  },
+};
+</script>
+<template>
+  <div
+    :id="`peek-view-${metric}`"
+    class="view"
+    v-if="currentRequest.details"
+  >
+    <button
+      :data-target="`#modal-peek-${metric}-details`"
+      class="btn-blank btn-link bold"
+      type="button"
+      data-toggle="modal"
+    >
+      {{ metricDetails.duration }}
+      /
+      {{ metricDetails.calls }}
+    </button>
+    <gl-modal
+      :id="`modal-peek-${metric}-details`"
+      :header-title-text="header"
+      class="performance-bar-modal"
+    >
+      <table
+        class="table"
+      >
+        <template v-if="detailsList.length">
+          <tr
+            v-for="(item, index) in detailsList"
+            :key="index"
+          >
+            <td><strong>{{ item.duration }}ms</strong></td>
+            <td
+              v-for="key in keys"
+              :key="key"
+              class="break-word"
+            >
+              {{ item[key] }}
+            </td>
+          </tr>
+        </template>
+        <template v-else>
+          <tr>
+            <td>
+              No {{ header.toLowerCase() }} for this request.
+            </td>
+          </tr>
+        </template>
+      </table>
+
+      <div slot="footer">
+      </div>
+    </gl-modal>
+    {{ metric }}
+  </div>
+</template>
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2fd1715ee798b81fea030dd2a58e04efaf672db7
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -0,0 +1,191 @@
+<script>
+import $ from 'jquery';
+
+import PerformanceBarService from '../services/performance_bar_service';
+import detailedMetric from './detailed_metric.vue';
+import requestSelector from './request_selector.vue';
+import simpleMetric from './simple_metric.vue';
+import upstreamPerformanceBar from './upstream_performance_bar.vue';
+
+import Flash from '../../flash';
+
+export default {
+  components: {
+    detailedMetric,
+    requestSelector,
+    simpleMetric,
+    upstreamPerformanceBar,
+  },
+  props: {
+    store: {
+      type: Object,
+      required: true,
+    },
+    env: {
+      type: String,
+      required: true,
+    },
+    requestId: {
+      type: String,
+      required: true,
+    },
+    peekUrl: {
+      type: String,
+      required: true,
+    },
+    profileUrl: {
+      type: String,
+      required: true,
+    },
+  },
+  detailedMetrics: [
+    { metric: 'pg', header: 'SQL queries', details: 'queries', keys: ['sql'] },
+    {
+      metric: 'gitaly',
+      header: 'Gitaly calls',
+      details: 'details',
+      keys: ['feature', 'request'],
+    },
+  ],
+  simpleMetrics: ['redis', 'sidekiq'],
+  data() {
+    return { currentRequestId: '' };
+  },
+  computed: {
+    requests() {
+      return this.store.requestsWithDetails();
+    },
+    currentRequest: {
+      get() {
+        return this.store.findRequest(this.currentRequestId);
+      },
+      set(requestId) {
+        this.currentRequestId = requestId;
+      },
+    },
+    initialRequest() {
+      return this.currentRequestId === this.requestId;
+    },
+    lineProfileModal() {
+      return $('#modal-peek-line-profile');
+    },
+  },
+  mounted() {
+    this.interceptor = PerformanceBarService.registerInterceptor(
+      this.peekUrl,
+      this.loadRequestDetails,
+    );
+
+    this.loadRequestDetails(this.requestId, window.location.href);
+    this.currentRequest = this.requestId;
+
+    if (this.lineProfileModal.length) {
+      this.lineProfileModal.modal('toggle');
+    }
+  },
+  beforeDestroy() {
+    PerformanceBarService.removeInterceptor(this.interceptor);
+  },
+  methods: {
+    loadRequestDetails(requestId, requestUrl) {
+      if (!this.store.canTrackRequest(requestUrl)) {
+        return;
+      }
+
+      this.store.addRequest(requestId, requestUrl);
+
+      PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
+        .then(res => {
+          this.store.addRequestDetails(requestId, res.data.data);
+        })
+        .catch(() =>
+          Flash(`Error getting performance bar results for ${requestId}`),
+        );
+    },
+    changeCurrentRequest(newRequestId) {
+      this.currentRequest = newRequestId;
+    },
+  },
+};
+</script>
+<template>
+  <div
+    id="js-peek"
+    :class="env"
+  >
+    <div
+      v-if="currentRequest"
+      class="container-fluid container-limited"
+    >
+      <div
+        id="peek-view-host"
+        class="view"
+      >
+        <span
+          v-if="currentRequest.details"
+          class="current-host"
+        >
+          {{ currentRequest.details.host.hostname }}
+        </span>
+      </div>
+      <upstream-performance-bar
+        v-if="initialRequest && currentRequest.details"
+      />
+      <detailed-metric
+        v-for="metric in $options.detailedMetrics"
+        :key="metric.metric"
+        :current-request="currentRequest"
+        :metric="metric.metric"
+        :header="metric.header"
+        :details="metric.details"
+        :keys="metric.keys"
+      />
+      <div
+        v-if="initialRequest"
+        id="peek-view-rblineprof"
+        class="view"
+      >
+        <button
+          v-if="lineProfileModal.length"
+          class="btn-link btn-blank"
+          data-toggle="modal"
+          data-target="#modal-peek-line-profile"
+        >
+          profile
+        </button>
+        <a
+          v-else
+          :href="profileUrl"
+        >
+          profile
+        </a>
+      </div>
+      <simple-metric
+        v-for="metric in $options.simpleMetrics"
+        :current-request="currentRequest"
+        :key="metric"
+        :metric="metric"
+      />
+      <div
+        id="peek-view-gc"
+        class="view"
+      >
+        <span
+          v-if="currentRequest.details"
+          class="bold"
+        >
+          <span title="Invoke Time">{{ currentRequest.details.gc.gc_time }}</span>ms
+          /
+          <span title="Invoke Count">{{ currentRequest.details.gc.invokes }}</span>
+          gc
+        </span>
+      </div>
+      <request-selector
+        v-if="currentRequest"
+        :current-request="currentRequest"
+        :requests="requests"
+        @change-current-request="changeCurrentRequest"
+      />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/performance_bar/components/request_selector.vue b/app/assets/javascripts/performance_bar/components/request_selector.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3ed07a4a47d463ffeed37c26956a8be7aa6b92d9
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/components/request_selector.vue
@@ -0,0 +1,52 @@
+<script>
+export default {
+  props: {
+    currentRequest: {
+      type: Object,
+      required: true,
+    },
+    requests: {
+      type: Array,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      currentRequestId: this.currentRequest.id,
+    };
+  },
+  watch: {
+    currentRequestId(newRequestId) {
+      this.$emit('change-current-request', newRequestId);
+    },
+  },
+  methods: {
+    truncatedUrl(requestUrl) {
+      const components = requestUrl.replace(/\/$/, '').split('/');
+      let truncated = components[components.length - 1];
+
+      if (truncated.match(/^\d+$/)) {
+        truncated = `${components[components.length - 2]}/${truncated}`;
+      }
+
+      return truncated;
+    },
+  },
+};
+</script>
+<template>
+  <div
+    id="peek-request-selector"
+    class="pull-right"
+  >
+    <select v-model="currentRequestId">
+      <option
+        v-for="request in requests"
+        :key="request.id"
+        :value="request.id"
+      >
+        {{ truncatedUrl(request.url) }}
+      </option>
+    </select>
+  </div>
+</template>
diff --git a/app/assets/javascripts/performance_bar/components/simple_metric.vue b/app/assets/javascripts/performance_bar/components/simple_metric.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b654bc6624982fdd091d963290cb6e2d99092339
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/components/simple_metric.vue
@@ -0,0 +1,30 @@
+<script>
+export default {
+  props: {
+    currentRequest: {
+      type: Object,
+      required: true,
+    },
+    metric: {
+      type: String,
+      required: true,
+    },
+  },
+};
+</script>
+<template>
+  <div
+    :id="`peek-view-${metric}`"
+    class="view"
+  >
+    <span
+      v-if="currentRequest.details"
+      class="bold"
+    >
+      {{ currentRequest.details[metric].duration }}
+      /
+      {{ currentRequest.details[metric].calls }}
+    </span>
+    {{ metric }}
+  </div>
+</template>
diff --git a/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue b/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2b5915f381f394ad358f32cdfbcffb0ff0d50b00
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue
@@ -0,0 +1,20 @@
+<script>
+export default {
+  mounted() {
+    const upstreamPerformanceBar = document
+      .getElementById('peek-view-performance-bar')
+      .cloneNode(true);
+
+    upstreamPerformanceBar.classList.remove('hidden');
+
+    this.$refs.wrapper.appendChild(upstreamPerformanceBar);
+  },
+};
+</script>
+<template>
+  <div
+    id="peek-view-performance-bar-vue"
+    class="view"
+    ref="wrapper"
+  ></div>
+</template>
diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0ddf36a672706a658ca2b49b4957b19a160f632
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/index.js
@@ -0,0 +1,37 @@
+import 'vendor/peek.performance_bar';
+
+import Vue from 'vue';
+import performanceBarApp from './components/performance_bar_app.vue';
+import PerformanceBarStore from './stores/performance_bar_store';
+
+export default ({ container }) =>
+  new Vue({
+    el: container,
+    components: {
+      performanceBarApp,
+    },
+    data() {
+      const performanceBarData = document.querySelector(this.$options.el)
+        .dataset;
+      const store = new PerformanceBarStore();
+
+      return {
+        store,
+        env: performanceBarData.env,
+        requestId: performanceBarData.requestId,
+        peekUrl: performanceBarData.peekUrl,
+        profileUrl: performanceBarData.profileUrl,
+      };
+    },
+    render(createElement) {
+      return createElement('performance-bar-app', {
+        props: {
+          store: this.store,
+          env: this.env,
+          requestId: this.requestId,
+          peekUrl: this.peekUrl,
+          profileUrl: this.profileUrl,
+        },
+      });
+    },
+  });
diff --git a/app/assets/javascripts/performance_bar/services/performance_bar_service.js b/app/assets/javascripts/performance_bar/services/performance_bar_service.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc71911ae358b5c2c038a4c4d51327782f71ed81
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/services/performance_bar_service.js
@@ -0,0 +1,41 @@
+import Vue from 'vue';
+import _ from 'underscore';
+import axios from '../../lib/utils/axios_utils';
+
+let vueResourceInterceptor;
+
+export default class PerformanceBarService {
+  static fetchRequestDetails(peekUrl, requestId) {
+    return axios.get(peekUrl, { params: { request_id: requestId } });
+  }
+
+  static registerInterceptor(peekUrl, callback) {
+    const interceptor = response => {
+      const requestId = response.headers['x-request-id'];
+      // Get the request URL from response.config for Axios, and response for
+      // Vue Resource.
+      const requestUrl = (response.config || response).url;
+      const cachedResponse = response.headers['x-gitlab-from-cache'] === 'true';
+
+      if (requestUrl !== peekUrl && requestId && !cachedResponse) {
+        callback(requestId, requestUrl);
+      }
+
+      return response;
+    };
+
+    vueResourceInterceptor = (request, next) => next(interceptor);
+
+    Vue.http.interceptors.push(vueResourceInterceptor);
+
+    return axios.interceptors.response.use(interceptor);
+  }
+
+  static removeInterceptor(interceptor) {
+    axios.interceptors.response.eject(interceptor);
+    Vue.http.interceptors = _.without(
+      Vue.http.interceptors,
+      vueResourceInterceptor,
+    );
+  }
+}
diff --git a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
new file mode 100644
index 0000000000000000000000000000000000000000..c6b2f55243c703a2bfdc2bb4f054e494bf4ca1d1
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
@@ -0,0 +1,39 @@
+export default class PerformanceBarStore {
+  constructor() {
+    this.requests = [];
+  }
+
+  addRequest(requestId, requestUrl, requestDetails) {
+    if (!this.findRequest(requestId)) {
+      this.requests.push({
+        id: requestId,
+        url: requestUrl,
+        details: requestDetails,
+      });
+    }
+
+    return this.requests;
+  }
+
+  findRequest(requestId) {
+    return this.requests.find(request => request.id === requestId);
+  }
+
+  addRequestDetails(requestId, requestDetails) {
+    const request = this.findRequest(requestId);
+
+    request.details = requestDetails;
+
+    return request;
+  }
+
+  requestsWithDetails() {
+    return this.requests.filter(request => request.details);
+  }
+
+  canTrackRequest(requestUrl) {
+    return (
+      this.requests.filter(request => request.url === requestUrl).length < 2
+    );
+  }
+}
diff --git a/app/assets/javascripts/pipelines/components/blank_state.vue b/app/assets/javascripts/pipelines/components/blank_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8d3d6223d7b6c271eb97e53c1cbb3e389069e39d
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/blank_state.vue
@@ -0,0 +1,32 @@
+<script>
+  export default {
+    name: 'PipelinesSvgState',
+    props: {
+      svgPath: {
+        type: String,
+        required: true,
+      },
+
+      message: {
+        type: String,
+        required: true,
+      },
+    },
+  };
+</script>
+
+<template>
+  <div class="row empty-state">
+    <div class="col-xs-12">
+      <div class="svg-content">
+        <img :src="svgPath" />
+      </div>
+    </div>
+
+    <div class="col-xs-12 text-center">
+      <div class="text-content">
+        <h4>{{ message }}</h4>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue
index dfaa2574091b864c26faa8422bab27eaa740225f..10ac8c08bedd7319d9cb3d23a303e06ccbcef800 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/empty_state.vue
@@ -1,5 +1,6 @@
 <script>
   export default {
+    name: 'PipelinesEmptyState',
     props: {
       helpPagePath: {
         type: String,
@@ -9,6 +10,10 @@
         type: String,
         required: true,
       },
+      canSetCi: {
+        type: Boolean,
+        required: true,
+      },
     },
   };
 </script>
@@ -22,22 +27,36 @@
 
     <div class="col-xs-12">
       <div class="text-content">
-        <h4 class="text-center">
-          {{ s__("Pipelines|Build with confidence") }}
-        </h4>
-        <p>
-          {{ s__(`Pipelines|Continous Integration can help
-catch bugs by running your tests automatically,
-while Continuous Deployment can help you deliver code to your product environment.`) }}
+
+        <template v-if="canSetCi">
+          <h4 class="text-center">
+            {{ s__('Pipelines|Build with confidence') }}
+          </h4>
+
+          <p>
+            {{ s__(`Pipelines|Continous Integration can help
+                catch bugs by running your tests automatically,
+                while Continuous Deployment can help you deliver
+                code to your product environment.`) }}
+          </p>
+
+          <div class="text-center">
+            <a
+              :href="helpPagePath"
+              class="btn btn-primary js-get-started-pipelines"
+            >
+              {{ s__('Pipelines|Get started with Pipelines') }}
+            </a>
+          </div>
+        </template>
+
+        <p
+          v-else
+          class="text-center"
+        >
+          {{ s__('Pipelines|This project is not currently set up to run pipelines.') }}
         </p>
-        <div class="text-center">
-          <a
-            :href="helpPagePath"
-            class="btn btn-info"
-          >
-            {{ s__("Pipelines|Get started with Pipelines") }}
-          </a>
-        </div>
+
       </div>
     </div>
   </div>
diff --git a/app/assets/javascripts/pipelines/components/error_state.vue b/app/assets/javascripts/pipelines/components/error_state.vue
deleted file mode 100644
index 012853b201d3461e5248c494e2c88ea83756fbdb..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/pipelines/components/error_state.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-export default {
-  props: {
-    errorStateSvgPath: {
-      type: String,
-      required: true,
-    },
-  },
-};
-</script>
-
-<template>
-  <div class="row empty-state js-pipelines-error-state">
-    <div class="col-xs-12">
-      <div class="svg-content">
-        <img :src="errorStateSvgPath"/>
-      </div>
-    </div>
-
-    <div class="col-xs-12 text-center">
-      <div class="text-content">
-        <h4>The API failed to fetch the pipelines.</h4>
-      </div>
-    </div>
-  </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index d7effb27bffcca07df38254d465144e8ba741cc7..e99d949801f3b23c68f78692afb185f186d3eab1 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -1,60 +1,72 @@
 <script>
-  import tooltip from '../../../vue_shared/directives/tooltip';
-  import icon from '../../../vue_shared/components/icon.vue';
-  import { dasherize } from '../../../lib/utils/text_utility';
-  /**
-   * Renders either a cancel, retry or play icon pointing to the given path.
-   * TODO: Remove UJS from here and use an async request instead.
-   */
-  export default {
-    components: {
-      icon,
-    },
+import $ from 'jquery';
+import tooltip from '../../../vue_shared/directives/tooltip';
+import Icon from '../../../vue_shared/components/icon.vue';
+import { dasherize } from '../../../lib/utils/text_utility';
+import eventHub from '../../event_hub';
+/**
+ * Renders either a cancel, retry or play icon pointing to the given path.
+ */
+export default {
+  components: {
+    Icon,
+  },
 
-    directives: {
-      tooltip,
-    },
+  directives: {
+    tooltip,
+  },
 
-    props: {
-      tooltipText: {
-        type: String,
-        required: true,
-      },
+  props: {
+    tooltipText: {
+      type: String,
+      required: true,
+    },
 
-      link: {
-        type: String,
-        required: true,
-      },
+    link: {
+      type: String,
+      required: true,
+    },
 
-      actionMethod: {
-        type: String,
-        required: true,
-      },
+    actionIcon: {
+      type: String,
+      required: true,
+    },
 
-      actionIcon: {
-        type: String,
-        required: true,
-      },
+    buttonDisabled: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
+  computed: {
+    cssClass() {
+      const actionIconDash = dasherize(this.actionIcon);
+      return `${actionIconDash} js-icon-${actionIconDash}`;
+    },
+    isDisabled() {
+      return this.buttonDisabled === this.link;
     },
+  },
 
-    computed: {
-      cssClass() {
-        const actionIconDash = dasherize(this.actionIcon);
-        return `${actionIconDash} js-icon-${actionIconDash}`;
-      },
+  methods: {
+    onClickAction() {
+      $(this.$el).tooltip('hide');
+      eventHub.$emit('graphAction', this.link);
     },
-  };
+  },
+};
 </script>
 <template>
-  <a
+  <button
+    type="button"
+    @click="onClickAction"
     v-tooltip
-    :data-method="actionMethod"
     :title="tooltipText"
-    :href="link"
-    class="ci-action-icon-container ci-action-icon-wrapper"
+    class="btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper"
     :class="cssClass"
     data-container="body"
+    :disabled="isDisabled"
   >
     <icon :name="actionIcon" />
-  </a>
+  </button>
 </template>
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 b86e95f0b4ab12020c87571eda6b52d75fd02467..be213c2ee78ff8a6fec6fce26d27082b8dc1a94c 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -1,4 +1,5 @@
 <script>
+  import $ from 'jquery';
   import jobNameComponent from './job_name_component.vue';
   import jobComponent from './job_component.vue';
   import tooltip from '../../../vue_shared/directives/tooltip';
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index ab84711d4a278898d93da60c2e2c7cff12ef90f1..ac9ce7e47d673d2a01ae0fa4edb8e9601d365932 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,54 +1,59 @@
 <script>
-  import loadingIcon from '~/vue_shared/components/loading_icon.vue';
-  import stageColumnComponent from './stage_column_component.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import StageColumnComponent from './stage_column_component.vue';
 
-  export default {
-    components: {
-      stageColumnComponent,
-      loadingIcon,
-    },
+export default {
+  components: {
+    StageColumnComponent,
+    LoadingIcon,
+  },
 
-    props: {
-      isLoading: {
-        type: Boolean,
-        required: true,
-      },
-      pipeline: {
-        type: Object,
-        required: true,
-      },
+  props: {
+    isLoading: {
+      type: Boolean,
+      required: true,
+    },
+    pipeline: {
+      type: Object,
+      required: true,
+    },
+    actionDisabled: {
+      type: String,
+      required: false,
+      default: null,
     },
+  },
 
-    computed: {
-      graph() {
-        return this.pipeline.details && this.pipeline.details.stages;
-      },
+  computed: {
+    graph() {
+      return this.pipeline.details && this.pipeline.details.stages;
     },
+  },
 
-    methods: {
-      capitalizeStageName(name) {
-        return name.charAt(0).toUpperCase() + name.slice(1);
-      },
+  methods: {
+    capitalizeStageName(name) {
+      return name.charAt(0).toUpperCase() + name.slice(1);
+    },
 
-      isFirstColumn(index) {
-        return index === 0;
-      },
+    isFirstColumn(index) {
+      return index === 0;
+    },
 
-      stageConnectorClass(index, stage) {
-        let className;
+    stageConnectorClass(index, stage) {
+      let className;
 
-        // If it's the first stage column and only has one job
-        if (index === 0 && stage.groups.length === 1) {
-          className = 'no-margin';
-        } else if (index > 0) {
-          // If it is not the first column
-          className = 'left-margin';
-        }
+      // If it's the first stage column and only has one job
+      if (index === 0 && stage.groups.length === 1) {
+        className = 'no-margin';
+      } else if (index > 0) {
+        // If it is not the first column
+        className = 'left-margin';
+      }
 
-        return className;
-      },
+      return className;
     },
-  };
+  },
+};
 </script>
 <template>
   <div class="build-content middle-block js-pipeline-graph">
@@ -70,6 +75,7 @@
           :key="stage.name"
           :stage-connector-class="stageConnectorClass(index, stage)"
           :is-first-column="isFirstColumn(index)"
+          :action-disabled="actionDisabled"
         />
       </ul>
     </div>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index 9b136573135bc3caa06c6c19552c03a81245de86..c6e5ae6df418ccb70824c0616e1ea4de163a118c 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -1,95 +1,102 @@
 <script>
-  import actionComponent from './action_component.vue';
-  import dropdownActionComponent from './dropdown_action_component.vue';
-  import jobNameComponent from './job_name_component.vue';
-  import tooltip from '../../../vue_shared/directives/tooltip';
-
-  /**
-   * Renders the badge for the pipeline graph and the job's dropdown.
-   *
-   * The following object should be provided as `job`:
-   *
-   * {
-   *   "id": 4256,
-   *   "name": "test",
-   *   "status": {
-   *     "icon": "icon_status_success",
-   *     "text": "passed",
-   *     "label": "passed",
-   *     "group": "success",
-   *     "details_path": "/root/ci-mock/builds/4256",
-   *     "action": {
-   *       "icon": "retry",
-   *       "title": "Retry",
-   *       "path": "/root/ci-mock/builds/4256/retry",
-   *       "method": "post"
-   *     }
-   *   }
-   * }
-   */
-
-  export default {
-    components: {
-      actionComponent,
-      dropdownActionComponent,
-      jobNameComponent,
+import ActionComponent from './action_component.vue';
+import DropdownActionComponent from './dropdown_action_component.vue';
+import JobNameComponent from './job_name_component.vue';
+import tooltip from '../../../vue_shared/directives/tooltip';
+
+/**
+ * Renders the badge for the pipeline graph and the job's dropdown.
+ *
+ * The following object should be provided as `job`:
+ *
+ * {
+ *   "id": 4256,
+ *   "name": "test",
+ *   "status": {
+ *     "icon": "icon_status_success",
+ *     "text": "passed",
+ *     "label": "passed",
+ *     "group": "success",
+ *     "tooltip": "passed",
+ *     "details_path": "/root/ci-mock/builds/4256",
+ *     "action": {
+ *       "icon": "retry",
+ *       "title": "Retry",
+ *       "path": "/root/ci-mock/builds/4256/retry",
+ *       "method": "post"
+ *     }
+ *   }
+ * }
+ */
+
+export default {
+  components: {
+    ActionComponent,
+    DropdownActionComponent,
+    JobNameComponent,
+  },
+
+  directives: {
+    tooltip,
+  },
+  props: {
+    job: {
+      type: Object,
+      required: true,
     },
 
-    directives: {
-      tooltip,
+    cssClassJobName: {
+      type: String,
+      required: false,
+      default: '',
     },
-    props: {
-      job: {
-        type: Object,
-        required: true,
-      },
-
-      cssClassJobName: {
-        type: String,
-        required: false,
-        default: '',
-      },
-
-      isDropdown: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
+
+    isDropdown: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+
+    actionDisabled: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
+
+  computed: {
+    status() {
+      return this.job && this.job.status ? this.job.status : {};
+    },
+
+    tooltipText() {
+      const textBuilder = [];
+
+      if (this.job.name) {
+        textBuilder.push(this.job.name);
+      }
+
+      if (this.job.name && this.status.tooltip) {
+        textBuilder.push('-');
+      }
+
+      if (this.status.tooltip) {
+        textBuilder.push(`${this.job.status.tooltip}`);
+      }
+
+      return textBuilder.join(' ');
     },
 
-    computed: {
-      status() {
-        return this.job && this.job.status ? this.job.status : {};
-      },
-
-      tooltipText() {
-        const textBuilder = [];
-
-        if (this.job.name) {
-          textBuilder.push(this.job.name);
-        }
-
-        if (this.job.name && this.status.label) {
-          textBuilder.push('-');
-        }
-
-        if (this.status.label) {
-          textBuilder.push(`${this.job.status.label}`);
-        }
-
-        return textBuilder.join(' ');
-      },
-
-      /**
-       * Verifies if the provided job has an action path
-       *
-       * @return {Boolean}
-       */
-      hasAction() {
-        return this.job.status && this.job.status.action && this.job.status.action.path;
-      },
+    /**
+     * Verifies if the provided job has an action path
+     *
+     * @return {Boolean}
+     */
+    hasAction() {
+      return this.job.status && this.job.status.action && this.job.status.action.path;
     },
-  };
+  },
+};
 </script>
 <template>
   <div class="ci-job-component">
@@ -100,6 +107,7 @@
       :title="tooltipText"
       :class="cssClassJobName"
       data-container="body"
+      data-html="true"
       class="js-pipeline-graph-job-link"
     >
 
@@ -115,6 +123,7 @@
       class="js-job-component-tooltip"
       :title="tooltipText"
       :class="cssClassJobName"
+      data-html="true"
       data-container="body"
     >
 
@@ -129,7 +138,7 @@
       :tooltip-text="status.action.title"
       :link="status.action.path"
       :action-icon="status.action.icon"
-      :action-method="status.action.method"
+      :button-disabled="actionDisabled"
     />
 
     <dropdown-action-component
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 7adcf4017b840ed5d17a2611790975d2e79e63fc..f6e6569e15b93cddcfd4384a0e3ea30b97d13a7e 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,50 +1,55 @@
 <script>
-  import jobComponent from './job_component.vue';
-  import dropdownJobComponent from './dropdown_job_component.vue';
+import JobComponent from './job_component.vue';
+import DropdownJobComponent from './dropdown_job_component.vue';
 
-  export default {
-    components: {
-      jobComponent,
-      dropdownJobComponent,
+export default {
+  components: {
+    JobComponent,
+    DropdownJobComponent,
+  },
+  props: {
+    title: {
+      type: String,
+      required: true,
     },
-    props: {
-      title: {
-        type: String,
-        required: true,
-      },
 
-      jobs: {
-        type: Array,
-        required: true,
-      },
+    jobs: {
+      type: Array,
+      required: true,
+    },
 
-      isFirstColumn: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
+    isFirstColumn: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
 
-      stageConnectorClass: {
-        type: String,
-        required: false,
-        default: '',
-      },
+    stageConnectorClass: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    actionDisabled: {
+      type: String,
+      required: false,
+      default: null,
     },
+  },
 
-    methods: {
-      firstJob(list) {
-        return list[0];
-      },
+  methods: {
+    firstJob(list) {
+      return list[0];
+    },
 
-      jobId(job) {
-        return `ci-badge-${job.name}`;
-      },
+    jobId(job) {
+      return `ci-badge-${job.name}`;
+    },
 
-      buildConnnectorClass(index) {
-        return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
-      },
+    buildConnnectorClass(index) {
+      return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
     },
-  };
+  },
+};
 </script>
 <template>
   <li
@@ -69,6 +74,7 @@
             v-if="job.size === 1"
             :job="job"
             css-class-job-name="build-content"
+            :action-disabled="actionDisabled"
           />
 
           <dropdown-job-component
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue
index f31a91c340311054b435db7c6b2675b63fe3f65e..eba5678e3e582388e111478181766dcf9c12be3f 100644
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/nav_controls.vue
@@ -1,67 +1,67 @@
 <script>
-export default {
-  name: 'PipelineNavControls',
-  props: {
-    newPipelinePath: {
-      type: String,
-      required: true,
-    },
+  import LoadingButton from '../../vue_shared/components/loading_button.vue';
 
-    hasCiEnabled: {
-      type: Boolean,
-      required: true,
+  export default {
+    name: 'PipelineNavControls',
+    components: {
+      LoadingButton,
     },
+    props: {
+      newPipelinePath: {
+        type: String,
+        required: false,
+        default: null,
+      },
 
-    helpPagePath: {
-      type: String,
-      required: true,
-    },
+      resetCachePath: {
+        type: String,
+        required: false,
+        default: null,
+      },
 
-    resetCachePath: {
-      type: String,
-      required: true,
-    },
+      ciLintPath: {
+        type: String,
+        required: false,
+        default: null,
+      },
 
-    ciLintPath: {
-      type: String,
-      required: true,
+      isResetCacheButtonLoading: {
+        type: Boolean,
+        required: false,
+        default: false,
+      },
     },
-
-    canCreatePipeline: {
-      type: Boolean,
-      required: true,
+    methods: {
+      onClickResetCache() {
+        this.$emit('resetRunnersCache', this.resetCachePath);
+      },
     },
-  },
-};
+  };
 </script>
 <template>
   <div class="nav-controls">
     <a
-      v-if="canCreatePipeline"
+      v-if="newPipelinePath"
       :href="newPipelinePath"
-      class="btn btn-create">
-      Run Pipeline
+      class="btn btn-create js-run-pipeline"
+    >
+      {{ s__('Pipelines|Run Pipeline') }}
     </a>
 
-    <a
-      v-if="!hasCiEnabled"
-      :href="helpPagePath"
-      class="btn btn-info">
-      Get started with Pipelines
-    </a>
-
-    <a
-      data-method="post"
-      rel="nofollow"
-      :href="resetCachePath"
-      class="btn btn-default">
-      Clear runner caches
-    </a>
+    <loading-button
+      v-if="resetCachePath"
+      @click="onClickResetCache"
+      :loading="isResetCacheButtonLoading"
+      class="btn btn-default js-clear-cache"
+      :label="s__('Pipelines|Clear Runner Caches')"
+    />
 
     <a
+      v-if="ciLintPath"
       :href="ciLintPath"
-      class="btn btn-default">
-      CI Lint
+      class="btn btn-default js-ci-lint"
+    >
+      {{ s__('Pipelines|CI Lint') }}
     </a>
   </div>
 </template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index 90930d5ff445bb02de7828b5b27f5abaebd95aa8..497a09cec659c14f7ea2d78007399e164cb917b9 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -1,27 +1,22 @@
 <script>
   import _ from 'underscore';
+  import { __, sprintf, s__ } from '../../locale';
+  import createFlash from '../../flash';
   import PipelinesService from '../services/pipelines_service';
   import pipelinesMixin from '../mixins/pipelines';
-  import tablePagination from '../../vue_shared/components/table_pagination.vue';
-  import navigationTabs from '../../vue_shared/components/navigation_tabs.vue';
-  import navigationControls from './nav_controls.vue';
-  import {
-    convertPermissionToBoolean,
-    getParameterByName,
-    parseQueryStringIntoObject,
-  } from '../../lib/utils/common_utils';
+  import TablePagination from '../../vue_shared/components/table_pagination.vue';
+  import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
+  import NavigationControls from './nav_controls.vue';
+  import { getParameterByName } from '../../lib/utils/common_utils';
   import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
 
   export default {
     components: {
-      tablePagination,
-      navigationTabs,
-      navigationControls,
+      TablePagination,
+      NavigationTabs,
+      NavigationControls,
     },
-    mixins: [
-      pipelinesMixin,
-      CIPaginationMixin,
-    ],
+    mixins: [pipelinesMixin, CIPaginationMixin],
     props: {
       store: {
         type: Object,
@@ -36,111 +31,188 @@
         required: false,
         default: 'root',
       },
+      endpoint: {
+        type: String,
+        required: true,
+      },
+      helpPagePath: {
+        type: String,
+        required: true,
+      },
+      emptyStateSvgPath: {
+        type: String,
+        required: true,
+      },
+      errorStateSvgPath: {
+        type: String,
+        required: true,
+      },
+      noPipelinesSvgPath: {
+        type: String,
+        required: true,
+      },
+      autoDevopsPath: {
+        type: String,
+        required: true,
+      },
+      hasGitlabCi: {
+        type: Boolean,
+        required: true,
+      },
+      canCreatePipeline: {
+        type: Boolean,
+        required: true,
+      },
+      ciLintPath: {
+        type: String,
+        required: false,
+        default: null,
+      },
+      resetCachePath: {
+        type: String,
+        required: false,
+        default: null,
+      },
+      newPipelinePath: {
+        type: String,
+        required: false,
+        default: null,
+      },
     },
     data() {
-      const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
-
       return {
-        endpoint: pipelinesData.endpoint,
-        helpPagePath: pipelinesData.helpPagePath,
-        emptyStateSvgPath: pipelinesData.emptyStateSvgPath,
-        errorStateSvgPath: pipelinesData.errorStateSvgPath,
-        autoDevopsPath: pipelinesData.helpAutoDevopsPath,
-        newPipelinePath: pipelinesData.newPipelinePath,
-        canCreatePipeline: pipelinesData.canCreatePipeline,
-        hasCi: pipelinesData.hasCi,
-        ciLintPath: pipelinesData.ciLintPath,
-        resetCachePath: pipelinesData.resetCachePath,
+        // Start with loading state to avoid a glitch when the empty state will be rendered
+        isLoading: true,
         state: this.store.state,
         scope: getParameterByName('scope') || 'all',
         page: getParameterByName('page') || '1',
         requestData: {},
+        isResetCacheButtonLoading: false,
       };
     },
-    computed: {
-      canCreatePipelineParsed() {
-        return convertPermissionToBoolean(this.canCreatePipeline);
-      },
+    stateMap: {
+      // with tabs
+      loading: 'loading',
+      tableList: 'tableList',
+      error: 'error',
+      emptyTab: 'emptyTab',
 
+      // without tabs
+      emptyState: 'emptyState',
+    },
+    scopes: {
+      all: 'all',
+      pending: 'pending',
+      running: 'running',
+      finished: 'finished',
+      branches: 'branches',
+      tags: 'tags',
+    },
+    computed: {
       /**
-      * The empty state should only be rendered when the request is made to fetch all pipelines
-      * and none is returned.
-      *
-      * @return {Boolean}
-      */
-      shouldRenderEmptyState() {
-        return !this.isLoading &&
-          !this.hasError &&
-          this.hasMadeRequest &&
-          !this.state.pipelines.length &&
-          (this.scope === 'all' || this.scope === null);
+       * `hasGitlabCi` handles both internal and external CI.
+       * The order on which  the checks are made in this method is
+       * important to guarantee we handle all the corner cases.
+       */
+      stateToRender() {
+        const { stateMap } = this.$options;
+
+        if (this.isLoading) {
+          return stateMap.loading;
+        }
+
+        if (this.hasError) {
+          return stateMap.error;
+        }
+
+        if (this.state.pipelines.length) {
+          return stateMap.tableList;
+        }
+
+        if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
+          return stateMap.emptyTab;
+        }
+
+        return stateMap.emptyState;
       },
       /**
-       * When a specific scope does not have pipelines we render a message.
-       *
-       * @return {Boolean}
+       * Tabs are rendered in all states except empty state.
+       * They are not rendered before the first request to avoid a flicker on first load.
        */
-      shouldRenderNoPipelinesMessage() {
-        return !this.isLoading &&
-          !this.hasError &&
-          !this.state.pipelines.length &&
-          this.scope !== 'all' &&
-          this.scope !== null;
+      shouldRenderTabs() {
+        const { stateMap } = this.$options;
+        return (
+          this.hasMadeRequest &&
+          [stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
+            this.stateToRender,
+          )
+        );
       },
 
-      shouldRenderTable() {
-        return !this.hasError &&
-          !this.isLoading && this.state.pipelines.length;
+      shouldRenderButtons() {
+        return (
+          (this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
+        );
       },
-      /**
-      * Pagination should only be rendered when there is more than one page.
-      *
-      * @return {Boolean}
-      */
+
       shouldRenderPagination() {
-        return !this.isLoading &&
+        return (
+          !this.isLoading &&
           this.state.pipelines.length &&
-          this.state.pageInfo.total > this.state.pageInfo.perPage;
+          this.state.pageInfo.total > this.state.pageInfo.perPage
+        );
       },
-      hasCiEnabled() {
-        return this.hasCi !== undefined;
+
+      emptyTabMessage() {
+        const { scopes } = this.$options;
+        const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
+
+        if (possibleScopes.includes(this.scope)) {
+          return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
+            scope: this.scope,
+          });
+        }
+
+        return s__('Pipelines|There are currently no pipelines.');
       },
 
       tabs() {
         const { count } = this.state;
+        const { scopes } = this.$options;
+
         return [
           {
-            name: 'All',
-            scope: 'all',
+            name: __('All'),
+            scope: scopes.all,
             count: count.all,
             isActive: this.scope === 'all',
           },
           {
-            name: 'Pending',
-            scope: 'pending',
+            name: __('Pending'),
+            scope: scopes.pending,
             count: count.pending,
             isActive: this.scope === 'pending',
           },
           {
-            name: 'Running',
-            scope: 'running',
+            name: __('Running'),
+            scope: scopes.running,
             count: count.running,
             isActive: this.scope === 'running',
           },
           {
-            name: 'Finished',
-            scope: 'finished',
+            name: __('Finished'),
+            scope: scopes.finished,
             count: count.finished,
             isActive: this.scope === 'finished',
           },
           {
-            name: 'Branches',
-            scope: 'branches',
+            name: __('Branches'),
+            scope: scopes.branches,
             isActive: this.scope === 'branches',
           },
           {
-            name: 'Tags',
-            scope: 'tags',
+            name: __('Tags'),
+            scope: scopes.tags,
             isActive: this.scope === 'tags',
           },
         ];
@@ -152,15 +224,13 @@
     },
     methods: {
       successCallback(resp) {
-        return resp.json().then((response) => {
-          // Because we are polling & the user is interacting verify if the response received
-          // matches the last request made
-          if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) {
-            this.store.storeCount(response.count);
-            this.store.storePagination(resp.headers);
-            this.setCommonData(response.pipelines);
-          }
-        });
+        // Because we are polling & the user is interacting verify if the response received
+        // matches the last request made
+        if (_.isEqual(resp.config.params, this.requestData)) {
+          this.store.storeCount(resp.data.count);
+          this.store.storePagination(resp.headers);
+          this.setCommonData(resp.data.pipelines);
+        }
       },
       /**
        * Handles URL and query parameter changes.
@@ -174,8 +244,9 @@
         this.updateInternalState(parameters);
 
         // fetch new data
-        return this.service.getPipelines(this.requestData)
-          .then((response) => {
+        return this.service
+          .getPipelines(this.requestData)
+          .then(response => {
             this.isLoading = false;
             this.successCallback(response);
 
@@ -187,7 +258,22 @@
             this.errorCallback();
 
             // restart polling
-            this.poll.restart();
+            this.poll.restart({ data: this.requestData });
+          });
+      },
+
+      handleResetRunnersCache(endpoint) {
+        this.isResetCacheButtonLoading = true;
+
+        this.service
+          .postAction(endpoint)
+          .then(() => {
+            this.isResetCacheButtonLoading = false;
+            createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
+          })
+          .catch(() => {
+            this.isResetCacheButtonLoading = false;
+            createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
           });
       },
     },
@@ -197,69 +283,72 @@
   <div class="pipelines-container">
     <div
       class="top-area scrolling-tabs-container inner-page-scroll-tabs"
-      v-if="!shouldRenderEmptyState"
+      v-if="shouldRenderTabs || shouldRenderButtons"
     >
       <div class="fade-left">
         <i
           class="fa fa-angle-left"
-          aria-hidden="true">
+          aria-hidden="true"
+        >
         </i>
       </div>
       <div class="fade-right">
         <i
           class="fa fa-angle-right"
-          aria-hidden="true">
+          aria-hidden="true"
+        >
         </i>
       </div>
 
       <navigation-tabs
+        v-if="shouldRenderTabs"
         :tabs="tabs"
         @onChangeTab="onChangeTab"
         scope="pipelines"
       />
 
       <navigation-controls
+        v-if="shouldRenderButtons"
         :new-pipeline-path="newPipelinePath"
-        :has-ci-enabled="hasCiEnabled"
-        :help-page-path="helpPagePath"
         :reset-cache-path="resetCachePath"
         :ci-lint-path="ciLintPath"
-        :can-create-pipeline="canCreatePipelineParsed "
+        @resetRunnersCache="handleResetRunnersCache"
+        :is-reset-cache-button-loading="isResetCacheButtonLoading"
       />
     </div>
 
     <div class="content-list pipelines">
 
       <loading-icon
-        label="Loading Pipelines"
+        v-if="stateToRender === $options.stateMap.loading"
+        :label="s__('Pipelines|Loading Pipelines')"
         size="3"
-        v-if="isLoading"
         class="prepend-top-20"
       />
 
       <empty-state
-        v-if="shouldRenderEmptyState"
+        v-else-if="stateToRender === $options.stateMap.emptyState"
         :help-page-path="helpPagePath"
         :empty-state-svg-path="emptyStateSvgPath"
+        :can-set-ci="canCreatePipeline"
       />
 
-      <error-state
-        v-if="shouldRenderErrorState"
-        :error-state-svg-path="errorStateSvgPath"
+      <svg-blank-state
+        v-else-if="stateToRender === $options.stateMap.error"
+        :svg-path="errorStateSvgPath"
+        :message="s__(`Pipelines|There was an error fetching the pipelines.
+        Try again in a few moments or contact your support team.`)"
       />
 
-      <div
-        class="blank-state-row"
-        v-if="shouldRenderNoPipelinesMessage"
-      >
-        <div class="blank-state-center">
-          <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
-        </div>
-      </div>
+      <svg-blank-state
+        v-else-if="stateToRender === $options.stateMap.emptyTab"
+        :svg-path="noPipelinesSvgPath"
+        :message="emptyTabMessage"
+      />
 
       <div
         class="table-holder"
-        v-if="shouldRenderTable"
+        v-else-if="stateToRender === $options.stateMap.tableList"
       >
 
         <pipelines-table-component
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index c9028952ddd31d4b208d2b9d14ce59dfae224516..714aed1333e23519cc0f9be1c29a2b379557d955 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -1,5 +1,5 @@
 <script>
-  import modal from '~/vue_shared/components/modal.vue';
+  import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
   import { s__, sprintf } from '~/locale';
   import pipelinesTableRowComponent from './pipelines_table_row.vue';
   import eventHub from '../event_hub';
@@ -12,7 +12,7 @@
   export default {
     components: {
       pipelinesTableRowComponent,
-      modal,
+      DeprecatedModal,
     },
     props: {
       pipelines: {
@@ -120,7 +120,7 @@
       :auto-devops-help-path="autoDevopsHelpPath"
       :view-type="viewType"
     />
-    <modal
+    <deprecated-modal
       id="confirmation-modal"
       :title="modalTitle"
       :text="modalText"
@@ -134,6 +134,6 @@
       >
         <p v-html="props.text"></p>
       </template>
-    </modal>
+    </deprecated-modal>
   </div>
 </template>
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index ecf2b10486ec0b09e675d80ef4c2bdd38604126e..32cf3dba3c3df3bd94e3a42f1119c0a59c6281d6 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -13,15 +13,18 @@
    * 4. Commit widget
    */
 
+  import $ from 'jquery';
   import Flash from '../../flash';
-  import icon from '../../vue_shared/components/icon.vue';
-  import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+  import axios from '../../lib/utils/axios_utils';
+  import eventHub from '../event_hub';
+  import Icon from '../../vue_shared/components/icon.vue';
+  import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
   import tooltip from '../../vue_shared/directives/tooltip';
 
   export default {
     components: {
-      loadingIcon,
-      icon,
+      LoadingIcon,
+      Icon,
     },
 
     directives: {
@@ -81,15 +84,15 @@
     methods: {
       onClickStage() {
         if (!this.isDropdownOpen()) {
+          eventHub.$emit('clickedDropdown');
           this.isLoading = true;
           this.fetchJobs();
         }
       },
 
       fetchJobs() {
-        this.$http.get(this.stage.dropdown_path)
-          .then(response => response.json())
-          .then((data) => {
+        axios.get(this.stage.dropdown_path)
+          .then(({ data }) => {
             this.dropdownContent = data.html;
             this.isLoading = false;
           })
@@ -97,8 +100,7 @@
             this.closeDropdown();
             this.isLoading = false;
 
-            const flash = new Flash('Something went wrong on our end.');
-            return flash;
+            Flash('Something went wrong on our end.');
           });
       },
 
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..b384c7500e7dfc70cabeaac72aef8c4fc5c2b0b7
--- /dev/null
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line import/prefer-default-export
+export const CANCEL_REQUEST = 'CANCEL_REQUEST';
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 50bdf80c3e3fc6c88237f8b8ff46f0e99a0f6c9b..6d87f75ae8e8dd28a68a88ae4c508afeee270557 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -1,23 +1,20 @@
 import Visibility from 'visibilityjs';
+import { __ } from '../../locale';
 import Flash from '../../flash';
 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 EmptyState from '../components/empty_state.vue';
+import SvgBlankState from '../components/blank_state.vue';
+import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
+import PipelinesTableComponent from '../components/pipelines_table.vue';
 import eventHub from '../event_hub';
+import { CANCEL_REQUEST } from '../constants';
 
 export default {
   components: {
-    pipelinesTableComponent,
-    errorState,
-    emptyState,
-    loadingIcon,
-  },
-  computed: {
-    shouldRenderErrorState() {
-      return this.hasError && !this.isLoading;
-    },
+    PipelinesTableComponent,
+    SvgBlankState,
+    EmptyState,
+    LoadingIcon,
   },
   data() {
     return {
@@ -55,36 +52,59 @@ export default {
       }
     });
 
-    eventHub.$on('refreshPipelines', this.fetchPipelines);
     eventHub.$on('postAction', this.postAction);
+    eventHub.$on('clickedDropdown', this.updateTable);
   },
   beforeDestroy() {
-    eventHub.$off('refreshPipelines');
-    eventHub.$on('postAction', this.postAction);
+    eventHub.$off('postAction', this.postAction);
+    eventHub.$off('clickedDropdown', this.updateTable);
   },
   destroyed() {
     this.poll.stop();
   },
   methods: {
+    updateTable() {
+      // Cancel ongoing request
+      if (this.isMakingRequest) {
+        this.service.cancelationSource.cancel(CANCEL_REQUEST);
+      }
+      // Stop polling
+      this.poll.stop();
+      // Update the table
+      return this.getPipelines()
+        .then(() => this.poll.restart());
+    },
     fetchPipelines() {
       if (!this.isMakingRequest) {
         this.isLoading = true;
 
-        this.service.getPipelines(this.requestData)
-          .then(response => this.successCallback(response))
-          .catch(() => this.errorCallback());
+        this.getPipelines();
       }
     },
+    getPipelines() {
+      return this.service.getPipelines(this.requestData)
+        .then(response => this.successCallback(response))
+        .catch((error) => this.errorCallback(error));
+    },
     setCommonData(pipelines) {
       this.store.storePipelines(pipelines);
       this.isLoading = false;
       this.updateGraphDropdown = true;
       this.hasMadeRequest = true;
+
+      // In case the previous polling request returned an error, we need to reset it
+      if (this.hasError) {
+        this.hasError = false;
+      }
     },
-    errorCallback() {
-      this.hasError = true;
+    errorCallback(error) {
+      this.hasMadeRequest = true;
       this.isLoading = false;
-      this.updateGraphDropdown = false;
+
+      if (error && error.message && error.message !== CANCEL_REQUEST) {
+        this.hasError = true;
+        this.updateGraphDropdown = false;
+      }
     },
     setIsMakingRequest(isMakingRequest) {
       this.isMakingRequest = isMakingRequest;
@@ -95,8 +115,8 @@ export default {
     },
     postAction(endpoint) {
       this.service.postAction(endpoint)
-        .then(() => eventHub.$emit('refreshPipelines'))
-        .catch(() => new Flash('An error occurred while making the request.'));
+        .then(() => this.fetchPipelines())
+        .catch(() => Flash(__('An error occurred while making the request.')));
     },
   },
 };
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 6b26708148c0da87f635d72b9770c51e771d942a..900eb7855f4da304c28a12876b01473255764463 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -25,13 +25,36 @@ export default () => {
     data() {
       return {
         mediator,
+        actionDisabled: null,
       };
     },
+    created() {
+      eventHub.$on('graphAction', this.postAction);
+    },
+    beforeDestroy() {
+      eventHub.$off('graphAction', this.postAction);
+    },
+    methods: {
+      postAction(action) {
+        this.actionDisabled = action;
+
+        this.mediator.service.postAction(action)
+          .then(() => {
+            this.mediator.refreshPipeline();
+            this.actionDisabled = null;
+          })
+          .catch(() => {
+            this.actionDisabled = null;
+            Flash(__('An error occurred while making the request.'));
+          });
+      },
+    },
     render(createElement) {
       return createElement('pipeline-graph', {
         props: {
           isLoading: this.mediator.state.isLoading,
           pipeline: this.mediator.store.state.pipeline,
+          actionDisabled: this.actionDisabled,
         },
       });
     },
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
index 10f238fe73b449952fb32099d5039e24aadb22eb..5633e54b28a0189f36f6c26bb58fcc6cd2eb27ed 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
@@ -40,10 +40,8 @@ export default class pipelinesMediator {
   }
 
   successCallback(response) {
-    return response.json().then((data) => {
-      this.state.isLoading = false;
-      this.store.storePipeline(data);
-    });
+    this.state.isLoading = false;
+    this.store.storePipeline(response.data);
   }
 
   errorCallback() {
@@ -52,8 +50,11 @@ export default class pipelinesMediator {
   }
 
   refreshPipeline() {
-    this.service.getPipeline()
+    this.poll.stop();
+
+    return this.service.getPipeline()
       .then(response => this.successCallback(response))
-      .catch(() => this.errorCallback());
+      .catch(() => this.errorCallback())
+      .finally(() => this.poll.restart());
   }
 }
diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js
index 3e0c52c772690423d279aeac324444b66974f55c..a53a9cc8365a9d6fe6259d5cd85e0653f73b2cb5 100644
--- a/app/assets/javascripts/pipelines/services/pipeline_service.js
+++ b/app/assets/javascripts/pipelines/services/pipeline_service.js
@@ -1,19 +1,16 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-
-Vue.use(VueResource);
+import axios from '../../lib/utils/axios_utils';
 
 export default class PipelineService {
   constructor(endpoint) {
-    this.pipeline = Vue.resource(endpoint);
+    this.pipeline = endpoint;
   }
 
   getPipeline() {
-    return this.pipeline.get();
+    return axios.get(this.pipeline);
   }
 
-  // eslint-disable-next-line
+  // eslint-disable-next-line class-methods-use-this
   postAction(endpoint) {
-    return Vue.http.post(`${endpoint}.json`);
+    return axios.post(`${endpoint}.json`);
   }
 }
diff --git a/app/assets/javascripts/pipelines/services/pipelines_service.js b/app/assets/javascripts/pipelines/services/pipelines_service.js
index 47736fc5f42d03dec9c098a53968d1a5feb43ff0..59c8b9c58e502bc613ce688b874fa336d56fb851 100644
--- a/app/assets/javascripts/pipelines/services/pipelines_service.js
+++ b/app/assets/javascripts/pipelines/services/pipelines_service.js
@@ -1,35 +1,32 @@
-/* eslint-disable class-methods-use-this */
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-import '../../vue_shared/vue_resource_interceptor';
-
-Vue.use(VueResource);
+import axios from '../../lib/utils/axios_utils';
 
 export default class PipelinesService {
-
   /**
-  * Commits and merge request endpoints need to be requested with `.json`.
-  *
-  * The url provided to request the pipelines in the new merge request
-  * page already has `.json`.
-  *
-  * @param  {String} root
-  */
+   * Commits and merge request endpoints need to be requested with `.json`.
+   *
+   * The url provided to request the pipelines in the new merge request
+   * page already has `.json`.
+   *
+   * @param  {String} root
+   */
   constructor(root) {
-    let endpoint;
-
     if (root.indexOf('.json') === -1) {
-      endpoint = `${root}.json`;
+      this.endpoint = `${root}.json`;
     } else {
-      endpoint = root;
+      this.endpoint = root;
     }
-
-    this.pipelines = Vue.resource(endpoint);
   }
 
   getPipelines(data = {}) {
     const { scope, page } = data;
-    return this.pipelines.get({ scope, page });
+    const CancelToken = axios.CancelToken;
+
+    this.cancelationSource = CancelToken.source();
+
+    return axios.get(this.endpoint, {
+      params: { scope, page },
+      cancelToken: this.cancelationSource.token,
+    });
   }
 
   /**
@@ -38,7 +35,8 @@ export default class PipelinesService {
    * @param  {String} endpoint
    * @return {Promise}
    */
+  // eslint-disable-next-line class-methods-use-this
   postAction(endpoint) {
-    return Vue.http.post(`${endpoint}.json`);
+    return axios.post(`${endpoint}.json`);
   }
 }
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 464bfb351e7d1202bcce7556048cf2c6acebd2ef..246a265ef2b596f63a3b9a4396e181498cba5396 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -1,5 +1,10 @@
 /* eslint-disable func-names, no-var, object-shorthand, comma-dangle, prefer-arrow-callback */
 
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
+
 // MarkdownPreview
 //
 // Handles toggling the "Write" and "Preview" tab clicks, rendering the preview
@@ -7,10 +12,6 @@
 // more than `x` users are referenced.
 //
 
-import axios from '~/lib/utils/axios_utils';
-import flash from '~/flash';
-import { __ } from '~/locale';
-
 var lastTextareaPreviewed;
 var lastTextareaHeight = null;
 var markdownPreview;
diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
index 1ffe482d7824e6c2cd2ff0e131522c697260e7c0..f50002afbf2ed820d87b9f08f39966572c04ae43 100644
--- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -1,11 +1,11 @@
 <script>
-  import modal from '~/vue_shared/components/modal.vue';
+  import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
   import { __, s__, sprintf } from '~/locale';
   import csrf from '~/lib/utils/csrf';
 
   export default {
     components: {
-      modal,
+      DeprecatedModal,
     },
     props: {
       actionUrl: {
@@ -76,7 +76,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
 </script>
 
 <template>
-  <modal
+  <deprecated-modal
     id="delete-account-modal"
     :title="s__('Profiles|Delete your account?')"
     :text="text"
@@ -131,5 +131,5 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
       </form>
     </template>
 
-  </modal>
+  </deprecated-modal>
 </template>
diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e5de3f69b01da92da37b445fdba927139da8076d
--- /dev/null
+++ b/app/assets/javascripts/profile/account/components/update_username.vue
@@ -0,0 +1,121 @@
+<script>
+import _ from 'underscore';
+import axios from '~/lib/utils/axios_utils';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import { s__, sprintf } from '~/locale';
+import Flash from '~/flash';
+
+export default {
+  components: {
+    GlModal,
+  },
+  props: {
+    actionUrl: {
+      type: String,
+      required: true,
+    },
+    rootUrl: {
+      type: String,
+      required: true,
+    },
+    initialUsername: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      isRequestPending: false,
+      username: this.initialUsername,
+      newUsername: this.initialUsername,
+    };
+  },
+  computed: {
+    path() {
+      return sprintf(s__('Profiles|Current path: %{path}'), {
+        path: `${this.rootUrl}${this.username}`,
+      });
+    },
+    modalText() {
+      return sprintf(
+        s__(`Profiles|
+You are going to change the username %{currentUsernameBold} to %{newUsernameBold}.
+Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group.
+Please update your Git repository remotes as soon as possible.`),
+        {
+          currentUsernameBold: `<strong>${_.escape(this.username)}</strong>`,
+          newUsernameBold: `<strong>${_.escape(this.newUsername)}</strong>`,
+          currentUsername: _.escape(this.username),
+          newUsername: _.escape(this.newUsername),
+        },
+        false,
+      );
+    },
+  },
+  methods: {
+    onConfirm() {
+      this.isRequestPending = true;
+      const username = this.newUsername;
+      const putData = {
+        user: {
+          username,
+        },
+      };
+
+      return axios
+        .put(this.actionUrl, putData)
+        .then(result => {
+          Flash(result.data.message, 'notice');
+          this.username = username;
+          this.isRequestPending = false;
+        })
+        .catch(error => {
+          Flash(error.response.data.message);
+          this.isRequestPending = false;
+          throw error;
+        });
+    },
+  },
+  modalId: 'username-change-confirmation-modal',
+  inputId: 'username-change-input',
+  buttonText: s__('Profiles|Update username'),
+};
+</script>
+<template>
+  <div>
+    <div class="form-group">
+      <label :for="$options.inputId">{{ s__('Profiles|Path') }}</label>
+      <div class="input-group">
+        <div class="input-group-addon">{{ rootUrl }}</div>
+        <input
+          :id="$options.inputId"
+          class="form-control"
+          required="required"
+          v-model="newUsername"
+          :disabled="isRequestPending"
+        />
+      </div>
+      <p class="help-block">
+        {{ path }}
+      </p>
+    </div>
+    <button
+      :data-target="`#${$options.modalId}`"
+      class="btn btn-warning"
+      type="button"
+      data-toggle="modal"
+      :disabled="isRequestPending || newUsername === username"
+    >
+      {{ $options.buttonText }}
+    </button>
+    <gl-modal
+      :id="$options.modalId"
+      :header-title-text="s__('Profiles|Change username') + '?'"
+      footer-primary-button-variant="warning"
+      :footer-primary-button-text="$options.buttonText"
+      @submit="onConfirm"
+    >
+      <span v-html="modalText"></span>
+    </gl-modal>
+  </div>
+</template>
diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js
index 84049a1f0b799bcea3b22813ec6d6e08096c7631..59c13e1a042efce7b23c438c30ade61ec288290a 100644
--- a/app/assets/javascripts/profile/account/index.js
+++ b/app/assets/javascripts/profile/account/index.js
@@ -1,10 +1,25 @@
 import Vue from 'vue';
 import Translate from '~/vue_shared/translate';
+import UpdateUsername from './components/update_username.vue';
 import deleteAccountModal from './components/delete_account_modal.vue';
 
 export default () => {
   Vue.use(Translate);
 
+  const updateUsernameElement = document.getElementById('update-username');
+  // eslint-disable-next-line no-new
+  new Vue({
+    el: updateUsernameElement,
+    components: {
+      UpdateUsername,
+    },
+    render(createElement) {
+      return createElement('update-username', {
+        props: { ...updateUsernameElement.dataset },
+      });
+    },
+  });
+
   const deleteAccountButton = document.getElementById('delete-account-button');
   const deleteAccountModalEl = document.getElementById('delete-account-modal');
   // eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index 4bdda611cfcb9ac4212875918b00f6fdb40e5fa2..8f93156cdd1aceef095f49fa5c725e50b9a9755b 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
 
+import $ from 'jquery';
 import 'cropper';
 import _ from 'underscore';
 
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index a811781853b1d57a37d857cfff35511087e9c862..0af34657d72d66462cc95baab20c6c37e25a1e12 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -1,5 +1,6 @@
 /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
-import Cookies from 'js-cookie';
+
+import $ from 'jquery';
 import axios from '~/lib/utils/axios_utils';
 import { __ } from '~/locale';
 import flash from '../flash';
@@ -8,7 +9,6 @@ export default class Profile {
   constructor({ form } = {}) {
     this.onSubmitForm = this.onSubmitForm.bind(this);
     this.form = form || $('.edit-user');
-    this.newRepoActivated = Cookies.get('new_repo');
     this.setRepoRadio();
     this.bindEvents();
     this.initAvatarGlCrop();
@@ -21,21 +21,28 @@ export default class Profile {
       modalCrop: '.modal-profile-crop',
       pickImageEl: '.js-choose-user-avatar-button',
       uploadImageBtn: '.js-upload-user-avatar',
-      modalCropImg: '.modal-profile-crop-image'
+      modalCropImg: '.modal-profile-crop-image',
     };
-    this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop');
+    this.avatarGlCrop = $('.js-user-avatar-input')
+      .glCrop(cropOpts)
+      .data('glcrop');
   }
 
   bindEvents() {
-    $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
-    $('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie);
+    $('.js-preferences-form').on(
+      'change.preference',
+      'input[type=radio]',
+      this.submitForm,
+    );
     $('#user_notification_email').on('change', this.submitForm);
     $('#user_notified_of_own_activity').on('change', this.submitForm);
     this.form.on('submit', this.onSubmitForm);
   }
 
   submitForm() {
-    return $(this).parents('form').submit();
+    return $(this)
+      .parents('form')
+      .submit();
   }
 
   onSubmitForm(e) {
@@ -57,21 +64,13 @@ export default class Profile {
       url: this.form.attr('action'),
       data: formData,
     })
-    .then(({ data }) => flash(data.message, 'notice'))
-    .then(() => {
-      window.scrollTo(0, 0);
-      // Enable submit button after requests ends
-      self.form.find(':input[disabled]').enable();
-    })
-    .catch(error => flash(error.message));
-  }
-
-  setNewRepoCookie() {
-    if (this.value === 'off') {
-      Cookies.remove('new_repo');
-    } else {
-      Cookies.set('new_repo', true, { expires_in: 365 });
-    }
+      .then(({ data }) => flash(data.message, 'notice'))
+      .then(() => {
+        window.scrollTo(0, 0);
+        // Enable submit button after requests ends
+        self.form.find(':input[disabled]').enable();
+      })
+      .catch(error => flash(error.message));
   }
 
   setRepoRadio() {
diff --git a/app/assets/javascripts/project_edit.js b/app/assets/javascripts/project_edit.js
index 7572fec15e001a2fe02cd574a554de14ee28fc31..47bf222678101a1996b37854b091d7be0337e3c8 100644
--- a/app/assets/javascripts/project_edit.js
+++ b/app/assets/javascripts/project_edit.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default function setupProjectEdit() {
   const $transferForm = $('.js-project-transfer-form');
   const $selectNamespace = $transferForm.find('select.select2');
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 4fd639cce8e1e21d18cf968909bd02ba1f0bcec3..4c4acd487f8970978e1e501ee442e75c397e4de5 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -1,5 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */
 
+import $ from 'jquery';
 import fuzzaldrinPlus from 'fuzzaldrin-plus';
 import axios from '~/lib/utils/axios_utils';
 import flash from '~/flash';
diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js
index 65d46fa9a7363e7452b6b8a90666f4bf3a6c8975..6fedd94a6a90c471dfdf8d8ab43db18f0d241b88 100644
--- a/app/assets/javascripts/project_fork.js
+++ b/app/assets/javascripts/project_fork.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default () => {
   $('.js-fork-thumbnail').on('click', function forkThumbnailClicked() {
     if ($(this).hasClass('disabled')) return false;
diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js
index 64b7dd540f99bffc5c2d441ba84e12d85f0e7422..f31beb4dc784a962b9599296300fb569eb49f90d 100644
--- a/app/assets/javascripts/project_label_subscription.js
+++ b/app/assets/javascripts/project_label_subscription.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { __ } from './locale';
 import axios from './lib/utils/axios_utils';
 import flash from './flash';
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 412aca7bfedb4c700fb48dfbb6113bb9f05f65df..cb2e6855d1dd5bacd2fba643a41bde65ce3f58be 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -1,4 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
+
+import $ from 'jquery';
 import Api from './api';
 import ProjectSelectComboButton from './project_select_combo_button';
 
diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js
index 99cea683d9a2fd37d008f57482abaaeb987b1bd8..9b404896e862b4dc7bad1a113d7b942943a4e216 100644
--- a/app/assets/javascripts/project_select_combo_button.js
+++ b/app/assets/javascripts/project_select_combo_button.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import AccessorUtilities from './lib/utils/accessor';
 
 export default class ProjectSelectComboButton {
diff --git a/app/assets/javascripts/project_visibility.js b/app/assets/javascripts/project_visibility.js
index c3f5e8cb90772db8b6c5f4ac81caaae2683bdb72..7c95c71e239d5b1968d17af44b01090fa5e6546c 100644
--- a/app/assets/javascripts/project_visibility.js
+++ b/app/assets/javascripts/project_visibility.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 function setVisibilityOptions(namespaceSelector) {
   if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
     return;
diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js
index d2c7d77bb2d5782c7f5a1ec02e835184c4d90ce2..4e20fce146089060355e7f06511fcc0936d45463 100644
--- a/app/assets/javascripts/projects/project_import_gitlab_project.js
+++ b/app/assets/javascripts/projects/project_import_gitlab_project.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { getParameterValues } from '../lib/utils/url_utility';
 
 export default () => {
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 8da37d14f0b464e9aafa5df4e3efa2948cf6e447..93603dfc14daae51e00e7e54e18fc3bb21930dfb 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
 
 let hasUserDefinedProjectPath = false;
diff --git a/app/assets/javascripts/projects_dropdown/index.js b/app/assets/javascripts/projects_dropdown/index.js
index e78ebce29238db27b50c4f85b182eb31c55daf4a..e1ca70c51a6d75076e0899e0d4c1df02845087ba 100644
--- a/app/assets/javascripts/projects_dropdown/index.js
+++ b/app/assets/javascripts/projects_dropdown/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 
 import Translate from '../vue_shared/translate';
diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
index e8126ac573db38a7b0cad091081d4992f063855a..0a60f4845b2154730a190054e6cab971034ad3dc 100644
--- a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
+++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
@@ -1,3 +1,6 @@
+import $ from 'jquery';
+import _ from 'underscore';
+import { s__, n__, sprintf } from '~/locale';
 import axios from '../lib/utils/axios_utils';
 import PANEL_STATE from './constants';
 import { backOff } from '../lib/utils/common_utils';
@@ -20,6 +23,7 @@ export default class PrometheusMetrics {
     this.$missingEnvVarMetricsList = this.$missingEnvVarPanel.find('.js-missing-var-metrics-list');
 
     this.activeMetricsEndpoint = this.$monitoredMetricsPanel.data('activeMetrics');
+    this.helpMetricsPath = this.$monitoredMetricsPanel.data('metrics-help-path');
 
     this.$panelToggle.on('click', e => this.handlePanelToggle(e));
   }
@@ -59,23 +63,39 @@ export default class PrometheusMetrics {
   populateActiveMetrics(metrics) {
     let totalMonitoredMetrics = 0;
     let totalMissingEnvVarMetrics = 0;
+    let totalExporters = 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;
+      if (metric.active_metrics > 0) {
+        totalExporters += 1;
+        this.$monitoredMetricsList.append(`<li>${_.escape(metric.group)}<span class="badge">${_.escape(metric.active_metrics)}</span></li>`);
+        totalMonitoredMetrics += metric.active_metrics;
+        if (metric.metrics_missing_requirements > 0) {
+          this.$missingEnvVarMetricsList.append(`<li>${_.escape(metric.group)}</li>`);
+          totalMissingEnvVarMetrics += 1;
+        }
       }
     });
 
-    this.$monitoredMetricsCount.text(totalMonitoredMetrics);
-    this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
+    if (totalMonitoredMetrics === 0) {
+      const emptyCommonMetricsText = sprintf(s__('PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>'), {
+        docsUrl: this.helpMetricsPath,
+      }, false);
+      this.$monitoredMetricsEmpty.empty();
+      this.$monitoredMetricsEmpty.append(emptyCommonMetricsText);
+      this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
+    } else {
+      const metricsCountText = sprintf(s__('PrometheusService|%{exporters} with %{metrics} were found'), {
+        exporters: n__('%d exporter', '%d exporters', totalExporters),
+        metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics),
+      });
+      this.$monitoredMetricsCount.text(metricsCountText);
+      this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
 
-    if (totalMissingEnvVarMetrics > 0) {
-      this.$missingEnvVarPanel.removeClass('hidden');
-      this.$missingEnvVarPanel.find('.flash-container').off('click');
-      this.$missingEnvVarMetricCount.text(totalMissingEnvVarMetrics);
+      if (totalMissingEnvVarMetrics > 0) {
+        this.$missingEnvVarPanel.removeClass('hidden');
+        this.$missingEnvVarMetricCount.text(totalMissingEnvVarMetrics);
+      }
     }
   }
 
@@ -97,15 +117,15 @@ export default class PrometheusMetrics {
         })
         .catch(stop);
     })
-    .then((res) => {
-      if (res && res.data && res.data.length) {
-        this.populateActiveMetrics(res.data);
-      } else {
+      .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);
-      }
-    })
-    .catch(() => {
-      this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
-    });
+      });
   }
 }
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 8fc87633e181f1fcd2c966a956b1fb74a18e7904..7c61c070a357ee5850335c6e5a2a1d2f429d6adf 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
 import CreateItemDropdown from '../create_item_dropdown';
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
index b40d3827c3067eefa47436f79fb9a658248043cd..10253c0febc22eb55eeabcc4aedc74a9239746d3 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-new */
 
+import $ from 'jquery';
 import ProtectedBranchEdit from './protected_branch_edit';
 
 export default class ProtectedBranchEditList {
diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js
index 2f94ffe25071a4a976cfe06d2c504b26bcdf181b..2f8116df0d28b8e4f1585972ea3569cd6e55ddea 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_create.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_create.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
 import CreateItemDropdown from '../create_item_dropdown';
 
diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit_list.js b/app/assets/javascripts/protected_tags/protected_tag_edit_list.js
index bd9fc8722664781c17c93fe7349c8e4f8c768fa9..b35bf4d4606da443535e9b124b9c4821afa3eec1 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_edit_list.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_edit_list.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-new */
 
+import $ from 'jquery';
 import ProtectedTagEdit from './protected_tag_edit';
 
 export default class ProtectedTagEditList {
diff --git a/app/assets/javascripts/ref_select_dropdown.js b/app/assets/javascripts/ref_select_dropdown.js
index 56c25a35e6d40e16025249c2f66484af20439192..95c5cf7b34530347c1f6a227051315cd99e88d26 100644
--- a/app/assets/javascripts/ref_select_dropdown.js
+++ b/app/assets/javascripts/ref_select_dropdown.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 class RefSelectDropdown {
   constructor($dropdownButton, availableRefs) {
     const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index b4906ba4ee53cab881bf996870085e2a56356a81..a03180e80e6da2387639376816db0d70c0ce331c 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -86,6 +86,7 @@
         v-if="repo.location"
         :text="clipboardText"
         :title="repo.location"
+        css-class="btn-default btn-transparent btn-clipboard"
       />
 
       <div class="controls hidden-xs pull-right">
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index bef850eddc0a030972f07c953ef949d60c018d84..ee4eb3581f354deda45022186193cadf3db597d8 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -90,6 +90,7 @@
               v-if="item.location"
               :title="item.location"
               :text="clipboardText(item.location)"
+              css-class="btn-default btn-transparent btn-clipboard"
             />
           </td>
           <td>
diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js
deleted file mode 100644
index 05a623ca6d993af16284858a2278b3ae345b80c5..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/render_gfm.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import renderMath from './render_math';
-import renderMermaid from './render_mermaid';
-import syntaxHighlight from './syntax_highlight';
-
-// Render Gitlab flavoured Markdown
-//
-// Delegates to syntax highlight and render math & mermaid diagrams.
-//
-$.fn.renderGFM = function renderGFM() {
-  syntaxHighlight(this.find('.js-syntax-highlight'));
-  renderMath(this.find('.js-render-math'));
-  renderMermaid(this.find('.js-render-mermaid'));
-  return this;
-};
-
-$(() => $('body').renderGFM());
diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js
deleted file mode 100644
index eabdb01b2a946182de1a0e67ac6306420f6199cd..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/render_math.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { __ } from './locale';
-import flash from './flash';
-
-// Renders math using KaTeX in any element with the
-// `js-render-math` class
-//
-// ### Example Markup
-//
-//   <code class="js-render-math"></div>
-//
-
-// Loop over all math elements and render math
-function renderWithKaTeX(elements, katex) {
-  elements.each(function katexElementsLoop() {
-    const mathNode = $('<span></span>');
-    const $this = $(this);
-
-    const display = $this.attr('data-math-style') === 'display';
-    try {
-      katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
-      mathNode.insertAfter($this);
-      $this.remove();
-    } catch (err) {
-      throw err;
-    }
-  });
-}
-
-export default function renderMath($els) {
-  if (!$els.length) return;
-  Promise.all([
-    import(/* webpackChunkName: 'katex' */ 'katex'),
-    import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.css'),
-  ]).then(([katex]) => {
-    renderWithKaTeX($els, katex);
-  }).catch(() => flash(__('An error occurred while rendering KaTeX')));
-}
diff --git a/app/assets/javascripts/render_mermaid.js b/app/assets/javascripts/render_mermaid.js
deleted file mode 100644
index d4f18955bd21e4298c66f1826a4eecdb901824c8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/render_mermaid.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// Renders diagrams and flowcharts from text using Mermaid in any element with the
-// `js-render-mermaid` class.
-//
-// Example markup:
-//
-// <pre class="js-render-mermaid">
-//  graph TD;
-//    A-- > B;
-//    A-- > C;
-//    B-- > D;
-//    C-- > D;
-// </pre>
-//
-
-import Flash from './flash';
-
-export default function renderMermaid($els) {
-  if (!$els.length) return;
-
-  import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
-    mermaid.initialize({
-      // mermaid core options
-      mermaid: {
-        startOnLoad: false,
-      },
-      // mermaidAPI options
-      theme: 'neutral',
-    });
-
-    $els.each((i, el) => {
-      const source = el.textContent;
-
-      // Remove any extra spans added by the backend syntax highlighting.
-      Object.assign(el, { textContent: source });
-
-      mermaid.init(undefined, el, (id) => {
-        const svg = document.getElementById(id);
-
-        svg.classList.add('mermaid');
-
-        // pre > code > svg
-        svg.closest('pre').replaceWith(svg);
-
-        // We need to add the original source into the DOM to allow Copy-as-GFM
-        // to access it.
-        const sourceEl = document.createElement('text');
-        sourceEl.classList.add('source');
-        sourceEl.setAttribute('display', 'none');
-        sourceEl.textContent = source;
-
-        svg.appendChild(sourceEl);
-      });
-    });
-  }).catch((err) => {
-    Flash(`Can't load mermaid module: ${err}`);
-  });
-}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 8d3cc849f813e9f6194091c4580b2d6f14f4a5d2..6eb0b62fa1c968be3197f673387b59f323cadb78 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,9 +1,11 @@
 /* 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 $ from 'jquery';
 import _ from 'underscore';
 import Cookies from 'js-cookie';
 import flash from './flash';
 import axios from './lib/utils/axios_utils';
+import { __ } from './locale';
 
 function Sidebar(currentUser) {
   this.toggleTodo = this.toggleTodo.bind(this);
@@ -40,12 +42,14 @@ Sidebar.prototype.addEventListeners = function() {
 };
 
 Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
-  var $allGutterToggleIcons, $this, $thisIcon;
+  var $allGutterToggleIcons, $this, isExpanded, tooltipLabel;
   e.preventDefault();
   $this = $(this);
-  $thisIcon = $this.find('i');
+  isExpanded = $this.find('i').hasClass('fa-angle-double-right');
+  tooltipLabel = isExpanded ? __('Expand sidebar') : __('Collapse sidebar');
   $allGutterToggleIcons = $('.js-sidebar-toggle i');
-  if ($thisIcon.hasClass('fa-angle-double-right')) {
+
+  if (isExpanded) {
     $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
     $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
     $('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
@@ -56,6 +60,9 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
 
     if (gl.lazyLoader) gl.lazyLoader.loadCheck();
   }
+
+  $this.attr('data-original-title', tooltipLabel);
+
   if (!triggered) {
     Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
   }
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index fdfa4f28aba28d29017b451b8f97637f103108cb..2da022fde63b786b672352a7ddadf478d4787e32 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
+
+import $ from 'jquery';
 import axios from './lib/utils/axios_utils';
 import DropdownUtils from './filtered_search/dropdown_utils';
 import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils';
@@ -231,21 +233,21 @@ export default class SearchAutocomplete {
     const issueItems = [
       {
         text: 'Issues assigned to me',
-        url: `${issuesPath}/?assignee_username=${userName}`,
+        url: `${issuesPath}/?assignee_id=${userId}`,
       },
       {
         text: "Issues I've created",
-        url: `${issuesPath}/?author_username=${userName}`,
+        url: `${issuesPath}/?author_id=${userId}`,
       },
     ];
     const mergeRequestItems = [
       {
         text: 'Merge requests assigned to me',
-        url: `${mrPath}/?assignee_username=${userName}`,
+        url: `${mrPath}/?assignee_id=${userId}`,
       },
       {
         text: "Merge requests I've created",
-        url: `${mrPath}/?author_username=${userName}`,
+        url: `${mrPath}/?author_id=${userId}`,
       },
     ];
 
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index d0e4f533d8a43f1b2abe023979c1bb288ec6e8ba..eecde4550f90c60da55cf084b80a9a6404ea35d1 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 function expandSection($section) {
   $section.find('.js-settings-toggle').text('Collapse');
   $section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js
index db466f722c4e95bd1bfa4b7fb15d47e8ae786d40..2f974d6ff9d84d08d560c8cdfbbe070a4a070a04 100644
--- a/app/assets/javascripts/shared/milestones/form.js
+++ b/app/assets/javascripts/shared/milestones/form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import ZenMode from '../../zen_mode';
 import DueDateSelectors from '../../due_date_select';
 import GLForm from '../../gl_form';
diff --git a/app/assets/javascripts/shared/popover.js b/app/assets/javascripts/shared/popover.js
new file mode 100644
index 0000000000000000000000000000000000000000..3fc03553bdd41756ebded8552864d815c315efce
--- /dev/null
+++ b/app/assets/javascripts/shared/popover.js
@@ -0,0 +1,33 @@
+import $ from 'jquery';
+import _ from 'underscore';
+
+export function togglePopover(show) {
+  const isAlreadyShown = this.hasClass('js-popover-show');
+  if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
+    return false;
+  }
+  this.popover(show ? 'show' : 'hide');
+  this.toggleClass('disable-animation js-popover-show', show);
+
+  return true;
+}
+
+export function mouseleave() {
+  if (!$('.popover:hover').length > 0) {
+    const $popover = $(this);
+    togglePopover.call($popover, false);
+  }
+}
+
+export function mouseenter() {
+  const $popover = $(this);
+
+  const showedPopover = togglePopover.call($popover, true);
+  if (showedPopover) {
+    $('.popover').on('mouseleave', mouseleave.bind($popover));
+  }
+}
+
+export function debouncedMouseleave(debounceTimeout = 300) {
+  return _.debounce(mouseleave, debounceTimeout);
+}
diff --git a/app/assets/javascripts/shared/sessions/u2f.js b/app/assets/javascripts/shared/sessions/u2f.js
index 1d075f7e8727afb5a255b3488225ae9d36ad8534..6ae9faf1dde0fdaead06bde29e69d243a47cf5f8 100644
--- a/app/assets/javascripts/shared/sessions/u2f.js
+++ b/app/assets/javascripts/shared/sessions/u2f.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import U2FAuthenticate from '../../u2f/authenticate';
 
 export default () => {
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index c5dddd001bbf8576622781e06199616ed93a6474..e31e067033fd89029c696355c3d2f3cfcc9fefe8 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 import Mousetrap from 'mousetrap';
 import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js
index 25f39e4fdb6383c4847bce9f51097d9609db2d77..9f69f110d06877172208b2bd1565d67114292dd0 100644
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js
+++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js
@@ -1,12 +1,15 @@
+import { visitUrl } from './lib/utils/url_utility';
+
 /**
  * Helper function that finds the href of the fiven selector and updates the location.
  *
  * @param  {String} selector
  */
-export default (selector) => {
-  const link = document.querySelector(selector).getAttribute('href');
+export default function findAndFollowLink(selector) {
+  const element = document.querySelector(selector);
+  const link = element && element.getAttribute('href');
 
   if (link) {
-    window.location = link;
+    visitUrl(link);
   }
-};
+}
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 14545824e74671e78ea334681946fe7da2d5fca1..193788f754f135ab951abd001f767e0e2a7f5f9c 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,8 +1,9 @@
+import $ from 'jquery';
 import Mousetrap from 'mousetrap';
 import _ from 'underscore';
 import Sidebar from './right_sidebar';
 import Shortcuts from './shortcuts';
-import { CopyAsGFM } from './behaviors/copy_as_gfm';
+import { CopyAsGFM } from './behaviors/markdown/copy_as_gfm';
 
 export default class ShortcutsIssuable extends Shortcuts {
   constructor(isMergeRequest) {
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
deleted file mode 100644
index 129ba2e4e899e6ea4a1eff10d71691d7fd03248a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
+++ /dev/null
@@ -1,59 +0,0 @@
-export default {
-  name: 'AssigneeTitle',
-  props: {
-    loading: {
-      type: Boolean,
-      required: false,
-      default: false,
-    },
-    numberOfAssignees: {
-      type: Number,
-      required: true,
-    },
-    editable: {
-      type: Boolean,
-      required: true,
-    },
-    showToggle: {
-      type: Boolean,
-      required: false,
-      default: false,
-    },
-  },
-  computed: {
-    assigneeTitle() {
-      const assignees = this.numberOfAssignees;
-      return assignees > 1 ? `${assignees} Assignees` : 'Assignee';
-    },
-  },
-  template: `
-    <div class="title hide-collapsed">
-      {{assigneeTitle}}
-      <i
-        v-if="loading"
-        aria-hidden="true"
-        class="fa fa-spinner fa-spin block-loading"
-      />
-      <a
-        v-if="editable"
-        class="js-sidebar-dropdown-toggle edit-link pull-right"
-        href="#"
-      >
-        {{ __('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/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5eeb2a41bae46567ef9bd2dbd20016c016b6aafd
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -0,0 +1,64 @@
+<script>
+export default {
+  name: 'AssigneeTitle',
+  props: {
+    loading: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    numberOfAssignees: {
+      type: Number,
+      required: true,
+    },
+    editable: {
+      type: Boolean,
+      required: true,
+    },
+    showToggle: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    assigneeTitle() {
+      const assignees = this.numberOfAssignees;
+      return assignees > 1 ? `${assignees} Assignees` : 'Assignee';
+    },
+  },
+};
+</script>
+<template>
+  <div class="title hide-collapsed">
+    {{ assigneeTitle }}
+    <i
+      v-if="loading"
+      aria-hidden="true"
+      class="fa fa-spinner fa-spin block-loading"
+    >
+
+    </i>
+    <a
+      v-if="editable"
+      class="js-sidebar-dropdown-toggle edit-link pull-right"
+      href="#"
+    >
+      {{ __('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"
+      >
+      </i>
+    </a>
+  </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
index 1e7f46454bf2bae68f8bc95671f94888abea38dc..2d00e8ac7e073266bd9232a98ad5a4ebd7b3f965 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
@@ -1,6 +1,12 @@
 <script>
+import { __ } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+
 export default {
   name: 'Assignees',
+  directives: {
+    tooltip,
+  },
   props: {
     rootPath: {
       type: String,
@@ -14,6 +20,11 @@ export default {
       type: Boolean,
       required: true,
     },
+    issuableType: {
+      type: String,
+      require: true,
+      default: 'issue',
+    },
   },
   data() {
     return {
@@ -62,6 +73,12 @@ export default {
         names.push(`+ ${this.users.length - maxRender} more`);
       }
 
+      if (!this.users.length) {
+        const emptyTooltipLabel = this.issuableType === 'issue' ?
+          __('Assignee(s)') : __('Assignee');
+        names.push(emptyTooltipLabel);
+      }
+
       return names.join(', ');
     },
     sidebarAvatarCounter() {
@@ -109,7 +126,8 @@ export default {
   <div>
     <div
       class="sidebar-collapsed-icon sidebar-collapsed-user"
-      :class="{ 'multiple-users': hasMoreThanOneAssignee, 'has-tooltip': hasAssignees }"
+      :class="{ 'multiple-users': hasMoreThanOneAssignee }"
+      v-tooltip
       data-container="body"
       data-placement="left"
       :title="collapsedTooltipTitle"
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
deleted file mode 100644
index 8269fe1281d1ca167bb9b31ac92d4c7a30287716..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import Flash from '../../../flash';
-import AssigneeTitle from './assignee_title';
-import Assignees from './assignees.vue';
-import Store from '../../stores/sidebar_store';
-import eventHub from '../../event_hub';
-
-export default {
-  name: 'SidebarAssignees',
-  data() {
-    return {
-      store: new Store(),
-      loading: false,
-    };
-  },
-  props: {
-    mediator: {
-      type: Object,
-      required: true,
-    },
-    field: {
-      type: String,
-      required: true,
-    },
-    signedIn: {
-      type: Boolean,
-      required: false,
-      default: false,
-    },
-  },
-  components: {
-    AssigneeTitle,
-    Assignees,
-  },
-  methods: {
-    assignSelf() {
-      // Notify gl dropdown that we are now assigning to current user
-      this.$el.parentElement.dispatchEvent(new Event('assignYourself'));
-
-      this.mediator.assignYourself();
-      this.saveAssignees();
-    },
-    saveAssignees() {
-      this.loading = true;
-
-      function setLoadingFalse() {
-        this.loading = false;
-      }
-
-      this.mediator.saveAssignees(this.field)
-        .then(setLoadingFalse.bind(this))
-        .catch(() => {
-          setLoadingFalse();
-          return new Flash('Error occurred when saving assignees');
-        });
-    },
-  },
-  created() {
-    this.removeAssignee = this.store.removeAssignee.bind(this.store);
-    this.addAssignee = this.store.addAssignee.bind(this.store);
-    this.removeAllAssignees = this.store.removeAllAssignees.bind(this.store);
-
-    // Get events from glDropdown
-    eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
-    eventHub.$on('sidebar.addAssignee', this.addAssignee);
-    eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees);
-    eventHub.$on('sidebar.saveAssignees', this.saveAssignees);
-  },
-  beforeDestroy() {
-    eventHub.$off('sidebar.removeAssignee', this.removeAssignee);
-    eventHub.$off('sidebar.addAssignee', this.addAssignee);
-    eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
-    eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
-  },
-  template: `
-    <div>
-      <assignee-title
-        :number-of-assignees="store.assignees.length"
-        :loading="loading || store.isFetching.assignees"
-        :editable="store.editable"
-        :show-toggle="!signedIn"
-      />
-      <assignees
-        v-if="!store.isFetching.assignees"
-        class="value"
-        :root-path="store.rootPath"
-        :users="store.assignees"
-        :editable="store.editable"
-        @assign-self="assignSelf"
-      />
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b04a2eff7985988d20aa5a2f2e0f412c1e8e936b
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -0,0 +1,101 @@
+<script>
+import Flash from '~/flash';
+import eventHub from '~/sidebar/event_hub';
+import Store from '~/sidebar/stores/sidebar_store';
+import AssigneeTitle from './assignee_title.vue';
+import Assignees from './assignees.vue';
+
+export default {
+  name: 'SidebarAssignees',
+  components: {
+    AssigneeTitle,
+    Assignees,
+  },
+  props: {
+    mediator: {
+      type: Object,
+      required: true,
+    },
+    field: {
+      type: String,
+      required: true,
+    },
+    signedIn: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    issuableType: {
+      type: String,
+      require: true,
+      default: 'issue',
+    },
+  },
+  data() {
+    return {
+      store: new Store(),
+      loading: false,
+    };
+  },
+  created() {
+    this.removeAssignee = this.store.removeAssignee.bind(this.store);
+    this.addAssignee = this.store.addAssignee.bind(this.store);
+    this.removeAllAssignees = this.store.removeAllAssignees.bind(this.store);
+
+    // Get events from glDropdown
+    eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
+    eventHub.$on('sidebar.addAssignee', this.addAssignee);
+    eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees);
+    eventHub.$on('sidebar.saveAssignees', this.saveAssignees);
+  },
+  beforeDestroy() {
+    eventHub.$off('sidebar.removeAssignee', this.removeAssignee);
+    eventHub.$off('sidebar.addAssignee', this.addAssignee);
+    eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
+    eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
+  },
+  methods: {
+    assignSelf() {
+      // Notify gl dropdown that we are now assigning to current user
+      this.$el.parentElement.dispatchEvent(new Event('assignYourself'));
+
+      this.mediator.assignYourself();
+      this.saveAssignees();
+    },
+    saveAssignees() {
+      this.loading = true;
+
+      function setLoadingFalse() {
+        this.loading = false;
+      }
+
+      this.mediator.saveAssignees(this.field)
+        .then(setLoadingFalse.bind(this))
+        .catch(() => {
+          setLoadingFalse();
+          return new Flash('Error occurred when saving assignees');
+        });
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <assignee-title
+      :number-of-assignees="store.assignees.length"
+      :loading="loading || store.isFetching.assignees"
+      :editable="store.editable"
+      :show-toggle="!signedIn"
+    />
+    <assignees
+      v-if="!store.isFetching.assignees"
+      class="value"
+      :root-path="store.rootPath"
+      :users="store.assignees"
+      :editable="store.editable"
+      @assign-self="assignSelf"
+      :issuable-type="issuableType"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 8a86c409b6292841468049d7a55c928a0006038d..7f0de722f619012732d59ed6da57fca533640d7c 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -1,59 +1,84 @@
 <script>
-  import Flash from '../../../flash';
-  import editForm from './edit_form.vue';
-  import Icon from '../../../vue_shared/components/icon.vue';
-  import { __ } from '../../../locale';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import tooltip from '~/vue_shared/directives/tooltip';
+import Icon from '~/vue_shared/components/icon.vue';
+import eventHub from '~/sidebar/event_hub';
+import editForm from './edit_form.vue';
 
-  export default {
-    components: {
-      editForm,
-      Icon,
+export default {
+  components: {
+    editForm,
+    Icon,
+  },
+  directives: {
+    tooltip,
+  },
+  props: {
+    isConfidential: {
+      required: true,
+      type: Boolean,
     },
-    props: {
-      isConfidential: {
-        required: true,
-        type: Boolean,
-      },
-      isEditable: {
-        required: true,
-        type: Boolean,
-      },
-      service: {
-        required: true,
-        type: Object,
-      },
+    isEditable: {
+      required: true,
+      type: Boolean,
     },
-    data() {
-      return {
-        edit: false,
-      };
+    service: {
+      required: true,
+      type: Object,
     },
-    computed: {
-      confidentialityIcon() {
-        return this.isConfidential ? 'eye-slash' : 'eye';
-      },
+  },
+  data() {
+    return {
+      edit: false,
+    };
+  },
+  computed: {
+    confidentialityIcon() {
+      return this.isConfidential ? 'eye-slash' : 'eye';
     },
-    methods: {
-      toggleForm() {
-        this.edit = !this.edit;
-      },
-      updateConfidentialAttribute(confidential) {
-        this.service.update('issue', { confidential })
-          .then(() => location.reload())
-          .catch(() => {
-            Flash(__('Something went wrong trying to change the confidentiality of this issue'));
-          });
-      },
+    tooltipLabel() {
+      return this.isConfidential ? __('Confidential') : __('Not confidential');
     },
-  };
+  },
+  created() {
+    eventHub.$on('closeConfidentialityForm', this.toggleForm);
+  },
+  beforeDestroy() {
+    eventHub.$off('closeConfidentialityForm', this.toggleForm);
+  },
+  methods: {
+    toggleForm() {
+      this.edit = !this.edit;
+    },
+    updateConfidentialAttribute(confidential) {
+      this.service
+        .update('issue', { confidential })
+        .then(() => location.reload())
+        .catch(() => {
+          Flash(
+            __(
+              'Something went wrong trying to change the confidentiality of this issue',
+            ),
+          );
+        });
+    },
+  },
+};
 </script>
 
 <template>
   <div class="block issuable-sidebar-item confidentiality">
-    <div class="sidebar-collapsed-icon">
+    <div
+      class="sidebar-collapsed-icon"
+      @click="toggleForm"
+      v-tooltip
+      data-container="body"
+      data-placement="left"
+      :title="tooltipLabel"
+    >
       <icon
         :name="confidentialityIcon"
-        :size="16"
         aria-hidden="true"
       />
     </div>
@@ -71,7 +96,6 @@
     <div class="value sidebar-item-value hide-collapsed">
       <editForm
         v-if="edit"
-        :toggle-form="toggleForm"
         :is-confidential="isConfidential"
         :update-confidential-attribute="updateConfidentialAttribute"
       />
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
index c569843b05fd9311c46b00d159a7699514da836f..3783f71a848640d7dca62f5b254eaafd6ffa3bb2 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
@@ -1,34 +1,34 @@
 <script>
-  import editFormButtons from './edit_form_buttons.vue';
-  import { s__ } from '../../../locale';
+import editFormButtons from './edit_form_buttons.vue';
+import { s__ } from '../../../locale';
 
-  export default {
-    components: {
-      editFormButtons,
+export default {
+  components: {
+    editFormButtons,
+  },
+  props: {
+    isConfidential: {
+      required: true,
+      type: Boolean,
     },
-    props: {
-      isConfidential: {
-        required: true,
-        type: Boolean,
-      },
-      toggleForm: {
-        required: true,
-        type: Function,
-      },
-      updateConfidentialAttribute: {
-        required: true,
-        type: Function,
-      },
+    updateConfidentialAttribute: {
+      required: true,
+      type: Function,
     },
-    computed: {
-      confidentialityOnWarning() {
-        return s__('confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue.');
-      },
-      confidentialityOffWarning() {
-        return s__('confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.');
-      },
+  },
+  computed: {
+    confidentialityOnWarning() {
+      return s__(
+        'confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue.',
+      );
     },
-  };
+    confidentialityOffWarning() {
+      return s__(
+        'confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.',
+      );
+    },
+  },
+};
 </script>
 
 <template>
@@ -45,7 +45,6 @@
         </p>
         <edit-form-buttons
           :is-confidential="isConfidential"
-          :toggle-form="toggleForm"
           :update-confidential-attribute="updateConfidentialAttribute"
         />
       </div>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
index 49d5dfeea1a8ac3734930749087b063796d1e399..38b1ddbfd5b34d195971cb9c0ee8fe658dd330d5 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
@@ -1,14 +1,13 @@
 <script>
+import $ from 'jquery';
+import eventHub from '../../event_hub';
+
 export default {
   props: {
     isConfidential: {
       required: true,
       type: Boolean,
     },
-    toggleForm: {
-      required: true,
-      type: Function,
-    },
     updateConfidentialAttribute: {
       required: true,
       type: Function,
@@ -22,6 +21,16 @@ export default {
       return !this.isConfidential;
     },
   },
+  methods: {
+    closeForm() {
+      eventHub.$emit('closeConfidentialityForm');
+      $(this.$el).trigger('hidden.gl.dropdown');
+    },
+    submitForm() {
+      this.closeForm();
+      this.updateConfidentialAttribute(this.updateConfidentialBool);
+    },
+  },
 };
 </script>
 
@@ -30,14 +39,14 @@ export default {
     <button
       type="button"
       class="btn btn-default append-right-10"
-      @click="toggleForm"
+      @click="closeForm"
     >
       {{ __('Cancel') }}
     </button>
     <button
       type="button"
       class="btn btn-close"
-      @click.prevent="updateConfidentialAttribute(updateConfidentialBool)"
+      @click.prevent="submitForm"
     >
       {{ toggleButtonText }}
     </button>
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form.vue b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
index bc32e974bc379b00b993f9a16b6b0ca6c5deb773..e1e4715826a27bdbedca3534fa2ba93851775c90 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
@@ -1,40 +1,43 @@
 <script>
-  import editFormButtons from './edit_form_buttons.vue';
-  import issuableMixin from '../../../vue_shared/mixins/issuable';
-  import { __, sprintf } from '../../../locale';
+import editFormButtons from './edit_form_buttons.vue';
+import issuableMixin from '../../../vue_shared/mixins/issuable';
+import { __, sprintf } from '../../../locale';
 
-  export default {
-    components: {
-      editFormButtons,
+export default {
+  components: {
+    editFormButtons,
+  },
+  mixins: [issuableMixin],
+  props: {
+    isLocked: {
+      required: true,
+      type: Boolean,
     },
-    mixins: [
-      issuableMixin,
-    ],
-    props: {
-      isLocked: {
-        required: true,
-        type: Boolean,
-      },
 
-      toggleForm: {
-        required: true,
-        type: Function,
-      },
-
-      updateLockedAttribute: {
-        required: true,
-        type: Function,
-      },
+    updateLockedAttribute: {
+      required: true,
+      type: Function,
+    },
+  },
+  computed: {
+    lockWarning() {
+      return sprintf(
+        __(
+          'Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment.',
+        ),
+        { issuableDisplayName: this.issuableDisplayName },
+      );
     },
-    computed: {
-      lockWarning() {
-        return sprintf(__('Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName });
-      },
-      unlockWarning() {
-        return sprintf(__('Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName });
-      },
+    unlockWarning() {
+      return sprintf(
+        __(
+          'Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment.',
+        ),
+        { issuableDisplayName: this.issuableDisplayName },
+      );
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -54,7 +57,6 @@
 
       <edit-form-buttons
         :is-locked="isLocked"
-        :toggle-form="toggleForm"
         :update-locked-attribute="updateLockedAttribute"
       />
     </div>
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
index c3a553a76059d1fc87f88882a8193263bbda83e0..5e7b8f9698f9f0d9044a47224f363c9f7a9f8f27 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
@@ -1,4 +1,7 @@
 <script>
+import $ from 'jquery';
+import eventHub from '../../event_hub';
+
 export default {
   props: {
     isLocked: {
@@ -6,11 +9,6 @@ export default {
       type: Boolean,
     },
 
-    toggleForm: {
-      required: true,
-      type: Function,
-    },
-
     updateLockedAttribute: {
       required: true,
       type: Function,
@@ -26,6 +24,17 @@ export default {
       return !this.isLocked;
     },
   },
+
+  methods: {
+    closeForm() {
+      eventHub.$emit('closeLockForm');
+      $(this.$el).trigger('hidden.gl.dropdown');
+    },
+    submitForm() {
+      this.closeForm();
+      this.updateLockedAttribute(this.toggleLock);
+    },
+  },
 };
 </script>
 
@@ -34,7 +43,7 @@ export default {
     <button
       type="button"
       class="btn btn-default append-right-10"
-      @click="toggleForm"
+      @click="closeForm"
     >
       {{ __('Cancel') }}
     </button>
@@ -42,7 +51,7 @@ export default {
     <button
       type="button"
       class="btn btn-close"
-      @click.prevent="updateLockedAttribute(toggleLock)"
+      @click.prevent="submitForm"
     >
       {{ buttonText }}
     </button>
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index 0686910fc7e3f451b8128b2600950ff7c251f049..1a5e7b67eca2ca7567344dcd9c12ebd4da1b6255 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -1,70 +1,108 @@
 <script>
-  import Flash from '~/flash';
-  import editForm from './edit_form.vue';
-  import issuableMixin from '../../../vue_shared/mixins/issuable';
-  import Icon from '../../../vue_shared/components/icon.vue';
-
-  export default {
-    components: {
-      editForm,
-      Icon,
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import tooltip from '~/vue_shared/directives/tooltip';
+import issuableMixin from '~/vue_shared/mixins/issuable';
+import Icon from '~/vue_shared/components/icon.vue';
+import eventHub from '~/sidebar/event_hub';
+import editForm from './edit_form.vue';
+
+export default {
+  components: {
+    editForm,
+    Icon,
+  },
+
+  directives: {
+    tooltip,
+  },
+
+  mixins: [issuableMixin],
+
+  props: {
+    isLocked: {
+      required: true,
+      type: Boolean,
     },
-    mixins: [
-      issuableMixin,
-    ],
-
-    props: {
-      isLocked: {
-        required: true,
-        type: Boolean,
-      },
 
-      isEditable: {
-        required: true,
-        type: Boolean,
-      },
+    isEditable: {
+      required: true,
+      type: Boolean,
+    },
 
-      mediator: {
-        required: true,
-        type: Object,
-        validator(mediatorObject) {
-          return mediatorObject.service && mediatorObject.service.update && mediatorObject.store;
-        },
+    mediator: {
+      required: true,
+      type: Object,
+      validator(mediatorObject) {
+        return (
+          mediatorObject.service &&
+          mediatorObject.service.update &&
+          mediatorObject.store
+        );
       },
     },
+  },
 
-    computed: {
-      lockIcon() {
-        return this.isLocked ? 'lock' : 'lock-open';
-      },
+  computed: {
+    lockIcon() {
+      return this.isLocked ? 'lock' : 'lock-open';
+    },
 
-      isLockDialogOpen() {
-        return this.mediator.store.isLockDialogOpen;
-      },
+    isLockDialogOpen() {
+      return this.mediator.store.isLockDialogOpen;
     },
 
-    methods: {
-      toggleForm() {
-        this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
-      },
+    tooltipLabel() {
+      return this.isLocked ? __('Locked') : __('Unlocked');
+    },
+  },
+
+  created() {
+    eventHub.$on('closeLockForm', this.toggleForm);
+  },
 
-      updateLockedAttribute(locked) {
-        this.mediator.service.update(this.issuableType, {
+  beforeDestroy() {
+    eventHub.$off('closeLockForm', this.toggleForm);
+  },
+
+  methods: {
+    toggleForm() {
+      this.mediator.store.isLockDialogOpen = !this.mediator.store
+        .isLockDialogOpen;
+    },
+
+    updateLockedAttribute(locked) {
+      this.mediator.service
+        .update(this.issuableType, {
           discussion_locked: locked,
         })
         .then(() => location.reload())
-        .catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${this.issuableDisplayName}`)));
-      },
+        .catch(() =>
+          Flash(
+            this.__(
+              `Something went wrong trying to change the locked state of this ${
+                this.issuableDisplayName
+              }`,
+            ),
+          ),
+        );
     },
-  };
+  },
+};
 </script>
 
 <template>
   <div class="block issuable-sidebar-item lock">
-    <div class="sidebar-collapsed-icon">
+    <div
+      class="sidebar-collapsed-icon"
+      @click="toggleForm"
+      v-tooltip
+      data-container="body"
+      data-placement="left"
+      :title="tooltipLabel"
+    >
       <icon
         :name="lockIcon"
-        :size="16"
         aria-hidden="true"
         class="sidebar-item-icon is-active"
       />
@@ -85,7 +123,6 @@
     <div class="value sidebar-item-value hide-collapsed">
       <edit-form
         v-if="isLockDialogOpen"
-        :toggle-form="toggleForm"
         :is-locked="isLocked"
         :update-locked-attribute="updateLockedAttribute"
         :issuable-type="issuableType"
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index 006a6d2905d56de702920b987ef1773db3641302..6d95153af28f26ab4b23b9c79b411669ce4bb146 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -1,9 +1,13 @@
 <script>
-  import { __, n__, sprintf } from '../../../locale';
-  import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
-  import userAvatarImage from '../../../vue_shared/components/user_avatar/user_avatar_image.vue';
+  import { __, n__, sprintf } from '~/locale';
+  import tooltip from '~/vue_shared/directives/tooltip';
+  import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+  import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
 
   export default {
+    directives: {
+      tooltip,
+    },
     components: {
       loadingIcon,
       userAvatarImage,
@@ -72,7 +76,13 @@
 
 <template>
   <div>
-    <div class="sidebar-collapsed-icon">
+    <div
+      class="sidebar-collapsed-icon"
+      v-tooltip
+      data-container="body"
+      data-placement="left"
+      :title="participantLabel"
+    >
       <i
         class="fa fa-users"
         aria-hidden="true"
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js
deleted file mode 100644
index a9fbc7f1a2f726d34c63c3a07fc04c6d6c97ae2e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import stopwatchSvg from 'icons/_icon_stopwatch.svg';
-import { abbreviateTime } from '../../../lib/utils/pretty_time';
-
-export default {
-  name: 'time-tracking-collapsed-state',
-  props: {
-    showComparisonState: {
-      type: Boolean,
-      required: true,
-    },
-    showSpentOnlyState: {
-      type: Boolean,
-      required: true,
-    },
-    showEstimateOnlyState: {
-      type: Boolean,
-      required: true,
-    },
-    showNoTimeTrackingState: {
-      type: Boolean,
-      required: true,
-    },
-    timeSpentHumanReadable: {
-      type: String,
-      required: false,
-      default: '',
-    },
-    timeEstimateHumanReadable: {
-      type: String,
-      required: false,
-      default: '',
-    },
-  },
-  computed: {
-    timeSpent() {
-      return this.abbreviateTime(this.timeSpentHumanReadable);
-    },
-    timeEstimate() {
-      return this.abbreviateTime(this.timeEstimateHumanReadable);
-    },
-    divClass() {
-      if (this.showComparisonState) {
-        return 'compare';
-      } else if (this.showEstimateOnlyState) {
-        return 'estimate-only';
-      } else if (this.showSpentOnlyState) {
-        return 'spend-only';
-      } else if (this.showNoTimeTrackingState) {
-        return 'no-tracking';
-      }
-
-      return '';
-    },
-    spanClass() {
-      if (this.showComparisonState) {
-        return '';
-      } else if (this.showEstimateOnlyState || this.showSpentOnlyState) {
-        return 'bold';
-      } else if (this.showNoTimeTrackingState) {
-        return 'no-value';
-      }
-
-      return '';
-    },
-    text() {
-      if (this.showComparisonState) {
-        return `${this.timeSpent} / ${this.timeEstimate}`;
-      } else if (this.showEstimateOnlyState) {
-        return `-- / ${this.timeEstimate}`;
-      } else if (this.showSpentOnlyState) {
-        return `${this.timeSpent} / --`;
-      } else if (this.showNoTimeTrackingState) {
-        return 'None';
-      }
-
-      return '';
-    },
-  },
-  methods: {
-    abbreviateTime(timeStr) {
-      return abbreviateTime(timeStr);
-    },
-  },
-  template: `
-    <div class="sidebar-collapsed-icon">
-      ${stopwatchSvg}
-      <div class="time-tracking-collapsed-summary">
-        <div :class="divClass">
-          <span :class="spanClass">
-            {{ text }}
-          </span>
-        </div>
-      </div>
-    </div>
-    `,
-};
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9d9ee9dea4d235d7248c729ec41e599fa165f5e3
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
@@ -0,0 +1,128 @@
+<script>
+  import { __, sprintf } from '~/locale';
+  import { abbreviateTime } from '~/lib/utils/pretty_time';
+  import icon from '~/vue_shared/components/icon.vue';
+  import tooltip from '~/vue_shared/directives/tooltip';
+
+  export default {
+    name: 'TimeTrackingCollapsedState',
+    components: {
+      icon,
+    },
+    directives: {
+      tooltip,
+    },
+    props: {
+      showComparisonState: {
+        type: Boolean,
+        required: true,
+      },
+      showSpentOnlyState: {
+        type: Boolean,
+        required: true,
+      },
+      showEstimateOnlyState: {
+        type: Boolean,
+        required: true,
+      },
+      showNoTimeTrackingState: {
+        type: Boolean,
+        required: true,
+      },
+      timeSpentHumanReadable: {
+        type: String,
+        required: false,
+        default: '',
+      },
+      timeEstimateHumanReadable: {
+        type: String,
+        required: false,
+        default: '',
+      },
+    },
+    computed: {
+      timeSpent() {
+        return this.abbreviateTime(this.timeSpentHumanReadable);
+      },
+      timeEstimate() {
+        return this.abbreviateTime(this.timeEstimateHumanReadable);
+      },
+      divClass() {
+        if (this.showComparisonState) {
+          return 'compare';
+        } else if (this.showEstimateOnlyState) {
+          return 'estimate-only';
+        } else if (this.showSpentOnlyState) {
+          return 'spend-only';
+        } else if (this.showNoTimeTrackingState) {
+          return 'no-tracking';
+        }
+
+        return '';
+      },
+      spanClass() {
+        if (this.showComparisonState) {
+          return '';
+        } else if (this.showEstimateOnlyState || this.showSpentOnlyState) {
+          return 'bold';
+        } else if (this.showNoTimeTrackingState) {
+          return 'no-value';
+        }
+
+        return '';
+      },
+      text() {
+        if (this.showComparisonState) {
+          return `${this.timeSpent} / ${this.timeEstimate}`;
+        } else if (this.showEstimateOnlyState) {
+          return `-- / ${this.timeEstimate}`;
+        } else if (this.showSpentOnlyState) {
+          return `${this.timeSpent} / --`;
+        } else if (this.showNoTimeTrackingState) {
+          return 'None';
+        }
+
+        return '';
+      },
+      timeTrackedTooltipText() {
+        let title;
+        if (this.showComparisonState) {
+          title = __('Time remaining');
+        } else if (this.showEstimateOnlyState) {
+          title = __('Estimated');
+        } else if (this.showSpentOnlyState) {
+          title = __('Time spent');
+        }
+
+        return sprintf('%{title}: %{text}', ({ title, text: this.text }));
+      },
+      tooltipText() {
+        return this.showNoTimeTrackingState ? __('Time tracking') : this.timeTrackedTooltipText;
+      },
+    },
+    methods: {
+      abbreviateTime(timeStr) {
+        return abbreviateTime(timeStr);
+      },
+    },
+  };
+</script>
+
+<template>
+  <div
+    class="sidebar-collapsed-icon"
+    v-tooltip
+    data-container="body"
+    data-placement="left"
+    :title="tooltipText"
+  >
+    <icon name="timer" />
+    <div class="time-tracking-collapsed-summary">
+      <div :class="divClass">
+        <span :class="spanClass">
+          {{ text }}
+        </span>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js
deleted file mode 100644
index b5ebccd379512bfc65fff346813aa127c9a7900c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
-
-export default {
-  name: 'time-tracking-comparison-pane',
-  props: {
-    timeSpent: {
-      type: Number,
-      required: true,
-    },
-    timeEstimate: {
-      type: Number,
-      required: true,
-    },
-    timeSpentHumanReadable: {
-      type: String,
-      required: true,
-    },
-    timeEstimateHumanReadable: {
-      type: String,
-      required: true,
-    },
-  },
-  computed: {
-    parsedTimeRemaining() {
-      const diffSeconds = this.timeEstimate - this.timeSpent;
-      return parseSeconds(diffSeconds);
-    },
-    timeRemainingHumanReadable() {
-      return stringifyTime(this.parsedTimeRemaining);
-    },
-    timeRemainingTooltip() {
-      const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
-      return `${prefix} ${this.timeRemainingHumanReadable}`;
-    },
-    /* Diff values for comparison meter */
-    timeRemainingMinutes() {
-      return this.timeEstimate - this.timeSpent;
-    },
-    timeRemainingPercent() {
-      return `${Math.floor((this.timeSpent / this.timeEstimate) * 100)}%`;
-    },
-    timeRemainingStatusClass() {
-      return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
-    },
-  },
-  template: `
-    <div class="time-tracking-comparison-pane">
-      <div
-        class="compare-meter"
-        data-toggle="tooltip"
-        data-placement="top"
-        role="timeRemainingDisplay"
-        :aria-valuenow="timeRemainingTooltip"
-        :title="timeRemainingTooltip"
-        :data-original-title="timeRemainingTooltip"
-        :class="timeRemainingStatusClass"
-      >
-        <div
-          class="meter-container"
-          role="timeSpentPercent"
-          :aria-valuenow="timeRemainingPercent"
-        >
-          <div
-            :style="{ width: timeRemainingPercent }"
-            class="meter-fill"
-          />
-        </div>
-        <div class="compare-display-container">
-          <div class="compare-display pull-left">
-            <span class="compare-label">
-            {{ s__('TimeTracking|Spent') }}
-            </span>
-            <span class="compare-value spent">
-              {{ timeSpentHumanReadable }}
-            </span>
-          </div>
-          <div class="compare-display estimated pull-right">
-            <span class="compare-label">
-              {{ s__('TimeTrackingEstimated|Est') }}
-            </span>
-            <span class="compare-value">
-              {{ timeEstimateHumanReadable }}
-            </span>
-          </div>
-        </div>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
new file mode 100644
index 0000000000000000000000000000000000000000..82c4562f9a9c63d234040c8ec0693492ae76c96d
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
@@ -0,0 +1,93 @@
+<script>
+import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
+
+export default {
+  name: 'TimeTrackingComparisonPane',
+  props: {
+    timeSpent: {
+      type: Number,
+      required: true,
+    },
+    timeEstimate: {
+      type: Number,
+      required: true,
+    },
+    timeSpentHumanReadable: {
+      type: String,
+      required: true,
+    },
+    timeEstimateHumanReadable: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    parsedTimeRemaining() {
+      const diffSeconds = this.timeEstimate - this.timeSpent;
+      return parseSeconds(diffSeconds);
+    },
+    timeRemainingHumanReadable() {
+      return stringifyTime(this.parsedTimeRemaining);
+    },
+    timeRemainingTooltip() {
+      const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
+      return `${prefix} ${this.timeRemainingHumanReadable}`;
+    },
+    /* Diff values for comparison meter */
+    timeRemainingMinutes() {
+      return this.timeEstimate - this.timeSpent;
+    },
+    timeRemainingPercent() {
+      return `${Math.floor((this.timeSpent / this.timeEstimate) * 100)}%`;
+    },
+    timeRemainingStatusClass() {
+      return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="time-tracking-comparison-pane">
+    <div
+      class="compare-meter"
+      data-toggle="tooltip"
+      data-placement="top"
+      role="timeRemainingDisplay"
+      :aria-valuenow="timeRemainingTooltip"
+      :title="timeRemainingTooltip"
+      :data-original-title="timeRemainingTooltip"
+      :class="timeRemainingStatusClass"
+    >
+      <div
+        class="meter-container"
+        role="timeSpentPercent"
+        :aria-valuenow="timeRemainingPercent"
+      >
+        <div
+          :style="{ width: timeRemainingPercent }"
+          class="meter-fill"
+        >
+        </div>
+      </div>
+      <div class="compare-display-container">
+        <div class="compare-display pull-left">
+          <span class="compare-label">
+            {{ s__('TimeTracking|Spent') }}
+          </span>
+          <span class="compare-value spent">
+            {{ timeSpentHumanReadable }}
+          </span>
+        </div>
+        <div class="compare-display estimated pull-right">
+          <span class="compare-label">
+            {{ s__('TimeTrackingEstimated|Est') }}
+          </span>
+          <span class="compare-value">
+            {{ timeEstimateHumanReadable }}
+          </span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js
deleted file mode 100644
index 2d324c713798d9786a9dedf9cd19d828c1b23d58..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export default {
-  name: 'time-tracking-estimate-only-pane',
-  props: {
-    timeEstimateHumanReadable: {
-      type: String,
-      required: true,
-    },
-  },
-  template: `
-    <div class="time-tracking-estimate-only-pane">
-      <span class="bold">
-        {{ s__('TimeTracking|Estimated:') }}
-      </span>
-      {{ timeEstimateHumanReadable }}
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
new file mode 100644
index 0000000000000000000000000000000000000000..08fce597e5060c7983293692983736e8129af32e
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
@@ -0,0 +1,20 @@
+<script>
+export default {
+  name: 'TimeTrackingEstimateOnlyPane',
+  props: {
+    timeEstimateHumanReadable: {
+      type: String,
+      required: true,
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="time-tracking-estimate-only-pane">
+    <span class="bold">
+      {{ s__('TimeTracking|Estimated:') }}
+    </span>
+    {{ timeEstimateHumanReadable }}
+  </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
deleted file mode 100644
index 19f74ad3c6dd8bd429ed98ceac1bcb4382a15052..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { sprintf, s__ } from '../../../locale';
-
-export default {
-  name: 'time-tracking-help-state',
-  props: {
-    rootPath: {
-      type: String,
-      required: true,
-    },
-  },
-  computed: {
-    href() {
-      return `${this.rootPath}help/workflow/time_tracking.md`;
-    },
-    estimateText() {
-      return sprintf(
-        s__('estimateCommand|%{slash_command} will update the estimated time with the latest command.'), {
-          slash_command: '<code>/estimate</code>',
-        }, false,
-      );
-    },
-    spendText() {
-      return sprintf(
-        s__('spendCommand|%{slash_command} will update the sum of the time spent.'), {
-          slash_command: '<code>/spend</code>',
-        }, false,
-      );
-    },
-  },
-  template: `
-    <div class="time-tracking-help-state">
-      <div class="time-tracking-info">
-        <h4>
-          {{ __('Track time with quick actions') }}
-        </h4>
-        <p>
-          {{ __('Quick actions can be used in the issues description and comment boxes.') }}
-        </p>
-        <p v-html="estimateText">
-        </p>
-        <p v-html="spendText">
-        </p>
-        <a
-          class="btn btn-default learn-more-button"
-          :href="href"
-        >
-          {{ __('Learn more') }}
-        </a>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..825063d9ba6b047a0c6132e6096f57e5ff9a9506
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
@@ -0,0 +1,55 @@
+<script>
+import { sprintf, s__ } from '../../../locale';
+
+export default {
+  name: 'TimeTrackingHelpState',
+  props: {
+    rootPath: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    href() {
+      return `${this.rootPath}help/workflow/time_tracking.md`;
+    },
+    estimateText() {
+      return sprintf(
+        s__('estimateCommand|%{slash_command} will update the estimated time with the latest command.'), {
+          slash_command: '<code>/estimate</code>',
+        }, false,
+      );
+    },
+    spendText() {
+      return sprintf(
+        s__('spendCommand|%{slash_command} will update the sum of the time spent.'), {
+          slash_command: '<code>/spend</code>',
+        }, false,
+      );
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="time-tracking-help-state">
+    <div class="time-tracking-info">
+      <h4>
+        {{ __('Track time with quick actions') }}
+      </h4>
+      <p>
+        {{ __('Quick actions can be used in the issues description and comment boxes.') }}
+      </p>
+      <p v-html="estimateText">
+      </p>
+      <p v-html="spendText">
+      </p>
+      <a
+        class="btn btn-default learn-more-button"
+        :href="href"
+      >
+        {{ __('Learn more') }}
+      </a>
+    </div>
+  </div>
+</template>
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 782e4ba4fad6bef21deabe7a493a3b26f1c22bd5..5626cccc022a3c05aab11abb3b54f41c788f8c54 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
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 
 import '~/smart_interval';
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 230736a56b861b1f6acba0c934616850f244cef0..71dca498b3dd3a80560f169be538fdf5bde72852 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -1,22 +1,22 @@
 <script>
-import timeTrackingHelpState from './help_state';
-import timeTrackingCollapsedState from './collapsed_state';
+import TimeTrackingHelpState from './help_state.vue';
+import TimeTrackingCollapsedState from './collapsed_state.vue';
 import timeTrackingSpentOnlyPane from './spent_only_pane';
 import timeTrackingNoTrackingPane from './no_tracking_pane';
-import timeTrackingEstimateOnlyPane from './estimate_only_pane';
-import timeTrackingComparisonPane from './comparison_pane';
+import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
+import TimeTrackingComparisonPane from './comparison_pane.vue';
 
 import eventHub from '../../event_hub';
 
 export default {
   name: 'IssuableTimeTracker',
   components: {
-    'time-tracking-collapsed-state': timeTrackingCollapsedState,
-    'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
+    TimeTrackingCollapsedState,
+    TimeTrackingEstimateOnlyPane,
     'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
     'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
-    'time-tracking-comparison-pane': timeTrackingComparisonPane,
-    'time-tracking-help-state': timeTrackingHelpState,
+    TimeTrackingComparisonPane,
+    TimeTrackingHelpState,
   },
   props: {
     time_estimate: {
diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
index b10e2cc60efd4974501c436eef58c195565595be..1eadebc70041ed6423bc3be7191b537c93b506f3 100644
--- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
+++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 function isValidProjectId(id) {
   return id > 0;
 }
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 56cc78ca0ca2bf2bc6fb798ad1dca66bf1a7a3ab..26eb4cffba3b4330837c23dc8944920ddfcb23ac 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -1,6 +1,7 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
-import SidebarAssignees from './components/assignees/sidebar_assignees';
+import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
 import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
 import SidebarMoveIssue from './lib/sidebar_move_issue';
 import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
@@ -26,6 +27,7 @@ function mountAssigneesComponent(mediator) {
         mediator,
         field: el.dataset.field,
         signedIn: el.hasAttribute('data-signed-in'),
+        issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
       },
     }),
   });
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 6142ce6c6a3ef34eeb20645aba569d48c3b83ed7..1afff0dba38cfbfea161365e3b7f7ce4ad499e13 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,5 +1,6 @@
 /* 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 */
 
+import $ from 'jquery';
 import { __ } from './locale';
 import axios from './lib/utils/axios_utils';
 import createFlash from './flash';
diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js
index 8e931995fc6655f8fc5a949f8286d12cc7b25a99..77ab7c964e64b3300fe04981b695d451211ba987 100644
--- a/app/assets/javascripts/smart_interval.js
+++ b/app/assets/javascripts/smart_interval.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 /**
  * Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
  * and controllable by a public API.
diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js
index ce0fd3f6ff8cf3a965ca87e26c4b550e253c6ab7..dcee17453b8bd1dc84f255131abebc35f53b98e0 100644
--- a/app/assets/javascripts/snippet/snippet_bundle.js
+++ b/app/assets/javascripts/snippet/snippet_bundle.js
@@ -1,5 +1,7 @@
 /* global ace */
 
+import $ from 'jquery';
+
 export default () => {
   const editor = ace.edit('editor');
 
diff --git a/app/assets/javascripts/snippet/snippet_embed.js b/app/assets/javascripts/snippet/snippet_embed.js
new file mode 100644
index 0000000000000000000000000000000000000000..81ec483f2d907b2256e537f54f8853229bb8f861
--- /dev/null
+++ b/app/assets/javascripts/snippet/snippet_embed.js
@@ -0,0 +1,23 @@
+export default () => {
+  const { protocol, host, pathname } = location;
+  const shareBtn = document.querySelector('.js-share-btn');
+  const embedBtn = document.querySelector('.js-embed-btn');
+  const snippetUrlArea = document.querySelector('.js-snippet-url-area');
+  const embedAction = document.querySelector('.js-embed-action');
+  const url = `${protocol}//${host + pathname}`;
+
+  shareBtn.addEventListener('click', () => {
+    shareBtn.classList.add('is-active');
+    embedBtn.classList.remove('is-active');
+    snippetUrlArea.value = url;
+    embedAction.innerText = 'Share';
+  });
+
+  embedBtn.addEventListener('click', () => {
+    embedBtn.classList.add('is-active');
+    shareBtn.classList.remove('is-active');
+    const scriptTag = `<script src="${url}.js"></script>`;
+    snippetUrlArea.value = scriptTag;
+    embedAction.innerText = 'Embed';
+  });
+};
diff --git a/app/assets/javascripts/sortable/sortable_config.js b/app/assets/javascripts/sortable/sortable_config.js
new file mode 100644
index 0000000000000000000000000000000000000000..43ef5d664227b5caed733e9654b57d413a755e6f
--- /dev/null
+++ b/app/assets/javascripts/sortable/sortable_config.js
@@ -0,0 +1,7 @@
+export default {
+  animation: 200,
+  forceFallback: true,
+  fallbackClass: 'is-dragging',
+  fallbackOnBody: true,
+  ghostClass: 'is-ghost',
+};
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 3deb629d5f2d008557207b172e431228c0c02a92..f5a7fdae5d7d17fd5fdad631ae90ca2f7336c93d 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Flash from './flash';
 import { __, s__ } from './locale';
 import { spriteIcon } from './lib/utils/common_utils';
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index 3ed064f87a926e5ab7fb854757eac3564bfcceef..ebe1c6dd02d9d426743f3da7168ad2cce277b19f 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default function subscriptionSelect() {
   $('.js-subscription-event').each((i, element) => {
     const fieldName = $(element).data('fieldName');
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 62bdef76c5519b4ab0c454305e9a2b9fb7d1107f..f52990ba232ba6d4beb7af52bb8df21c486ec8e8 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -1,5 +1,7 @@
 /* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len */
 
+import $ from 'jquery';
+
 // Syntax Highlighter
 //
 // Applies a syntax highlighting color scheme CSS class to any element with the
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
index 8fa78b636f853fd9c37706560393e07398ff13ff..48782e63b9b14957ba7ff09aa0fc91670f490249 100644
--- a/app/assets/javascripts/task_list.js
+++ b/app/assets/javascripts/task_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import 'deckar01-task_list';
 import axios from './lib/utils/axios_utils';
 import Flash from './flash';
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index b5b64f44a11adef1b1d922494006220291ec22dc..6fea03af46aa09d38ff587b5e805b588f4d183ab 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-useless-return, max-len */
 
+import $ from 'jquery';
 import Api from '../api';
 import TemplateSelector from '../blob/template_selector';
 
diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js b/app/assets/javascripts/templates/issuable_template_selectors.js
index 66d868c58393db25d2c141b9f3c7d4f40ac9a6b2..50e58ec5c4624a385d8855741e9eb8907b254c56 100644
--- a/app/assets/javascripts/templates/issuable_template_selectors.js
+++ b/app/assets/javascripts/templates/issuable_template_selectors.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-new, class-methods-use-this */
+
+import $ from 'jquery';
 import IssuableTemplateSelector from './issuable_template_selector';
 
 export default class IssuableTemplateSelectors {
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index 6b9422b1816194c452743efd29d3405fe937d837..caffcddf3b0822e2f1112e792a19697e1c898aa8 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -1,13 +1,21 @@
 /* global Terminal */
 
+import $ from 'jquery';
+
 (() => {
   class GLTerminal {
 
     constructor(options) {
       this.options = options || {};
 
-      this.options.cursorBlink = options.cursorBlink || true;
-      this.options.screenKeys = options.screenKeys || true;
+      if (!Object.prototype.hasOwnProperty.call(this.options, 'cursorBlink')) {
+        this.options.cursorBlink = true;
+      }
+
+      if (!Object.prototype.hasOwnProperty.call(this.options, 'screenKeys')) {
+        this.options.screenKeys = true;
+      }
+
       this.container = document.querySelector(options.selector);
 
       this.setSocketUrl();
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 1a0b2c0415b229aacee9c57e14bbd470b32ef607..afbb958d05896ff0300675ee32d35999224ac730 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,4 +1,6 @@
 /* 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, class-methods-use-this */
+
+import $ from 'jquery';
 import { visitUrl } from './lib/utils/url_utility';
 
 export default class TreeView {
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index fd42f9c3baa9d8cf4993a5c476d8e33671f54da7..96af6d2fccaa9acab1e3892c0c27682e7648009e 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import importU2FLibrary from './util';
 import U2FError from './error';
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index 869fac658e8eb0ab59d09a82d5af25f31af9e7ac..01e259a741d577d02d8ad86faff41209d1ff3b24 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import importU2FLibrary from './util';
 import U2FError from './error';
diff --git a/app/assets/javascripts/ui_development_kit.js b/app/assets/javascripts/ui_development_kit.js
index 78dda172ee6374a20f610d5dce27f65d7e5cdaa9..9b242ea779d03f5f92ad5aa6d65a034507a20460 100644
--- a/app/assets/javascripts/ui_development_kit.js
+++ b/app/assets/javascripts/ui_development_kit.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Api from './api';
 
 export default () => {
diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js
index a783122d500b1152a6aac51075c7ff96352ec0ff..97d5cf96bcb202f1eda6d7fc131f8d38dd29e408 100644
--- a/app/assets/javascripts/user_callout.js
+++ b/app/assets/javascripts/user_callout.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Cookies from 'js-cookie';
 
 export default class UserCallout {
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 3385aba02798c3b3c895da699ea9eaf847563231..8486019897db7d7fd9a4e5a837f3e5c57854c138 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -1,8 +1,12 @@
 /* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */
 /* global Issuable */
 /* global emitSidebarEvent */
+
+import $ from 'jquery';
 import _ from 'underscore';
 import axios from './lib/utils/axios_utils';
+import { __ } from './locale';
+import ModalStore from './boards/stores/modal_store';
 
 // TODO: remove eventHub hack after code splitting refactor
 window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
@@ -179,7 +183,7 @@ function UsersSelect(currentUser, els, options = {}) {
 
         return axios.put(issueURL, data)
           .then(({ data }) => {
-            var user;
+            var user, tooltipTitle;
             $dropdown.trigger('loaded.gl.dropdown');
             $loading.fadeOut();
             if (data.assignee) {
@@ -188,15 +192,17 @@ function UsersSelect(currentUser, els, options = {}) {
                 username: data.assignee.username,
                 avatar: data.assignee.avatar_url
               };
+              tooltipTitle = _.escape(user.name);
             } else {
               user = {
                 name: 'Unassigned',
                 username: '',
                 avatar: ''
               };
+              tooltipTitle = __('Assignee');
             }
             $value.html(assigneeTemplate(user));
-            $collapsedSidebar.attr('title', _.escape(user.name)).tooltip('fixTitle');
+            $collapsedSidebar.attr('title', tooltipTitle).tooltip('fixTitle');
             return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
           });
       };
@@ -439,7 +445,7 @@ function UsersSelect(currentUser, els, options = {}) {
             return;
           }
           if ($el.closest('.add-issues-modal').length) {
-            gl.issueBoards.ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
+            ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
           } else if (handleClick) {
             e.preventDefault();
             handleClick(user, isMarking);
diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js
deleted file mode 100644
index 0c928d0d5f63469bc0e5d2acbc78d994fe4ed3c6..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/visibility_select.js
+++ /dev/null
@@ -1,21 +0,0 @@
-export default 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;
-    }
-  }
-
-  updateHelpText() {
-    this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
-  }
-}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7bef2e973495890b49723251534732eb6aec037e
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -0,0 +1,144 @@
+<script>
+import timeagoMixin from '../../vue_shared/mixins/timeago';
+import tooltip from '../../vue_shared/directives/tooltip';
+import LoadingButton from '../../vue_shared/components/loading_button.vue';
+import { visitUrl } from '../../lib/utils/url_utility';
+import createFlash from '../../flash';
+import MemoryUsage from './memory_usage.vue';
+import StatusIcon from './mr_widget_status_icon.vue';
+import MRWidgetService from '../services/mr_widget_service';
+
+export default {
+  name: 'Deployment',
+  components: {
+    LoadingButton,
+    MemoryUsage,
+    StatusIcon,
+  },
+  directives: {
+    tooltip,
+  },
+  mixins: [
+    timeagoMixin,
+  ],
+  props: {
+    deployment: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      isStopping: false,
+    };
+  },
+  computed: {
+    deployTimeago() {
+      return this.timeFormated(this.deployment.deployed_at);
+    },
+    hasExternalUrls() {
+      return !!(this.deployment.external_url && this.deployment.external_url_formatted);
+    },
+    hasDeploymentTime() {
+      return !!(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
+    },
+    hasDeploymentMeta() {
+      return !!(this.deployment.url && this.deployment.name);
+    },
+    hasMetrics() {
+      return !!(this.deployment.metrics_url);
+    },
+  },
+  methods: {
+    stopEnvironment() {
+      const msg = 'Are you sure you want to stop this environment?';
+      const isConfirmed = confirm(msg); // eslint-disable-line
+
+      if (isConfirmed) {
+        this.isStopping = true;
+
+        MRWidgetService.stopEnvironment(this.deployment.stop_url)
+          .then(res => res.data)
+          .then((data) => {
+            if (data.redirect_url) {
+              visitUrl(data.redirect_url);
+            }
+
+            this.isStopping = false;
+          })
+          .catch(() => {
+            createFlash('Something went wrong while stopping this environment. Please try again.');
+            this.isStopping = false;
+          });
+      }
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="mr-widget-heading deploy-heading">
+    <div class="ci-widget media">
+      <div class="ci-status-icon ci-status-icon-success">
+        <span class="js-icon-link icon-link">
+          <status-icon status="success" />
+        </span>
+      </div>
+      <div class="media-body">
+        <div class="deploy-body">
+          <template v-if="hasDeploymentMeta">
+            <span>
+              Deployed to
+            </span>
+            <a
+              :href="deployment.url"
+              target="_blank"
+              rel="noopener noreferrer nofollow"
+              class="deploy-link js-deploy-meta"
+            >
+              {{ deployment.name }}
+            </a>
+          </template>
+          <template v-if="hasExternalUrls">
+            <span>
+              on
+            </span>
+            <a
+              :href="deployment.external_url"
+              target="_blank"
+              rel="noopener noreferrer nofollow"
+              class="deploy-link js-deploy-url"
+            >
+              <i
+                class="fa fa-external-link"
+                aria-hidden="true"
+              >
+              </i>
+              {{ deployment.external_url_formatted }}
+            </a>
+          </template>
+          <span
+            v-if="hasDeploymentTime"
+            v-tooltip
+            :title="deployment.deployed_at_formatted"
+            class="js-deploy-time"
+          >
+            {{ deployTimeago }}
+          </span>
+          <loading-button
+            v-if="deployment.stop_url"
+            container-class="btn btn-default btn-xs prepend-left-default"
+            label="Stop environment"
+            :loading="isStopping"
+            @click="stopEnvironment"
+          />
+        </div>
+        <memory-usage
+          v-if="hasMetrics"
+          :metrics-url="deployment.metrics_url"
+          :metrics-monitoring-url="deployment.metrics_monitoring_url"
+        />
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f012f9c677259a1adde8e643be9fb08cd72bb227
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
@@ -0,0 +1,170 @@
+<script>
+import { sprintf, s__ } from '~/locale';
+import statusCodes from '../../lib/utils/http_status';
+import { bytesToMiB } from '../../lib/utils/number_utils';
+import { backOff } from '../../lib/utils/common_utils';
+import MemoryGraph from '../../vue_shared/components/memory_graph.vue';
+import MRWidgetService from '../services/mr_widget_service';
+
+export default {
+  name: 'MemoryUsage',
+  components: {
+    MemoryGraph,
+  },
+  props: {
+    metricsUrl: {
+      type: String,
+      required: true,
+    },
+    metricsMonitoringUrl: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      memoryFrom: 0,
+      memoryTo: 0,
+      memoryMetrics: [],
+      deploymentTime: 0,
+      hasMetrics: false,
+      loadFailed: false,
+      loadingMetrics: true,
+      backOffRequestCounter: 0,
+    };
+  },
+  computed: {
+    shouldShowLoading() {
+      return this.loadingMetrics && !this.hasMetrics && !this.loadFailed;
+    },
+    shouldShowMemoryGraph() {
+      return !this.loadingMetrics && this.hasMetrics && !this.loadFailed;
+    },
+    shouldShowLoadFailure() {
+      return !this.loadingMetrics && !this.hasMetrics && this.loadFailed;
+    },
+    shouldShowMetricsUnavailable() {
+      return !this.loadingMetrics && !this.hasMetrics && !this.loadFailed;
+    },
+    memoryChangeMessage() {
+      const messageProps = {
+        memoryFrom: this.memoryFrom,
+        memoryTo: this.memoryTo,
+        metricsLinkStart: `<a href="${this.metricsMonitoringUrl}">`,
+        metricsLinkEnd: '</a>',
+        emphasisStart: '<b>',
+        emphasisEnd: '</b>',
+      };
+      const memoryTo = Number(this.memoryTo);
+      const memoryFrom = Number(this.memoryFrom);
+      let memoryUsageMsg = '';
+
+      if (memoryTo > memoryFrom) {
+        memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false);
+      } else if (memoryTo < memoryFrom) {
+        memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false);
+      } else {
+        memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB'), messageProps, false);
+      }
+
+      return memoryUsageMsg;
+    },
+  },
+  mounted() {
+    this.loadingMetrics = true;
+    this.loadMetrics();
+  },
+  methods: {
+    getMegabytes(bytesString) {
+      const valueInBytes = Number(bytesString).toFixed(2);
+      return (bytesToMiB(valueInBytes)).toFixed(2);
+    },
+    computeGraphData(metrics, deploymentTime) {
+      this.loadingMetrics = false;
+      const { memory_before, memory_after, memory_values } = metrics;
+
+      // Both `memory_before` and `memory_after` objects
+      // have peculiar structure where accessing only a specific
+      // index yeilds correct value that we can use to show memory delta.
+      if (memory_before.length > 0) {
+        this.memoryFrom = this.getMegabytes(memory_before[0].value[1]);
+      }
+
+      if (memory_after.length > 0) {
+        this.memoryTo = this.getMegabytes(memory_after[0].value[1]);
+      }
+
+      if (memory_values.length > 0) {
+        this.hasMetrics = true;
+        this.memoryMetrics = memory_values[0].values;
+        this.deploymentTime = deploymentTime;
+      }
+    },
+    loadMetrics() {
+      backOff((next, stop) => {
+        MRWidgetService.fetchMetrics(this.metricsUrl)
+          .then((res) => {
+            if (res.status === statusCodes.NO_CONTENT) {
+              this.backOffRequestCounter = this.backOffRequestCounter += 1;
+              /* eslint-disable no-unused-expressions */
+              this.backOffRequestCounter < 3 ? next() : stop(res);
+            } else {
+              stop(res);
+            }
+          })
+          .catch(stop);
+      })
+        .then((res) => {
+          if (res.status === statusCodes.NO_CONTENT) {
+            return res;
+          }
+
+          return res.data;
+        })
+        .then((data) => {
+          this.computeGraphData(data.metrics, data.deployment_time);
+          return data;
+        })
+        .catch(() => {
+          this.loadFailed = true;
+          this.loadingMetrics = false;
+        });
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage">
+    <p
+      v-if="shouldShowLoading"
+      class="usage-info js-usage-info usage-info-loading">
+      <i
+        class="fa fa-spinner fa-spin usage-info-load-spinner"
+        aria-hidden="true">
+      </i>{{ s__('mrWidget|Loading deployment statistics') }}
+    </p>
+    <p
+      v-if="shouldShowMemoryGraph"
+      class="usage-info js-usage-info"
+      v-html="memoryChangeMessage">
+    </p>
+    <p
+      v-if="shouldShowLoadFailure"
+      class="usage-info js-usage-info usage-info-failed">
+      {{ s__('mrWidget|Failed to load deployment statistics') }}
+    </p>
+    <p
+      v-if="shouldShowMetricsUnavailable"
+      class="usage-info js-usage-info usage-info-unavailable">
+      {{ s__('mrWidget|Deployment statistics are not available currently') }}
+    </p>
+    <memory-graph
+      v-if="shouldShowMemoryGraph"
+      :metrics="memoryMetrics"
+      :deployment-time="deploymentTime"
+      height="25"
+      width="100"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
deleted file mode 100644
index d174a900f63dfb9edb7b3fc8595a44fb5f9c1cac..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { getTimeago } from '~/lib/utils/datetime_utility';
-import { visitUrl } from '../../lib/utils/url_utility';
-import Flash from '../../flash';
-import MemoryUsage from './mr_widget_memory_usage';
-import StatusIcon from './mr_widget_status_icon.vue';
-import MRWidgetService from '../services/mr_widget_service';
-
-export default {
-  name: 'MRWidgetDeployment',
-  props: {
-    mr: { type: Object, required: true },
-    service: { type: Object, required: true },
-  },
-  components: {
-    'mr-widget-memory-usage': MemoryUsage,
-    'status-icon': StatusIcon,
-  },
-  methods: {
-    formatDate(date) {
-      return getTimeago().format(date);
-    },
-    hasExternalUrls(deployment = {}) {
-      return deployment.external_url && deployment.external_url_formatted;
-    },
-    hasDeploymentTime(deployment = {}) {
-      return deployment.deployed_at && deployment.deployed_at_formatted;
-    },
-    hasDeploymentMeta(deployment = {}) {
-      return deployment.url && deployment.name;
-    },
-    stopEnvironment(deployment) {
-      const msg = 'Are you sure you want to stop this environment?';
-      const isConfirmed = confirm(msg); // eslint-disable-line
-
-      if (isConfirmed) {
-        MRWidgetService.stopEnvironment(deployment.stop_url)
-          .then(res => res.data)
-          .then((data) => {
-            if (data.redirect_url) {
-              visitUrl(data.redirect_url);
-            }
-          })
-          .catch(() => {
-            new Flash('Something went wrong while stopping this environment. Please try again.'); // eslint-disable-line
-          });
-      }
-    },
-  },
-  template: `
-    <div class="mr-widget-heading deploy-heading">
-      <div v-for="deployment in mr.deployments">
-        <div class="ci-widget media">
-          <div class="ci-status-icon ci-status-icon-success">
-            <span class="js-icon-link icon-link">
-              <status-icon status="success" />
-            </span>
-          </div>
-          <div class="media-body space-children">
-            <span>
-              <span
-                v-if="hasDeploymentMeta(deployment)">
-                Deployed to
-              </span>
-              <a
-                v-if="hasDeploymentMeta(deployment)"
-                :href="deployment.url"
-                target="_blank"
-                rel="noopener noreferrer nofollow"
-                class="js-deploy-meta inline">
-                {{deployment.name}}
-              </a>
-              <span
-                v-if="hasExternalUrls(deployment)">
-                on
-              </span>
-              <a
-                v-if="hasExternalUrls(deployment)"
-                :href="deployment.external_url"
-                target="_blank"
-                rel="noopener noreferrer nofollow"
-                class="js-deploy-url inline">
-                <i
-                  class="fa fa-external-link"
-                  aria-hidden="true" />
-                {{deployment.external_url_formatted}}
-              </a>
-              <span
-                v-if="hasDeploymentTime(deployment)"
-                :data-title="deployment.deployed_at_formatted"
-                class="js-deploy-time"
-                data-toggle="tooltip"
-                data-placement="top">
-                {{formatDate(deployment.deployed_at)}}
-              </span>
-            </span>
-            <button
-              type="button"
-              v-if="deployment.stop_url"
-              @click="stopEnvironment(deployment)"
-              class="btn btn-default btn-xs">
-              Stop environment
-            </button>
-            <mr-widget-memory-usage
-              v-if="deployment.metrics_url"
-              :metrics-url="deployment.metrics_url"
-              :metrics-monitoring-url="deployment.metrics_monitoring_url"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 18a3787857deb362c9818e77a7836d8b4cc95eec..18ee4c62bf11aa7ab8d4451bd0daec3cb03931ab 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -1,53 +1,57 @@
 <script>
-  import tooltip from '~/vue_shared/directives/tooltip';
-  import { n__ } from '~/locale';
-  import icon from '~/vue_shared/components/icon.vue';
-  import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import { n__ } from '~/locale';
+import { webIDEUrl } from '~/lib/utils/url_utility';
+import icon from '~/vue_shared/components/icon.vue';
+import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
 
-  export default {
-    name: 'MRWidgetHeader',
-    directives: {
-      tooltip,
+export default {
+  name: 'MRWidgetHeader',
+  directives: {
+    tooltip,
+  },
+  components: {
+    icon,
+    clipboardButton,
+  },
+  props: {
+    mr: {
+      type: Object,
+      required: true,
     },
-    components: {
-      icon,
-      clipboardButton,
+  },
+  computed: {
+    shouldShowCommitsBehindText() {
+      return this.mr.divergedCommitsCount > 0;
     },
-    props: {
-      mr: {
-        type: Object,
-        required: true,
-      },
+    commitsText() {
+      return n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount);
     },
-    computed: {
-      shouldShowCommitsBehindText() {
-        return this.mr.divergedCommitsCount > 0;
-      },
-      commitsText() {
-        return n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount);
-      },
-      branchNameClipboardData() {
-        // This supports code in app/assets/javascripts/copy_to_clipboard.js that
-        // works around ClipboardJS limitations to allow the context-specific
-        // copy/pasting of plain text or GFM.
-        return JSON.stringify({
-          text: this.mr.sourceBranch,
-          gfm: `\`${this.mr.sourceBranch}\``,
-        });
-      },
-      isSourceBranchLong() {
-        return this.isBranchTitleLong(this.mr.sourceBranch);
-      },
-      isTargetBranchLong() {
-        return this.isBranchTitleLong(this.mr.targetBranch);
-      },
+    branchNameClipboardData() {
+      // This supports code in app/assets/javascripts/copy_to_clipboard.js that
+      // works around ClipboardJS limitations to allow the context-specific
+      // copy/pasting of plain text or GFM.
+      return JSON.stringify({
+        text: this.mr.sourceBranch,
+        gfm: `\`${this.mr.sourceBranch}\``,
+      });
     },
-    methods: {
-      isBranchTitleLong(branchTitle) {
-        return branchTitle.length > 32;
-      },
+    isSourceBranchLong() {
+      return this.isBranchTitleLong(this.mr.sourceBranch);
     },
-  };
+    isTargetBranchLong() {
+      return this.isBranchTitleLong(this.mr.targetBranch);
+    },
+    webIdePath() {
+      return webIDEUrl(this.mr.statusPath.replace('.json', ''));
+    },
+  },
+  methods: {
+    isBranchTitleLong(branchTitle) {
+      return branchTitle.length > 32;
+    },
+  },
+};
 </script>
 <template>
   <div class="mr-source-target">
@@ -67,6 +71,7 @@
         <clipboard-button
           :text="branchNameClipboardData"
           :title="__('Copy branch name to clipboard')"
+          css-class="btn-default btn-transparent btn-clipboard"
         />
 
         {{ s__("mrWidget|into") }}
@@ -95,6 +100,13 @@
     </div>
 
     <div v-if="mr.isOpen">
+      <a
+        v-if="!mr.sourceBranchRemoved"
+        :href="webIdePath"
+        class="btn btn-sm btn-default inline js-web-ide"
+      >
+        {{ s__("mrWidget|Web IDE") }}
+      </a>
       <button
         data-target="#modal_merge_info"
         data-toggle="modal"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f0298f732ea20360e44765e05f8701fce529db57
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue
@@ -0,0 +1,20 @@
+<script>
+  export default {
+    name: 'MRWidgetMaintainerEdit',
+    props: {
+      maintainerEditAllowed: {
+        type: Boolean,
+        default: false,
+        required: false,
+      },
+    },
+  };
+</script>
+
+<template>
+  <section class="mr-info-list mr-links">
+    <p v-if="maintainerEditAllowed">
+      {{ s__("mrWidget|Allows edits from maintainers") }}
+    </p>
+  </section>
+</template>
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
deleted file mode 100644
index 69e70ba1dd6b7d95156ee71e70a82eb195ed5918..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ /dev/null
@@ -1,153 +0,0 @@
-import statusCodes from '../../lib/utils/http_status';
-import { bytesToMiB } from '../../lib/utils/number_utils';
-import { backOff } from '../../lib/utils/common_utils';
-import MemoryGraph from '../../vue_shared/components/memory_graph';
-import MRWidgetService from '../services/mr_widget_service';
-
-export default {
-  name: 'MemoryUsage',
-  props: {
-    metricsUrl: {
-      type: String,
-      required: true,
-    },
-    metricsMonitoringUrl: {
-      type: String,
-      required: true,
-    },
-  },
-  data() {
-    return {
-      memoryFrom: 0,
-      memoryTo: 0,
-      memoryMetrics: [],
-      deploymentTime: 0,
-      hasMetrics: false,
-      loadFailed: false,
-      loadingMetrics: true,
-      backOffRequestCounter: 0,
-    };
-  },
-  components: {
-    'mr-memory-graph': MemoryGraph,
-  },
-  computed: {
-    shouldShowLoading() {
-      return this.loadingMetrics && !this.hasMetrics && !this.loadFailed;
-    },
-    shouldShowMemoryGraph() {
-      return !this.loadingMetrics && this.hasMetrics && !this.loadFailed;
-    },
-    shouldShowLoadFailure() {
-      return !this.loadingMetrics && !this.hasMetrics && this.loadFailed;
-    },
-    shouldShowMetricsUnavailable() {
-      return !this.loadingMetrics && !this.hasMetrics && !this.loadFailed;
-    },
-    memoryChangeType() {
-      const memoryTo = Number(this.memoryTo);
-      const memoryFrom = Number(this.memoryFrom);
-
-      if (memoryTo > memoryFrom) {
-        return 'increased';
-      } else if (memoryTo < memoryFrom) {
-        return 'decreased';
-      }
-
-      return 'unchanged';
-    },
-  },
-  methods: {
-    getMegabytes(bytesString) {
-      const valueInBytes = Number(bytesString).toFixed(2);
-      return (bytesToMiB(valueInBytes)).toFixed(2);
-    },
-    computeGraphData(metrics, deploymentTime) {
-      this.loadingMetrics = false;
-      const { memory_before, memory_after, memory_values } = metrics;
-
-      // Both `memory_before` and `memory_after` objects
-      // have peculiar structure where accessing only a specific
-      // index yeilds correct value that we can use to show memory delta.
-      if (memory_before.length > 0) {
-        this.memoryFrom = this.getMegabytes(memory_before[0].value[1]);
-      }
-
-      if (memory_after.length > 0) {
-        this.memoryTo = this.getMegabytes(memory_after[0].value[1]);
-      }
-
-      if (memory_values.length > 0) {
-        this.hasMetrics = true;
-        this.memoryMetrics = memory_values[0].values;
-        this.deploymentTime = deploymentTime;
-      }
-    },
-    loadMetrics() {
-      backOff((next, stop) => {
-        MRWidgetService.fetchMetrics(this.metricsUrl)
-          .then((res) => {
-            if (res.status === statusCodes.NO_CONTENT) {
-              this.backOffRequestCounter = this.backOffRequestCounter += 1;
-              /* eslint-disable no-unused-expressions */
-              this.backOffRequestCounter < 3 ? next() : stop(res);
-            } else {
-              stop(res);
-            }
-          })
-          .catch(stop);
-      })
-        .then((res) => {
-          if (res.status === statusCodes.NO_CONTENT) {
-            return res;
-          }
-
-          return res.data;
-        })
-        .then((data) => {
-          this.computeGraphData(data.metrics, data.deployment_time);
-          return data;
-        })
-        .catch(() => {
-          this.loadFailed = true;
-          this.loadingMetrics = false;
-        });
-    },
-  },
-  mounted() {
-    this.loadingMetrics = true;
-    this.loadMetrics();
-  },
-  template: `
-    <div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage">
-      <p
-        v-if="shouldShowLoading"
-        class="usage-info js-usage-info usage-info-loading">
-        <i
-          class="fa fa-spinner fa-spin usage-info-load-spinner"
-          aria-hidden="true" />Loading deployment statistics
-      </p>
-      <p
-        v-if="shouldShowMemoryGraph"
-        class="usage-info js-usage-info">
-        <a :href="metricsMonitoringUrl">Memory</a> usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
-      </p>
-      <p
-        v-if="shouldShowLoadFailure"
-        class="usage-info js-usage-info usage-info-failed">
-        Failed to load deployment statistics
-      </p>
-      <p
-        v-if="shouldShowMetricsUnavailable"
-        class="usage-info js-usage-info usage-info-unavailable">
-        Deployment statistics are not available currently
-      </p>
-      <mr-memory-graph
-        v-if="shouldShowMemoryGraph"
-        :metrics="memoryMetrics"
-        :deploymentTime="deploymentTime"
-        height="25"
-        width="100" />
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index 54a98abf860be16eb4bed9b0349f3dc4a9860600..48dff8c4916f024d422d68be6f3f7bdd54fb3a7b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -1,56 +1,61 @@
 <script>
-  /* eslint-disable vue/require-default-prop */
-  import pipelineStage from '~/pipelines/components/stage.vue';
-  import ciIcon from '~/vue_shared/components/ci_icon.vue';
-  import icon from '~/vue_shared/components/icon.vue';
+/* eslint-disable vue/require-default-prop */
+import PipelineStage from '~/pipelines/components/stage.vue';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
 
-  export default {
-    name: 'MRWidgetPipeline',
-    components: {
-      pipelineStage,
-      ciIcon,
-      icon,
+export default {
+  name: 'MRWidgetPipeline',
+  components: {
+    PipelineStage,
+    CiIcon,
+    Icon,
+  },
+  props: {
+    pipeline: {
+      type: Object,
+      required: true,
     },
-    props: {
-      pipeline: {
-        type: Object,
-        required: true,
-      },
-      // This prop needs to be camelCase, html attributes are case insensive
-      // https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
-      hasCi: {
-        type: Boolean,
-        required: false,
-      },
-      ciStatus: {
-        type: String,
-        required: false,
-      },
+    // This prop needs to be camelCase, html attributes are case insensive
+    // https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
+    hasCi: {
+      type: Boolean,
+      required: false,
     },
-    computed: {
-      hasPipeline() {
-        return this.pipeline && Object.keys(this.pipeline).length > 0;
-      },
-      hasCIError() {
-        return this.hasCi && !this.ciStatus;
-      },
-      status() {
-        return this.pipeline.details &&
-          this.pipeline.details.status ? this.pipeline.details.status : {};
-      },
-      hasStages() {
-        return this.pipeline.details &&
-          this.pipeline.details.stages &&
-          this.pipeline.details.stages.length;
-      },
+    ciStatus: {
+      type: String,
+      required: false,
     },
-  };
+  },
+  computed: {
+    hasPipeline() {
+      return this.pipeline && Object.keys(this.pipeline).length > 0;
+    },
+    hasCIError() {
+      return this.hasCi && !this.ciStatus;
+    },
+    status() {
+      return this.pipeline.details && this.pipeline.details.status
+        ? this.pipeline.details.status
+        : {};
+    },
+    hasStages() {
+      return (
+        this.pipeline.details && this.pipeline.details.stages && this.pipeline.details.stages.length
+      );
+    },
+    hasCommitInfo() {
+      return this.pipeline.commit && Object.keys(this.pipeline.commit).length > 0;
+    },
+  },
+};
 </script>
 
 <template>
   <div
     v-if="hasPipeline || hasCIError"
-    class="mr-widget-heading">
+    class="mr-widget-heading"
+  >
     <div class="ci-widget media">
       <template v-if="hasCIError">
         <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
@@ -77,13 +82,17 @@
             #{{ pipeline.id }}
           </a>
 
-          {{ pipeline.details.status.label }} for
+          {{ pipeline.details.status.label }}
 
-          <a
-            :href="pipeline.commit.commit_path"
-            class="commit-sha js-commit-link"
-          >
-          {{ pipeline.commit.short_id }}</a>.
+          <template v-if="hasCommitInfo">
+            for
+
+            <a
+              :href="pipeline.commit.commit_path"
+              class="commit-sha js-commit-link"
+            >
+            {{ pipeline.commit.short_id }}</a>.
+          </template>
 
           <span class="mr-widget-pipeline-graph">
             <span
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
new file mode 100644
index 0000000000000000000000000000000000000000..460437ceeff7392929c617996c1f0aed76907990
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -0,0 +1,34 @@
+<script>
+  import tooltip from '../../vue_shared/directives/tooltip';
+  import { __ } from '../../locale';
+
+  export default {
+    directives: {
+      tooltip,
+    },
+    created() {
+      this.removesBranchText = __('<strong>Removes</strong> source branch');
+      this.tooltipTitle = __('A user with write access to the source branch selected this option');
+    },
+  };
+</script>
+
+<template>
+  <p
+    v-once
+    class="mr-info-list mr-links source-branch-removal-status append-bottom-0"
+  >
+    <span
+      class="status-text"
+      v-html="removesBranchText"
+    >
+    </span>
+    <i
+      v-tooltip
+      class="fa fa-question-circle"
+      :title="tooltipTitle"
+      :aria-label="tooltipTitle"
+    >
+    </i>
+  </p>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
index de98a77be6fcebcd981f50dd23f9d8ba86d36f9a..7ff7fc7988ab64a45d222cfa1a45562ebef80ab5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
@@ -63,7 +63,7 @@
         };
 
         this.isRemovingSourceBranch = true;
-        this.service.mergeResource.save(options)
+        this.service.merge(options)
           .then(res => res.data)
           .then((data) => {
             if (data.status === 'merge_when_pipeline_succeeds') {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
deleted file mode 100644
index ebfd6765934a5fe61bc23bc60c1526bfb2b09406..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
-
-export default {
-  name: 'MRWidgetNothingToMerge',
-  props: {
-    mr: {
-      type: Object,
-      required: true,
-    },
-  },
-  data() {
-    return { emptyStateSVG };
-  },
-  template: `
-    <div class="mr-widget-body mr-widget-empty-state">
-      <div class="row">
-        <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
-          <span v-html="emptyStateSVG"></span>
-        </div>
-        <div class="text col-sm-7 col-sm-pull-5 col-xs-12">
-          <span>
-            Merge requests are a place to propose changes you have made to a project
-            and discuss those changes with others.
-          </span>
-          <p>
-            Interested parties can even contribute by pushing commits if they want to.
-          </p>
-          <p>
-            Currently there are no changes in this merge request's source branch.
-            Please push new commits or use a different branch.
-          </p>
-          <div>
-            <a
-              v-if="mr.newBlobPath"
-              :href="mr.newBlobPath"
-              class="btn btn-inverted btn-save">
-              Create file
-            </a>
-          </div>
-        </div>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
deleted file mode 100644
index 4d9a2ca530f68806aed83e12e63525be21334a2e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import statusIcon from '../mr_widget_status_icon.vue';
-
-export default {
-  name: 'MRWidgetPipelineBlocked',
-  components: {
-    statusIcon,
-  },
-  template: `
-    <div class="mr-widget-body media">
-      <status-icon status="warning" :show-disabled-button="true" />
-      <div class="media-body space-children">
-        <span class="bold">
-          The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
-        </span>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
deleted file mode 100644
index 162f048aac7d182d3522d95a89580bf06785aa65..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ /dev/null
@@ -1,352 +0,0 @@
-import successSvg from 'icons/_icon_status_success.svg';
-import warningSvg from 'icons/_icon_status_warning.svg';
-import simplePoll from '~/lib/utils/simple_poll';
-import MergeRequest from '../../../merge_request';
-import Flash from '../../../flash';
-import statusIcon from '../mr_widget_status_icon.vue';
-import eventHub from '../../event_hub';
-
-export default {
-  name: 'MRWidgetReadyToMerge',
-  props: {
-    mr: { type: Object, required: true },
-    service: { type: Object, required: true },
-  },
-  data() {
-    return {
-      removeSourceBranch: this.mr.shouldRemoveSourceBranch,
-      mergeWhenBuildSucceeds: false,
-      useCommitMessageWithDescription: false,
-      setToMergeWhenPipelineSucceeds: false,
-      showCommitMessageEditor: false,
-      isMakingRequest: false,
-      isMergingImmediately: false,
-      commitMessage: this.mr.commitMessage,
-      successSvg,
-      warningSvg,
-    };
-  },
-  components: {
-    statusIcon,
-  },
-  computed: {
-    shouldShowMergeWhenPipelineSucceedsText() {
-      return this.mr.isPipelineActive;
-    },
-    commitMessageLinkTitle() {
-      const withDesc = 'Include description in commit message';
-      const withoutDesc = "Don't include description in commit message";
-
-      return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
-    },
-    status() {
-      const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
-
-      if (hasCI && !ciStatus) {
-        return 'failed';
-      } else if (!pipeline) {
-        return 'success';
-      } else if (isPipelineActive) {
-        return 'pending';
-      } else if (isPipelineFailed) {
-        return 'failed';
-      }
-
-      return 'success';
-    },
-    mergeButtonClass() {
-      const defaultClass = 'btn btn-sm btn-success accept-merge-request';
-      const failedClass = `${defaultClass} btn-danger`;
-      const inActionClass = `${defaultClass} btn-info`;
-
-      if (this.status === 'failed') {
-        return failedClass;
-      } else if (this.status === 'pending') {
-        return inActionClass;
-      }
-
-      return defaultClass;
-    },
-    iconClass() {
-      if (this.status === 'failed' || !this.commitMessage.length || !this.mr.isMergeAllowed || this.mr.preventMerge) {
-        return 'warning';
-      }
-      return 'success';
-    },
-    mergeButtonText() {
-      if (this.isMergingImmediately) {
-        return 'Merge in progress';
-      } else if (this.shouldShowMergeWhenPipelineSucceedsText) {
-        return 'Merge when pipeline succeeds';
-      }
-
-      return 'Merge';
-    },
-    shouldShowMergeOptionsDropdown() {
-      return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
-    },
-    isMergeButtonDisabled() {
-      const { commitMessage } = this;
-      return Boolean(!commitMessage.length
-        || !this.shouldShowMergeControls()
-        || this.isMakingRequest
-        || this.mr.preventMerge);
-    },
-    isRemoveSourceBranchButtonDisabled() {
-      return this.isMergeButtonDisabled || !this.mr.canRemoveSourceBranch;
-    },
-    shouldShowSquashBeforeMerge() {
-      const { commitsCount, enableSquashBeforeMerge } = this.mr;
-      return enableSquashBeforeMerge && commitsCount > 1;
-    },
-  },
-  methods: {
-    shouldShowMergeControls() {
-      return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
-    },
-    updateCommitMessage() {
-      const cmwd = this.mr.commitMessageWithDescription;
-      this.useCommitMessageWithDescription = !this.useCommitMessageWithDescription;
-      this.commitMessage = this.useCommitMessageWithDescription ? cmwd : this.mr.commitMessage;
-    },
-    toggleCommitMessageEditor() {
-      this.showCommitMessageEditor = !this.showCommitMessageEditor;
-    },
-    handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
-      // TODO: Remove no-param-reassign
-      if (mergeWhenBuildSucceeds === undefined) {
-        mergeWhenBuildSucceeds = this.mr.isPipelineActive; // eslint-disable-line no-param-reassign
-      } else if (mergeImmediately) {
-        this.isMergingImmediately = true;
-      }
-
-      this.setToMergeWhenPipelineSucceeds = mergeWhenBuildSucceeds === true;
-
-      const options = {
-        sha: this.mr.sha,
-        commit_message: this.commitMessage,
-        merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
-        should_remove_source_branch: this.removeSourceBranch === true,
-      };
-
-      // Only truthy in EE extension of this component
-      if (this.setAdditionalParams) {
-        this.setAdditionalParams(options);
-      }
-
-      this.isMakingRequest = true;
-      this.service.merge(options)
-        .then(res => res.data)
-        .then((data) => {
-          const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
-
-          if (data.status === 'merge_when_pipeline_succeeds') {
-            eventHub.$emit('MRWidgetUpdateRequested');
-          } else if (data.status === 'success') {
-            this.initiateMergePolling();
-          } else if (hasError) {
-            eventHub.$emit('FailedToMerge', data.merge_error);
-          }
-        })
-        .catch(() => {
-          this.isMakingRequest = false;
-          new Flash('Something went wrong. Please try again.'); // eslint-disable-line
-        });
-    },
-    initiateMergePolling() {
-      simplePoll((continuePolling, stopPolling) => {
-        this.handleMergePolling(continuePolling, stopPolling);
-      });
-    },
-    handleMergePolling(continuePolling, stopPolling) {
-      this.service.poll()
-        .then(res => res.data)
-        .then((data) => {
-          if (data.state === 'merged') {
-            // If state is merged we should update the widget and stop the polling
-            eventHub.$emit('MRWidgetUpdateRequested');
-            eventHub.$emit('FetchActionsContent');
-            MergeRequest.setStatusBoxToMerged();
-            MergeRequest.hideCloseButton();
-            MergeRequest.decreaseCounter();
-            stopPolling();
-
-            // If user checked remove source branch and we didn't remove the branch yet
-            // we should start another polling for source branch remove process
-            if (this.removeSourceBranch && data.source_branch_exists) {
-              this.initiateRemoveSourceBranchPolling();
-            }
-          } else if (data.merge_error) {
-            eventHub.$emit('FailedToMerge', data.merge_error);
-            stopPolling();
-          } else {
-            // MR is not merged yet, continue polling until the state becomes 'merged'
-            continuePolling();
-          }
-        })
-        .catch(() => {
-          new Flash('Something went wrong while merging this merge request. Please try again.'); // eslint-disable-line
-        });
-    },
-    initiateRemoveSourceBranchPolling() {
-      // We need to show source branch is being removed spinner in another component
-      eventHub.$emit('SetBranchRemoveFlag', [true]);
-
-      simplePoll((continuePolling, stopPolling) => {
-        this.handleRemoveBranchPolling(continuePolling, stopPolling);
-      });
-    },
-    handleRemoveBranchPolling(continuePolling, stopPolling) {
-      this.service.poll()
-        .then(res => res.data)
-        .then((data) => {
-          // If source branch exists then we should continue polling
-          // because removing a source branch is a background task and takes time
-          if (data.source_branch_exists) {
-            continuePolling();
-          } else {
-            // Branch is removed. Update widget, stop polling and hide the spinner
-            eventHub.$emit('MRWidgetUpdateRequested', () => {
-              eventHub.$emit('SetBranchRemoveFlag', [false]);
-            });
-            stopPolling();
-          }
-        })
-        .catch(() => {
-          new Flash('Something went wrong while removing the source branch. Please try again.'); // eslint-disable-line
-        });
-    },
-  },
-  template: `
-    <div class="mr-widget-body media">
-      <status-icon :status="iconClass" />
-      <div class="media-body">
-        <div class="mr-widget-body-controls media space-children">
-          <span class="btn-group append-bottom-5">
-            <button
-              @click="handleMergeButtonClick()"
-              :disabled="isMergeButtonDisabled"
-              :class="mergeButtonClass"
-              type="button"
-              class="qa-merge-button">
-              <i
-                v-if="isMakingRequest"
-                class="fa fa-spinner fa-spin"
-                aria-hidden="true" />
-              {{mergeButtonText}}
-            </button>
-            <button
-              v-if="shouldShowMergeOptionsDropdown"
-              :disabled="isMergeButtonDisabled"
-              type="button"
-              class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
-              data-toggle="dropdown"
-              aria-label="Select merge moment">
-              <i
-                class="fa fa-chevron-down"
-                aria-hidden="true" />
-            </button>
-            <ul
-              v-if="shouldShowMergeOptionsDropdown"
-              class="dropdown-menu dropdown-menu-right"
-              role="menu">
-              <li>
-                <a
-                  @click.prevent="handleMergeButtonClick(true)"
-                  class="merge_when_pipeline_succeeds"
-                  href="#">
-                  <span class="media">
-                    <span
-                      v-html="successSvg"
-                      class="merge-opt-icon"
-                      aria-hidden="true"></span>
-                    <span class="media-body merge-opt-title">Merge when pipeline succeeds</span>
-                  </span>
-                </a>
-              </li>
-              <li>
-                <a
-                  @click.prevent="handleMergeButtonClick(false, true)"
-                  class="accept-merge-request"
-                  href="#">
-                  <span class="media">
-                    <span
-                      v-html="warningSvg"
-                      class="merge-opt-icon"
-                      aria-hidden="true"></span>
-                    <span class="media-body merge-opt-title">Merge immediately</span>
-                  </span>
-                </a>
-              </li>
-            </ul>
-          </span>
-          <div class="media-body-wrap space-children">
-            <template v-if="shouldShowMergeControls()">
-              <label>
-                <input
-                  id="remove-source-branch-input"
-                  v-model="removeSourceBranch"
-                  class="js-remove-source-branch-checkbox"
-                  :disabled="isRemoveSourceBranchButtonDisabled"
-                  type="checkbox"/> Remove source branch
-              </label>
-
-              <!-- Placeholder for EE extension of this component -->
-              <squash-before-merge
-                v-if="shouldShowSquashBeforeMerge"
-                :mr="mr"
-                :is-merge-button-disabled="isMergeButtonDisabled" />
-
-              <span
-                v-if="mr.ffOnlyEnabled"
-                class="js-fast-forward-message">
-                Fast-forward merge without a merge commit
-              </span>
-              <button
-                v-else
-                @click="toggleCommitMessageEditor"
-                :disabled="isMergeButtonDisabled"
-                class="js-modify-commit-message-button btn btn-default btn-xs"
-                type="button">
-                Modify commit message
-              </button>
-            </template>
-            <template v-else>
-              <span class="bold js-resolve-mr-widget-items-message">
-                You can only merge once the items above are resolved
-              </span>
-            </template>
-          </div>
-        </div>
-        <div
-          v-if="showCommitMessageEditor"
-          class="prepend-top-default commit-message-editor">
-          <div class="form-group clearfix">
-            <label
-              class="control-label"
-              for="commit-message">
-              Commit message
-            </label>
-            <div class="col-sm-10">
-              <div class="commit-message-container">
-                <div class="max-width-marker"></div>
-                <textarea
-                  v-model="commitMessage"
-                  class="form-control js-commit-message"
-                  required="required"
-                  rows="14"
-                  name="Commit message"></textarea>
-              </div>
-              <p class="hint">Try to keep the first line under 52 characters and the others under 72</p>
-              <div class="hint">
-                <a
-                  @click.prevent="updateCommitMessage"
-                  href="#">{{commitMessageLinkTitle}}</a>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
deleted file mode 100644
index 142ddf477f1d1c114c0f4a876b6d51b2af213162..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import statusIcon from '../mr_widget_status_icon.vue';
-
-export default {
-  name: 'MRWidgetSHAMismatch',
-  components: {
-    statusIcon,
-  },
-  template: `
-    <div class="mr-widget-body media">
-      <status-icon status="warning" :show-disabled-button="true" />
-      <div class="media-body space-children">
-        <span class="bold">
-          The source branch HEAD has recently changed. Please reload the page and review the changes before merging
-        </span>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
deleted file mode 100644
index 67b271c69caf24b34efc1b2b0412cf09bbb0d968..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import statusIcon from '../mr_widget_status_icon.vue';
-
-export default {
-  name: 'MRWidgetUnresolvedDiscussions',
-  props: {
-    mr: { type: Object, required: true },
-  },
-  components: {
-    statusIcon,
-  },
-  template: `
-    <div class="mr-widget-body media">
-      <status-icon status="warning" :show-disabled-button="true" />
-      <div class="media-body space-children">
-        <span class="bold">
-          There are unresolved discussions. Please resolve these discussions
-        </span>
-        <a
-          v-if="mr.createIssueToResolveDiscussionsPath"
-          :href="mr.createIssueToResolveDiscussionsPath"
-          class="btn btn-default btn-xs js-create-issue">
-          Create an issue to resolve them later
-        </a>
-      </div>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
index bbca641f65eca3ab7a2eb9a75f0d4ceb7b9e0f20..44e1a616a191194d476ef449d0a8e50359391875 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import statusIcon from '../mr_widget_status_icon.vue';
 import tooltip from '../../../vue_shared/directives/tooltip';
 import eventHub from '../../event_hub';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3d9161f6926d304c08ac5c66b1246dd68de125f9
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -0,0 +1,47 @@
+<script>
+import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
+
+export default {
+  name: 'MRWidgetNothingToMerge',
+  props: {
+    mr: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return { emptyStateSVG };
+  },
+};
+</script>
+
+<template>
+  <div class="mr-widget-body mr-widget-empty-state">
+    <div class="row">
+      <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
+        <span v-html="emptyStateSVG"></span>
+      </div>
+      <div class="text col-sm-7 col-sm-pull-5 col-xs-12">
+        <span>
+          Merge requests are a place to propose changes you have made to a project
+          and discuss those changes with others.
+        </span>
+        <p>
+          Interested parties can even contribute by pushing commits if they want to.
+        </p>
+        <p>
+          Currently there are no changes in this merge request's source branch.
+          Please push new commits or use a different branch.
+        </p>
+        <div>
+          <a
+            v-if="mr.newBlobPath"
+            :href="mr.newBlobPath"
+            class="btn btn-inverted btn-save">
+            Create file
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8d55477929f24eb9d9d3abbaf15a2966315aba5d
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
@@ -0,0 +1,25 @@
+<script>
+import statusIcon from '../mr_widget_status_icon.vue';
+
+export default {
+  name: 'PipelineFailed',
+  components: {
+    statusIcon,
+  },
+};
+</script>
+
+<template>
+  <div class="mr-widget-body media">
+    <status-icon
+      status="warning"
+      :show-disabled-button="true"
+    />
+    <div class="media-body space-children">
+      <span class="bold">
+        {{ s__(`mrWidget|The pipeline for this merge request failed.
+Please retry the job or push a new commit to fix the failure`) }}
+      </span>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0264625a526e22023ef08d208a45841ceda8efa4
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -0,0 +1,363 @@
+<script>
+import successSvg from 'icons/_icon_status_success.svg';
+import warningSvg from 'icons/_icon_status_warning.svg';
+import simplePoll from '~/lib/utils/simple_poll';
+import MergeRequest from '../../../merge_request';
+import Flash from '../../../flash';
+import statusIcon from '../mr_widget_status_icon.vue';
+import eventHub from '../../event_hub';
+
+export default {
+  name: 'ReadyToMerge',
+  components: {
+    statusIcon,
+  },
+  props: {
+    mr: { type: Object, required: true },
+    service: { type: Object, required: true },
+  },
+  data() {
+    return {
+      removeSourceBranch: this.mr.shouldRemoveSourceBranch,
+      mergeWhenBuildSucceeds: false,
+      useCommitMessageWithDescription: false,
+      setToMergeWhenPipelineSucceeds: false,
+      showCommitMessageEditor: false,
+      isMakingRequest: false,
+      isMergingImmediately: false,
+      commitMessage: this.mr.commitMessage,
+      successSvg,
+      warningSvg,
+    };
+  },
+  computed: {
+    shouldShowMergeWhenPipelineSucceedsText() {
+      return this.mr.isPipelineActive;
+    },
+    commitMessageLinkTitle() {
+      const withDesc = 'Include description in commit message';
+      const withoutDesc = "Don't include description in commit message";
+
+      return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
+    },
+    status() {
+      const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
+
+      if (hasCI && !ciStatus) {
+        return 'failed';
+      } else if (!pipeline) {
+        return 'success';
+      } else if (isPipelineActive) {
+        return 'pending';
+      } else if (isPipelineFailed) {
+        return 'failed';
+      }
+
+      return 'success';
+    },
+    mergeButtonClass() {
+      const defaultClass = 'btn btn-sm btn-success accept-merge-request';
+      const failedClass = `${defaultClass} btn-danger`;
+      const inActionClass = `${defaultClass} btn-info`;
+
+      if (this.status === 'failed') {
+        return failedClass;
+      } else if (this.status === 'pending') {
+        return inActionClass;
+      }
+
+      return defaultClass;
+    },
+    iconClass() {
+      if (this.status === 'failed' || !this.commitMessage.length || !this.mr.isMergeAllowed || this.mr.preventMerge) {
+        return 'warning';
+      }
+      return 'success';
+    },
+    mergeButtonText() {
+      if (this.isMergingImmediately) {
+        return 'Merge in progress';
+      } else if (this.shouldShowMergeWhenPipelineSucceedsText) {
+        return 'Merge when pipeline succeeds';
+      }
+
+      return 'Merge';
+    },
+    shouldShowMergeOptionsDropdown() {
+      return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
+    },
+    isMergeButtonDisabled() {
+      const { commitMessage } = this;
+      return Boolean(!commitMessage.length
+        || !this.shouldShowMergeControls()
+        || this.isMakingRequest
+        || this.mr.preventMerge);
+    },
+    isRemoveSourceBranchButtonDisabled() {
+      return this.isMergeButtonDisabled;
+    },
+    shouldShowSquashBeforeMerge() {
+      const { commitsCount, enableSquashBeforeMerge } = this.mr;
+      return enableSquashBeforeMerge && commitsCount > 1;
+    },
+  },
+  methods: {
+    shouldShowMergeControls() {
+      return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
+    },
+    updateCommitMessage() {
+      const cmwd = this.mr.commitMessageWithDescription;
+      this.useCommitMessageWithDescription = !this.useCommitMessageWithDescription;
+      this.commitMessage = this.useCommitMessageWithDescription ? cmwd : this.mr.commitMessage;
+    },
+    toggleCommitMessageEditor() {
+      this.showCommitMessageEditor = !this.showCommitMessageEditor;
+    },
+    handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
+      // TODO: Remove no-param-reassign
+      if (mergeWhenBuildSucceeds === undefined) {
+        mergeWhenBuildSucceeds = this.mr.isPipelineActive; // eslint-disable-line no-param-reassign
+      } else if (mergeImmediately) {
+        this.isMergingImmediately = true;
+      }
+
+      this.setToMergeWhenPipelineSucceeds = mergeWhenBuildSucceeds === true;
+
+      const options = {
+        sha: this.mr.sha,
+        commit_message: this.commitMessage,
+        merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
+        should_remove_source_branch: this.removeSourceBranch === true,
+      };
+
+      // Only truthy in EE extension of this component
+      if (this.setAdditionalParams) {
+        this.setAdditionalParams(options);
+      }
+
+      this.isMakingRequest = true;
+      this.service.merge(options)
+        .then(res => res.data)
+        .then((data) => {
+          const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
+
+          if (data.status === 'merge_when_pipeline_succeeds') {
+            eventHub.$emit('MRWidgetUpdateRequested');
+          } else if (data.status === 'success') {
+            this.initiateMergePolling();
+          } else if (hasError) {
+            eventHub.$emit('FailedToMerge', data.merge_error);
+          }
+        })
+        .catch(() => {
+          this.isMakingRequest = false;
+          new Flash('Something went wrong. Please try again.'); // eslint-disable-line
+        });
+    },
+    initiateMergePolling() {
+      simplePoll((continuePolling, stopPolling) => {
+        this.handleMergePolling(continuePolling, stopPolling);
+      });
+    },
+    handleMergePolling(continuePolling, stopPolling) {
+      this.service.poll()
+        .then(res => res.data)
+        .then((data) => {
+          if (data.state === 'merged') {
+            // If state is merged we should update the widget and stop the polling
+            eventHub.$emit('MRWidgetUpdateRequested');
+            eventHub.$emit('FetchActionsContent');
+            MergeRequest.setStatusBoxToMerged();
+            MergeRequest.hideCloseButton();
+            MergeRequest.decreaseCounter();
+            stopPolling();
+
+            // If user checked remove source branch and we didn't remove the branch yet
+            // we should start another polling for source branch remove process
+            if (this.removeSourceBranch && data.source_branch_exists) {
+              this.initiateRemoveSourceBranchPolling();
+            }
+          } else if (data.merge_error) {
+            eventHub.$emit('FailedToMerge', data.merge_error);
+            stopPolling();
+          } else {
+            // MR is not merged yet, continue polling until the state becomes 'merged'
+            continuePolling();
+          }
+        })
+        .catch(() => {
+          new Flash('Something went wrong while merging this merge request. Please try again.'); // eslint-disable-line
+        });
+    },
+    initiateRemoveSourceBranchPolling() {
+      // We need to show source branch is being removed spinner in another component
+      eventHub.$emit('SetBranchRemoveFlag', [true]);
+
+      simplePoll((continuePolling, stopPolling) => {
+        this.handleRemoveBranchPolling(continuePolling, stopPolling);
+      });
+    },
+    handleRemoveBranchPolling(continuePolling, stopPolling) {
+      this.service.poll()
+        .then(res => res.data)
+        .then((data) => {
+          // If source branch exists then we should continue polling
+          // because removing a source branch is a background task and takes time
+          if (data.source_branch_exists) {
+            continuePolling();
+          } else {
+            // Branch is removed. Update widget, stop polling and hide the spinner
+            eventHub.$emit('MRWidgetUpdateRequested', () => {
+              eventHub.$emit('SetBranchRemoveFlag', [false]);
+            });
+            stopPolling();
+          }
+        })
+        .catch(() => {
+          new Flash('Something went wrong while removing the source branch. Please try again.'); // eslint-disable-line
+        });
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="mr-widget-body media">
+    <status-icon :status="iconClass" />
+    <div class="media-body">
+      <div class="mr-widget-body-controls media space-children">
+        <span class="btn-group append-bottom-5">
+          <button
+            @click="handleMergeButtonClick()"
+            :disabled="isMergeButtonDisabled"
+            :class="mergeButtonClass"
+            type="button"
+            class="qa-merge-button">
+            <i
+              v-if="isMakingRequest"
+              class="fa fa-spinner fa-spin"
+              aria-hidden="true"
+            ></i>
+            {{ mergeButtonText }}
+          </button>
+          <button
+            v-if="shouldShowMergeOptionsDropdown"
+            :disabled="isMergeButtonDisabled"
+            type="button"
+            class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
+            data-toggle="dropdown"
+            aria-label="Select merge moment">
+            <i
+              class="fa fa-chevron-down"
+              aria-hidden="true"
+            ></i>
+          </button>
+          <ul
+            v-if="shouldShowMergeOptionsDropdown"
+            class="dropdown-menu dropdown-menu-right"
+            role="menu">
+            <li>
+              <a
+                @click.prevent="handleMergeButtonClick(true)"
+                class="merge_when_pipeline_succeeds"
+                href="#">
+                <span class="media">
+                  <span
+                    v-html="successSvg"
+                    class="merge-opt-icon"
+                    aria-hidden="true"></span>
+                  <span class="media-body merge-opt-title">Merge when pipeline succeeds</span>
+                </span>
+              </a>
+            </li>
+            <li>
+              <a
+                @click.prevent="handleMergeButtonClick(false, true)"
+                class="accept-merge-request"
+                href="#">
+                <span class="media">
+                  <span
+                    v-html="warningSvg"
+                    class="merge-opt-icon"
+                    aria-hidden="true"></span>
+                  <span class="media-body merge-opt-title">Merge immediately</span>
+                </span>
+              </a>
+            </li>
+          </ul>
+        </span>
+        <div class="media-body-wrap space-children">
+          <template v-if="shouldShowMergeControls()">
+            <label v-if="mr.canRemoveSourceBranch">
+              <input
+                id="remove-source-branch-input"
+                v-model="removeSourceBranch"
+                class="js-remove-source-branch-checkbox"
+                :disabled="isRemoveSourceBranchButtonDisabled"
+                type="checkbox"/> Remove source branch
+            </label>
+
+            <!-- Placeholder for EE extension of this component -->
+            <squash-before-merge
+              v-if="shouldShowSquashBeforeMerge"
+              :mr="mr"
+              :is-merge-button-disabled="isMergeButtonDisabled" />
+
+            <span
+              v-if="mr.ffOnlyEnabled"
+              class="js-fast-forward-message">
+              Fast-forward merge without a merge commit
+            </span>
+            <button
+              v-else
+              @click="toggleCommitMessageEditor"
+              :disabled="isMergeButtonDisabled"
+              class="js-modify-commit-message-button btn btn-default btn-xs"
+              type="button">
+              Modify commit message
+            </button>
+          </template>
+          <template v-else>
+            <span class="bold js-resolve-mr-widget-items-message">
+              You can only merge once the items above are resolved
+            </span>
+          </template>
+        </div>
+      </div>
+      <div
+        v-if="showCommitMessageEditor"
+        class="prepend-top-default commit-message-editor">
+        <div class="form-group clearfix">
+          <label
+            class="control-label"
+            for="commit-message">
+            Commit message
+          </label>
+          <div class="col-sm-10">
+            <div class="commit-message-container">
+              <div class="max-width-marker"></div>
+              <textarea
+                id="commit-message"
+                v-model="commitMessage"
+                class="form-control js-commit-message"
+                required="required"
+                rows="14"
+                name="Commit message"></textarea>
+            </div>
+            <p class="hint">
+              Try to keep the first line under 52 characters and the others under 72
+            </p>
+            <div class="hint">
+              <a
+                @click.prevent="updateCommitMessage"
+                href="#"
+              >
+                {{ commitMessageLinkTitle }}
+              </a>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7cc074019111f0910453bb5226e993d3a2290172
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
@@ -0,0 +1,25 @@
+<script>
+import statusIcon from '../mr_widget_status_icon.vue';
+
+export default {
+  name: 'ShaMismatch',
+  components: {
+    statusIcon,
+  },
+};
+</script>
+
+<template>
+  <div class="mr-widget-body media">
+    <status-icon
+      status="warning"
+      :show-disabled-button="true"
+    />
+    <div class="media-body space-children">
+      <span class="bold">
+        {{ s__(`mrWidget|The source branch HEAD has recently changed.
+Please reload the page and review the changes before merging`) }}
+      </span>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a1f7e69679504cdd145d90fb9b78103c2a33e967
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
@@ -0,0 +1,37 @@
+<script>
+import statusIcon from '../mr_widget_status_icon.vue';
+
+export default {
+  name: 'UnresolvedDiscussions',
+  components: {
+    statusIcon,
+  },
+  props: {
+    mr: {
+      type: Object,
+      required: true,
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="mr-widget-body media">
+    <status-icon
+      status="warning"
+      :show-disabled-button="true"
+    />
+    <div class="media-body space-children">
+      <span class="bold">
+        {{ s__("mrWidget|There are unresolved discussions. Please resolve these discussions") }}
+      </span>
+      <a
+        v-if="mr.createIssueToResolveDiscussionsPath"
+        :href="mr.createIssueToResolveDiscussionsPath"
+        class="btn btn-default btn-xs js-create-issue"
+      >
+        {{ s__("mrWidget|Create an issue to resolve them later") }}
+      </a>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index edb3baa39e4e50e8187e969c8006f1f55f6d47da..3b5c973e4a0ea7049ce7e74efab14f0a0baeee42 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -14,7 +14,8 @@ export { default as SmartInterval } from '~/smart_interval';
 export { default as WidgetHeader } from './components/mr_widget_header.vue';
 export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
 export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
-export { default as WidgetDeployment } from './components/mr_widget_deployment';
+export { default as Deployment } from './components/deployment.vue';
+export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue';
 export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
 export { default as MergedState } from './components/states/mr_widget_merged.vue';
 export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
@@ -23,14 +24,14 @@ export { default as MergingState } from './components/states/mr_widget_merging.v
 export { default as WipState } from './components/states/mr_widget_wip';
 export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
 export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
-export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
+export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
 export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
 export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
-export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
-export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
-export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
+export { default as ReadyToMergeState } from './components/states/ready_to_merge.vue';
+export { default as ShaMismatchState } from './components/states/sha_mismatch.vue';
+export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue';
 export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
-export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
+export { default as PipelineFailedState } from './components/states/pipeline_failed.vue';
 export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
 export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
 export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue';
@@ -39,7 +40,9 @@ export { default as MRWidgetStore } from './stores/mr_widget_store';
 export { default as MRWidgetService } from './services/mr_widget_service';
 export { default as eventHub } from './event_hub';
 export { default as getStateKey } from './stores/get_state_key';
-export { default as mrWidgetOptions } from './mr_widget_options';
 export { default as stateMaps } from './stores/state_maps';
 export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge';
 export { default as notify } from '../lib/utils/notify';
+export { default as SourceBranchRemovalStatus } from './components/source_branch_removal_status.vue';
+
+export { default as mrWidgetOptions } from './mr_widget_options';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 797f0f6ec0f6f39cb959c0e9a1c8b1fe0c960488..0be5d9e5a55f4850a478448eb8b488a3db0b57cd 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -5,7 +5,8 @@ import {
   WidgetHeader,
   WidgetMergeHelp,
   WidgetPipeline,
-  WidgetDeployment,
+  Deployment,
+  WidgetMaintainerEdit,
   WidgetRelatedLinks,
   MergedState,
   ClosedState,
@@ -18,7 +19,7 @@ import {
   MissingBranchState,
   NotAllowedState,
   ReadyToMergeState,
-  SHAMismatchState,
+  ShaMismatchState,
   UnresolvedDiscussionsState,
   PipelineBlockedState,
   PipelineFailedState,
@@ -32,6 +33,7 @@ import {
   stateMaps,
   SquashBeforeMerge,
   notify,
+  SourceBranchRemovalStatus,
 } from './dependencies';
 import { setFavicon } from '../lib/utils/common_utils';
 
@@ -65,8 +67,9 @@ export default {
     shouldRenderRelatedLinks() {
       return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
     },
-    shouldRenderDeployments() {
-      return this.mr.deployments.length;
+    shouldRenderSourceBranchRemovalStatus() {
+      return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
+        (!this.mr.isNothingToMergeState && !this.mr.isMergedState);
     },
   },
   methods: {
@@ -210,7 +213,8 @@ export default {
     'mr-widget-header': WidgetHeader,
     'mr-widget-merge-help': WidgetMergeHelp,
     'mr-widget-pipeline': WidgetPipeline,
-    'mr-widget-deployment': WidgetDeployment,
+    Deployment,
+    'mr-widget-maintainer-edit': WidgetMaintainerEdit,
     'mr-widget-related-links': WidgetRelatedLinks,
     'mr-widget-merged': MergedState,
     'mr-widget-closed': ClosedState,
@@ -223,7 +227,7 @@ export default {
     'mr-widget-not-allowed': NotAllowedState,
     'mr-widget-missing-branch': MissingBranchState,
     'mr-widget-ready-to-merge': ReadyToMergeState,
-    'mr-widget-sha-mismatch': SHAMismatchState,
+    'mr-widget-sha-mismatch': ShaMismatchState,
     'mr-widget-squash-before-merge': SquashBeforeMerge,
     'mr-widget-checking': CheckingState,
     'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
@@ -232,6 +236,7 @@ export default {
     'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
     'mr-widget-auto-merge-failed': AutoMergeFailed,
     'mr-widget-rebase': RebaseState,
+    SourceBranchRemovalStatus,
   },
   template: `
     <div class="mr-state-widget prepend-top-default">
@@ -242,20 +247,25 @@ export default {
         :ci-status="mr.ciStatus"
         :has-ci="mr.hasCI"
         />
-      <mr-widget-deployment
-        v-if="shouldRenderDeployments"
-        :mr="mr"
-        :service="service" />
+      <deployment
+        v-for="deployment in mr.deployments"
+        :key="deployment.id"
+        :deployment="deployment"
+      />
       <div class="mr-widget-section">
         <component
           :is="componentName"
           :mr="mr"
           :service="service" />
+        <mr-widget-maintainer-edit
+          :maintainerEditAllowed="mr.maintainerEditAllowed" />
         <mr-widget-related-links
           v-if="shouldRenderRelatedLinks"
           :state="mr.state"
-          :related-links="mr.relatedLinks"
-          />
+          :related-links="mr.relatedLinks" />
+        <source-branch-removal-status
+          v-if="shouldRenderSourceBranchRemovalStatus"
+        />
       </div>
       <div
         class="mr-widget-footer"
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 9a750ce42bd8271087d2703f4d6494c2a4bbd32b..a47ca9fae86dee36894d536eda15bceb3f6d44e8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -76,6 +76,7 @@ export default class MergeRequestStore {
     this.canBeMerged = data.can_be_merged || false;
     this.isMergeAllowed = data.mergeable || false;
     this.mergeOngoing = data.merge_ongoing;
+    this.maintainerEditAllowed = data.allow_maintainer_to_push;
 
     // Cherry-pick and Revert actions related
     this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
@@ -124,6 +125,10 @@ export default class MergeRequestStore {
     return this.state === stateKey.nothingToMerge;
   }
 
+  get isMergedState() {
+    return this.state === stateKey.merged;
+  }
+
   initRebase(data) {
     this.canPushToSourceBranch = data.can_push_to_source_branch;
     this.rebaseInProgress = data.rebase_in_progress;
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
index 29d5bd4a1dac347be809d8269e086b644fa1739c..e080ce5c22947dbe2a842511aa6e0633293e4bbe 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
@@ -16,7 +16,7 @@ const stateToComponentMap = {
   mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds',
   failedToMerge: 'mr-widget-failed-to-merge',
   autoMergeFailed: 'mr-widget-auto-merge-failed',
-  shaMismatch: 'mr-widget-sha-mismatch',
+  shaMismatch: 'sha-mismatch',
   rebase: 'mr-widget-rebase',
 };
 
@@ -49,6 +49,7 @@ export const stateKey = {
   notAllowedToMerge: 'notAllowedToMerge',
   readyToMerge: 'readyToMerge',
   rebase: 'rebase',
+  merged: 'merged',
 };
 
 export default {
diff --git a/app/assets/javascripts/vue_shared/components/callout.vue b/app/assets/javascripts/vue_shared/components/callout.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ccf802c456c8320fe2ea37e8b6f95bd29d06bc78
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/callout.vue
@@ -0,0 +1,27 @@
+<script>
+const calloutVariants = ['danger', 'success', 'info', 'warning'];
+
+export default {
+  props: {
+    category: {
+      type: String,
+      required: false,
+      default: calloutVariants[0],
+      validator: value => calloutVariants.includes(value),
+    },
+    message: {
+      type: String,
+      required: true,
+    },
+  },
+};
+</script>
+<template>
+  <div
+    :class="`bs-callout bs-callout-${category}`"
+    role="alert"
+    aria-live="assertive"
+  >
+    {{ message }}
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
index 3b6c2da166453e7bf134719ed8966dcac86e20f6..cab126a7ecafcdf113f715694675e069f63b8248 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -31,7 +31,7 @@
       cssClass: {
         type: String,
         required: false,
-        default: 'btn btn-default btn-transparent btn-clipboard',
+        default: 'btn-default',
       },
     },
   };
@@ -40,6 +40,7 @@
 <template>
   <button
     type="button"
+    class="btn"
     :class="cssClass"
     :title="title"
     :data-clipboard-text="text"
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index 97789636787c1e3df1de94676ed292ca95043ad7..b8875d0448866b3d85c866712e88a009d657f35b 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -175,7 +175,7 @@
         </a>
       </span>
       <span v-else>
-        Cant find HEAD commit for this branch
+        Can't find HEAD commit for this branch
       </span>
     </div>
   </div>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4155e1bab9ca449b70a5e316347aea97ab7c1722
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
@@ -0,0 +1,58 @@
+<script>
+import { viewerInformationForPath } from './lib/viewer_utils';
+import MarkdownViewer from './viewers/markdown_viewer.vue';
+import ImageViewer from './viewers/image_viewer.vue';
+import DownloadViewer from './viewers/download_viewer.vue';
+
+export default {
+  props: {
+    content: {
+      type: String,
+      default: '',
+    },
+    path: {
+      type: String,
+      required: true,
+    },
+    fileSize: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
+    projectPath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
+  computed: {
+    viewer() {
+      if (!this.path) return null;
+
+      const previewInfo = viewerInformationForPath(this.path);
+      if (!previewInfo) return DownloadViewer;
+
+      switch (previewInfo.id) {
+        case 'markdown':
+          return MarkdownViewer;
+        case 'image':
+          return ImageViewer;
+        default:
+          return DownloadViewer;
+      }
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="preview-container">
+    <component
+      :is="viewer"
+      :path="path"
+      :file-size="fileSize"
+      :project-path="projectPath"
+      :content="content"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..f01a51da0b326c814e5c9042ed35bedf455135c7
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
@@ -0,0 +1,32 @@
+const viewers = {
+  image: {
+    id: 'image',
+  },
+  markdown: {
+    id: 'markdown',
+    previewTitle: 'Preview Markdown',
+  },
+};
+
+const fileNameViewers = {};
+const fileExtensionViewers = {
+  jpg: 'image',
+  jpeg: 'image',
+  gif: 'image',
+  png: 'image',
+  bmp: 'image',
+  ico: 'image',
+  md: 'markdown',
+  markdown: 'markdown',
+};
+
+export function viewerInformationForPath(path) {
+  if (!path) return null;
+  const name = path.split('/').pop();
+  const viewerName =
+    fileNameViewers[name] || fileExtensionViewers[name ? name.split('.').pop() : ''] || '';
+
+  return viewers[viewerName];
+}
+
+export default viewers;
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..395a71acccf53218b1aeb4d49013e48e08b395c7
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
@@ -0,0 +1,52 @@
+<script>
+import Icon from '../../icon.vue';
+import { numberToHumanSize } from '../../../../lib/utils/number_utils';
+
+export default {
+  components: {
+    Icon,
+  },
+  props: {
+    path: {
+      type: String,
+      required: true,
+    },
+    fileSize: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
+  },
+  computed: {
+    fileSizeReadable() {
+      return numberToHumanSize(this.fileSize);
+    },
+    fileName() {
+      return this.path.split('/').pop();
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="file-container">
+    <div class="file-content">
+      <p class="prepend-top-10 file-info">
+        {{ fileName }} ({{ fileSizeReadable }})
+      </p>
+      <a
+        :href="path"
+        class="btn btn-default"
+        rel="nofollow"
+        download
+        target="_blank">
+        <icon
+          name="download"
+          css-classes="pull-left append-right-8"
+          :size="16"
+        />
+        {{ __('Download') }}
+      </a>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a5999f909ca176ada897b2ab04120737891f0af3
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
@@ -0,0 +1,68 @@
+<script>
+import { numberToHumanSize } from '../../../../lib/utils/number_utils';
+
+export default {
+  props: {
+    path: {
+      type: String,
+      required: true,
+    },
+    fileSize: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
+  },
+  data() {
+    return {
+      width: 0,
+      height: 0,
+      isZoomable: false,
+      isZoomed: false,
+    };
+  },
+  computed: {
+    fileSizeReadable() {
+      return numberToHumanSize(this.fileSize);
+    },
+  },
+  methods: {
+    onImgLoad() {
+      const contentImg = this.$refs.contentImg;
+      this.isZoomable =
+        contentImg.naturalWidth > contentImg.width || contentImg.naturalHeight > contentImg.height;
+
+      this.width = contentImg.naturalWidth;
+      this.height = contentImg.naturalHeight;
+    },
+    onImgClick() {
+      if (this.isZoomable) this.isZoomed = !this.isZoomed;
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="file-container">
+    <div class="file-content image_file">
+      <img
+        ref="contentImg"
+        :class="{ 'isZoomable': isZoomable, 'isZoomed': isZoomed }"
+        :src="path"
+        :alt="path"
+        @load="onImgLoad"
+        @click="onImgClick"/>
+      <p class="file-info prepend-top-10">
+        <template v-if="fileSize>0">
+          {{ fileSizeReadable }}
+        </template>
+        <template v-if="fileSize>0 && width && height">
+          -
+        </template>
+        <template v-if="width && height">
+          {{ width }} x {{ height }}
+        </template>
+      </p>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..09e0094054d074c4d4f8885302431b666aa03fa4
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -0,0 +1,90 @@
+<script>
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import $ from 'jquery';
+import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
+
+const CancelToken = axios.CancelToken;
+let axiosSource;
+
+export default {
+  components: {
+    SkeletonLoadingContainer,
+  },
+  props: {
+    content: {
+      type: String,
+      required: true,
+    },
+    projectPath: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      previewContent: null,
+      isLoading: false,
+    };
+  },
+  watch: {
+    content() {
+      this.previewContent = null;
+    },
+  },
+  created() {
+    axiosSource = CancelToken.source();
+    this.fetchMarkdownPreview();
+  },
+  updated() {
+    this.fetchMarkdownPreview();
+  },
+  destroyed() {
+    if (this.isLoading) axiosSource.cancel('Cancelling Preview');
+  },
+  methods: {
+    fetchMarkdownPreview() {
+      if (this.content && this.previewContent === null) {
+        this.isLoading = true;
+        const postBody = {
+          text: this.content,
+        };
+        const postOptions = {
+          cancelToken: axiosSource.token,
+        };
+
+        axios
+          .post(
+            `${gon.relative_url_root}/${this.projectPath}/preview_markdown`,
+            postBody,
+            postOptions,
+          )
+          .then(({ data }) => {
+            this.previewContent = data.body;
+            this.isLoading = false;
+
+            this.$nextTick(() => {
+              $(this.$refs['markdown-preview']).renderGFM();
+            });
+          })
+          .catch(() => {
+            this.previewContent = __('An error occurred while fetching markdown preview');
+            this.isLoading = false;
+          });
+      }
+    },
+  },
+};
+</script>
+
+<template>
+  <div
+    ref="markdown-preview"
+    class="md md-previewer">
+    <skeleton-loading-container v-if="isLoading" />
+    <div
+      v-else
+      v-html="previewContent">
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..dcf1489b37c2de3438730a3f6d14d12ae4ce2272
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
@@ -0,0 +1,173 @@
+<script>
+  /* eslint-disable vue/require-default-prop */
+  export default {
+    name: 'DeprecatedModal', // use GlModal instead
+
+    props: {
+      id: {
+        type: String,
+        required: false,
+      },
+      title: {
+        type: String,
+        required: false,
+      },
+      text: {
+        type: String,
+        required: false,
+      },
+      hideFooter: {
+        type: Boolean,
+        required: false,
+        default: false,
+      },
+      kind: {
+        type: String,
+        required: false,
+        default: 'primary',
+      },
+      modalDialogClass: {
+        type: String,
+        required: false,
+        default: '',
+      },
+      closeKind: {
+        type: String,
+        required: false,
+        default: 'default',
+      },
+      closeButtonLabel: {
+        type: String,
+        required: false,
+        default: 'Cancel',
+      },
+      primaryButtonLabel: {
+        type: String,
+        required: false,
+        default: '',
+      },
+      secondaryButtonLabel: {
+        type: String,
+        required: false,
+        default: '',
+      },
+      submitDisabled: {
+        type: Boolean,
+        required: false,
+        default: false,
+      },
+    },
+
+    computed: {
+      btnKindClass() {
+        return {
+          [`btn-${this.kind}`]: true,
+        };
+      },
+      btnCancelKindClass() {
+        return {
+          [`btn-${this.closeKind}`]: true,
+        };
+      },
+    },
+
+    methods: {
+      emitCancel(event) {
+        this.$emit('cancel', event);
+      },
+      emitSubmit(event) {
+        this.$emit('submit', event);
+      },
+    },
+  };
+</script>
+
+<template>
+  <div class="modal-open">
+    <div
+      :id="id"
+      class="modal"
+      :class="id ? '' : 'show'"
+      role="dialog"
+      tabindex="-1"
+    >
+      <div
+        :class="modalDialogClass"
+        class="modal-dialog"
+        role="document"
+      >
+        <div class="modal-content">
+          <div class="modal-header">
+            <slot name="header">
+              <h4 class="modal-title pull-left">
+                {{ title }}
+              </h4>
+              <button
+                type="button"
+                class="close pull-right"
+                @click="emitCancel($event)"
+                data-dismiss="modal"
+                aria-label="Close"
+              >
+                <span aria-hidden="true">&times;</span>
+              </button>
+            </slot>
+          </div>
+          <div class="modal-body">
+            <slot
+              name="body"
+              :text="text"
+            >
+              <p>{{ text }}</p>
+            </slot>
+          </div>
+          <div
+            class="modal-footer"
+            v-if="!hideFooter"
+          >
+            <button
+              type="button"
+              class="btn"
+              :class="btnCancelKindClass"
+              @click="emitCancel($event)"
+              data-dismiss="modal"
+            >
+              {{ closeButtonLabel }}
+            </button>
+
+            <slot
+              v-if="secondaryButtonLabel"
+              name="secondary-button"
+            >
+              <button
+                v-if="secondaryButtonLabel"
+                type="button"
+                class="btn"
+                data-dismiss="modal"
+              >
+                {{ secondaryButtonLabel }}
+              </button>
+            </slot>
+
+            <button
+              v-if="primaryButtonLabel"
+              type="button"
+              class="btn js-primary-button"
+              :disabled="submitDisabled"
+              :class="btnKindClass"
+              @click="emitSubmit($event)"
+              data-dismiss="modal"
+            >
+              {{ primaryButtonLabel }}
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div
+      v-if="!id"
+      class="modal-backdrop fade in"
+    >
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue
index c9d7c0f4999498a5b1ec13511e78c4549389ecdf..ee1c349874807bd9cf24591eb15bd55a757d8013 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/file_icon.vue
@@ -62,8 +62,7 @@
         return `${gon.sprite_file_icons}#${iconName}`;
       },
       folderIconName() {
-        // We don't have a open folder icon yet
-        return this.opened ? 'folder' : 'folder';
+        return this.opened ? 'folder-open' : 'folder';
       },
       iconSizeClass() {
         return this.size ? `s${this.size}` : '';
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
index 67c9181c7b1c26f4e67bd51627c8c25fe97c5b45..f28e5e2715d610b57c6e17bf7f38f81315f4f991 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -1,47 +1,42 @@
 <script>
-  const buttonVariants = [
-    'danger',
-    'primary',
-    'success',
-    'warning',
-  ];
+const buttonVariants = ['danger', 'primary', 'success', 'warning'];
 
-  export default {
-    name: 'GlModal',
+export default {
+  name: 'GlModal',
 
-    props: {
-      id: {
-        type: String,
-        required: false,
-        default: null,
-      },
-      headerTitleText: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      footerPrimaryButtonVariant: {
-        type: String,
-        required: false,
-        default: 'primary',
-        validator: value => buttonVariants.indexOf(value) !== -1,
-      },
-      footerPrimaryButtonText: {
-        type: String,
-        required: false,
-        default: '',
-      },
+  props: {
+    id: {
+      type: String,
+      required: false,
+      default: null,
     },
+    headerTitleText: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    footerPrimaryButtonVariant: {
+      type: String,
+      required: false,
+      default: 'primary',
+      validator: value => buttonVariants.includes(value),
+    },
+    footerPrimaryButtonText: {
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
 
-    methods: {
-      emitCancel(event) {
-        this.$emit('cancel', event);
-      },
-      emitSubmit(event) {
-        this.$emit('submit', event);
-      },
+  methods: {
+    emitCancel(event) {
+      this.$emit('cancel', event);
+    },
+    emitSubmit(event) {
+      this.$emit('submit', event);
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -60,7 +55,7 @@
           <slot name="header">
             <button
               type="button"
-              class="close"
+              class="close js-modal-close-action"
               data-dismiss="modal"
               :aria-label="s__('Modal|Close')"
               @click="emitCancel($event)"
@@ -83,7 +78,7 @@
           <slot name="footer">
             <button
               type="button"
-              class="btn"
+              class="btn js-modal-cancel-action"
               data-dismiss="modal"
               @click="emitCancel($event)"
             >
@@ -91,7 +86,7 @@
             </button>
             <button
               type="button"
-              class="btn"
+              class="btn js-modal-primary-action"
               :class="`btn-${footerPrimaryButtonVariant}`"
               data-dismiss="modal"
               @click="emitSubmit($event)"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index d2e968a84194ad271ccdff5fc659f522c91dd0c1..12c7d1250627d2564af71d280966ca62148fffba 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,4 +1,5 @@
 <script>
+  import $ from 'jquery';
   import Flash from '../../../flash';
   import GLForm from '../../../gl_form';
   import markdownHeader from './header.vue';
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 177d2cfc8dae05a70525809c8e3738ac3712a1c7..db453c305760c103ff75d29099174343c5dda3c3 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,4 +1,5 @@
 <script>
+  import $ from 'jquery';
   import tooltip from '../../directives/tooltip';
   import toolbarButton from './toolbar_button.vue';
   import icon from '../icon.vue';
@@ -26,20 +27,22 @@
       $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
     },
     methods: {
-      isMarkdownForm(form) {
-        return form && !form.find('.js-vue-markdown-field').length;
+      isValid(form) {
+        return !form ||
+          form.find('.js-vue-markdown-field').length ||
+          $(this.$el).closest('form') === form[0];
       },
 
       previewMarkdownTab(event, form) {
         if (event.target.blur) event.target.blur();
-        if (this.isMarkdownForm(form)) return;
+        if (!this.isValid(form)) return;
 
         this.$emit('preview-markdown');
       },
 
       writeMarkdownTab(event, form) {
         if (event.target.blur) event.target.blur();
-        if (this.isMarkdownForm(form)) return;
+        if (!this.isValid(form)) return;
 
         this.$emit('write-markdown');
       },
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.js b/app/assets/javascripts/vue_shared/components/memory_graph.js
deleted file mode 100644
index f37ef1a5ca3d539356c5222e57386d1e8d490991..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_shared/components/memory_graph.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import { getTimeago } from '../../lib/utils/datetime_utility';
-
-export default {
-  name: 'MemoryGraph',
-  props: {
-    metrics: { type: Array, required: true },
-    deploymentTime: { type: Number, required: true },
-    width: { type: String, required: true },
-    height: { type: String, required: true },
-  },
-  data() {
-    return {
-      pathD: '',
-      pathViewBox: '',
-      dotX: '',
-      dotY: '',
-    };
-  },
-  computed: {
-    getFormattedMedian() {
-      const deployedSince = getTimeago().format(this.deploymentTime * 1000);
-      return `Deployed ${deployedSince}`;
-    },
-  },
-  methods: {
-    /**
-     * Returns metric value index in metrics array
-     * with timestamp closest to matching median
-     */
-    getMedianMetricIndex(median, metrics) {
-      let matchIndex = 0;
-      let timestampDiff = 0;
-      let smallestDiff = 0;
-
-      const metricTimestamps = metrics.map(v => v[0]);
-
-      // Find metric timestamp which is closest to deploymentTime
-      timestampDiff = Math.abs(metricTimestamps[0] - median);
-      metricTimestamps.forEach((timestamp, index) => {
-        if (index === 0) { // Skip first element
-          return;
-        }
-
-        smallestDiff = Math.abs(timestamp - median);
-        if (smallestDiff < timestampDiff) {
-          matchIndex = index;
-          timestampDiff = smallestDiff;
-        }
-      });
-
-      return matchIndex;
-    },
-
-    /**
-     * Get Graph Plotting values to render Line and Dot
-     */
-    getGraphPlotValues(median, metrics) {
-      const renderData = metrics.map(v => v[1]);
-      const medianMetricIndex = this.getMedianMetricIndex(median, metrics);
-      let cx = 0;
-      let cy = 0;
-
-      // Find Maximum and Minimum values from `renderData` array
-      const maxMemory = Math.max.apply(null, renderData);
-      const minMemory = Math.min.apply(null, renderData);
-
-      // Find difference between extreme ends
-      const diff = maxMemory - minMemory;
-      const lineWidth = renderData.length;
-
-      // Iterate over metrics values and perform following
-      // 1. Find x & y co-ords for deploymentTime's memory value
-      // 2. Return line path against maxMemory
-      const linePath = renderData.map((y, x) => {
-        if (medianMetricIndex === x) {
-          cx = x;
-          cy = maxMemory - y;
-        }
-        return `${x} ${maxMemory - y}`;
-      });
-
-      return {
-        pathD: linePath,
-        pathViewBox: {
-          lineWidth,
-          diff,
-        },
-        dotX: cx,
-        dotY: cy,
-      };
-    },
-
-    /**
-     * Render Graph based on provided median and metrics values
-     */
-    renderGraph(median, metrics) {
-      const { pathD, pathViewBox, dotX, dotY } = this.getGraphPlotValues(median, metrics);
-
-      // Set props and update graph on UI.
-      this.pathD = `M ${pathD}`;
-      this.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`;
-      this.dotX = dotX;
-      this.dotY = dotY;
-    },
-  },
-  mounted() {
-    this.renderGraph(this.deploymentTime, this.metrics);
-  },
-  template: `
-    <div class="memory-graph-container">
-      <svg class="has-tooltip" :title="getFormattedMedian" :width="width" :height="height" xmlns="http://www.w3.org/2000/svg">
-        <path :d="pathD" :viewBox="pathViewBox" />
-        <circle r="1.5" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" />
-      </svg>
-    </div>
-  `,
-};
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.vue b/app/assets/javascripts/vue_shared/components/memory_graph.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b07f6b07afe246e59ca7e36374a076491c843991
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.vue
@@ -0,0 +1,133 @@
+<script>
+import { getTimeago } from '../../lib/utils/datetime_utility';
+
+export default {
+  name: 'MemoryGraph',
+  props: {
+    metrics: { type: Array, required: true },
+    deploymentTime: { type: Number, required: true },
+    width: { type: String, required: true },
+    height: { type: String, required: true },
+  },
+  data() {
+    return {
+      pathD: '',
+      pathViewBox: '',
+      dotX: '',
+      dotY: '',
+    };
+  },
+  computed: {
+    getFormattedMedian() {
+      const deployedSince = getTimeago().format(this.deploymentTime * 1000);
+      return `Deployed ${deployedSince}`;
+    },
+  },
+  mounted() {
+    this.renderGraph(this.deploymentTime, this.metrics);
+  },
+  methods: {
+    /**
+     * Returns metric value index in metrics array
+     * with timestamp closest to matching median
+     */
+    getMedianMetricIndex(median, metrics) {
+      let matchIndex = 0;
+      let timestampDiff = 0;
+      let smallestDiff = 0;
+
+      const metricTimestamps = metrics.map(v => v[0]);
+
+      // Find metric timestamp which is closest to deploymentTime
+      timestampDiff = Math.abs(metricTimestamps[0] - median);
+      metricTimestamps.forEach((timestamp, index) => {
+        if (index === 0) { // Skip first element
+          return;
+        }
+
+        smallestDiff = Math.abs(timestamp - median);
+        if (smallestDiff < timestampDiff) {
+          matchIndex = index;
+          timestampDiff = smallestDiff;
+        }
+      });
+
+      return matchIndex;
+    },
+
+    /**
+     * Get Graph Plotting values to render Line and Dot
+     */
+    getGraphPlotValues(median, metrics) {
+      const renderData = metrics.map(v => v[1]);
+      const medianMetricIndex = this.getMedianMetricIndex(median, metrics);
+      let cx = 0;
+      let cy = 0;
+
+      // Find Maximum and Minimum values from `renderData` array
+      const maxMemory = Math.max.apply(null, renderData);
+      const minMemory = Math.min.apply(null, renderData);
+
+      // Find difference between extreme ends
+      const diff = maxMemory - minMemory;
+      const lineWidth = renderData.length;
+
+      // Iterate over metrics values and perform following
+      // 1. Find x & y co-ords for deploymentTime's memory value
+      // 2. Return line path against maxMemory
+      const linePath = renderData.map((y, x) => {
+        if (medianMetricIndex === x) {
+          cx = x;
+          cy = maxMemory - y;
+        }
+        return `${x} ${maxMemory - y}`;
+      });
+
+      return {
+        pathD: linePath,
+        pathViewBox: {
+          lineWidth,
+          diff,
+        },
+        dotX: cx,
+        dotY: cy,
+      };
+    },
+
+    /**
+     * Render Graph based on provided median and metrics values
+     */
+    renderGraph(median, metrics) {
+      const { pathD, pathViewBox, dotX, dotY } = this.getGraphPlotValues(median, metrics);
+
+      // Set props and update graph on UI.
+      this.pathD = `M ${pathD}`;
+      this.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`;
+      this.dotX = dotX;
+      this.dotY = dotY;
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="memory-graph-container">
+    <svg
+      class="has-tooltip"
+      :title="getFormattedMedian"
+      :width="width"
+      :height="height"
+      xmlns="http://www.w3.org/2000/svg">
+      <path
+        :d="pathD"
+        :viewBox="pathViewBox"
+      />
+      <circle
+        r="1.5"
+        :cx="dotX"
+        :cy="dotY"
+        tranform="translate(0 -1)"
+      />
+    </svg>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue
deleted file mode 100644
index 5f1364421aa1c12db13d36024018ec5ee9e34733..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_shared/components/modal.vue
+++ /dev/null
@@ -1,173 +0,0 @@
-<script>
-  /* eslint-disable vue/require-default-prop */
-  export default {
-    name: 'Modal',
-
-    props: {
-      id: {
-        type: String,
-        required: false,
-      },
-      title: {
-        type: String,
-        required: false,
-      },
-      text: {
-        type: String,
-        required: false,
-      },
-      hideFooter: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
-      kind: {
-        type: String,
-        required: false,
-        default: 'primary',
-      },
-      modalDialogClass: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      closeKind: {
-        type: String,
-        required: false,
-        default: 'default',
-      },
-      closeButtonLabel: {
-        type: String,
-        required: false,
-        default: 'Cancel',
-      },
-      primaryButtonLabel: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      secondaryButtonLabel: {
-        type: String,
-        required: false,
-        default: '',
-      },
-      submitDisabled: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
-    },
-
-    computed: {
-      btnKindClass() {
-        return {
-          [`btn-${this.kind}`]: true,
-        };
-      },
-      btnCancelKindClass() {
-        return {
-          [`btn-${this.closeKind}`]: true,
-        };
-      },
-    },
-
-    methods: {
-      emitCancel(event) {
-        this.$emit('cancel', event);
-      },
-      emitSubmit(event) {
-        this.$emit('submit', event);
-      },
-    },
-  };
-</script>
-
-<template>
-  <div class="modal-open">
-    <div
-      :id="id"
-      class="modal"
-      :class="id ? '' : 'show'"
-      role="dialog"
-      tabindex="-1"
-    >
-      <div
-        :class="modalDialogClass"
-        class="modal-dialog"
-        role="document"
-      >
-        <div class="modal-content">
-          <div class="modal-header">
-            <slot name="header">
-              <h4 class="modal-title pull-left">
-                {{ title }}
-              </h4>
-              <button
-                type="button"
-                class="close pull-right"
-                @click="emitCancel($event)"
-                data-dismiss="modal"
-                aria-label="Close"
-              >
-                <span aria-hidden="true">&times;</span>
-              </button>
-            </slot>
-          </div>
-          <div class="modal-body">
-            <slot
-              name="body"
-              :text="text"
-            >
-              <p>{{ text }}</p>
-            </slot>
-          </div>
-          <div
-            class="modal-footer"
-            v-if="!hideFooter"
-          >
-            <button
-              type="button"
-              class="btn"
-              :class="btnCancelKindClass"
-              @click="emitCancel($event)"
-              data-dismiss="modal"
-            >
-              {{ closeButtonLabel }}
-            </button>
-
-            <slot
-              v-if="secondaryButtonLabel"
-              name="secondary-button"
-            >
-              <button
-                v-if="secondaryButtonLabel"
-                type="button"
-                class="btn"
-                data-dismiss="modal"
-              >
-                {{ secondaryButtonLabel }}
-              </button>
-            </slot>
-
-            <button
-              v-if="primaryButtonLabel"
-              type="button"
-              class="btn js-primary-button"
-              :disabled="submitDisabled"
-              :class="btnKindClass"
-              @click="emitSubmit($event)"
-              data-dismiss="modal"
-            >
-              {{ primaryButtonLabel }}
-            </button>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div
-      v-if="!id"
-      class="modal-backdrop fade in"
-    >
-    </div>
-  </div>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index 63d8329e4959f3e3a3a8e98613bceacb9d764a14..b33a0101dbfdcc68d2a461c66980418c9832e70a 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -1,4 +1,6 @@
 <script>
+  import $ from 'jquery';
+
   /**
    * Given an array of tabs, renders non linked bootstrap tabs.
    * When a tab is clicked it will trigger an event and provide the clicked scope.
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index c35621c9ef3fee99223b076f095c8f9b4a80056c..21ffdc1dc861c1702fc29f4f66d5530c26c63296 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -1,11 +1,11 @@
 <script>
-  import modal from './modal.vue';
+  import DeprecatedModal from './deprecated_modal.vue';
 
   export default {
     name: 'RecaptchaModal',
 
     components: {
-      modal,
+      DeprecatedModal,
     },
 
     props: {
@@ -65,7 +65,7 @@
 </script>
 
 <template>
-  <modal
+  <deprecated-modal
     kind="warning"
     class="recaptcha-modal js-recaptcha-modal"
     :hide-footer="true"
@@ -82,5 +82,5 @@
       >
       </div>
     </div>
-  </modal>
+  </deprecated-modal>
 </template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5ede53d8d01f01341d7164c4062c76c3e89df0ad
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
@@ -0,0 +1,173 @@
+<script>
+import { __ } from '~/locale';
+import LabelsSelect from '~/labels_select';
+import LoadingIcon from '../../loading_icon.vue';
+
+import DropdownTitle from './dropdown_title.vue';
+import DropdownValue from './dropdown_value.vue';
+import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
+import DropdownButton from './dropdown_button.vue';
+import DropdownHiddenInput from './dropdown_hidden_input.vue';
+import DropdownHeader from './dropdown_header.vue';
+import DropdownSearchInput from './dropdown_search_input.vue';
+import DropdownFooter from './dropdown_footer.vue';
+import DropdownCreateLabel from './dropdown_create_label.vue';
+
+export default {
+  components: {
+    LoadingIcon,
+    DropdownTitle,
+    DropdownValue,
+    DropdownValueCollapsed,
+    DropdownButton,
+    DropdownHiddenInput,
+    DropdownHeader,
+    DropdownSearchInput,
+    DropdownFooter,
+    DropdownCreateLabel,
+  },
+  props: {
+    showCreate: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    isProject: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    abilityName: {
+      type: String,
+      required: true,
+    },
+    context: {
+      type: Object,
+      required: true,
+    },
+    namespace: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    updatePath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    labelsPath: {
+      type: String,
+      required: true,
+    },
+    labelsWebUrl: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    labelFilterBasePath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    canEdit: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    hiddenInputName() {
+      return this.showCreate ? `${this.abilityName}[label_names][]` : 'label_id[]';
+    },
+    createLabelTitle() {
+      if (this.isProject) {
+        return __('Create project label');
+      }
+
+      return __('Create group label');
+    },
+    manageLabelsTitle() {
+      if (this.isProject) {
+        return __('Manage project labels');
+      }
+
+      return __('Manage group labels');
+    },
+  },
+  mounted() {
+    this.labelsDropdown = new LabelsSelect(this.$refs.dropdownButton, {
+      handleClick: this.handleClick,
+    });
+  },
+  methods: {
+    handleClick(label) {
+      this.$emit('onLabelClick', label);
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="block labels js-labels-block">
+    <dropdown-value-collapsed
+      v-if="showCreate"
+      :labels="context.labels"
+    />
+    <dropdown-title
+      :can-edit="canEdit"
+    />
+    <dropdown-value
+      :labels="context.labels"
+      :label-filter-base-path="labelFilterBasePath"
+    >
+      <slot></slot>
+    </dropdown-value>
+    <div
+      v-if="canEdit"
+      class="selectbox js-selectbox"
+      style="display: none;"
+    >
+      <dropdown-hidden-input
+        v-for="label in context.labels"
+        :key="label.id"
+        :name="hiddenInputName"
+        :label="label"
+      />
+      <div class="dropdown">
+        <dropdown-button
+          :ability-name="abilityName"
+          :field-name="hiddenInputName"
+          :update-path="updatePath"
+          :labels-path="labelsPath"
+          :namespace="namespace"
+          :labels="context.labels"
+          :show-extra-options="!showCreate"
+        />
+        <div
+          class="dropdown-menu dropdown-select dropdown-menu-paging
+dropdown-menu-labels dropdown-menu-selectable"
+        >
+          <div class="dropdown-page-one">
+            <dropdown-header v-if="showCreate" />
+            <dropdown-search-input/>
+            <div class="dropdown-content"></div>
+            <div class="dropdown-loading">
+              <loading-icon />
+            </div>
+            <dropdown-footer
+              v-if="showCreate"
+              :labels-web-url="labelsWebUrl"
+              :create-label-title="createLabelTitle"
+              :manage-labels-title="manageLabelsTitle"
+            />
+          </div>
+          <dropdown-create-label
+            v-if="showCreate"
+            :is-project="isProject"
+            :header-title="createLabelTitle"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..47497c1de98589a3a0ddb8fe3eb703db73edd9fa
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
@@ -0,0 +1,78 @@
+<script>
+import { __, s__, sprintf } from '~/locale';
+
+export default {
+  props: {
+    abilityName: {
+      type: String,
+      required: true,
+    },
+    fieldName: {
+      type: String,
+      required: true,
+    },
+    updatePath: {
+      type: String,
+      required: true,
+    },
+    labelsPath: {
+      type: String,
+      required: true,
+    },
+    namespace: {
+      type: String,
+      required: true,
+    },
+    labels: {
+      type: Array,
+      required: true,
+    },
+    showExtraOptions: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  computed: {
+    dropdownToggleText() {
+      if (this.labels.length === 0) {
+        return __('Label');
+      }
+
+      if (this.labels.length > 1) {
+        return sprintf(s__('LabelSelect|%{firstLabelName} +%{remainingLabelCount} more'), {
+          firstLabelName: this.labels[0].title,
+          remainingLabelCount: this.labels.length - 1,
+        });
+      }
+
+      return this.labels[0].title;
+    },
+  },
+};
+</script>
+
+<template>
+  <button
+    type="button"
+    ref="dropdownButton"
+    class="dropdown-menu-toggle wide js-label-select js-multiselect js-context-config-modal"
+    data-toggle="dropdown"
+    :class="{ 'js-extra-options': showExtraOptions }"
+    :data-ability-name="abilityName"
+    :data-field-name="fieldName"
+    :data-issue-update="updatePath"
+    :data-labels="labelsPath"
+    :data-namespace-path="namespace"
+    :data-show-any="showExtraOptions"
+  >
+    <span class="dropdown-toggle-text">
+      {{ dropdownToggleText }}
+    </span>
+    <i
+      aria-hidden="true"
+      class="fa fa-chevron-down"
+      data-hidden="true"
+    >
+    </i>
+  </button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
new file mode 100644
index 0000000000000000000000000000000000000000..34a07f33a2373c6cf87c6712fcda9f96dc1e7e31
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
@@ -0,0 +1,93 @@
+<script>
+import { __ } from '~/locale';
+
+export default {
+  props: {
+    headerTitle: {
+      type: String,
+      required: false,
+      default: () => __('Create new label'),
+    },
+  },
+  created() {
+    this.suggestedColors = gon.suggested_label_colors;
+  },
+};
+</script>
+
+<template>
+  <div class="dropdown-page-two dropdown-new-label">
+    <div class="dropdown-title">
+      <button
+        type="button"
+        class="dropdown-title-button dropdown-menu-back"
+        :aria-label="__('Go back')"
+      >
+        <i
+          aria-hidden="true"
+          class="fa fa-arrow-left"
+          data-hidden="true"
+        >
+        </i>
+      </button>
+      {{ headerTitle }}
+      <button
+        type="button"
+        class="dropdown-title-button dropdown-menu-close"
+        :aria-label="__('Close')"
+      >
+        <i
+          aria-hidden="true"
+          class="fa fa-times dropdown-menu-close-icon"
+          data-hidden="true"
+        >
+        </i>
+      </button>
+    </div>
+    <div class="dropdown-content">
+      <div class="dropdown-labels-error js-label-error"></div>
+      <input
+        id="new_label_name"
+        type="text"
+        class="default-dropdown-input"
+        :placeholder="__('Name new label')"
+      />
+      <div class="suggest-colors suggest-colors-dropdown">
+        <a
+          v-for="(color, index) in suggestedColors"
+          href="#"
+          :key="index"
+          :data-color="color"
+          :style="{
+            backgroundColor: color,
+          }"
+        >
+          &nbsp;
+        </a>
+      </div>
+      <div class="dropdown-label-color-input">
+        <div class="dropdown-label-color-preview js-dropdown-label-color-preview"></div>
+        <input
+          id="new_label_color"
+          type="text"
+          class="default-dropdown-input"
+          :placeholder="__('Assign custom color like #FF0000')"
+        />
+      </div>
+      <div class="clearfix">
+        <button
+          type="button"
+          class="btn btn-primary pull-left js-new-label-btn disabled"
+        >
+          {{ __('Create') }}
+        </button>
+        <button
+          type="button"
+          class="btn btn-default pull-right js-cancel-label-btn"
+        >
+          {{ __('Cancel') }}
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5f61e9fbe80cf57690545b170df5aff2d4960be2
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue
@@ -0,0 +1,46 @@
+<script>
+import { __ } from '~/locale';
+
+export default {
+  props: {
+    labelsWebUrl: {
+      type: String,
+      required: true,
+    },
+    createLabelTitle: {
+      type: String,
+      required: false,
+      default: () => __('Create new label'),
+    },
+    manageLabelsTitle: {
+      type: String,
+      required: false,
+      default: () => __('Manage labels'),
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="dropdown-footer">
+    <ul class="dropdown-footer-list">
+      <li>
+        <a
+          href="#"
+          class="dropdown-toggle-page"
+        >
+          {{ createLabelTitle }}
+        </a>
+      </li>
+      <li>
+        <a
+          data-is-link="true"
+          class="dropdown-external-link"
+          :href="labelsWebUrl"
+        >
+          {{ manageLabelsTitle }}
+        </a>
+      </li>
+    </ul>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7664acdf19c7c2a2cd15beda5f038c38f377a399
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue
@@ -0,0 +1,21 @@
+<script>
+export default {};
+</script>
+
+<template>
+  <div class="dropdown-title">
+    <span>{{ __('Assign labels') }}</span>
+    <button
+      type="button"
+      class="dropdown-title-button dropdown-menu-close"
+      :aria-label="__('Close')"
+    >
+      <i
+        aria-hidden="true"
+        class="fa fa-times dropdown-menu-close-icon"
+        data-hidden="true"
+      >
+      </i>
+    </button>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1832c3c175782a1b74eba1489cc2840a178be532
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue
@@ -0,0 +1,22 @@
+<script>
+export default {
+  props: {
+    name: {
+      type: String,
+      required: true,
+    },
+    label: {
+      type: Object,
+      required: true,
+    },
+  },
+};
+</script>
+
+<template>
+  <input
+    type="hidden"
+    :name="name"
+    :value="label.id"
+  />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ae633460c957fb6b6fcff66c63354a9b4533a675
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue
@@ -0,0 +1,27 @@
+<script>
+export default {};
+</script>
+
+<template>
+  <div class="dropdown-input">
+    <input
+      autocomplete="off"
+      class="dropdown-input-field"
+      type="search"
+      :placeholder="__('Search')"
+    />
+    <i
+      aria-hidden="true"
+      class="fa fa-search dropdown-input-search"
+      data-hidden="true"
+    >
+    </i>
+    <i
+      aria-hidden="true"
+      class="fa fa-times dropdown-input-clear js-dropdown-input-clear"
+      data-hidden="true"
+      role="button"
+    >
+    </i>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7da82e90e2940ee6accb860e771441fb36c9f1c2
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue
@@ -0,0 +1,30 @@
+<script>
+export default {
+  props: {
+    canEdit: {
+      type: Boolean,
+      required: true,
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="title hide-collapsed append-bottom-10">
+    {{ __('Labels') }}
+    <template v-if="canEdit">
+      <i
+        aria-hidden="true"
+        class="fa fa-spinner fa-spin block-loading"
+        data-hidden="true"
+      >
+      </i>
+      <button
+        type="button"
+        class="edit-link btn btn-blank pull-right js-sidebar-dropdown-toggle"
+      >
+        {{ __('Edit') }}
+      </button>
+    </template>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue
new file mode 100644
index 0000000000000000000000000000000000000000..69d588eb25d42d29d3decda042a8a3a3dacc0870
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue
@@ -0,0 +1,63 @@
+<script>
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  directives: {
+    tooltip,
+  },
+  props: {
+    labels: {
+      type: Array,
+      required: true,
+    },
+    labelFilterBasePath: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    isEmpty() {
+      return this.labels.length === 0;
+    },
+  },
+  methods: {
+    labelFilterUrl(label) {
+      return `${this.labelFilterBasePath}?label_name[]=${encodeURIComponent(label.title)}`;
+    },
+    labelStyle(label) {
+      return {
+        color: label.textColor,
+        backgroundColor: label.color,
+      };
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="hide-collapsed value issuable-show-labels js-value">
+    <span
+      v-if="isEmpty"
+      class="text-secondary"
+    >
+      <slot>{{ __('None') }}</slot>
+    </span>
+    <a
+      v-else
+      v-for="label in labels"
+      :key="label.id"
+      :href="labelFilterUrl(label)"
+    >
+      <span
+        v-tooltip
+        class="label color-label"
+        data-placement="bottom"
+        data-container="body"
+        :style="labelStyle(label)"
+        :title="label.description"
+      >
+        {{ label.title }}
+      </span>
+    </a>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5cf728fe050fbed0bf05ae413502b04cecc02975
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
@@ -0,0 +1,48 @@
+<script>
+import { s__, sprintf } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  directives: {
+    tooltip,
+  },
+  props: {
+    labels: {
+      type: Array,
+      required: true,
+    },
+  },
+  computed: {
+    labelsList() {
+      const labelsString = this.labels.slice(0, 5).map(label => label.title).join(', ');
+
+      if (this.labels.length > 5) {
+        return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), {
+          labelsString,
+          remainingLabelCount: this.labels.length - 5,
+        });
+      }
+
+      return labelsString;
+    },
+  },
+};
+</script>
+
+<template>
+  <div
+    v-tooltip
+    class="sidebar-collapsed-icon"
+    data-placement="left"
+    data-container="body"
+    :title="labelsList"
+  >
+    <i
+      aria-hidden="true"
+      data-hidden="true"
+      class="fa fa-tags"
+    >
+    </i>
+    <span>{{ labels.length }}</span>
+  </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
index 8211d425b1f1f5b7cef5846e1747ba3de945e36d..de6f8c32e74c2abdb892d1874c0433b4d6053a30 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
@@ -1,18 +1,29 @@
 <script>
-  export default {
-    name: 'ToggleSidebar',
-    props: {
-      collapsed: {
-        type: Boolean,
-        required: true,
-      },
+import { __ } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+  name: 'ToggleSidebar',
+  directives: {
+    tooltip,
+  },
+  props: {
+    collapsed: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  computed: {
+    tooltipLabel() {
+      return this.collapsed ? __('Expand sidebar') : __('Collapse sidebar');
     },
-    methods: {
-      toggle() {
-        this.$emit('toggle');
-      },
+  },
+  methods: {
+    toggle() {
+      this.$emit('toggle');
     },
-  };
+  },
+};
 </script>
 
 <template>
@@ -20,6 +31,10 @@
     type="button"
     class="btn btn-blank gutter-toggle btn-sidebar-action"
     @click="toggle"
+    v-tooltip
+    data-container="body"
+    data-placement="left"
+    :title="tooltipLabel"
   >
     <i
       aria-label="toggle collapse"
diff --git a/app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue b/app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue
index b06493e6c66097e61d465ea7d28a0ef21e504e52..16304e4815df7d3e23c7a7d6c22c35ca20203868 100644
--- a/app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue
+++ b/app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue
@@ -9,7 +9,7 @@
       lines: {
         type: Number,
         required: false,
-        default: 6,
+        default: 3,
       },
     },
     computed: {
diff --git a/app/assets/javascripts/vue_shared/directives/popover.js b/app/assets/javascripts/vue_shared/directives/popover.js
index 05fa563cbd0482df668a117c49c25604aee0440a..eb35294906b3c8275f0471fbddddedc2fa274879 100644
--- a/app/assets/javascripts/vue_shared/directives/popover.js
+++ b/app/assets/javascripts/vue_shared/directives/popover.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 /**
  * Helper to user bootstrap popover in vue.js.
  * Follow docs for html attributes: https://getbootstrap.com/docs/3.3/javascript/#static-popover
diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js
index dc896cf5c7ddff2dfff43e08e7d20c9ac80274df..b7f7e9fec15b65b26f97c1b315ef4c211ac772d3 100644
--- a/app/assets/javascripts/vue_shared/directives/tooltip.js
+++ b/app/assets/javascripts/vue_shared/directives/tooltip.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
 export default {
   bind(el) {
     $(el).tooltip();
diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js
new file mode 100644
index 0000000000000000000000000000000000000000..70b9efe0c6827b9dcdea846dc320bc64a75d54dd
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/models/label.js
@@ -0,0 +1,13 @@
+class ListLabel {
+  constructor(obj) {
+    this.id = obj.id;
+    this.title = obj.title;
+    this.type = obj.type;
+    this.color = obj.color;
+    this.textColor = obj.text_color;
+    this.description = obj.description;
+    this.priority = (obj.priority !== null) ? obj.priority : Infinity;
+  }
+}
+
+window.ListLabel = ListLabel;
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index 4592003f57e281fa50da3256c7040b023ef6d32f..f68a4f287141668b9d5de7f4d9318ca8ab326261 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -5,6 +5,7 @@
 /*= provides zen_mode:enter */
 /*= provides zen_mode:leave */
 
+import $ from 'jquery';
 import 'vendor/jquery.scrollTo';
 import Dropzone from 'dropzone';
 import Mousetrap from 'mousetrap';
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 0665622fe4a899420303d36726713035e172d6ff..f2950308019fc4a6dd2b509f49064f0cbc83b74c 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -37,7 +37,11 @@
 /*
  * Code highlight
  */
-@import "highlight/**/*";
+@import "highlight/dark";
+@import "highlight/monokai";
+@import "highlight/solarized_dark";
+@import "highlight/solarized_light";
+@import "highlight/white";
 
 /*
  * Styles for JS behaviors.
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 728f9a27aca407375a9a646d60aca51e737a64e1..14cd32da9eb7db3f04b9b6e19882f01130c22713 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -187,12 +187,9 @@ a {
   animation: fadeInFull $fade-in-duration 1;
 }
 
-
 .animation-container {
-  background: $repo-editor-grey;
   height: 40px;
   overflow: hidden;
-  position: relative;
 
   &.animation-container-small {
     height: 12px;
@@ -205,60 +202,43 @@ a {
     }
   }
 
-  &::before {
-    animation-duration: 1s;
-    animation-fill-mode: forwards;
-    animation-iteration-count: infinite;
-    animation-name: blockTextShine;
-    animation-timing-function: linear;
-    background-image: $repo-editor-linear-gradient;
-    background-repeat: no-repeat;
-    background-size: 800px 45px;
-    content: ' ';
-    display: block;
-    height: 100%;
+  [class^="skeleton-line-"] {
     position: relative;
-  }
-
-  div {
-    background: $white-light;
-    height: 6px;
-    left: 0;
-    position: absolute;
-    right: 0;
-  }
-
-  .skeleton-line-1 {
-    left: 0;
-    top: 8px;
-  }
-
-  .skeleton-line-2 {
-    left: 150px;
-    top: 0;
+    background-color: $theme-gray-100;
     height: 10px;
-  }
+    overflow: hidden;
 
-  .skeleton-line-3 {
-    left: 0;
-    top: 23px;
-  }
+    &:not(:last-of-type) {
+      margin-bottom: 4px;
+    }
 
-  .skeleton-line-4 {
-    left: 0;
-    top: 38px;
+    &::after {
+      content: ' ';
+      display: block;
+      animation: blockTextShine 1s linear infinite forwards;
+      background-repeat: no-repeat;
+      background-size: cover;
+      background-image: linear-gradient(
+        to right,
+        $theme-gray-100 0%,
+        $theme-gray-50 20%,
+        $theme-gray-100 40%,
+        $theme-gray-100 100%
+      );
+      height: 10px;
+    }
   }
+}
 
-  .skeleton-line-5 {
-    left: 200px;
-    top: 28px;
-    height: 10px;
-  }
+$skeleton-line-widths: (
+  156px,
+  235px,
+  200px,
+);
 
-  .skeleton-line-6 {
-    top: 14px;
-    left: 230px;
-    height: 10px;
+@for $count from 1 through length($skeleton-line-widths) {
+  .skeleton-line-#{$count} {
+    width: nth($skeleton-line-widths, $count);
   }
 }
 
diff --git a/app/assets/stylesheets/framework/banner.scss b/app/assets/stylesheets/framework/banner.scss
index 6433b0c785574d38a44b8148139452793ec113cf..02f3896d5913f6aa635c45bc726d55ad8d031f72 100644
--- a/app/assets/stylesheets/framework/banner.scss
+++ b/app/assets/stylesheets/framework/banner.scss
@@ -1,7 +1,7 @@
 .banner-callout {
   display: flex;
   position: relative;
-  flex-wrap: wrap;
+  align-items: start;
 
   .banner-close {
     position: absolute;
@@ -16,10 +16,25 @@
   }
 
   .banner-graphic {
-    margin: 20px auto;
+    margin: 0 $gl-padding $gl-padding 0;
   }
 
   &.banner-non-empty-state {
     border-bottom: 1px solid $border-color;
   }
+
+  @media (max-width: $screen-xs-max) {
+    justify-content: center;
+    flex-direction: column;
+    align-items: center;
+
+    .banner-title,
+    .banner-buttons {
+      text-align: center;
+    }
+
+    .banner-graphic {
+      margin-left: $gl-padding;
+    }
+  }
 }
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 6b89387ab5f518e357fd0694216fd54fcf58cd41..f4f5926e19803d62c6e5176270cb3e228570c25b 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -422,25 +422,43 @@
   }
 }
 
-.btn-link.btn-secondary-hover-link {
-  color: $gl-text-color-secondary;
+.btn-link {
+  padding: 0;
+  background-color: transparent;
+  color: $blue-600;
+  font-weight: normal;
+  border-radius: 0;
+  border-color: transparent;
 
   &:hover,
   &:active,
   &:focus {
-    color: $gl-link-color;
-    text-decoration: none;
+    color: $blue-800;
+    text-decoration: underline;
+    background-color: transparent;
+    border-color: transparent;
   }
-}
 
-.btn-link.btn-primary-hover-link {
-  color: inherit;
+  &.btn-secondary-hover-link {
+    color: $gl-text-color-secondary;
 
-  &:hover,
-  &:active,
-  &:focus {
-    color: $gl-link-color;
-    text-decoration: none;
+    &:hover,
+    &:active,
+    &:focus {
+      color: $gl-link-color;
+      text-decoration: none;
+    }
+  }
+
+  &.btn-primary-hover-link {
+    color: inherit;
+
+    &:hover,
+    &:active,
+    &:focus {
+      color: $gl-link-color;
+      text-decoration: none;
+    }
   }
 }
 
@@ -485,3 +503,7 @@ fieldset[disabled] .btn,
     @extend %disabled;
   }
 }
+
+.btn-no-padding {
+  padding: 0;
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index ae517c41cb27e991c40bf14fcaf08e5aeea36aee..d0dda50a8353a3aed861c8641f3356ba53dc12db 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -14,6 +14,10 @@
   color: $gl-text-color-secondary;
 }
 
+.text-tertiary {
+  color: $gl-text-color-tertiary;
+}
+
 .text-primary,
 .text-primary:hover {
   color: $brand-primary;
@@ -442,6 +446,10 @@ img.emoji {
   opacity: .5;
 }
 
+.break-word {
+  word-wrap: break-word;
+}
+
 /** COMMON CLASSES **/
 .prepend-top-0 { margin-top: 0; }
 .prepend-top-5 { margin-top: 5px; }
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index 1acde98c3ae0c87766004c981b3279efb3efb202..e2d97d0298fa9ab02e84c49823a1eeb709ae0c8c 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -9,7 +9,8 @@
     padding-left: $contextual-sidebar-width;
   }
 
-  .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
+  .issues-bulk-update.right-sidebar.right-sidebar-expanded
+    .issuable-sidebar-header {
     padding: 10px 0 15px;
   }
 }
@@ -61,7 +62,8 @@
 }
 
 .nav-sidebar {
-  transition: width $sidebar-transition-duration, left $sidebar-transition-duration;
+  transition: width $sidebar-transition-duration,
+    left $sidebar-transition-duration;
   position: fixed;
   z-index: 400;
   width: $contextual-sidebar-width;
@@ -75,7 +77,7 @@
   &:not(.sidebar-collapsed-desktop) {
     @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
       box-shadow: inset -2px 0 0 $border-color,
-                  2px 1px 3px $dropdown-shadow-color;
+        2px 1px 3px $dropdown-shadow-color;
     }
   }
 
@@ -234,7 +236,7 @@
           border-radius: 0 3px 3px 0;
 
           &::before {
-            content: "";
+            content: '';
             position: absolute;
             top: -30px;
             bottom: -30px;
@@ -305,7 +307,6 @@
   }
 }
 
-
 // Collapsed nav
 
 .toggle-sidebar-button,
@@ -454,18 +455,3 @@
     z-index: 300;
   }
 }
-
-
-// Make issue boards full-height now that sub-nav is gone
-
-.boards-list {
-  height: calc(100vh - #{$header-height});
-
-  @media (min-width: $screen-sm-min) {
-    height: calc(100vh - 180px);
-  }
-}
-
-.with-performance-bar .boards-list {
-  height: calc(100vh - #{$header-height} - #{$performance-bar-height});
-}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 1d7b0b602cc07bb9ee994683aa14b98c9fe68022..cc5fac6816d976eb58d4b7ccf504dc49eae0572a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -272,7 +272,7 @@
 
   .divider {
     height: 1px;
-    margin: 6px 0;
+    margin: #{$grid-size / 2} 0;
     padding: 0;
     background-color: $dropdown-divider-color;
 
@@ -481,7 +481,8 @@
 
 .dropdown-menu-selectable {
   li {
-    a {
+    a,
+    button {
       padding: 8px 40px;
       position: relative;
 
@@ -501,10 +502,8 @@
           -moz-osx-font-smoothing: grayscale;
         }
 
-        &.dropdown-menu-user-link {
-          &::before {
-            top: 50%;
-          }
+        &.dropdown-menu-user-link::before {
+          top: 50%;
         }
       }
 
@@ -624,7 +623,7 @@
 }
 
 .dropdown-content {
-  max-height: $dropdown-max-height;
+  max-height: 252px;
   overflow-y: auto;
 }
 
@@ -701,6 +700,31 @@
   border-radius: $border-radius-base;
 }
 
+.git-revision-dropdown {
+  .dropdown-content {
+    max-height: 215px;
+  }
+}
+
+.sidebar-move-issue-dropdown {
+  .dropdown-content {
+    max-height: 160px;
+  }
+}
+
+.dropdown-menu-author {
+  .dropdown-content {
+    max-height: 215px;
+  }
+}
+
+.dropdown-menu-labels {
+  .dropdown-content {
+    max-height: 128px;
+  }
+}
+
+
 .dropdown-menu-due-date {
   .dropdown-content {
     max-height: 230px;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 88ce119ee3a2f8ba1dba7fadfc2bcc7fef05c947..cb2f71b003346b4ef2daf9cf9f137d15d874e5c6 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -12,6 +12,12 @@
     margin: 0;
   }
 
+  .flash-warning {
+    @extend .alert;
+    @extend .alert-warning;
+    margin: 0;
+  }
+
   .flash-alert {
     @extend .alert;
     @extend .alert-danger;
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index db36e27fa74be55d4652e48597750fe1b0dc4bd1..05cb0196cedf285c9ebf05456287ea203134c64d 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -2,7 +2,15 @@
  * Styles the GitLab application with a specific color theme
  */
 
-@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) {
+@mixin gitlab-theme(
+  $color-100,
+  $color-200,
+  $color-500,
+  $color-700,
+  $color-800,
+  $color-900,
+  $color-alternate
+) {
   // Header
 
   .navbar-gitlab {
@@ -23,7 +31,7 @@
       > li {
         > a:hover,
         > a:focus {
-          background-color: rgba($color-200, .2);
+          background-color: rgba($color-200, 0.2);
         }
 
         &.active > a,
@@ -33,7 +41,7 @@
         }
 
         &.line-separator {
-          border-left: 1px solid rgba($color-200, .2);
+          border-left: 1px solid rgba($color-200, 0.2);
         }
       }
     }
@@ -56,7 +64,7 @@
           &:hover,
           &:focus {
             @media (min-width: $screen-sm-min) {
-              background-color: rgba($color-200, .2);
+              background-color: rgba($color-200, 0.2);
             }
 
             svg {
@@ -91,34 +99,34 @@
     > a {
       &:hover,
       &:focus {
-        background-color: rgba($color-200, .2);
+        background-color: rgba($color-200, 0.2);
       }
     }
   }
 
   .search {
     form {
-      background-color: rgba($color-200, .2);
+      background-color: rgba($color-200, 0.2);
 
       &:hover {
-        background-color: rgba($color-200, .3);
+        background-color: rgba($color-200, 0.3);
       }
     }
 
     .location-badge {
       color: $color-100;
-      background-color: rgba($color-200, .1);
+      background-color: rgba($color-200, 0.1);
       border-right: 1px solid $color-800;
     }
 
     .search-input::placeholder {
-      color: rgba($color-200, .8);
+      color: rgba($color-200, 0.8);
     }
 
     .search-input-wrap {
       .search-icon,
       .clear-icon {
-        fill: rgba($color-200, .8);
+        fill: rgba($color-200, 0.8);
       }
     }
 
@@ -133,7 +141,7 @@
 
       .search-input-wrap {
         .search-icon {
-          fill: rgba($color-200, .8);
+          fill: rgba($color-200, 0.8);
         }
       }
     }
@@ -144,7 +152,6 @@
     color: $color-900;
   }
 
-
   // Sidebar
   .nav-sidebar li.active {
     box-shadow: inset 4px 0 0 $color-700;
@@ -169,28 +176,94 @@
       font-weight: $gl-font-weight-bold;
     }
   }
-}
 
+  // Web IDE
+  .ide-sidebar-link {
+    color: $color-200;
+    background-color: $color-700;
+
+    &:hover,
+    &:focus {
+      background-color: $color-500;
+    }
+
+    &:active {
+      background: $color-800;
+    }
+  }
+
+  .branch-container {
+    border-left-color: $color-700;
+  }
+
+  .branch-header-title {
+    color: $color-700;
+  }
+
+  .ide-file-list .file.file-active {
+    color: $color-700;
+  }
+}
 
 body {
   &.ui_indigo {
-    @include gitlab-theme($indigo-100, $indigo-200, $indigo-500, $indigo-700, $indigo-800, $indigo-900, $white-light);
+    @include gitlab-theme(
+      $indigo-100,
+      $indigo-200,
+      $indigo-500,
+      $indigo-700,
+      $indigo-800,
+      $indigo-900,
+      $white-light
+    );
   }
 
   &.ui_dark {
-    @include gitlab-theme($theme-gray-100, $theme-gray-200, $theme-gray-500, $theme-gray-700, $theme-gray-800, $theme-gray-900, $white-light);
+    @include gitlab-theme(
+      $theme-gray-100,
+      $theme-gray-200,
+      $theme-gray-500,
+      $theme-gray-700,
+      $theme-gray-800,
+      $theme-gray-900,
+      $white-light
+    );
   }
 
   &.ui_blue {
-    @include gitlab-theme($theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, $theme-blue-800, $theme-blue-900, $white-light);
+    @include gitlab-theme(
+      $theme-blue-100,
+      $theme-blue-200,
+      $theme-blue-500,
+      $theme-blue-700,
+      $theme-blue-800,
+      $theme-blue-900,
+      $white-light
+    );
   }
 
   &.ui_green {
-    @include gitlab-theme($theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, $theme-green-800, $theme-green-900, $white-light);
+    @include gitlab-theme(
+      $theme-green-100,
+      $theme-green-200,
+      $theme-green-500,
+      $theme-green-700,
+      $theme-green-800,
+      $theme-green-900,
+      $white-light
+    );
   }
 
   &.ui_light {
-    @include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700);
+    @include gitlab-theme(
+      $theme-gray-900,
+      $theme-gray-700,
+      $theme-gray-800,
+      $theme-gray-700,
+      $theme-gray-700,
+      $theme-gray-100,
+      $theme-gray-700
+    );
 
     .navbar-gitlab {
       background-color: $theme-gray-100;
@@ -270,5 +343,9 @@ body {
     .sidebar-top-level-items > li.active .badge {
       color: $theme-gray-900;
     }
+
+    .ide-sidebar-link {
+      color: $white-light;
+    }
   }
 }
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 634593aefd0879da4efef4f8bb15ae39c5d338f1..0136af76a13fe6ea86ffb8d7aa40dffbf11f0cc4 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -1,60 +1,24 @@
 .navbar-gitlab {
-  &.navbar-gitlab {
-    padding: 0 16px;
-    z-index: 1000;
-    margin-bottom: 0;
-    min-height: $header-height;
-    border: 0;
-    border-bottom: 1px solid $border-color;
-    position: fixed;
-    top: 0;
-    left: 0;
-    right: 0;
-    border-radius: 0;
-
-    .logo-text {
-      line-height: initial;
-
-      svg {
-        width: 55px;
-        height: 14px;
-        margin: 0;
-        fill: $white-light;
-      }
-    }
-
-    .container-fluid {
-      padding: 0;
-
-      .user-counter {
-        svg {
-          margin-right: 3px;
-        }
-      }
-
-      .navbar-toggle {
-        right: -10px;
-        border-radius: 0;
-        min-width: 45px;
-        padding: 0;
-        margin-right: -7px;
-        font-size: 14px;
-        text-align: center;
-        color: currentColor;
-
-        &:hover,
-        &:focus,
-        &.active {
-          color: currentColor;
-          background-color: transparent;
-        }
-
-        .more-icon,
-        .close-icon {
-          fill: $white-light;
-          margin: auto;
-        }
-      }
+  padding: 0 16px;
+  z-index: 1000;
+  margin-bottom: 0;
+  min-height: $header-height;
+  border: 0;
+  border-bottom: 1px solid $border-color;
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  border-radius: 0;
+
+  .logo-text {
+    line-height: initial;
+
+    svg {
+      width: 55px;
+      height: 14px;
+      margin: 0;
+      fill: $white-light;
     }
   }
 
@@ -184,6 +148,37 @@
   }
 
   .container-fluid {
+    padding: 0;
+
+    .user-counter {
+      svg {
+        margin-right: 3px;
+      }
+    }
+
+    .navbar-toggle {
+      right: -10px;
+      border-radius: 0;
+      min-width: 45px;
+      padding: 0;
+      margin-right: -7px;
+      font-size: 14px;
+      text-align: center;
+      color: currentColor;
+
+      &:hover,
+      &:focus,
+      &.active {
+        color: currentColor;
+        background-color: transparent;
+      }
+
+      .more-icon,
+      .close-icon {
+        fill: $white-light;
+        margin: auto;
+      }
+    }
 
     .navbar-nav {
       @media (max-width: $screen-xs-max) {
@@ -337,7 +332,7 @@
 .breadcrumbs {
   display: -webkit-flex;
   display: flex;
-  min-height: 48px;
+  min-height: $breadcrumb-min-height;
   color: $gl-text-color;
 }
 
@@ -466,7 +461,7 @@
       padding: 0 5px;
       line-height: 12px;
       border-radius: 7px;
-      box-shadow: 0 1px 0 rgba($gl-header-color, .2);
+      box-shadow: 0 1px 0 rgba($gl-header-color, 0.2);
 
       &.issues-count {
         background-color: $green-500;
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index 2d015ef086b918e05c59488f7c3a7cf543b7907e..62a0fba3da3934f4389b520ed3bc694671758b5b 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -20,7 +20,7 @@
     width: 100%;
   }
 
-  $image-widths: 250 306 394 430;
+  $image-widths: 80 130 250 306 394 430;
   @each $width in $image-widths {
     &.svg-#{$width} {
       img,
@@ -39,12 +39,35 @@
 svg {
   fill: currentColor;
 
-  &.s8 { @include svg-size(8px); }
-  &.s12 { @include svg-size(12px); }
-  &.s16 { @include svg-size(16px); }
-  &.s18 { @include svg-size(18px); }
-  &.s24 { @include svg-size(24px); }
-  &.s32 { @include svg-size(32px); }
-  &.s48 { @include svg-size(48px); }
-  &.s72 { @include svg-size(72px); }
+  &.s8 {
+    @include svg-size(8px);
+  }
+
+  &.s12 {
+    @include svg-size(12px);
+  }
+
+  &.s16 {
+    @include svg-size(16px);
+  }
+
+  &.s18 {
+    @include svg-size(18px);
+  }
+
+  &.s24 {
+    @include svg-size(24px);
+  }
+
+  &.s32 {
+    @include svg-size(32px);
+  }
+
+  &.s48 {
+    @include svg-size(48px);
+  }
+
+  &.s72 {
+    @include svg-size(72px);
+  }
 }
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 7e829826ebae493cd662ad627be4a03808f387d2..f1a8a46dda45687d03183f4d007bd6d010e0c0da 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -24,6 +24,10 @@
       color: $list-text-disabled-color;
     }
 
+    &:not(.ui-sort-disabled):hover {
+      background: $row-hover;
+    }
+
     &.unstyled {
       &:hover {
         background: none;
@@ -34,14 +38,15 @@
       background-color: $list-warning-row-bg;
       border-color: $list-warning-row-border;
       color: $list-warning-row-color;
-    }
 
-    &.smoke { background-color: $gray-light; }
+      &:hover {
+        background: $list-warning-row-bg;
+      }
 
-    &:not(.ui-sort-disabled):hover {
-      background: $row-hover;
     }
 
+    &.smoke { background-color: $gray-light; }
+
     &:last-child {
       border-bottom: 0;
 
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index ddd9dbb2be4bd20c04b188bab0a34ea23d60eb95..e12b5aab38156098b1aebea320924ec09e2f47f1 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -17,8 +17,6 @@
  */
 @mixin markdown-table {
   width: auto;
-  display: block;
-  overflow-x: auto;
 }
 
 /*
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index a6b1bf9b0996318bc44a5ec1cbcfc268c0227bbd..eb789cc64b0a8d9f261e0049d406cdf08ffd5fff 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -2,14 +2,23 @@
   background-color: $modal-body-bg;
   padding: #{3 * $grid-size} #{2 * $grid-size};
 
-  .page-title {
-    margin-top: 0;
+  .page-title,
+  .modal-title {
+    .modal-title-with-label span {
+      vertical-align: middle;
+      display: inline-block;
+    }
 
     .color-label {
       font-size: $gl-font-size;
       padding: $gl-vert-padding $label-padding-modal;
+      vertical-align: middle;
     }
   }
+
+  .page-title {
+    margin-top: 0;
+  }
 }
 
 .modal-body {
diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index 7829d7225606458797a1d7b84606823eb0002cb4..34fccf6f0a45ead8895b1878960d3748056ab06b 100644
--- a/app/assets/stylesheets/framework/responsive_tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -39,7 +39,7 @@
 .table-section {
   white-space: nowrap;
 
-  $section-widths: 10 15 20 25 30 40 100;
+  $section-widths: 10 15 20 25 30 40 50 100;
   @each $width in $section-widths {
     &.section-#{$width} {
       flex: 0 0 #{$width + '%'};
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index d1d98270ad9d1015d85d2f3f64e85c639f600317..64fff7463d2d5031c719f5627c77b5772e0755e2 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -16,7 +16,7 @@
 .nav-header-btn {
   padding: 10px $gl-sidebar-padding;
   color: inherit;
-  transition-duration: .3s;
+  transition-duration: 0.3s;
   position: absolute;
   top: 0;
   cursor: pointer;
@@ -88,7 +88,6 @@
 
 .right-sidebar {
   border-left: 1px solid $border-color;
-  height: calc(100% - #{$header-height});
 }
 
 .with-performance-bar .right-sidebar.affix {
@@ -138,6 +137,12 @@
   }
 }
 
+.issuable-sidebar .labels {
+  .value.dont-hide ~ .selectbox {
+    padding-top: $gl-padding-8;
+  }
+}
+
 .pikaday-container {
   .pika-single {
     margin-top: 2px;
diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss
index 30c15c231d54fa309be2a7306f3ea2d3a283213e..606d4675f19fb0c8c04432d286ea0498f9b90a4f 100644
--- a/app/assets/stylesheets/framework/snippets.scss
+++ b/app/assets/stylesheets/framework/snippets.scss
@@ -29,8 +29,10 @@
 }
 
 .snippet-title {
-  font-size: 24px;
+  color: $gl-text-color;
+  font-size: 2em;
   font-weight: $gl-font-weight-bold;
+  min-height: $header-height;
 }
 
 .snippet-edited-ago {
@@ -46,3 +48,26 @@
 .snippet-scope-menu .btn-new {
   margin-top: 15px;
 }
+
+.snippet-embed-input {
+  height: 35px;
+}
+
+.embed-snippet {
+  padding-right: 0;
+  padding-top: $gl-padding;
+
+  .form-control {
+    cursor: auto;
+    width: 101%;
+    margin-left: -1px;
+  }
+
+  .embed-toggle-list li button {
+    padding: 8px 40px;
+  }
+
+  .embed-toggle {
+    height: 35px;
+  }
+}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 294c59f037f54ef608996a44df26b97d70df136c..9e1371648ed6e4aa1218a35636c59b7301119bed 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -289,6 +289,11 @@ body {
   &:last-child {
     margin-bottom: 0;
   }
+
+  &.with-button {
+    line-height: 34px;
+  }
+
 }
 
 .page-title-empty {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index a5a8f6d22063f06f7bb2439153c0a3f126eb688b..8c44ebc85ef05229f94edd00fa3e51d4697e5214 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -5,9 +5,9 @@ $grid-size: 8px;
 $gutter_collapsed_width: 62px;
 $gutter_width: 290px;
 $gutter_inner_width: 250px;
-$sidebar-transition-duration: .3s;
+$sidebar-transition-duration: 0.3s;
 $sidebar-breakpoint: 1024px;
-$default-transition-duration: .15s;
+$default-transition-duration: 0.15s;
 $contextual-sidebar-width: 220px;
 $contextual-sidebar-collapsed-width: 50px;
 
@@ -129,7 +129,6 @@ $theme-green-800: #145d33;
 $theme-green-900: #0d4524;
 $theme-green-950: #072d16;
 
-
 $black: #000;
 $black-transparent: rgba(0, 0, 0, 0.3);
 $almost-black: #242424;
@@ -163,7 +162,7 @@ $gl-text-color-secondary: #707070;
 $gl-text-color-tertiary: #949494;
 $gl-text-color-quaternary: #d6d6d6;
 $gl-text-color-inverted: rgba(255, 255, 255, 1);
-$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85);
+$gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85);
 $gl-text-color-disabled: #919191;
 $gl-text-green: $green-600;
 $gl-text-green-hover: $green-700;
@@ -248,6 +247,7 @@ $btn-sm-side-margin: 7px;
 $btn-xs-side-margin: 5px;
 $issue-status-expired: $orange-500;
 $issuable-sidebar-color: $gl-text-color-secondary;
+$sidebar-block-hover-color: #ebebeb;
 $group-path-color: #999;
 $namespace-kind-color: #aaa;
 $panel-heading-link-color: #777;
@@ -262,6 +262,7 @@ $highlight-changes-color: rgb(235, 255, 232);
 $performance-bar-height: 35px;
 $flash-height: 52px;
 $context-header-height: 60px;
+$breadcrumb-min-height: 48px;
 
 /*
 * Common component specific colors
@@ -296,7 +297,7 @@ $tanuki-yellow: #fca326;
  */
 $gl-primary: $blue-500;
 $gl-success: $green-500;
-$gl-success-focus: rgba($gl-success, .4);
+$gl-success-focus: rgba($gl-success, 0.4);
 $gl-info: $blue-500;
 $gl-warning: $orange-500;
 $gl-danger: $red-500;
@@ -331,8 +332,11 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
 /*
  * Fonts
  */
-$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas',
+  'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
+$regular_font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+  Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif,
+  'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
 
 /*
 * Dropdowns
@@ -343,16 +347,16 @@ $dropdown-max-height: 312px;
 $dropdown-vertical-offset: 4px;
 $dropdown-link-color: #555;
 $dropdown-link-hover-bg: $row-hover;
-$dropdown-empty-row-bg: rgba(#000, .04);
+$dropdown-empty-row-bg: rgba(#000, 0.04);
 $dropdown-border-color: $border-color;
-$dropdown-shadow-color: rgba(#000, .1);
-$dropdown-divider-color: rgba(#000, .1);
+$dropdown-shadow-color: rgba(#000, 0.1);
+$dropdown-divider-color: rgba(#000, 0.1);
 $dropdown-title-btn-color: #bfbfbf;
 $dropdown-input-color: #555;
 $dropdown-input-fa-color: #c7c7c7;
 $dropdown-input-focus-border: $focus-border-color;
-$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
-$dropdown-loading-bg: rgba(#fff, .6);
+$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, 0.4);
+$dropdown-loading-bg: rgba(#fff, 0.6);
 $dropdown-chevron-size: 10px;
 $dropdown-toggle-active-border-color: darken($border-color, 14%);
 $dropdown-item-hover-bg: $gray-darker;
@@ -367,9 +371,11 @@ $dropdown-hover-color: $blue-400;
 /*
 * Contextual Sidebar
 */
-$link-active-background: rgba(0, 0, 0, .04);
-$link-hover-background: rgba(0, 0, 0, .06);
-$inactive-badge-background: rgba(0, 0, 0, .08);
+$link-active-background: rgba(0, 0, 0, 0.04);
+$link-hover-background: rgba(0, 0, 0, 0.06);
+$inactive-badge-background: rgba(0, 0, 0, 0.08);
+$sidebar-toggle-height: 60px;
+$sidebar-milestone-toggle-bottom-margin: 10px;
 
 /*
 * Buttons
@@ -397,14 +403,14 @@ $status-icon-margin: $gl-btn-padding;
 /*
  *  Award emoji
  */
-$award-emoji-menu-shadow: rgba(0, 0, 0, .175);
+$award-emoji-menu-shadow: rgba(0, 0, 0, 0.175);
 $award-emoji-positive-add-bg: #fed159;
 $award-emoji-positive-add-lines: #bb9c13;
 
 /*
  * Search Box
  */
-$search-input-border-color: rgba($blue-400, .8);
+$search-input-border-color: rgba($blue-400, 0.8);
 $search-input-focus-shadow-color: $dropdown-input-focus-shadow;
 $search-input-width: 220px;
 $location-badge-active-bg: $blue-500;
@@ -429,7 +435,7 @@ $zen-control-color: #555;
 * Calendar
 */
 $calendar-hover-bg: #ecf3fe;
-$calendar-border-color: rgba(#000, .1);
+$calendar-border-color: rgba(#000, 0.1);
 $calendar-user-contrib-text: #959494;
 
 /*
@@ -452,6 +458,17 @@ $ci-skipped-color: #888;
 */
 $issue-boards-font-size: 14px;
 $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
+/*
+ The following heights are used in boards.scss and are used for calculation of the board height.
+ They probably should be derived in a smarter way.
+*/
+$issue-boards-filter-height: 68px;
+$issue-boards-breadcrumbs-height-xs: 63px;
+$issue-board-list-difference-xs: $header-height +
+  $issue-boards-breadcrumbs-height-xs;
+$issue-board-list-difference-sm: $header-height + $breadcrumb-min-height;
+$issue-board-list-difference-md: $issue-board-list-difference-sm +
+  $issue-boards-filter-height;
 
 /*
 * Avatar
@@ -567,14 +584,14 @@ $label-padding: 7px;
 $label-padding-modal: 10px;
 $label-gray-bg: #f8fafc;
 $label-inverse-bg: #333;
-$label-remove-border: rgba(0, 0, 0, .1);
+$label-remove-border: rgba(0, 0, 0, 0.1);
 $label-border-radius: 100px;
 
 /*
 * Animation
 */
 $fade-in-duration: 200ms;
-$fade-mask-transition-duration: .1s;
+$fade-mask-transition-duration: 0.1s;
 $fade-mask-transition-curve: ease-in-out;
 
 /*
@@ -642,7 +659,6 @@ $stat-graph-selection-stroke: #333;
 $select2-drop-shadow1: rgba(76, 86, 103, 0.247059);
 $select2-drop-shadow2: rgba(31, 37, 50, 0.317647);
 
-
 /*
 * Todo
 */
@@ -679,7 +695,6 @@ CI variable lists
 */
 $ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
 
-
 /*
 Filtered Search
 */
@@ -701,13 +716,6 @@ $color-high-score: $green-400;
 $color-average-score: $orange-400;
 $color-low-score: $red-400;
 
-/*
-Repo editor
-*/
-$repo-editor-grey: #f6f7f9;
-$repo-editor-grey-darker: #e9ebee;
-$repo-editor-linear-gradient: linear-gradient(to right, $repo-editor-grey 0%, $repo-editor-grey-darker, 20%, $repo-editor-grey 40%, $repo-editor-grey 100%);
-
 /*
 Performance Bar
 */
@@ -717,8 +725,8 @@ $perf-bar-staging: #291430;
 $perf-bar-development: #4c1210;
 $perf-bar-bucket-bg: #111;
 $perf-bar-bucket-color: #ccc;
-$perf-bar-bucket-box-shadow-from: rgba($white-light, .2);
-$perf-bar-bucket-box-shadow-to: rgba($black, .25);
+$perf-bar-bucket-box-shadow-from: rgba($white-light, 0.2);
+$perf-bar-bucket-box-shadow-to: rgba($black, 0.25);
 
 /*
 Issuable warning
@@ -748,3 +756,8 @@ $border-color-settings: #e1e1e1;
 Modals
 */
 $modal-body-height: 134px;
+
+/*
+Prometheus
+*/
+$prometheus-table-row-highlight-color: $theme-gray-100;
diff --git a/app/assets/stylesheets/highlight/embedded.scss b/app/assets/stylesheets/highlight/embedded.scss
new file mode 100644
index 0000000000000000000000000000000000000000..44c8a1d39ec1cca0518b568d8a4c6c1dcf8aad18
--- /dev/null
+++ b/app/assets/stylesheets/highlight/embedded.scss
@@ -0,0 +1,3 @@
+.code {
+  @import "white_base";
+}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index c3d8f0c61a25a7f9262e560a7a04a8c04543eb7e..355c8d223f7923db2dfa762b33fb37196282fec7 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,292 +1,3 @@
-/* https://github.com/aahan/pygments-github-style */
-
-/*
-* White Syntax Colors
-*/
-$white-code-color: $gl-text-color;
-$white-highlight: #fafe3d;
-$white-pre-hll-bg: #f8eec7;
-$white-hll-bg: #f8f8f8;
-$white-over-bg: #ded7fc;
-$white-expanded-border: #e0e0e0;
-$white-expanded-bg: #f7f7f7;
-$white-c: #998;
-$white-err: #a61717;
-$white-err-bg: #e3d2d2;
-$white-cm: #998;
-$white-cp: #999;
-$white-c1: #998;
-$white-cs: #999;
-$white-gd: $black;
-$white-gd-bg: #fdd;
-$white-gd-x: $black;
-$white-gd-x-bg: #faa;
-$white-gr: #a00;
-$white-gh: #999;
-$white-gi: $black;
-$white-gi-bg: #dfd;
-$white-gi-x: $black;
-$white-gi-x-bg: #afa;
-$white-go: #888;
-$white-gp: #555;
-$white-gu: #800080;
-$white-gt: #a00;
-$white-kt: #458;
-$white-m: #099;
-$white-s: #d14;
-$white-n: #333;
-$white-na: teal;
-$white-nb: #0086b3;
-$white-nc: #458;
-$white-no: teal;
-$white-ni: purple;
-$white-ne: #900;
-$white-nf: #900;
-$white-nn: #555;
-$white-nt: navy;
-$white-nv: teal;
-$white-w: #bbb;
-$white-mf: #099;
-$white-mh: #099;
-$white-mi: #099;
-$white-mo: #099;
-$white-sb: #d14;
-$white-sc: #d14;
-$white-sd: #d14;
-$white-s2: #d14;
-$white-se: #d14;
-$white-sh: #d14;
-$white-si: #d14;
-$white-sx: #d14;
-$white-sr: #009926;
-$white-s1: #d14;
-$white-ss: #990073;
-$white-bp: #999;
-$white-vc: teal;
-$white-vg: teal;
-$white-vi: teal;
-$white-il: #099;
-$white-gc-color: #999;
-$white-gc-bg: #eaf2f5;
-
-
-@mixin matchLine {
-  color: $black-transparent;
-  background-color: $gray-light;
-}
-
 .code.white {
-  // Line numbers
-  .line-numbers,
-  .diff-line-num {
-    background-color: $gray-light;
-  }
-
-  .diff-line-num,
-  .diff-line-num a {
-    color: $black-transparent;
-  }
-
-  // Code itself
-  pre.code,
-  .diff-line-num {
-    border-color: $white-normal;
-  }
-
-  &,
-  pre.code,
-  .line_holder .line_content {
-    background-color: $white-light;
-    color: $white-code-color;
-  }
-
-  // Diff line
-  .line_holder {
-
-    &.match .line_content {
-      @include matchLine;
-    }
-
-    .diff-line-num {
-      &.old {
-        background-color: $line-number-old;
-        border-color: $line-removed-dark;
-
-        a {
-          color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
-        }
-      }
-
-      &.new {
-        background-color: $line-number-new;
-        border-color: $line-added-dark;
-
-        a {
-          color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
-        }
-      }
-
-      &.is-over,
-      &.hll:not(.empty-cell).is-over {
-        background-color: $white-over-bg;
-        border-color: darken($white-over-bg, 5%);
-
-        a {
-          color: darken($white-over-bg, 15%);
-        }
-      }
-
-      &.hll:not(.empty-cell) {
-        background-color: $line-number-select;
-        border-color: $line-select-yellow-dark;
-      }
-    }
-
-    &:not(.diff-expanded) + .diff-expanded,
-    &.diff-expanded + .line_holder:not(.diff-expanded) {
-      > .diff-line-num,
-      > .line_content {
-        border-top: 1px solid $white-expanded-border;
-      }
-    }
-
-    &.diff-expanded {
-      > .diff-line-num,
-      > .line_content {
-        background: $white-expanded-bg;
-        border-color: $white-expanded-bg;
-      }
-    }
-
-    .line_content {
-      &.old {
-        background-color: $line-removed;
-
-        &::before {
-          color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
-        }
-
-        span.idiff {
-          background-color: $line-removed-dark;
-        }
-      }
-
-      &.new {
-        background-color: $line-added;
-
-        &::before {
-          color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
-        }
-
-        span.idiff {
-          background-color: $line-added-dark;
-        }
-      }
-
-      &.match {
-        @include matchLine;
-      }
-
-      &.hll:not(.empty-cell) {
-        background-color: $line-select-yellow;
-      }
-    }
-  }
-
-  // highlight line via anchor
-  pre .hll {
-    background-color: $white-pre-hll-bg !important;
-  }
-
-  // Search result highlight
-  span.highlight_word {
-    background-color: $white-highlight !important;
-  }
-
-  // Links to URLs, emails, or dependencies
-  .line a {
-    color: $white-nb;
-  }
-
-  .hll { background-color: $white-hll-bg; }
-  .c { color: $white-c; font-style: italic; }
-  .err { color: $white-err; background-color: $white-err-bg; }
-  .k { font-weight: $gl-font-weight-bold; }
-  .o { font-weight: $gl-font-weight-bold; }
-  .cm { color: $white-cm; font-style: italic; }
-  .cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
-  .c1 { color: $white-c1; font-style: italic; }
-  .cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
-
-  .gd {
-    color: $white-gd;
-    background-color: $white-gd-bg;
-
-    .x {
-      color: $white-gd-x;
-      background-color: $white-gd-x-bg;
-    }
-  }
-
-  .ge { font-style: italic; }
-  .gr { color: $white-gr; }
-  .gh { color: $white-gh; }
-
-  .gi {
-    color: $white-gi;
-    background-color: $white-gi-bg;
-
-    .x {
-      color: $white-gi-x;
-      background-color: $white-gi-x-bg;
-    }
-  }
-
-  .go { color: $white-go; }
-  .gp { color: $white-gp; }
-  .gs { font-weight: $gl-font-weight-bold; }
-  .gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
-  .gt { color: $white-gt; }
-  .kc { font-weight: $gl-font-weight-bold; }
-  .kd { font-weight: $gl-font-weight-bold; }
-  .kn { font-weight: $gl-font-weight-bold; }
-  .kp { font-weight: $gl-font-weight-bold; }
-  .kr { font-weight: $gl-font-weight-bold; }
-  .kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
-  .m { color: $white-m; }
-  .s { color: $white-s; }
-  .n { color: $white-n; }
-  .na { color: $white-na; }
-  .nb { color: $white-nb; }
-  .nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
-  .no { color: $white-no; }
-  .ni { color: $white-ni; }
-  .ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
-  .nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
-  .nn { color: $white-nn; }
-  .nt { color: $white-nt; }
-  .nv { color: $white-nv; }
-  .ow { font-weight: $gl-font-weight-bold; }
-  .w { color: $white-w; }
-  .mf { color: $white-mf; }
-  .mh { color: $white-mh; }
-  .mi { color: $white-mi; }
-  .mo { color: $white-mo; }
-  .sb { color: $white-sb; }
-  .sc { color: $white-sc; }
-  .sd { color: $white-sd; }
-  .s2 { color: $white-s2; }
-  .se { color: $white-se; }
-  .sh { color: $white-sh; }
-  .si { color: $white-si; }
-  .sx { color: $white-sx; }
-  .sr { color: $white-sr; }
-  .s1 { color: $white-s1; }
-  .ss { color: $white-ss; }
-  .bp { color: $white-bp; }
-  .vc { color: $white-vc; }
-  .vg { color: $white-vg; }
-  .vi { color: $white-vi; }
-  .il { color: $white-il; }
-  .gc { color: $white-gc-color; background-color: $white-gc-bg; }
+  @import "white_base";
 }
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
new file mode 100644
index 0000000000000000000000000000000000000000..8cc5252648d2756b3ed348b75ca84d9e36550ce0
--- /dev/null
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -0,0 +1,290 @@
+/* https://github.com/aahan/pygments-github-style */
+
+/*
+* White Syntax Colors
+*/
+$white-code-color: $gl-text-color;
+$white-highlight: #fafe3d;
+$white-pre-hll-bg: #f8eec7;
+$white-hll-bg: #f8f8f8;
+$white-over-bg: #ded7fc;
+$white-expanded-border: #e0e0e0;
+$white-expanded-bg: #f7f7f7;
+$white-c: #998;
+$white-err: #a61717;
+$white-err-bg: #e3d2d2;
+$white-cm: #998;
+$white-cp: #999;
+$white-c1: #998;
+$white-cs: #999;
+$white-gd: $black;
+$white-gd-bg: #fdd;
+$white-gd-x: $black;
+$white-gd-x-bg: #faa;
+$white-gr: #a00;
+$white-gh: #999;
+$white-gi: $black;
+$white-gi-bg: #dfd;
+$white-gi-x: $black;
+$white-gi-x-bg: #afa;
+$white-go: #888;
+$white-gp: #555;
+$white-gu: #800080;
+$white-gt: #a00;
+$white-kt: #458;
+$white-m: #099;
+$white-s: #d14;
+$white-n: #333;
+$white-na: teal;
+$white-nb: #0086b3;
+$white-nc: #458;
+$white-no: teal;
+$white-ni: purple;
+$white-ne: #900;
+$white-nf: #900;
+$white-nn: #555;
+$white-nt: navy;
+$white-nv: teal;
+$white-w: #bbb;
+$white-mf: #099;
+$white-mh: #099;
+$white-mi: #099;
+$white-mo: #099;
+$white-sb: #d14;
+$white-sc: #d14;
+$white-sd: #d14;
+$white-s2: #d14;
+$white-se: #d14;
+$white-sh: #d14;
+$white-si: #d14;
+$white-sx: #d14;
+$white-sr: #009926;
+$white-s1: #d14;
+$white-ss: #990073;
+$white-bp: #999;
+$white-vc: teal;
+$white-vg: teal;
+$white-vi: teal;
+$white-il: #099;
+$white-gc-color: #999;
+$white-gc-bg: #eaf2f5;
+
+
+@mixin matchLine {
+  color: $black-transparent;
+  background-color: $gray-light;
+}
+
+  // Line numbers
+.line-numbers,
+.diff-line-num {
+  background-color: $gray-light;
+}
+
+.diff-line-num,
+.diff-line-num a {
+  color: $black-transparent;
+}
+
+// Code itself
+pre.code,
+.diff-line-num {
+  border-color: $white-normal;
+}
+
+&,
+pre.code,
+.line_holder .line_content {
+  background-color: $white-light;
+  color: $white-code-color;
+}
+
+// Diff line
+.line_holder {
+
+  &.match .line_content {
+    @include matchLine;
+  }
+
+  .diff-line-num {
+    &.old {
+      background-color: $line-number-old;
+      border-color: $line-removed-dark;
+
+      a {
+        color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
+      }
+    }
+
+    &.new {
+      background-color: $line-number-new;
+      border-color: $line-added-dark;
+
+      a {
+        color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
+      }
+    }
+
+    &.is-over,
+    &.hll:not(.empty-cell).is-over {
+      background-color: $white-over-bg;
+      border-color: darken($white-over-bg, 5%);
+
+      a {
+        color: darken($white-over-bg, 15%);
+      }
+    }
+
+    &.hll:not(.empty-cell) {
+      background-color: $line-number-select;
+      border-color: $line-select-yellow-dark;
+    }
+  }
+
+  &:not(.diff-expanded) + .diff-expanded,
+  &.diff-expanded + .line_holder:not(.diff-expanded) {
+    > .diff-line-num,
+    > .line_content {
+      border-top: 1px solid $white-expanded-border;
+    }
+  }
+
+  &.diff-expanded {
+    > .diff-line-num,
+    > .line_content {
+      background: $white-expanded-bg;
+      border-color: $white-expanded-bg;
+    }
+  }
+
+  .line_content {
+    &.old {
+      background-color: $line-removed;
+
+      &::before {
+        color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
+      }
+
+      span.idiff {
+        background-color: $line-removed-dark;
+      }
+    }
+
+    &.new {
+      background-color: $line-added;
+
+      &::before {
+        color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
+      }
+
+      span.idiff {
+        background-color: $line-added-dark;
+      }
+    }
+
+    &.match {
+      @include matchLine;
+    }
+
+    &.hll:not(.empty-cell) {
+      background-color: $line-select-yellow;
+    }
+  }
+}
+
+// highlight line via anchor
+pre .hll {
+  background-color: $white-pre-hll-bg !important;
+}
+
+  // Search result highlight
+span.highlight_word {
+  background-color: $white-highlight !important;
+}
+
+  // Links to URLs, emails, or dependencies
+.line a {
+  color: $white-nb;
+}
+
+.hll { background-color: $white-hll-bg; }
+.c { color: $white-c; font-style: italic; }
+.err { color: $white-err; background-color: $white-err-bg; }
+.k { font-weight: $gl-font-weight-bold; }
+.o { font-weight: $gl-font-weight-bold; }
+.cm { color: $white-cm; font-style: italic; }
+.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
+.c1 { color: $white-c1; font-style: italic; }
+.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
+
+.gd {
+  color: $white-gd;
+  background-color: $white-gd-bg;
+
+  .x {
+    color: $white-gd-x;
+    background-color: $white-gd-x-bg;
+  }
+}
+
+.ge { font-style: italic; }
+.gr { color: $white-gr; }
+.gh { color: $white-gh; }
+
+.gi {
+  color: $white-gi;
+  background-color: $white-gi-bg;
+
+  .x {
+    color: $white-gi-x;
+    background-color: $white-gi-x-bg;
+  }
+}
+
+.go { color: $white-go; }
+.gp { color: $white-gp; }
+.gs { font-weight: $gl-font-weight-bold; }
+.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
+.gt { color: $white-gt; }
+.kc { font-weight: $gl-font-weight-bold; }
+.kd { font-weight: $gl-font-weight-bold; }
+.kn { font-weight: $gl-font-weight-bold; }
+.kp { font-weight: $gl-font-weight-bold; }
+.kr { font-weight: $gl-font-weight-bold; }
+.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
+.m { color: $white-m; }
+.s { color: $white-s; }
+.n { color: $white-n; }
+.na { color: $white-na; }
+.nb { color: $white-nb; }
+.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
+.no { color: $white-no; }
+.ni { color: $white-ni; }
+.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
+.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
+.nn { color: $white-nn; }
+.nt { color: $white-nt; }
+.nv { color: $white-nv; }
+.ow { font-weight: $gl-font-weight-bold; }
+.w { color: $white-w; }
+.mf { color: $white-mf; }
+.mh { color: $white-mh; }
+.mi { color: $white-mi; }
+.mo { color: $white-mo; }
+.sb { color: $white-sb; }
+.sc { color: $white-sc; }
+.sd { color: $white-sd; }
+.s2 { color: $white-s2; }
+.se { color: $white-se; }
+.sh { color: $white-sh; }
+.si { color: $white-si; }
+.sx { color: $white-sx; }
+.sr { color: $white-sr; }
+.s1 { color: $white-s1; }
+.ss { color: $white-ss; }
+.bp { color: $white-bp; }
+.vc { color: $white-vc; }
+.vg { color: $white-vg; }
+.vi { color: $white-vi; }
+.il { color: $white-il; }
+.gc { color: $white-gc-color; background-color: $white-gc-bg; }
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 2803144ef1d5e56ee7cec6a1a6eac3c52899dea2..318d3ddaecef1df940f51dbe5086eb60c3a542f3 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -1,4 +1,4 @@
-@import "./issues/issue_count_badge";
+@import './issues/issue_count_badge';
 
 [v-cloak] {
   display: none;
@@ -31,8 +31,12 @@
 .dropdown-menu-issues-board-new {
   width: 320px;
 
+  .open & {
+    max-height: 400px;
+  }
+
   .dropdown-content {
-    max-height: 150px;
+    max-height: 162px;
   }
 }
 
@@ -72,22 +76,37 @@
 }
 
 .boards-list {
-  height: calc(100vh - 105px);
+  height: calc(100vh - #{$issue-board-list-difference-xs});
   width: 100%;
-  padding-top: 25px;
-  padding-bottom: 25px;
-  padding-right: ($gl-padding / 2);
-  padding-left: ($gl-padding / 2);
+  padding: $gl-padding ($gl-padding / 2);
   overflow-x: scroll;
   white-space: nowrap;
+  min-height: 200px;
 
   @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
-    height: calc(100vh - 90px);
+    height: calc(100vh - #{$issue-board-list-difference-sm});
   }
 
   @media (min-width: $screen-md-min) {
-    height: calc(100vh - 160px);
-    min-height: 475px;
+    height: calc(100vh - #{$issue-board-list-difference-md});
+  }
+
+  .with-performance-bar & {
+    height: calc(
+      100vh - #{$issue-board-list-difference-xs} - #{$performance-bar-height}
+    );
+
+    @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
+      height: calc(
+        100vh - #{$issue-board-list-difference-sm} - #{$performance-bar-height}
+      );
+    }
+
+    @media (min-width: $screen-md-min) {
+      height: calc(
+        100vh - #{$issue-board-list-difference-md} - #{$performance-bar-height}
+      );
+    }
   }
 }
 
@@ -454,7 +473,7 @@
   &.boards-sidebar-slide-enter-active,
   &.boards-sidebar-slide-leave-active {
     transition: width $sidebar-transition-duration,
-                padding $sidebar-transition-duration;
+      padding $sidebar-transition-duration;
   }
 
   &.boards-sidebar-slide-enter,
@@ -473,7 +492,7 @@
   right: 0;
   bottom: 0;
   left: 0;
-  background-color: rgba($black, .3);
+  background-color: rgba($black, 0.3);
   z-index: 9999;
 }
 
@@ -490,7 +509,7 @@
   padding: 25px 15px 0;
   background-color: $white-light;
   border-radius: $border-radius-default;
-  box-shadow: 0 2px 12px rgba($black, .5);
+  box-shadow: 0 2px 12px rgba($black, 0.5);
 
   .empty-state {
     display: -webkit-flex;
@@ -568,7 +587,7 @@
 
   .card {
     border: 1px solid $border-gray-dark;
-    box-shadow: 0 1px 2px rgba($issue-boards-card-shadow, .3);
+    box-shadow: 0 1px 2px rgba($issue-boards-card-shadow, 0.3);
     cursor: pointer;
   }
 }
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
index 3e2fa8ca88d7eea3547e463a8f3712f58e4f6a4a..49fe50977f5cd3e014e7d3bebf55499528f6fd4e 100644
--- a/app/assets/stylesheets/pages/branches.scss
+++ b/app/assets/stylesheets/pages/branches.scss
@@ -1,6 +1,17 @@
+.content-list > .branch-item,
+.branch-title {
+  display: flex;
+  align-items: center;
+}
+
+.branch-info {
+  flex: auto;
+  min-width: 0;
+  overflow: hidden;
+}
+
 .divergence-graph {
-  padding: 12px 12px 0 0;
-  float: right;
+  padding: 0 6px;
 
   .graph-side {
     position: relative;
@@ -53,3 +64,9 @@
     background-color: $divergence-graph-separator-bg;
   }
 }
+
+.divergence-graph,
+.branch-item .controls {
+  flex: 0 0 auto;
+  white-space: nowrap;
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 98d460339cd09a608005c8909f1cda0077a9d354..50f32660445f7827df12ea19bf136f6443791ac5 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -1,39 +1,56 @@
 @keyframes fade-out-status {
-  0%, 50% { opacity: 1; }
-  100% { opacity: 0; }
+  0%,
+  50% {
+    opacity: 1;
+  }
+
+  100% {
+    opacity: 0;
+  }
 }
 
 @keyframes blinking-dots {
   0% {
     background-color: rgba($white-light, 1);
     box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
-                24px 0 0 0 rgba($white-light, 0.2);
+      24px 0 0 0 rgba($white-light, 0.2);
   }
 
   25% {
     background-color: rgba($white-light, 0.4);
     box-shadow: 12px 0 0 0 rgba($white-light, 2),
-                24px 0 0 0 rgba($white-light, 0.2);
+      24px 0 0 0 rgba($white-light, 0.2);
   }
 
   75% {
     background-color: rgba($white-light, 0.4);
     box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
-                24px 0 0 0 rgba($white-light, 1);
+      24px 0 0 0 rgba($white-light, 1);
   }
 
   100% {
     background-color: rgba($white-light, 1);
     box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
-                24px 0 0 0 rgba($white-light, 0.2);
+      24px 0 0 0 rgba($white-light, 0.2);
   }
 }
 
 @keyframes blinking-scroll-button {
-  0% { opacity: 0.2; }
-  25% { opacity: 0.5; }
-  50% { opacity: 0.7; }
-  100% { opacity: 1; }
+  0% {
+    opacity: 0.2;
+  }
+
+  25% {
+    opacity: 0.5;
+  }
+
+  50% {
+    opacity: 0.7;
+  }
+
+  100% {
+    opacity: 1;
+  }
 }
 
 .build-page {
@@ -125,12 +142,12 @@
       .btn-scroll.animate {
         .first-triangle {
           animation: blinking-scroll-button 1s ease infinite;
-          animation-delay: .3s;
+          animation-delay: 0.3s;
         }
 
         .second-triangle {
           animation: blinking-scroll-button 1s ease infinite;
-          animation-delay: .2s;
+          animation-delay: 0.2s;
         }
 
         .third-triangle {
@@ -391,7 +408,7 @@
       }
 
       &:hover {
-        background-color: $row-hover;
+        background-color: $dropdown-item-hover-bg;
       }
 
       .icon-retry {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 8b680c2dc52d9cfc2009eb483e0d7ab9b6e92537..e9384d41e00df83e71d7e22d3c3127e24802ad3b 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -107,7 +107,6 @@
   }
 }
 
-
 .commits-compare-switch {
   float: left;
   margin-right: 9px;
@@ -179,12 +178,8 @@
 .commit-detail {
   display: flex;
   justify-content: space-between;
-  align-items: flex-start;
+  align-items: center;
   flex-grow: 1;
-
-  .merge-request-branches & {
-    flex-direction: column;
-  }
 }
 
 .commit-content {
@@ -194,45 +189,69 @@
 
 .commit-actions {
   @media (min-width: $screen-sm-min) {
-    font-size: 0;
-
     .fa-spinner {
       font-size: 12px;
     }
   }
 
   .ci-status-link {
-    display: inline-block;
-    position: relative;
-    top: 1px;
+    display: inline-flex;
   }
 
-  .btn-clipboard,
-  .btn-transparent {
-    padding-left: 0;
-    padding-right: 0;
+  > .ci-status-link,
+  > .btn,
+  > .commit-sha-group {
+    margin-left: $gl-padding-8;
   }
+}
+
+.commit-sha-group {
+  display: inline-flex;
 
+  .label,
   .btn {
-    &:not(:first-child) {
-      margin-left: $gl-padding;
-    }
+    padding: $gl-vert-padding $gl-btn-padding;
+    border: 1px $border-color solid;
+    font-size: $gl-font-size;
+    line-height: $line-height-base;
+    border-radius: 0;
+    display: flex;
+    align-items: center;
+  }
+
+  .label-monospace {
+    @extend .monospace;
+    user-select: text;
+    color: $gl-text-color;
+    background-color: $gray-light;
   }
 
-  .commit-sha {
-    font-size: 14px;
-    font-weight: $gl-font-weight-bold;
+  .btn svg {
+    top: auto;
+    fill: $gl-text-color-secondary;
   }
 
-  .ci-status-icon {
-    position: relative;
-    top: 1px;
+  .fa-clipboard {
+    color: $gl-text-color-secondary;
+  }
+
+  :first-child {
+    border-bottom-left-radius: $border-radius-default;
+    border-top-left-radius: $border-radius-default;
+  }
+
+  :not(:first-child) {
+    border-left: 0;
+  }
+
+  :last-child {
+    border-bottom-right-radius: $border-radius-default;
+    border-top-right-radius: $border-radius-default;
   }
 }
 
 .commit,
 .generic_commit_status {
-
   a,
   button {
     color: $gl-text-color;
@@ -305,10 +324,8 @@
   }
 }
 
-
 .gpg-status-box {
   padding: 2px 10px;
-  margin-right: $gl-padding;
 
   &:empty {
     display: none;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 7f037582ca099e10b590c0e96ebb0a5a5af5e9d4..11052be40a80fe638d6ff66141db50d3e5cf59e9 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -160,6 +160,11 @@
         }
       }
     }
+
+    .diff-loading-error-block {
+      padding: $gl-padding * 2 $gl-padding;
+      text-align: center;
+    }
   }
 
   .image {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 884665d35c7d5da90c4e442e945a51bdc26d4d38..3a300086fa3697026f7fcf20893dce46fd071bab 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -273,21 +273,6 @@
     line-height: 1.2;
   }
 
-  table {
-    border-collapse: collapse;
-    padding: 0;
-    margin: 0;
-  }
-
-  td {
-    vertical-align: middle;
-
-    + td {
-      padding-left: 5px;
-      vertical-align: top;
-    }
-  }
-
   .deploy-meta-content {
     border-bottom: 1px solid $white-dark;
 
@@ -323,6 +308,26 @@
   }
 }
 
+.prometheus-table {
+  border-collapse: collapse;
+  padding: 0;
+  margin: 0;
+
+  td {
+    vertical-align: middle;
+
+    + td {
+      padding-left: 5px;
+      vertical-align: top;
+    }
+  }
+
+  .legend-metric-title {
+    font-size: 12px;
+    vertical-align: middle;
+  }
+}
+
 .prometheus-svg-container {
   position: relative;
   height: 0;
@@ -330,8 +335,7 @@
   padding: 0;
   padding-bottom: 100%;
 
-  .text-metric-usage,
-  .legend-metric-title {
+  .text-metric-usage {
     fill: $black;
     font-weight: $gl-font-weight-normal;
     font-size: 12px;
@@ -369,14 +373,11 @@
       }
 
       > text {
-        font-size: 12px;
+        fill: $theme-gray-600;
+        font-size: 10px;
       }
     }
 
-    .text-metric-title {
-      font-size: 12px;
-    }
-
     .y-label-text,
     .x-label-text {
       fill: $gray-darkest;
@@ -413,3 +414,7 @@
     }
   }
 }
+
+.prometheus-table-row-highlight {
+  background-color: $prometheus-table-row-highlight-color;
+}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 8871a069d5dc6e07476daedc530bd979fc7bc069..d9267f5cdf3bd815f5e10d101d82682a233499d1 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -162,17 +162,14 @@
  * Last push widget
  */
 .event-last-push {
-  overflow: auto;
   width: 100%;
+  display: flex;
+  align-items: center;
 
   .event-last-push-text {
     @include str-truncated(100%);
-    padding: 4px 0;
     font-size: 13px;
-    float: left;
-    margin-right: -150px;
-    padding-right: 150px;
-    line-height: 20px;
+    margin-right: $gl-padding;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 4c9732c26d9b3ef2673c299ff104285b263fd437..b2dad4a358ae55d9b74b2cf3e2499db28e0ec532 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -137,12 +137,22 @@
   z-index: 200;
   overflow: hidden;
 
-  a:not(.btn-retry),
-  .btn-link {
+  a:not(.btn) {
     color: inherit;
+
+    &:hover {
+      color: $gl-link-hover-color;
+
+      .avatar {
+        border-color: rgba($avatar-border, .2);
+      }
+
+    }
+
   }
 
   .btn-link {
+    color: inherit;
     outline: none;
   }
 
@@ -177,7 +187,12 @@
       padding-left: 10px;
 
       &:hover {
-        color: $gray-darkest;
+        color: $gl-text-color;
+      }
+
+      &:hover,
+      &:focus {
+        text-decoration: none;
       }
     }
 
@@ -214,7 +229,7 @@
 
       &:hover {
         text-decoration: underline;
-        color: $md-link-color;
+        color: $gl-link-hover-color;
       }
     }
   }
@@ -358,6 +373,14 @@
       padding: 15px 0 0;
       border-bottom: 0;
       overflow: hidden;
+
+      &:hover {
+        background-color: $sidebar-block-hover-color;
+      }
+
+      &.issuable-sidebar-header {
+        padding-top: 0;
+      }
     }
 
     .participants {
@@ -370,8 +393,17 @@
 
     .gutter-toggle {
       width: 100%;
+      height: $sidebar-toggle-height;
       margin-left: 0;
-      padding-left: 25px;
+      padding-left: 0;
+      border-bottom: 1px solid $border-gray-dark;
+    }
+
+    a.gutter-toggle {
+      display: flex;
+      justify-content: center;
+      flex-direction: column;
+      text-align: center;
     }
 
     .sidebar-collapsed-icon {
@@ -418,10 +450,10 @@
 
       .btn-clipboard {
         border: 0;
+        background: transparent;
         color: $issuable-sidebar-color;
 
         &:hover {
-          background: transparent;
           color: $gl-text-color;
         }
       }
@@ -486,16 +518,6 @@
     }
   }
 
-  a:not(.btn-retry) {
-    &:hover {
-      color: $md-link-color;
-
-      .avatar {
-        border-color: rgba($avatar-border, .2);
-      }
-    }
-  }
-
   .dropdown-menu-toggle {
     width: 100%;
     padding-top: 6px;
@@ -503,15 +525,25 @@
 
   .dropdown-menu {
     width: 100%;
+
+    /*
+     * Overwrite hover style for dropdown items, so that they are not blue
+     * This should be removed during dev of https://gitlab.com/gitlab-org/gitlab-ce/issues/44040
+     */
+    li a {
+      &:hover,
+      &:active,
+      &:focus,
+      &.is-focused {
+        @include dropdown-item-hover;
+      }
+    }
+
   }
 }
 
 .with-performance-bar .right-sidebar {
   top: $header-height + $performance-bar-height;
-
-  .issuable-sidebar {
-    height: calc(100% - #{$performance-bar-height});
-  }
 }
 
 .sidebar-move-issue-confirmation-button {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 0f49d15203b3ebed38f5e7de6ac79b334adf0e70..b0852adb459f64b8afdf650843d95bbcb9ec053d 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -26,9 +26,15 @@
   }
 }
 
+.dropdown-menu-labels {
+  .dropdown-content {
+    max-height: 135px;
+  }
+}
+
 .dropdown-new-label {
   .dropdown-content {
-    max-height: 260px;
+    max-height: 136px;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss
deleted file mode 100644
index 68b6c5ecbd43bd2c6ebc1d26e91337d80a64e858..0000000000000000000000000000000000000000
--- a/app/assets/stylesheets/pages/lint.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.ci-body {
-  .incorrect-syntax {
-    font-size: 18px;
-    color: $lint-incorrect-color;
-  }
-
-  .correct-syntax {
-    font-size: 18px;
-    color: $lint-correct-color;
-  }
-}
-
-.ci-linter {
-  .ci-editor {
-    height: 400px;
-  }
-
-  .ci-template pre {
-    white-space: pre-wrap;
-  }
-}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index b2250a1ce2f8ee6193c0036915648eb8b309db94..97303d0266660a9265ea592d5fda8d5cdf74794d 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -154,26 +154,10 @@
       a {
         width: 100%;
         font-size: 18px;
-        margin-right: 0;
-
-        &:hover {
-          border: 1px solid transparent;
-        }
       }
 
-      &.active {
-        border-bottom: 1px solid $border-color;
-
-        a {
-          border: 0;
-          border-bottom: 2px solid $link-underline-blue;
-          margin-right: 0;
-          color: $black;
-
-          &:hover {
-            border-bottom: 2px solid $link-underline-blue;
-          }
-        }
+      &.active > a {
+        cursor: default;
       }
     }
   }
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f887a11004f7911da91e1f1bbbc2b52b04c224ef..66db4917178644cd54689fefaa37940c138c1a0e 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -718,6 +718,8 @@
 }
 
 .mr-memory-usage {
+  width: 100%;
+
   p.usage-info-loading .usage-info-load-spinner {
     margin-right: 10px;
     font-size: 16px;
@@ -727,3 +729,53 @@
 .fork-sprite {
   margin-right: -5px;
 }
+
+.deploy-heading {
+  .media-body {
+    min-width: 0;
+  }
+}
+
+.deploy-body {
+  display: flex;
+  flex-wrap: wrap;
+
+  @media (min-width: $screen-xs) {
+    flex-wrap: nowrap;
+    white-space: nowrap;
+  }
+
+  > *:not(:last-child) {
+    margin-right: .3em;
+  }
+}
+
+.deploy-link {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  min-width: 100px;
+  max-width: 150px;
+
+  @media (min-width: $screen-xs) {
+    min-width: 0;
+    max-width: 100%;
+  }
+}
+
+// Hack alert: we've rewritten `btn` class in a way that
+// we've broken it and it is not possible to use with `btn-link`
+// which causes a blank button when it's disabled and hovering
+// The css in here is the boostrap one
+.btn-link-retry {
+  &[disabled] {
+    cursor: not-allowed;
+    box-shadow: none;
+    opacity: .65;
+
+    &:hover {
+      color: $file-mode-changed;
+      text-decoration: none;
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index e5afa8fffcb5512d8a850ca123ce561c6568eb22..bac3b70c73421896e507e2b455b43bfbd866f325 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -53,10 +53,6 @@
 }
 
 .milestone-sidebar {
-  .gutter-toggle {
-    margin-bottom: 10px;
-  }
-
   .milestone-progress {
     .title {
       padding-top: 5px;
@@ -102,7 +98,17 @@
     margin-right: 0;
   }
 
+  .right-sidebar-expanded & {
+    .gutter-toggle {
+      margin-bottom: $sidebar-milestone-toggle-bottom-margin;
+    }
+  }
+
   .right-sidebar-collapsed & {
+    .milestone-progress {
+      padding-top: 0;
+    }
+
     .reference {
       border-top: 1px solid $border-gray-normal;
     }
@@ -194,3 +200,38 @@
 .issuable-row {
   background-color: $white-light;
 }
+
+.milestone-deprecation-message {
+  .popover {
+    padding: 0;
+  }
+
+  .popover-content {
+    padding: 0;
+  }
+}
+
+.milestone-popover-body {
+  padding: $gl-padding-8;
+  background-color: $gray-light;
+}
+
+.milestone-popover-footer {
+  padding: $gl-padding-8 $gl-padding;
+  border-top: 1px solid $white-dark;
+}
+
+.milestone-popover-instructions-list {
+  padding-left: 2em;
+
+  > li {
+    padding-left: 1em;
+  }
+}
+
+@media (max-width: $screen-xs-max) {
+  .milestone-banner-text,
+  .milestone-banner-link {
+    display: inline;
+  }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 3c5658373836e92ec8e32249889f5402c1119822..81e98f358a860c979cdc6824babec3eb5572bf8f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -16,7 +16,7 @@ ul.notes {
 
   .note-created-ago,
   .note-updated-at {
-    white-space: nowrap;
+    white-space: normal;
   }
 
   .discussion-body {
@@ -140,12 +140,6 @@ ul.notes {
         @include bulleted-list;
         word-wrap: break-word;
 
-        ul.task-list {
-          ul:not(.task-list) {
-            padding-left: 1.3em;
-          }
-        }
-
         table {
           @include markdown-table;
         }
diff --git a/app/assets/stylesheets/pages/pages.scss b/app/assets/stylesheets/pages/pages.scss
new file mode 100644
index 0000000000000000000000000000000000000000..fb42dee66d2074dee9b4143c6cf46d345e62a054
--- /dev/null
+++ b/app/assets/stylesheets/pages/pages.scss
@@ -0,0 +1,60 @@
+.pages-domain-list {
+  &-item {
+    position: relative;
+    display: flex;
+    align-items: center;
+
+    .domain-status {
+      display: inline-flex;
+      left: $gl-padding;
+      position: absolute;
+    }
+
+    .domain-name {
+      flex-grow: 1;
+    }
+
+  }
+
+  &.has-verification-status > li {
+    padding-left: 3 * $gl-padding;
+  }
+
+}
+
+.status-badge {
+
+  display: inline-flex;
+  margin-bottom: $gl-padding-8;
+
+  // Most of the following settings "stolen" from btn-sm
+  // Border radius is overwritten for both
+  .label,
+  .btn {
+    padding: $gl-padding-4 $gl-padding-8;
+    font-size: $gl-font-size;
+    line-height: $gl-btn-line-height;
+    border-radius: 0;
+    display: flex;
+    align-items: center;
+  }
+
+  .btn svg {
+    top: auto;
+  }
+
+  :first-child {
+    border-bottom-left-radius: $border-radius-default;
+    border-top-left-radius: $border-radius-default;
+  }
+
+  :not(:first-child) {
+    border-left: 0;
+  }
+
+  :last-child {
+    border-bottom-right-radius: $border-radius-default;
+    border-top-right-radius: $border-radius-default;
+  }
+
+}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 42772f13155d84c3985aebc5e70b60d42eb80270..855ebf7d86d0d13ab8c66df840d6b3b754a37e9d 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -14,6 +14,11 @@
 
   .commit-title {
     margin: 0;
+    white-space: normal;
+
+    @media (max-width: $screen-sm-max) {
+      justify-content: flex-end;
+    }
   }
 
   .ci-table {
@@ -344,7 +349,6 @@
 
   svg {
     vertical-align: middle;
-    margin-right: 3px;
   }
 
   .stage-column {
@@ -495,17 +499,12 @@
         svg {
           fill: $gl-text-color-secondary;
           position: relative;
-          left: 5px;
-          top: 2px;
-          width: 18px;
-          height: 18px;
+          top: -1px;
         }
 
         &.play {
           svg {
-            width: #{$ci-action-icon-size - 8};
-            height: #{$ci-action-icon-size - 8};
-            left: 8px;
+            left: 2px;
           }
         }
       }
@@ -706,8 +705,8 @@ button.mini-pipeline-graph-dropdown-toggle {
 // dropdown content for big and mini pipeline
 .big-pipeline-graph-dropdown-menu,
 .mini-pipeline-graph-dropdown-menu {
-  width: 195px;
-  max-width: 195px;
+  width: 240px;
+  max-width: 240px;
 
   .scrollable-menu {
     padding: 0;
@@ -750,7 +749,7 @@ button.mini-pipeline-graph-dropdown-toggle {
         height: #{$ci-action-icon-size - 6};
         left: -3px;
         position: relative;
-        top: -2px;
+        top: -1px;
 
         &.icon-action-stop,
         &.icon-action-cancel {
@@ -931,13 +930,11 @@ button.mini-pipeline-graph-dropdown-toggle {
    */
   &.dropdown-menu {
     transform: translate(-80%, 0);
-    min-width: 150px;
 
     @media(min-width: $screen-md-min) {
       transform: translate(-50%, 0);
       right: auto;
       left: 50%;
-      min-width: 240px;
     }
   }
 }
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index ac745019319618a7020b16205a2ca8e49c91c54c..b199f9876d3f3f6f26f4f3c91d024f3835262e1b 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -210,13 +210,8 @@
 }
 
 .created-personal-access-token-container {
-  #created-personal-access-token {
-    width: 90%;
-    display: inline;
-  }
-
   .btn-clipboard {
-    margin-left: 5px;
+    border: 1px solid $border-color;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 85de0d8e70ffd5a01718ddc182d822e3a12aebfd..d7d343b088a993a3ee6193af95453816450fa83c 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -9,7 +9,6 @@
 .new_project,
 .edit-project,
 .import-project {
-
   .help-block {
     margin-bottom: 10px;
   }
@@ -18,18 +17,25 @@
     border-radius: $border-radius-base;
   }
 
-  .input-group > div {
+  .input-group {
+    display: flex;
 
-    &:last-child {
-      padding-right: 0;
+    .select2-container {
+      display: unset;
+      max-width: unset;
+      width: unset !important;
+      flex-grow: 1;
+    }
+
+    > div {
+      &:last-child {
+        padding-right: 0;
+      }
     }
   }
 
   @media (max-width: $screen-xs-max) {
     .input-group > div {
-
-      margin-bottom: 14px;
-
       &:last-child {
         margin-bottom: 0;
       }
@@ -41,17 +47,24 @@
   }
 
   .input-group-addon {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    line-height: unset;
+    width: unset;
+    max-width: 50%;
+    text-align: left;
 
     &.static-namespace {
       height: 35px;
       border-radius: 3px;
       border: 1px solid $border-color;
+      max-width: 100%;
+      flex-grow: 1;
     }
 
     + .select2 a,
     + .btn-default {
-      border-top-left-radius: 0;
-      border-bottom-left-radius: 0;
+      border-radius: 0 $border-radius-base $border-radius-base 0;
     }
   }
 }
@@ -290,7 +303,7 @@
       font-size: 13px;
       font-weight: $gl-font-weight-bold;
       line-height: 13px;
-      letter-spacing: .4px;
+      letter-spacing: 0.4px;
       padding: 6px 14px;
       text-align: center;
       vertical-align: middle;
@@ -443,7 +456,7 @@ a.deploy-project-label {
     text-decoration: none;
 
     &.disabled {
-      opacity: .3;
+      opacity: 0.3;
       cursor: not-allowed;
     }
   }
@@ -600,26 +613,26 @@ a.deploy-project-label {
   }
 
   .first-column {
-    @media(min-width: $screen-xs-min) {
+    @media (min-width: $screen-xs-min) {
       max-width: 50%;
       padding-right: 30px;
     }
 
-    @media(max-width: $screen-xs-max) {
+    @media (max-width: $screen-xs-max) {
       max-width: 100%;
       width: 100%;
     }
   }
 
   .second-column {
-    @media(min-width: $screen-xs-min) {
+    @media (min-width: $screen-xs-min) {
       width: 50%;
       flex: 1;
       padding-left: 30px;
       position: relative;
     }
 
-    @media(max-width: $screen-xs-max) {
+    @media (max-width: $screen-xs-max) {
       max-width: 100%;
       width: 100%;
       padding-left: 0;
@@ -632,7 +645,7 @@ a.deploy-project-label {
     }
 
     &::before {
-      content: "OR";
+      content: 'OR';
       position: absolute;
       left: -10px;
       top: 50%;
@@ -656,7 +669,7 @@ a.deploy-project-label {
     }
 
     &::after {
-      content: "";
+      content: '';
       position: absolute;
       background-color: $border-color;
       bottom: 0;
@@ -921,14 +934,6 @@ pre.light-well {
       border-right: solid 1px transparent;
     }
   }
-}
-
-.protected-tags-list,
-.protected-branches-list {
-  .dropdown-menu-toggle {
-    width: 100%;
-    max-width: 300px;
-  }
 
   .flash-container {
     padding: 0;
@@ -1111,3 +1116,33 @@ pre.light-well {
   padding-top: $gl-padding;
   padding-bottom: 37px;
 }
+
+.project-ci-body {
+  .incorrect-syntax {
+    font-size: 18px;
+    color: $lint-incorrect-color;
+  }
+
+  .correct-syntax {
+    font-size: 18px;
+    color: $lint-correct-color;
+  }
+}
+
+.project-ci-linter {
+  .ci-editor {
+    height: 400px;
+  }
+
+  .ci-template pre {
+    white-space: pre-wrap;
+  }
+}
+
+.project-badge {
+  opacity: 0.9;
+
+  &:hover {
+    opacity: 1;
+  }
+}
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 8265b8370f71f0352cbeed863eb5b2ae1805919d..450ef7d6b7e123a4c9c2dd1669d8f0e903f466bf 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -19,7 +19,7 @@
 .ide-view {
   display: flex;
   height: calc(100vh - #{$header-height});
-  color: $almost-black;
+  margin-top: 0;
   border-top: 1px solid $white-dark;
   border-bottom: 1px solid $white-dark;
 
@@ -28,6 +28,11 @@
       max-width: 250px;
     }
   }
+
+  .file-status-icon {
+    width: 10px;
+    height: 10px;
+  }
 }
 
 .ide-file-list {
@@ -37,34 +42,56 @@
     cursor: pointer;
 
     &.file-open {
-      background: $white-normal;
+      background: $link-active-background;
     }
 
-    .repo-file-name {
+    &.file-active {
+      font-weight: $gl-font-weight-bold;
+    }
+
+    .ide-file-name {
+      flex: 1;
       white-space: nowrap;
       text-overflow: ellipsis;
+      max-width: inherit;
+
+      svg {
+        vertical-align: middle;
+        margin-right: 2px;
+      }
+
+      .loading-container {
+        margin-right: 4px;
+        display: inline-block;
+      }
     }
 
-    .unsaved-icon {
-      color: $indigo-700;
-      float: right;
-      font-size: smaller;
-      line-height: 20px;
+    .ide-file-changed-icon {
+      margin-left: auto;
+
+      > svg {
+        display: block;
+      }
     }
 
-    .repo-new-btn {
+    .ide-new-btn {
       display: none;
-      margin-top: -4px;
       margin-bottom: -4px;
+      margin-right: -8px;
     }
 
-    &:hover {
-      .repo-new-btn {
+    &:hover,
+    &:focus {
+      background: $link-active-background;
+
+      .ide-new-btn {
         display: block;
       }
+    }
 
-      .unsaved-icon {
-        display: none;
+    &.folder {
+      svg {
+        fill: $gl-text-color-secondary;
       }
     }
   }
@@ -79,10 +106,10 @@
   }
 }
 
-.multi-file-table-name,
-.multi-file-table-col-commit-message {
+.file-name,
+.file-col-commit-message {
+  display: flex;
   overflow: visible;
-  max-width: 0;
   padding: 6px 12px;
 }
 
@@ -99,21 +126,6 @@
   }
 }
 
-table.table tr td.multi-file-table-name {
-  width: 350px;
-  padding: 6px 12px;
-
-  svg {
-    vertical-align: middle;
-    margin-right: 2px;
-  }
-
-  .loading-container {
-    margin-right: 4px;
-    display: inline-block;
-  }
-}
-
 .multi-file-table-col-commit-message {
   white-space: nowrap;
   width: 50%;
@@ -129,13 +141,35 @@ table.table tr td.multi-file-table-name {
 
 .multi-file-tabs {
   display: flex;
-  overflow-x: auto;
   background-color: $white-normal;
   box-shadow: inset 0 -1px $white-dark;
 
-  > li {
+  > ul {
+    display: flex;
+    overflow-x: auto;
+  }
+
+  li {
     position: relative;
   }
+
+  .dropdown {
+    display: flex;
+    margin-left: auto;
+    margin-bottom: 1px;
+    padding: 0 $grid-size;
+    border-left: 1px solid $white-dark;
+    background-color: $white-light;
+
+    &.shadow {
+      box-shadow: 0 0 10px $dropdown-shadow-color;
+    }
+
+    .btn {
+      margin-top: auto;
+      margin-bottom: auto;
+    }
+  }
 }
 
 .multi-file-tab {
@@ -160,20 +194,32 @@ table.table tr td.multi-file-table-name {
   position: absolute;
   right: 8px;
   top: 50%;
+  width: 16px;
+  height: 16px;
   padding: 0;
   background: none;
   border: 0;
-  font-size: $gl-font-size;
-  color: $gray-darkest;
+  border-radius: $border-radius-default;
+  color: $theme-gray-900;
   transform: translateY(-50%);
 
-  &:not(.modified):hover,
-  &:not(.modified):focus {
-    color: $hint-color;
+  svg {
+    position: relative;
+    top: -1px;
+  }
+
+  &:hover {
+    background-color: $theme-gray-200;
   }
 
-  &.modified {
-    color: $indigo-700;
+  &:focus {
+    background-color: $blue-500;
+    color: $white-light;
+    outline: 0;
+
+    svg {
+      fill: currentColor;
+    }
   }
 }
 
@@ -192,24 +238,155 @@ table.table tr td.multi-file-table-name {
   .vertical-center {
     min-height: auto;
   }
+
+  .monaco-editor .lines-content .cigr {
+    display: none;
+  }
+
+  .monaco-diff-editor.vs {
+    .editor.modified {
+      box-shadow: none;
+    }
+
+    .diagonal-fill {
+      display: none !important;
+    }
+
+    .diffOverview {
+      background-color: $white-light;
+      border-left: 1px solid $white-dark;
+      cursor: ns-resize;
+    }
+
+    .diffViewport {
+      display: none;
+    }
+
+    .char-insert {
+      background-color: $line-added-dark;
+    }
+
+    .char-delete {
+      background-color: $line-removed-dark;
+    }
+
+    .line-numbers {
+      color: $black-transparent;
+    }
+
+    .view-overlays {
+      .line-insert {
+        background-color: $line-added;
+      }
+
+      .line-delete {
+        background-color: $line-removed;
+      }
+    }
+
+    .margin {
+      background-color: $gray-light;
+      border-right: 1px solid $white-normal;
+
+      .line-insert {
+        border-right: 1px solid $line-added-dark;
+      }
+
+      .line-delete {
+        border-right: 1px solid $line-removed-dark;
+      }
+    }
+
+    .margin-view-overlays .insert-sign,
+    .margin-view-overlays .delete-sign {
+      opacity: 0.4;
+    }
+
+    .cursors-layer {
+      display: none;
+    }
+  }
 }
 
 .multi-file-editor-holder {
   height: 100%;
 }
 
-.multi-file-editor-btn-group {
-  padding: $gl-bar-padding $gl-padding;
-  border-top: 1px solid $white-dark;
+.preview-container {
+  height: 100%;
+  overflow: auto;
+
+  .file-container {
+    background-color: $gray-darker;
+    display: flex;
+    height: 100%;
+    align-items: center;
+    justify-content: center;
+
+    text-align: center;
+
+    .file-content {
+      padding: $gl-padding;
+      max-width: 100%;
+      max-height: 100%;
+
+      img {
+        max-width: 90%;
+        max-height: 90%;
+      }
+
+      .isZoomable {
+        cursor: pointer;
+        cursor: zoom-in;
+
+        &.isZoomed {
+          cursor: pointer;
+          cursor: zoom-out;
+          max-width: none;
+          max-height: none;
+          margin-right: $gl-padding;
+        }
+      }
+    }
+
+    .file-info {
+      font-size: $label-font-size;
+      color: $diff-image-info-color;
+    }
+  }
+
+  .md-previewer {
+    padding: $gl-padding;
+  }
+}
+
+.ide-mode-tabs {
   border-bottom: 1px solid $white-dark;
-  background: $white-light;
+
+  .nav-links {
+    border-bottom: 0;
+
+    li a {
+      padding: $gl-padding-8 $gl-padding;
+      line-height: $gl-btn-line-height;
+    }
+  }
+}
+
+.ide-btn-group {
+  padding: $gl-padding-4 $gl-vert-padding;
 }
 
 .ide-status-bar {
+  border-top: 1px solid $white-dark;
   padding: $gl-bar-padding $gl-padding;
   background: $white-light;
   display: flex;
-  justify-content: space-between;
+  justify-content: flex-end;
+
+  > div + div {
+    padding-left: $gl-padding;
+  }
 
   svg {
     vertical-align: middle;
@@ -252,7 +429,7 @@ table.table tr td.multi-file-table-name {
   display: flex;
   position: relative;
   flex-direction: column;
-  width: 290px;
+  width: 340px;
   padding: 0;
   background-color: $gray-light;
   padding-right: 3px;
@@ -260,6 +437,7 @@ table.table tr td.multi-file-table-name {
   .projects-sidebar {
     display: flex;
     flex-direction: column;
+    height: 100%;
 
     .context-header {
       width: auto;
@@ -269,8 +447,8 @@ table.table tr td.multi-file-table-name {
 
   .multi-file-commit-panel-inner {
     display: flex;
-    flex: 1;
     flex-direction: column;
+    height: 100%;
   }
 
   .multi-file-commit-panel-inner-scroll {
@@ -299,7 +477,7 @@ table.table tr td.multi-file-table-name {
   }
 
   .branch-container {
-    border-left: 4px solid $indigo-700;
+    border-left: 4px solid;
     margin-bottom: $gl-bar-padding;
   }
 
@@ -311,7 +489,6 @@ table.table tr td.multi-file-table-name {
   .branch-header-title {
     flex: 1;
     padding: $grid-size $gl-padding;
-    color: $indigo-700;
     font-weight: $gl-font-weight-bold;
 
     svg {
@@ -348,98 +525,149 @@ table.table tr td.multi-file-table-name {
   display: flex;
   flex-direction: column;
   flex: 1;
+  max-height: 100%;
+  overflow: auto;
+}
+
+.ide-commit-empty-state {
+  padding: 0 $gl-padding;
+}
+
+.ide-commit-empty-state-container {
+  margin-top: auto;
+  margin-bottom: auto;
 }
 
 .multi-file-commit-panel-header {
   display: flex;
   align-items: center;
-  margin-bottom: 12px;
+  margin-bottom: 0;
   border-bottom: 1px solid $white-dark;
   padding: $gl-btn-padding 0;
-
-  &.is-collapsed {
-    border-bottom: 1px solid $white-dark;
-
-    svg {
-      margin-left: auto;
-      margin-right: auto;
-    }
-
-    .multi-file-commit-panel-collapse-btn {
-      margin-right: auto;
-      margin-left: auto;
-      border-left: 0;
-    }
-  }
 }
 
 .multi-file-commit-panel-header-title {
   display: flex;
   flex: 1;
-  padding: $gl-btn-padding;
+  padding-left: $grid-size;
 
   svg {
     margin-right: $gl-btn-padding;
+    color: $theme-gray-700;
   }
 }
 
 .multi-file-commit-panel-collapse-btn {
   border-left: 1px solid $white-dark;
+  margin-left: auto;
 }
 
 .multi-file-commit-list {
   flex: 1;
   overflow: auto;
-  padding: $gl-padding;
+  padding: $gl-padding 0;
+  min-height: 60px;
 }
 
 .multi-file-commit-list-item {
   display: flex;
+  padding: 0;
   align-items: center;
+  border-radius: $border-radius-default;
+
+  .multi-file-discard-btn {
+    display: none;
+    margin-top: -2px;
+    margin-left: auto;
+    margin-right: $grid-size;
+    color: $gl-link-color;
+
+    &:focus,
+    &:hover {
+      text-decoration: underline;
+    }
+  }
+
+  &:hover {
+    background: $white-normal;
+
+    .multi-file-discard-btn {
+      display: flex;
+    }
+  }
 }
 
-.multi-file-addition {
+.multi-file-additions,
+.multi-file-additions-solid {
   fill: $green-500;
 }
 
-.multi-file-modified {
+.multi-file-modified,
+.multi-file-modified-solid {
   fill: $orange-500;
 }
 
 .multi-file-commit-list-collapsed {
   display: flex;
   flex-direction: column;
+  padding: $gl-padding 0;
 
-  > svg {
+  svg {
+    display: block;
     margin-left: auto;
     margin-right: auto;
+    color: $theme-gray-700;
+  }
+
+  .file-status-icon {
+    width: 10px;
+    height: 10px;
+    margin-left: 3px;
   }
 }
 
 .multi-file-commit-list-path {
+  padding: $grid-size / 2;
+  padding-left: $grid-size;
+  background: none;
+  border: 0;
+  text-align: left;
+  width: 100%;
+  min-width: 0;
+
+  svg {
+    min-width: 16px;
+    vertical-align: middle;
+    display: inline-block;
+  }
+
+  &:hover,
+  &:focus {
+    outline: 0;
+  }
+}
+
+.multi-file-commit-list-file-path {
   @include str-truncated(100%);
+
+  &:hover {
+    text-decoration: underline;
+  }
+
+  &:active {
+    text-decoration: none;
+  }
 }
 
 .multi-file-commit-form {
   padding: $gl-padding;
   border-top: 1px solid $white-dark;
-}
-
-.multi-file-commit-fieldset {
-  display: flex;
-  align-items: center;
-  padding-bottom: 12px;
 
   .btn {
-    flex: 1;
+    font-size: $gl-font-size;
   }
 }
 
-.multi-file-commit-message.form-control {
-  height: 80px;
-  resize: none;
-}
-
 .dirty-diff {
   // !important need to override monaco inline style
   width: 4px !important;
@@ -468,7 +696,7 @@ table.table tr td.multi-file-table-name {
       top: 0;
       width: 100px;
       height: 1px;
-      background-color: rgba($red-500, .5);
+      background-color: rgba($red-500, 0.5);
     }
   }
 }
@@ -487,7 +715,7 @@ table.table tr td.multi-file-table-name {
   justify-content: center;
 }
 
-.repo-new-btn {
+.ide-new-btn {
   .dropdown-toggle svg {
     margin-top: -2px;
     margin-bottom: 2px;
@@ -505,73 +733,70 @@ table.table tr td.multi-file-table-name {
   }
 }
 
-.ide.nav-only {
-  .flash-container {
-    margin-top: $header-height;
-    margin-bottom: 0;
-  }
+.ide {
+  overflow: hidden;
 
-  .alert-wrapper .flash-container .flash-alert:last-child,
-  .alert-wrapper .flash-container .flash-notice:last-child {
-    margin-bottom: 0;
-  }
+  &.nav-only {
+    padding-top: $header-height;
 
-  .content {
-    margin-top: $header-height;
-  }
+    .with-performance-bar & {
+      padding-top: $header-height + $performance-bar-height;
+    }
 
-  .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
-    max-height: calc(100vh - #{$header-height + $context-header-height});
-  }
+    .flash-container {
+      margin-top: 0;
+      margin-bottom: 0;
+    }
 
-  &.flash-shown {
-    .content {
+    .alert-wrapper .flash-container .flash-alert:last-child,
+    .alert-wrapper .flash-container .flash-notice:last-child {
+      margin-bottom: 0;
+    }
+
+    .content-wrapper {
       margin-top: 0;
+      padding-bottom: 0;
     }
 
-    .ide-view {
-      height: calc(100vh - #{$header-height + $flash-height});
+    &.flash-shown {
+      .content-wrapper {
+        margin-top: 0;
+      }
+
+      .ide-view {
+        height: calc(100vh - #{$header-height + $flash-height});
+      }
     }
 
-    .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
-      max-height: calc(100vh - #{$header-height + $flash-height + $context-header-height});
+    .projects-sidebar {
+      .multi-file-commit-panel-inner-scroll {
+        flex: 1;
+      }
     }
   }
 }
 
 .with-performance-bar .ide.nav-only {
   .flash-container {
-    margin-top: #{$header-height + $performance-bar-height};
+    margin-top: 0;
   }
 
-  .content {
-    margin-top: #{$header-height + $performance-bar-height};
+  .content-wrapper {
+    margin-top: 0;
+    padding-bottom: 0;
   }
 
   .ide-view {
     height: calc(100vh - #{$header-height + $performance-bar-height});
   }
 
-  .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
-    max-height: calc(100vh - #{$header-height + $performance-bar-height + 60});
-  }
-
   &.flash-shown {
-    .content {
-      margin-top: 0;
-    }
-
     .ide-view {
       height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
     }
-
-    .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
-      max-height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height + $context-header-height});
-    }
   }
 }
 
-
 .dragHandle {
   position: absolute;
   top: 0;
@@ -587,3 +812,137 @@ table.table tr td.multi-file-table-name {
     left: 0;
   }
 }
+
+.ide-commit-list-container {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  padding: 0 16px;
+
+  &:not(.is-collapsed) {
+    flex: 1;
+    min-height: 140px;
+  }
+
+  &.is-collapsed {
+    .multi-file-commit-panel-header {
+      margin-left: -$gl-padding;
+      margin-right: -$gl-padding;
+
+      svg {
+        margin-left: auto;
+        margin-right: auto;
+      }
+
+      .multi-file-commit-panel-collapse-btn {
+        margin-right: auto;
+        margin-left: auto;
+        border-left: 0;
+      }
+    }
+  }
+}
+
+.ide-staged-action-btn {
+  margin-left: auto;
+  color: $gl-link-color;
+}
+
+.ide-commit-radios {
+  label {
+    font-weight: normal;
+  }
+
+  .help-block {
+    margin-top: 0;
+    line-height: 0;
+  }
+}
+
+.ide-commit-new-branch {
+  margin-left: 25px;
+}
+
+.ide-external-links {
+  p {
+    margin: 0;
+  }
+}
+
+.ide-sidebar-link {
+  padding: $gl-padding-8 $gl-padding;
+  display: flex;
+  align-items: center;
+  font-weight: $gl-font-weight-bold;
+}
+
+.ide-commit-message-field {
+  height: 200px;
+  background-color: $white-light;
+
+  .md-area {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+
+  .nav-links {
+    height: 30px;
+  }
+
+  .help-block {
+    margin-top: 2px;
+    color: $blue-500;
+    cursor: pointer;
+  }
+}
+
+.ide-commit-message-textarea-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+
+  .note-textarea {
+    font-family: $monospace_font;
+  }
+}
+
+.ide-commit-message-highlights-container {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: -100px;
+  bottom: 0;
+  padding-right: 100px;
+  pointer-events: none;
+  z-index: 1;
+
+  .highlights {
+    white-space: pre-wrap;
+    word-wrap: break-word;
+    color: transparent;
+  }
+
+  mark {
+    margin-left: -1px;
+    padding: 0 2px;
+    border-radius: $border-radius-small;
+    background-color: $orange-200;
+    color: transparent;
+    opacity: 0.6;
+  }
+}
+
+.ide-commit-message-textarea {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 2;
+  background: transparent;
+  resize: none;
+}
diff --git a/app/assets/stylesheets/pages/repo.scss.orig b/app/assets/stylesheets/pages/repo.scss.orig
new file mode 100644
index 0000000000000000000000000000000000000000..57b995adb64129e3f9c2268e5a074722409b8d00
--- /dev/null
+++ b/app/assets/stylesheets/pages/repo.scss.orig
@@ -0,0 +1,786 @@
+.project-refs-form,
+.project-refs-target-form {
+  display: inline-block;
+}
+
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+
+.commit-message {
+  @include str-truncated(250px);
+}
+
+.editable-mode {
+  display: inline-block;
+}
+
+.ide-view {
+  display: flex;
+  height: calc(100vh - #{$header-height});
+  margin-top: 40px;
+  color: $almost-black;
+  border-top: 1px solid $white-dark;
+  border-bottom: 1px solid $white-dark;
+
+  &.is-collapsed {
+    .ide-file-list {
+      max-width: 250px;
+    }
+  }
+
+  .file-status-icon {
+    width: 10px;
+    height: 10px;
+  }
+}
+
+.ide-file-list {
+  flex: 1;
+
+  .file {
+    cursor: pointer;
+
+    &.file-open {
+      background: $white-normal;
+    }
+
+    .ide-file-name {
+      flex: 1;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+
+      svg {
+        vertical-align: middle;
+        margin-right: 2px;
+      }
+
+      .loading-container {
+        margin-right: 4px;
+        display: inline-block;
+      }
+    }
+
+    .ide-file-changed-icon {
+      margin-left: auto;
+    }
+
+    .ide-new-btn {
+      display: none;
+      margin-bottom: -4px;
+      margin-right: -8px;
+    }
+
+    &:hover {
+      .ide-new-btn {
+        display: block;
+      }
+    }
+
+    &.folder {
+      svg {
+        fill: $gl-text-color-secondary;
+      }
+    }
+  }
+
+  a {
+    color: $gl-text-color;
+  }
+
+  th {
+    position: sticky;
+    top: 0;
+  }
+}
+
+.file-name,
+.file-col-commit-message {
+  display: flex;
+  overflow: visible;
+  padding: 6px 12px;
+}
+
+.multi-file-loading-container {
+  margin-top: 10px;
+  padding: 10px;
+
+  .animation-container {
+    background: $gray-light;
+
+    div {
+      background: $gray-light;
+    }
+  }
+}
+
+.multi-file-table-col-commit-message {
+  white-space: nowrap;
+  width: 50%;
+}
+
+.multi-file-edit-pane {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  border-left: 1px solid $white-dark;
+  overflow: hidden;
+}
+
+.multi-file-tabs {
+  display: flex;
+  background-color: $white-normal;
+  box-shadow: inset 0 -1px $white-dark;
+
+  > ul {
+    display: flex;
+    overflow-x: auto;
+  }
+
+  li {
+    position: relative;
+  }
+
+  .dropdown {
+    display: flex;
+    margin-left: auto;
+    margin-bottom: 1px;
+    padding: 0 $grid-size;
+    border-left: 1px solid $white-dark;
+    background-color: $white-light;
+
+    &.shadow {
+      box-shadow: 0 0 10px $dropdown-shadow-color;
+    }
+
+    .btn {
+      margin-top: auto;
+      margin-bottom: auto;
+    }
+  }
+}
+
+.multi-file-tab {
+  @include str-truncated(150px);
+  padding: ($gl-padding / 2) ($gl-padding + 12) ($gl-padding / 2) $gl-padding;
+  background-color: $gray-normal;
+  border-right: 1px solid $white-dark;
+  border-bottom: 1px solid $white-dark;
+  cursor: pointer;
+
+  svg {
+    vertical-align: middle;
+  }
+
+  &.active {
+    background-color: $white-light;
+    border-bottom-color: $white-light;
+  }
+}
+
+.multi-file-tab-close {
+  position: absolute;
+  right: 8px;
+  top: 50%;
+  width: 16px;
+  height: 16px;
+  padding: 0;
+  background: none;
+  border: 0;
+  border-radius: $border-radius-default;
+  color: $theme-gray-900;
+  transform: translateY(-50%);
+
+  svg {
+    position: relative;
+    top: -1px;
+  }
+
+  &:hover {
+    background-color: $theme-gray-200;
+  }
+
+  &:focus {
+    background-color: $blue-500;
+    color: $white-light;
+    outline: 0;
+
+    svg {
+      fill: currentColor;
+    }
+  }
+}
+
+.multi-file-edit-pane-content {
+  flex: 1;
+  height: 0;
+}
+
+.blob-editor-container {
+  flex: 1;
+  height: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+
+  .vertical-center {
+    min-height: auto;
+  }
+
+  .monaco-editor .lines-content .cigr {
+    display: none;
+  }
+
+  .monaco-diff-editor.vs {
+    .editor.modified {
+      box-shadow: none;
+    }
+
+    .diagonal-fill {
+      display: none !important;
+    }
+
+    .diffOverview {
+      background-color: $white-light;
+      border-left: 1px solid $white-dark;
+      cursor: ns-resize;
+    }
+
+    .diffViewport {
+      display: none;
+    }
+
+    .char-insert {
+      background-color: $line-added-dark;
+    }
+
+    .char-delete {
+      background-color: $line-removed-dark;
+    }
+
+    .line-numbers {
+      color: $black-transparent;
+    }
+
+    .view-overlays {
+      .line-insert {
+        background-color: $line-added;
+      }
+
+      .line-delete {
+        background-color: $line-removed;
+      }
+    }
+
+    .margin {
+      background-color: $gray-light;
+      border-right: 1px solid $white-normal;
+
+      .line-insert {
+        border-right: 1px solid $line-added-dark;
+      }
+
+      .line-delete {
+        border-right: 1px solid $line-removed-dark;
+      }
+    }
+
+    .margin-view-overlays .insert-sign,
+    .margin-view-overlays .delete-sign {
+      opacity: 0.4;
+    }
+
+    .cursors-layer {
+      display: none;
+    }
+  }
+}
+
+.multi-file-editor-holder {
+  height: 100%;
+}
+
+.multi-file-editor-btn-group {
+  padding: $gl-bar-padding $gl-padding;
+  border-top: 1px solid $white-dark;
+  border-bottom: 1px solid $white-dark;
+  background: $white-light;
+}
+
+.ide-status-bar {
+  padding: $gl-bar-padding $gl-padding;
+  background: $white-light;
+  display: flex;
+  justify-content: space-between;
+
+  svg {
+    vertical-align: middle;
+  }
+}
+
+// Not great, but this is to deal with our current output
+.multi-file-preview-holder {
+  height: 100%;
+  overflow: scroll;
+
+  .file-content.code {
+    display: flex;
+
+    i {
+      margin-left: -10px;
+    }
+  }
+
+  .line-numbers {
+    min-width: 50px;
+  }
+
+  .file-content,
+  .line-numbers,
+  .blob-content,
+  .code {
+    min-height: 100%;
+  }
+}
+
+.file-content.blob-no-preview {
+  a {
+    margin-left: auto;
+    margin-right: auto;
+  }
+}
+
+.multi-file-commit-panel {
+  display: flex;
+  position: relative;
+  flex-direction: column;
+  width: 340px;
+  padding: 0;
+  background-color: $gray-light;
+  padding-right: 3px;
+
+  .projects-sidebar {
+    display: flex;
+    flex-direction: column;
+
+    .context-header {
+      width: auto;
+      margin-right: 0;
+    }
+  }
+
+  .multi-file-commit-panel-inner {
+    display: flex;
+    flex: 1;
+    flex-direction: column;
+  }
+
+  .multi-file-commit-panel-inner-scroll {
+    display: flex;
+    flex: 1;
+    flex-direction: column;
+    overflow: auto;
+  }
+
+  &.is-collapsed {
+    width: 60px;
+
+    .multi-file-commit-list {
+      padding-top: $gl-padding;
+      overflow: hidden;
+    }
+
+    .multi-file-context-bar-icon {
+      align-items: center;
+
+      svg {
+        float: none;
+        margin: 0;
+      }
+    }
+  }
+
+  .branch-container {
+    border-left: 4px solid $indigo-700;
+    margin-bottom: $gl-bar-padding;
+  }
+
+  .branch-header {
+    background: $white-dark;
+    display: flex;
+  }
+
+  .branch-header-title {
+    flex: 1;
+    padding: $grid-size $gl-padding;
+    color: $indigo-700;
+    font-weight: $gl-font-weight-bold;
+
+    svg {
+      vertical-align: middle;
+    }
+  }
+
+  .branch-header-btns {
+    padding: $gl-vert-padding $gl-padding;
+  }
+
+  .left-collapse-btn {
+    display: none;
+    background: $gray-light;
+    text-align: left;
+    border-top: 1px solid $white-dark;
+
+    svg {
+      vertical-align: middle;
+    }
+  }
+}
+
+.multi-file-context-bar-icon {
+  padding: 10px;
+
+  svg {
+    margin-right: 10px;
+    float: left;
+  }
+}
+
+.multi-file-commit-panel-section {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+}
+
+.multi-file-commit-empty-state-container {
+  align-items: center;
+  justify-content: center;
+}
+
+.multi-file-commit-panel-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+  border-bottom: 1px solid $white-dark;
+  padding: $gl-btn-padding 0;
+
+  &.is-collapsed {
+    border-bottom: 1px solid $white-dark;
+
+    svg {
+      margin-left: auto;
+      margin-right: auto;
+    }
+
+    .multi-file-commit-panel-collapse-btn {
+      margin-right: auto;
+      margin-left: auto;
+      border-left: 0;
+    }
+  }
+}
+
+.multi-file-commit-panel-header-title {
+  display: flex;
+  flex: 1;
+  padding: 0 $gl-btn-padding;
+
+  svg {
+    margin-right: $gl-btn-padding;
+  }
+}
+
+.multi-file-commit-panel-collapse-btn {
+  border-left: 1px solid $white-dark;
+}
+
+.multi-file-commit-list {
+  flex: 1;
+  overflow: auto;
+  padding: $gl-padding 0;
+  min-height: 60px;
+}
+
+.multi-file-commit-list-item {
+  display: flex;
+  padding: 0;
+  align-items: center;
+
+  .multi-file-discard-btn {
+    display: none;
+    margin-left: auto;
+    color: $gl-link-color;
+    padding: 0 2px;
+
+    &:focus,
+    &:hover {
+      text-decoration: underline;
+    }
+  }
+
+  &:hover {
+    background: $white-normal;
+
+    .multi-file-discard-btn {
+      display: block;
+    }
+  }
+}
+
+.multi-file-addition {
+  fill: $green-500;
+}
+
+.multi-file-modified {
+  fill: $orange-500;
+}
+
+.multi-file-commit-list-collapsed {
+  display: flex;
+  flex-direction: column;
+
+  > svg {
+    margin-left: auto;
+    margin-right: auto;
+  }
+
+  .file-status-icon {
+    width: 10px;
+    height: 10px;
+    margin-left: 3px;
+  }
+}
+
+.multi-file-commit-list-path {
+  padding: $grid-size / 2;
+  padding-left: $gl-padding;
+  background: none;
+  border: 0;
+  text-align: left;
+  width: 100%;
+  min-width: 0;
+
+  svg {
+    min-width: 16px;
+    vertical-align: middle;
+    display: inline-block;
+  }
+
+  &:hover,
+  &:focus {
+    outline: 0;
+  }
+}
+
+.multi-file-commit-list-file-path {
+  @include str-truncated(100%);
+
+  &:hover {
+    text-decoration: underline;
+  }
+
+  &:active {
+    text-decoration: none;
+  }
+}
+
+.multi-file-commit-form {
+  padding: $gl-padding;
+  border-top: 1px solid $white-dark;
+
+  .btn {
+    font-size: $gl-font-size;
+  }
+}
+
+.multi-file-commit-message.form-control {
+  height: 160px;
+  resize: none;
+}
+
+.dirty-diff {
+  // !important need to override monaco inline style
+  width: 4px !important;
+  left: 0 !important;
+
+  &-modified {
+    background-color: $blue-500;
+  }
+
+  &-added {
+    background-color: $green-600;
+  }
+
+  &-removed {
+    height: 0 !important;
+    width: 0 !important;
+    bottom: -2px;
+    border-style: solid;
+    border-width: 5px;
+    border-color: transparent transparent transparent $red-500;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100px;
+      height: 1px;
+      background-color: rgba($red-500, 0.5);
+    }
+  }
+}
+
+.ide-loading {
+  display: flex;
+  height: 100vh;
+  align-items: center;
+  justify-content: center;
+}
+
+.ide-empty-state {
+  display: flex;
+  height: 100vh;
+  align-items: center;
+  justify-content: center;
+}
+
+.ide-new-btn {
+  .dropdown-toggle svg {
+    margin-top: -2px;
+    margin-bottom: 2px;
+  }
+
+  .dropdown-menu {
+    left: auto;
+    right: 0;
+
+    label {
+      font-weight: $gl-font-weight-normal;
+      padding: 5px 8px;
+      margin-bottom: 0;
+    }
+  }
+}
+
+.ide {
+  overflow: hidden;
+
+  &.nav-only {
+    .flash-container {
+      margin-top: $header-height;
+      margin-bottom: 0;
+    }
+
+    .alert-wrapper .flash-container .flash-alert:last-child,
+    .alert-wrapper .flash-container .flash-notice:last-child {
+      margin-bottom: 0;
+    }
+
+    .content-wrapper {
+      margin-top: $header-height;
+      padding-bottom: 0;
+    }
+
+    &.flash-shown {
+      .content-wrapper {
+        margin-top: 0;
+      }
+
+      .ide-view {
+        height: calc(100vh - #{$header-height + $flash-height});
+      }
+    }
+
+    .projects-sidebar {
+      .multi-file-commit-panel-inner-scroll {
+        flex: 1;
+      }
+    }
+  }
+}
+
+.with-performance-bar .ide.nav-only {
+  .flash-container {
+    margin-top: #{$header-height + $performance-bar-height};
+  }
+
+  .content-wrapper {
+    margin-top: #{$header-height + $performance-bar-height};
+    padding-bottom: 0;
+  }
+
+  .ide-view {
+    height: calc(100vh - #{$header-height + $performance-bar-height});
+  }
+
+  &.flash-shown {
+    .content-wrapper {
+      margin-top: 0;
+    }
+
+    .ide-view {
+      height: calc(
+        100vh - #{$header-height + $performance-bar-height + $flash-height}
+      );
+    }
+  }
+}
+
+.dragHandle {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  width: 3px;
+  background-color: $white-dark;
+
+  &.dragright {
+    right: 0;
+  }
+
+  &.dragleft {
+    left: 0;
+  }
+}
+
+.ide-commit-radios {
+  label {
+    font-weight: normal;
+  }
+
+  .help-block {
+    margin-top: 0;
+    line-height: 0;
+  }
+}
+
+.ide-commit-new-branch {
+  margin-left: 25px;
+}
+
+.ide-external-links {
+  p {
+    margin: 0;
+  }
+}
+
+.ide-sidebar-link {
+  padding: $gl-padding-8 $gl-padding;
+  background: $indigo-700;
+  color: $white-light;
+  text-decoration: none;
+  display: flex;
+  align-items: center;
+
+  &:focus,
+  &:hover {
+    color: $white-light;
+    text-decoration: underline;
+    background: $indigo-500;
+  }
+
+  &:active {
+    background: $indigo-800;
+  }
+}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index c9363188505e6010c413e20d276ae632c376257d..dbde0720993762aa113218219f7aaa9f6fea55e7 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -112,7 +112,7 @@ input[type="checkbox"]:hover {
     }
 
     .dropdown-content {
-      max-height: 350px;
+      max-height: 302px;
     }
   }
 
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 47672783d5afbd479a4a534d568070a8d14b2da8..c410049bc0bab52ceab729a049d791119b962407 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -205,7 +205,8 @@
     }
 
     .badge {
-      font-size: inherit;
+      font-size: 12px;
+      line-height: 12px;
     }
 
     .panel-heading .badge-count {
@@ -283,3 +284,23 @@
 .deprecated-service {
   cursor: default;
 }
+
+.personal-access-tokens-never-expires-label {
+  color: $note-disabled-comment-color;
+}
+
+.created-deploy-token-container {
+  .deploy-token-field {
+    width: 90%;
+    display: inline;
+  }
+
+  .btn-clipboard {
+    margin-left: 5px;
+  }
+
+  .deploy-token-help-block {
+    display: block;
+    margin-bottom: 0;
+  }
+}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index e70a57c2a678a4d9da117906d7ca28b8be485257..9a0ec9369793b81afb5292089228bed3b37bb86b 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -180,6 +180,11 @@ ul.wiki-pages-list.content-list {
   }
 }
 
+.wiki-holder {
+  overflow-x: auto;
+  overflow-y: hidden;
+}
+
 .wiki {
   table {
     @include markdown-table;
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 6e539e39ca14cb2894e329edce28711c88c05a8d..45ae94abafffdbe47bab1776155649d73e0f7dd9 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -1,8 +1,8 @@
-@import "framework/variables";
-@import "peek/views/performance_bar";
-@import "peek/views/rblineprof";
+@import 'framework/variables';
+@import 'peek/views/performance_bar';
+@import 'peek/views/rblineprof';
 
-#peek {
+#js-peek {
   position: fixed;
   left: 0;
   top: 0;
@@ -15,26 +15,36 @@
   line-height: $performance-bar-height;
   color: $perf-bar-text;
 
+  select {
+    width: 200px;
+  }
+
   &.disabled {
     display: none;
   }
 
   &.production {
     background-color: $perf-bar-production;
+
+    select {
+      background: $perf-bar-production;
+    }
   }
 
   &.staging {
     background-color: $perf-bar-staging;
+
+    select {
+      background: $perf-bar-staging;
+    }
   }
 
   &.development {
     background-color: $perf-bar-development;
-  }
 
-  .wrapper {
-    width: 80%;
-    height: $performance-bar-height;
-    margin: 0 auto;
+    select {
+      background: $perf-bar-development;
+    }
   }
 
   // UI Elements
@@ -42,11 +52,12 @@
     background: $perf-bar-bucket-bg;
     display: inline-block;
     padding: 4px 6px;
-    font-family: Consolas, "Liberation Mono", Courier, monospace;
+    font-family: Consolas, 'Liberation Mono', Courier, monospace;
     line-height: 1;
     color: $perf-bar-bucket-color;
     border-radius: 3px;
-    box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from, inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
+    box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from,
+      inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
 
     .hidden {
       display: none;
@@ -94,6 +105,16 @@
       max-width: 10000px !important;
     }
   }
+
+  .performance-bar-modal {
+    .modal-footer {
+      display: none;
+    }
+
+    .modal-dialog {
+      width: 860px;
+    }
+  }
 }
 
 #modal-peek-pg-queries-content {
diff --git a/app/assets/stylesheets/snippets.scss b/app/assets/stylesheets/snippets.scss
new file mode 100644
index 0000000000000000000000000000000000000000..0d6b0735f700d876d12de1b6b9d90308d2e865d2
--- /dev/null
+++ b/app/assets/stylesheets/snippets.scss
@@ -0,0 +1,156 @@
+@import "framework/variables";
+
+.gitlab-embed-snippets {
+  @import "highlight/embedded";
+  @import "framework/images";
+
+  $border-style: 1px solid $border-color;
+
+  font-family: $regular_font;
+  font-size: $gl-font-size;
+  line-height: $code_line_height;
+  color: $gl-text-color;
+  margin: 20px;
+  font-weight: 200;
+
+  .gl-snippet-icon {
+    display: inline-block;
+    background: url(asset_path('ext_snippet_icons/ext_snippet_icons.png')) no-repeat;
+    overflow: hidden;
+    text-align: left;
+    width: 16px;
+    height: 16px;
+    background-size: cover;
+
+    &.gl-snippet-icon-doc_code { background-position: 0 0;  }
+    &.gl-snippet-icon-doc_text {  background-position: 0 -16px; }
+    &.gl-snippet-icon-download {  background-position: 0 -32px; }
+  }
+
+  .blob-viewer {
+    background-color: $white-light;
+    text-align: left;
+  }
+
+  .file-content.code {
+    border: $border-style;
+    border-radius: 0 0 4px 4px;
+    display: flex;
+    box-shadow: none;
+    margin: 0;
+    padding: 0;
+    table-layout: fixed;
+
+    .blob-content {
+      overflow-x: auto;
+
+      pre {
+        padding: 10px;
+        border: 0;
+        border-radius: 0;
+        font-family: $monospace_font;
+        font-size: $code_font_size;
+        line-height: $code_line_height;
+        margin: 0;
+        overflow: auto;
+        overflow-y: hidden;
+        white-space: pre;
+        word-wrap: normal;
+        border-left: $border-style;
+      }
+    }
+
+    .line-numbers {
+      padding: 10px;
+      text-align: right;
+      float: left;
+
+      .diff-line-num {
+        font-family: $monospace_font;
+        display: block;
+        font-size: $code_font_size;
+        min-height: $code_line_height;
+        white-space: nowrap;
+        color: $black-transparent;
+        min-width: 30px;
+      }
+
+      .diff-line-num:hover {
+        color: $almost-black;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .file-title-flex-parent {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    background-color: $gray-light;
+    border: $border-style;
+    border-bottom: 0;
+    padding: $gl-padding-top $gl-padding;
+    margin: 0;
+    border-radius: $border-radius-default $border-radius-default 0 0;
+
+    .file-header-content {
+      .file-title-name {
+        font-weight: $gl-font-weight-bold;
+      }
+
+      .gitlab-embedded-snippets-title {
+        text-decoration: none;
+        color: $gl-text-color;
+
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+
+      .gitlab-logo {
+        display: inline-block;
+        padding-left: 5px;
+        text-decoration: none;
+        color: $gl-text-color-secondary;
+
+        .logo-text {
+          background: image_url('ext_snippet_icons/logo.png') no-repeat left center;
+          background-size: 18px;
+          font-weight: $gl-font-weight-normal;
+          padding-left: 24px;
+        }
+      }
+    }
+
+    img,
+    .gl-snippet-icon {
+      display: inline-block;
+      vertical-align: middle;
+    }
+  }
+
+  .btn-group {
+    a.btn {
+      background-color: $white-light;
+      text-decoration: none;
+      padding: 7px 9px;
+      border: $border-style;
+      border-right: 0;
+
+      &:hover {
+        background-color: $white-normal;
+        border-color: $border-white-normal;
+        text-decoration: none;
+      }
+
+      &:first-child {
+        border-radius: 3px 0 0 3px;
+      }
+
+      &:last-child {
+        border-radius: 0 3px 3px 0;
+        border-right: $border-style;
+      }
+    }
+  }
+}
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index dd0b38970bd870896572e3c33c6891625ff43930..ea302f17d16dab67eb7668b34b30a73f06587940 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -50,9 +50,19 @@ class Admin::AppearancesController < Admin::ApplicationController
 
   # Only allow a trusted parameter "white list" through.
   def appearance_params
-    params.require(:appearance).permit(
-      :title, :description, :logo, :logo_cache, :header_logo, :header_logo_cache,
-      :new_project_guidelines, :updated_by
-    )
+    params.require(:appearance).permit(allowed_appearance_params)
+  end
+
+  def allowed_appearance_params
+    %i[
+      title
+      description
+      logo
+      logo_cache
+      header_logo
+      header_logo_cache
+      new_project_guidelines
+      updated_by
+    ]
   end
 end
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index c27f2ee3c09d5c63e3778e478e8805c4aa2640dc..a4648b33cfaf0f9708093c05ebd6ca62d87a46d1 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -3,23 +3,9 @@
 # Automatically sets the layout and ensures an administrator is logged in
 class Admin::ApplicationController < ApplicationController
   before_action :authenticate_admin!
-  before_action :display_read_only_information
   layout 'admin'
 
   def authenticate_admin!
     render_404 unless current_user.admin?
   end
-
-  def display_read_only_information
-    return unless Gitlab::Database.read_only?
-
-    flash.now[:notice] = read_only_message
-  end
-
-  private
-
-  # Overridden in EE
-  def read_only_message
-    _('You are on a read-only GitLab instance.')
-  end
 end
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4dfb397e82c7e1c029f828962dc5cf0d93e6486c..8958eab04239f29d00858ee5b722cf6294d7f4f6 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -56,21 +56,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
   end
 
   def application_setting_params
-    import_sources = params[:application_setting][:import_sources]
-    if import_sources.nil?
-      params[:application_setting][:import_sources] = []
-    else
-      import_sources.map! do |source|
-        source.to_str
-      end
-    end
+    params[:application_setting] ||= {}
 
-    enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
+    if params[:application_setting].key?(:enabled_oauth_sign_in_sources)
+      enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
+      enabled_oauth_sign_in_sources&.delete("")
 
-    params[:application_setting][:disabled_oauth_sign_in_sources] =
-      AuthHelper.button_based_providers.map(&:to_s) -
-      Array(enabled_oauth_sign_in_sources)
+      params[:application_setting][:disabled_oauth_sign_in_sources] =
+        AuthHelper.button_based_providers.map(&:to_s) -
+        Array(enabled_oauth_sign_in_sources)
+    end
 
+    params[:application_setting][:import_sources]&.delete("")
     params[:application_setting][:restricted_visibility_levels]&.delete("")
     params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
 
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index cc38608eda58e6ce202d2476e7fef76a1193950c..001f652009369bf2bcf81f4a931a9ebf00bc8505 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -5,7 +5,7 @@ class Admin::GroupsController < Admin::ApplicationController
 
   def index
     @groups = Group.with_statistics.with_route
-    @groups = @groups.sort(@sort = params[:sort])
+    @groups = @groups.sort_by_attribute(@sort = params[:sort])
     @groups = @groups.search(params[:name]) if params[:name].present?
     @groups = @groups.page(params[:page])
   end
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb
index 7a2c7234a1efc48d00e86ac371462b122a505164..a7b562b1d8e8861455343a00f9d59edfb3db2331 100644
--- a/app/controllers/admin/impersonation_tokens_controller.rb
+++ b/app/controllers/admin/impersonation_tokens_controller.rb
@@ -9,7 +9,6 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
     @impersonation_token = finder.build(impersonation_token_params)
 
     if @impersonation_token.save
-      flash[:impersonation_token] = @impersonation_token.token
       redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
     else
       set_index_vars
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 156a8e2c5157d397ca3d4929c6245bccff4e4797..bfeb5a2d097c2c13aa753cd0090b8f05f306522b 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -4,7 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
   def index
     @users = User.order_name_asc.filter(params[:filter])
     @users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present?
-    @users = @users.sort(@sort = params[:sort])
+    @users = @users.sort_by_attribute(@sort = params[:sort])
     @users = @users.page(params[:page])
   end
 
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7f83bd10e93e61855a3cde1b774ea46c87f2f634..0fdd4d2cb47fac14ec21d036f3aec9fc4e05da69 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base
   include Gitlab::GonHelper
   include GitlabRoutingHelper
   include PageLayoutHelper
+  include SafeParamsHelper
   include SentryHelper
   include WorkhorseHelper
   include EnforcesTwoFactorAuthentication
@@ -229,10 +230,6 @@ class ApplicationController < ActionController::Base
     @event_filter ||= EventFilter.new(filters)
   end
 
-  def gitlab_ldap_access(&block)
-    Gitlab::Auth::LDAP::Access.open { |access| yield(access) }
-  end
-
   # JSON for infinite scroll via Pager object
   def pager_json(partial, count, locals = {})
     html = render_to_string(
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 352f12a89fda2707e0c1ac7defeba0d21926c108..7d7ff217e5d0662686f14f60d50065aa04691d23 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -1,6 +1,9 @@
 module Boards
   class IssuesController < Boards::ApplicationController
     include BoardsResponses
+    include ControllerWithCrossProjectAccessCheck
+
+    requires_cross_project_access if: -> { board&.group_board? }
 
     before_action :whitelist_query_limiting, only: [:index, :update]
     before_action :authorize_read_issue, only: [:index]
@@ -64,11 +67,19 @@ module Boards
     end
 
     def issues_finder
-      IssuesFinder.new(current_user, project_id: board_parent.id)
+      if board.group_board?
+        IssuesFinder.new(current_user, group_id: board_parent.id)
+      else
+        IssuesFinder.new(current_user, project_id: board_parent.id)
+      end
     end
 
     def project
-      board_parent
+      @project ||= if board.group_board?
+                     Project.find(issue_params[:project_id])
+                   else
+                     board_parent
+                   end
     end
 
     def move_params
@@ -85,7 +96,8 @@ module Boards
       resource.as_json(
         only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
         labels: true,
-        sidebar_endpoints: true,
+        issue_endpoints: true,
+        include_full_project_path: board.group_board?,
         include: {
           project: { only: [:id, :path] },
           assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index e9bd1689a1ee1ca6b5f070bd8b1c9186267ff08e..738a6a5173eef4ea3c2688fa888c3aef8d5aa192 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -4,20 +4,5 @@ module Ci
 
     def show
     end
-
-    def create
-      @content = params[:content]
-      @error = Gitlab::Ci::YamlProcessor.validation_message(@content)
-      @status = @error.blank?
-
-      if @error.blank?
-        @config_processor = Gitlab::Ci::YamlProcessor.new(@content)
-        @stages = @config_processor.stages
-        @builds = @config_processor.builds
-        @jobs = @config_processor.jobs
-      end
-
-      render :show
-    end
   end
 end
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index db8c362f1256b5d39632350432c50d173eef8f90..2fdf346ef442f9ffa94e45a7e847f4aaede79f8c 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -10,7 +10,7 @@ module AuthenticatesWithTwoFactor
     # This action comes from DeviseController, but because we call `sign_in`
     # manually, not skipping this action would cause a "You are already signed
     # in." error message to be shown upon successful login.
-    skip_before_action :require_no_authentication, only: [:create]
+    skip_before_action :require_no_authentication, only: [:create], raise: false
   end
 
   # Store the user's ID in the session for later retrieval and render the
@@ -56,6 +56,7 @@ module AuthenticatesWithTwoFactor
       session.delete(:otp_user_id)
 
       remember_me(user) if user_params[:remember_me] == '1'
+      user.save!
       sign_in(user)
     else
       user.increment_failed_attempts!
diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb
index a145049dc7de3c71e8fafe23dac0357b330ce91b..da830ec2cb18fbfd46eca3f02d87107081c99eae 100644
--- a/app/controllers/concerns/boards_responses.rb
+++ b/app/controllers/concerns/boards_responses.rb
@@ -1,10 +1,46 @@
 module BoardsResponses
+  include Gitlab::Utils::StrongMemoize
+
+  def board_params
+    params.require(:board).permit(:name, :weight, :milestone_id, :assignee_id, label_ids: [])
+  end
+
+  def parent
+    strong_memoize(:parent) do
+      group? ? group : project
+    end
+  end
+
+  def boards_path
+    if group?
+      group_boards_path(parent)
+    else
+      project_boards_path(parent)
+    end
+  end
+
+  def board_path(board)
+    if group?
+      group_board_path(parent, board)
+    else
+      project_board_path(parent, board)
+    end
+  end
+
+  def group?
+    instance_variable_defined?(:@group)
+  end
+
   def authorize_read_list
-    authorize_action_for!(board.parent, :read_list)
+    ability = board.group_board? ? :read_group : :read_list
+
+    authorize_action_for!(board.parent, ability)
   end
 
   def authorize_read_issue
-    authorize_action_for!(board.parent, :read_issue)
+    ability = board.group_board? ? :read_group : :read_issue
+
+    authorize_action_for!(board.parent, ability)
   end
 
   def authorize_update_issue
@@ -31,6 +67,10 @@ module BoardsResponses
     respond_with(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
   end
 
+  def serialize_as_json(resource)
+    resource.as_json(only: [:id])
+  end
+
   def respond_with(resource)
     respond_to do |format|
       format.html
diff --git a/app/controllers/concerns/checks_collaboration.rb b/app/controllers/concerns/checks_collaboration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..81367663a0667ca2ab0ff95f3f8a80ff151a737d
--- /dev/null
+++ b/app/controllers/concerns/checks_collaboration.rb
@@ -0,0 +1,21 @@
+module ChecksCollaboration
+  def can_collaborate_with_project?(project, ref: nil)
+    return true if can?(current_user, :push_code, project)
+
+    can_create_merge_request =
+      can?(current_user, :create_merge_request_in, project) &&
+      current_user.already_forked?(project)
+
+    can_create_merge_request ||
+      user_access(project).can_push_to_branch?(ref)
+  end
+
+  # rubocop:disable Gitlab/ModuleWithInstanceVariables
+  # enabling this so we can easily cache the user access value as it might be
+  # used across multiple calls in the view
+  def user_access(project)
+    @user_access ||= {}
+    @user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
+  end
+  # rubocop:enable Gitlab/ModuleWithInstanceVariables
+end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 6f4fdcdaa4feddc4386f255fd2291a13ad58d36f..b26a76d2b62dfb4ae19efa9f2cb91c9d113deb17 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -4,7 +4,7 @@ module CreatesCommit
 
   # rubocop:disable Gitlab/ModuleWithInstanceVariables
   def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
-    if can?(current_user, :push_code, @project)
+    if user_access(@project).can_push_to_branch?(branch_name_or_ref)
       @project_to_commit_into = @project
       @branch_name ||= @ref
     else
@@ -50,7 +50,7 @@ module CreatesCommit
   # rubocop:enable Gitlab/ModuleWithInstanceVariables
 
   def authorize_edit_tree!
-    return if can_collaborate_with_project?
+    return if can_collaborate_with_project?(project, ref: branch_name_or_ref)
 
     access_denied!
   end
@@ -123,4 +123,8 @@ module CreatesCommit
     params[:create_merge_request].present? &&
       (different_project? || @start_branch != @branch_name) # rubocop:disable Gitlab/ModuleWithInstanceVariables
   end
+
+  def branch_name_or_ref
+    @branch_name || @ref # rubocop:disable Gitlab/ModuleWithInstanceVariables
+  end
 end
diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb
index fafb10090ca411686d4670f4f167e5ba1e440b8e..56770a1740602cf9f764bf65bd524d5a9d5eea4a 100644
--- a/app/controllers/concerns/group_tree.rb
+++ b/app/controllers/concerns/group_tree.rb
@@ -14,7 +14,7 @@ module GroupTree
               end
 
     @groups = @groups.with_selects_for_list(archived: params[:archived])
-                .sort(@sort = params[:sort])
+                .sort_by_attribute(@sort = params[:sort])
                 .page(params[:page])
 
     respond_to do |format|
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index a21e658fda12cbc05d622318ec276079dfc95036..0379f76fc3d9f6524633fcfe18812ba10ca9db72 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -88,11 +88,15 @@ module IssuableActions
 
     discussions = Discussion.build_collection(notes, issuable)
 
-    render json: DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user).represent(discussions, context: self)
+    render json: discussion_serializer.represent(discussions, context: self)
   end
 
   private
 
+  def discussion_serializer
+    DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user, note_entity: ProjectNoteEntity)
+  end
+
   def recaptcha_check_if_spammable(should_redirect = true, &block)
     return yield unless issuable.is_a? Spammable
 
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 03ed5b5310bf58200765ee9c7ea0b8b9630e080a..0c34e49206a6c6a42d73fff8daeb923d537d1b55 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -41,7 +41,7 @@ module NotesActions
     @note = Notes::CreateService.new(note_project, current_user, create_params).execute
 
     if @note.is_a?(Note)
-      Notes::RenderService.new(current_user).execute([@note], @project)
+      Notes::RenderService.new(current_user).execute([@note])
     end
 
     respond_to do |format|
@@ -56,7 +56,7 @@ module NotesActions
     @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
 
     if @note.is_a?(Note)
-      Notes::RenderService.new(current_user).execute([@note], @project)
+      Notes::RenderService.new(current_user).execute([@note])
     end
 
     respond_to do |format|
@@ -212,12 +212,12 @@ module NotesActions
   end
 
   def note_serializer
-    NoteSerializer.new(project: project, noteable: noteable, current_user: current_user)
+    ProjectNoteSerializer.new(project: project, noteable: noteable, current_user: current_user)
   end
 
   def note_project
     strong_memoize(:note_project) do
-      return nil unless project
+      next nil unless project
 
       note_project_id = params[:note_project_id]
 
@@ -228,7 +228,7 @@ module NotesActions
           project
         end
 
-      return access_denied! unless can?(current_user, :create_note, the_project)
+      next access_denied! unless can?(current_user, :create_note, the_project)
 
       the_project
     end
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index e7ef297879f2f4d5e88874361ddfbcb322dc92f8..36e3d76ecfe7d1995a86d39a10e3f5b62f5d7702 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -4,7 +4,7 @@ module RendersNotes
     preload_noteable_for_regular_notes(notes)
     preload_max_access_for_authors(notes, @project)
     preload_first_time_contribution_for_authors(noteable, notes)
-    Notes::RenderService.new(current_user).execute(notes, @project)
+    Notes::RenderService.new(current_user).execute(notes)
 
     notes
   end
diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb
new file mode 100644
index 0000000000000000000000000000000000000000..55011c89886c3ab95bc7584d46d1b43a3e85dc06
--- /dev/null
+++ b/app/controllers/concerns/send_file_upload.rb
@@ -0,0 +1,17 @@
+module SendFileUpload
+  def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment')
+    if attachment
+      redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}" }
+      send_params.merge!(filename: attachment, disposition: disposition)
+    end
+
+    if file_upload.file_storage?
+      send_file file_upload.path, send_params
+    elsif file_upload.class.proxy_download_enabled?
+      headers.store(*Gitlab::Workhorse.send_url(file_upload.url(**redirect_params)))
+      head :ok
+    else
+      redirect_to file_upload.url(**redirect_params)
+    end
+  end
+end
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index 9095cc7f78304925dd6e32714d9f707cfec083c6..120614739aa38f83c659152beb66c9965291058b 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -17,6 +17,10 @@ module SnippetsActions
   end
   # rubocop:enable Gitlab/ModuleWithInstanceVariables
 
+  def js_request?
+    request.format.js?
+  end
+
   private
 
   def convert_line_endings(content)
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 3dbfabcae8a597d290fe7661ace5d8e026f3d184..b9b9b6e4e880dbb7eb849c310308a1c0a7033858 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -1,5 +1,6 @@
 module UploadsActions
   include Gitlab::Utils::StrongMemoize
+  include SendFileUpload
 
   UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo).freeze
 
@@ -26,14 +27,11 @@ module UploadsActions
   def show
     return render_404 unless uploader&.exists?
 
-    if uploader.file_storage?
-      disposition = uploader.image_or_video? ? 'inline' : 'attachment'
-      expires_in 0.seconds, must_revalidate: true, private: true
+    expires_in 0.seconds, must_revalidate: true, private: true
 
-      send_file uploader.file.path, disposition: disposition
-    else
-      redirect_to uploader.url
-    end
+    disposition = uploader.image_or_video? ? 'inline' : 'attachment'
+
+    send_upload(uploader, attachment: uploader.filename, disposition: disposition)
   end
 
   private
@@ -62,19 +60,27 @@ module UploadsActions
   end
 
   def build_uploader_from_upload
-    return nil unless params[:secret] && params[:filename]
+    return unless uploader = build_uploader
 
-    upload_path = uploader_class.upload_path(params[:secret], params[:filename])
-    upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_path)
+    upload_paths = uploader.upload_paths(params[:filename])
+    upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_paths)
     upload&.build_uploader
   end
 
   def build_uploader_from_params
+    return unless uploader = build_uploader
+
+    uploader.retrieve_from_store!(params[:filename])
+    uploader
+  end
+
+  def build_uploader
+    return unless params[:secret] && params[:filename]
+
     uploader = uploader_class.new(model, secret: params[:secret])
 
-    return nil unless uploader.model_valid?
+    return unless uploader.model_valid?
 
-    uploader.retrieve_from_store!(params[:filename])
     uploader
   end
 
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index e89eaf7eddaba7d016e96dfda8b9e59ce19624cb..f9e8fe624e8572c8046b519c61e4febcf84c4adb 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -86,7 +86,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
     out_of_range = todos.current_page > total_pages
 
     if out_of_range
-      redirect_to url_for(params.merge(page: total_pages, only_path: true))
+      redirect_to url_for(safe_params.merge(page: total_pages, only_path: true))
     end
 
     out_of_range
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 280ed93faf80540357c061c0a2beb517fc9cf881..68d328fa79712bb5844f759ed6a124c0f366801e 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -2,9 +2,17 @@ class DashboardController < Dashboard::ApplicationController
   include IssuesAction
   include MergeRequestsAction
 
+  FILTER_PARAMS = [
+    :author_id,
+    :assignee_id,
+    :milestone_title,
+    :label_name
+  ].freeze
+
   before_action :event_filter, only: :activity
   before_action :projects, only: [:issues, :merge_requests]
   before_action :set_show_full_reference, only: [:issues, :merge_requests]
+  before_action :check_filters_presence!, only: [:issues, :merge_requests]
 
   respond_to :html
 
@@ -39,4 +47,15 @@ class DashboardController < Dashboard::ApplicationController
   def set_show_full_reference
     @show_full_reference = true
   end
+
+  def check_filters_presence!
+    @no_filters_set = FILTER_PARAMS.none? { |k| params.key?(k) }
+
+    return unless @no_filters_set
+
+    respond_to do |format|
+      format.html
+      format.atom { head :bad_request }
+    end
+  end
 end
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7c2016f03262250a9dec79d57a48a12ac556fdff
--- /dev/null
+++ b/app/controllers/groups/boards_controller.rb
@@ -0,0 +1,27 @@
+class Groups::BoardsController < Groups::ApplicationController
+  include BoardsResponses
+
+  before_action :assign_endpoint_vars
+
+  def index
+    @boards = Boards::ListService.new(group, current_user).execute
+
+    respond_with_boards
+  end
+
+  def show
+    @board = group.boards.find(params[:id])
+
+    respond_with_board
+  end
+
+  def assign_endpoint_vars
+    @boards_endpoint = group_boards_url(group)
+    @namespace_path = group.to_param
+    @labels_endpoint = group_labels_url(group)
+  end
+
+  def serialize_as_json(resource)
+    resource.as_json(only: [:id])
+  end
+end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index f210434b2d72e72d4dc301496ea45e32aafecd66..134b0dfc0db74af671be9780d6326340f2fe83c1 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -17,7 +17,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
     @members = GroupMembersFinder.new(@group).execute
     @members = @members.non_invite unless can?(current_user, :admin_group, @group)
     @members = @members.search(params[:search]) if params[:search].present?
-    @members = @members.sort(@sort)
+    @members = @members.sort_by_attribute(@sort)
     @members = @members.page(params[:page]).per(50)
     @members = present_members(@members.includes(:user))
 
diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb
index ac1d97dc54aba49910ec6e2dbf1d5a11b8b52592..58be330f46653ac6b071b7b49d3e93cb4686f705 100644
--- a/app/controllers/groups/labels_controller.rb
+++ b/app/controllers/groups/labels_controller.rb
@@ -35,10 +35,18 @@ class Groups::LabelsController < Groups::ApplicationController
   def create
     @label = Labels::CreateService.new(label_params).execute(group: group)
 
-    if @label.valid?
-      redirect_to group_labels_path(@group)
-    else
-      render :new
+    respond_to do |format|
+      format.html do
+        if @label.valid?
+          redirect_to group_labels_path(@group)
+        else
+          render :new
+        end
+      end
+
+      format.json do
+        render json: LabelSerializer.new.represent_appearance(@label)
+      end
     end
   end
 
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index acf6aaf57f46978deff2d164b4568c603eba68d8..5903689dc62699bfc7b05cc7608a05563c48ef27 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -12,7 +12,7 @@ class Groups::MilestonesController < Groups::ApplicationController
         @milestones = Kaminari.paginate_array(milestones).page(params[:page])
       end
       format.json do
-        render json: milestones.map { |m| m.for_display.slice(:title, :name) }
+        render json: milestones.map { |m| m.for_display.slice(:id, :title, :name) }
       end
     end
   end
diff --git a/app/controllers/groups/settings/badges_controller.rb b/app/controllers/groups/settings/badges_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..edb334a3d88b41d379ff549b24e773b08fa6afb5
--- /dev/null
+++ b/app/controllers/groups/settings/badges_controller.rb
@@ -0,0 +1,13 @@
+module Groups
+  module Settings
+    class BadgesController < Groups::ApplicationController
+      include GrapeRouteHelpers::NamedRouteMatcher
+
+      before_action :authorize_admin_group!
+
+      def index
+        @badge_api_endpoint = api_v4_groups_badges_path(id: @group.id)
+      end
+    end
+  end
+end
diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb
index cb8771bc97e6d2d60017baad5f17b08e3ba669d2..4d8a20de0177386f7a83b71621f2eacceb49fbfd 100644
--- a/app/controllers/groups/variables_controller.rb
+++ b/app/controllers/groups/variables_controller.rb
@@ -15,7 +15,7 @@ module Groups
     def update
       if @group.update(group_variables_params)
         respond_to do |format|
-          format.json { return render_group_variables }
+          format.json { render_group_variables }
         end
       else
         respond_to do |format|
@@ -39,7 +39,7 @@ module Groups
     end
 
     def variable_params_attributes
-      %i[id key value protected _destroy]
+      %i[id key secret_value protected _destroy]
     end
 
     def authorize_admin_build!
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 283c3e5f1e0b453184d74fbb4e4762007ce307c3..79fa581835906af9c3e58ee179404a08c9fc1ac3 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController
       .new(@projects, offset: params[:offset].to_i, filter: event_filter)
       .to_a
 
-    Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
+    Events::RenderService
+      .new(current_user)
+      .execute(@events, atom_request: request.format.atom?)
   end
 
   def user_actions
@@ -187,6 +189,6 @@ class GroupsController < Groups::ApplicationController
 
     params[:id] = group.to_param
 
-    url_for(params)
+    url_for(safe_params)
   end
 end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 69fb8121ded45cc96086ddf4d55521fad921c896..eb7d5fca36795257cb3bde697a220f4259f16311 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -42,7 +42,9 @@ class Import::GithubController < Import::BaseController
     target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
 
     if can?(current_user, :create_projects, target_namespace)
-      project = Gitlab::LegacyGithubImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, access_params, type: provider).execute
+      project = Gitlab::LegacyGithubImport::ProjectCreator
+                  .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
+                  .execute(extra_project_attrs)
 
       if project.persisted?
         render json: ProjectSerializer.new.represent(project)
@@ -73,15 +75,15 @@ class Import::GithubController < Import::BaseController
   end
 
   def new_import_url
-    public_send("new_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend
+    public_send("new_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
   end
 
   def status_import_url
-    public_send("status_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend
+    public_send("status_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
   end
 
   def callback_import_url
-    public_send("callback_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend
+    public_send("callback_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
   end
 
   def provider_unauthorized
@@ -116,4 +118,12 @@ class Import::GithubController < Import::BaseController
   def client_options
     {}
   end
+
+  def extra_project_attrs
+    {}
+  end
+
+  def extra_import_params
+    {}
+  end
 end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 52430ea771fecbaba9a7694d923207408de425ba..025d8270b7c76db0d877ac5875d3abb848cbb680 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -62,7 +62,7 @@ class InvitesController < ApplicationController
     case source
     when Project
       project = member.source
-      label = "project #{project.name_with_namespace}"
+      label = "project #{project.full_name}"
       path = project_path(project)
     when Group
       group = member.source
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 7d6fe6a02326ea4de5aabf73a4798e5183691a2d..67057b5b1269a805e96822a0eefce1dc26730c2b 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -25,8 +25,7 @@ class JwtController < ApplicationController
     authenticate_with_http_basic do |login, password|
       @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
 
-      if @authentication_result.failed? ||
-          (@authentication_result.actor.present? && !@authentication_result.actor.is_a?(User))
+      if @authentication_result.failed?
         render_unauthorized
       end
     end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 8440945ab43c3ee7b30508fbf473c8f1653d8f55..5e6676ea513422c7a670fb165f67353f1e595abe 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -18,6 +18,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
     end
   end
 
+  # Extend the standard implementation to also increment
+  # the number of failed sign in attempts
+  def failure
+    if params[:username].present? && AuthHelper.form_based_provider?(failed_strategy.name)
+      user = User.by_login(params[:username])
+
+      user&.increment_failed_attempts!
+    end
+
+    super
+  end
+
   # Extend the standard message generation to accept our custom exception
   def failure_message
     exception = env["omniauth.error"]
@@ -95,6 +107,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
     handle_omniauth
   end
 
+  def auth0
+    if oauth['uid'].blank?
+      fail_auth0_login
+    else
+      handle_omniauth
+    end
+  end
+
   private
 
   def handle_omniauth
@@ -170,6 +190,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
     redirect_to new_user_session_path
   end
 
+  def fail_auth0_login
+    flash[:alert] = 'Wrong extern UID provided. Make sure Auth0 is configured correctly.'
+
+    redirect_to new_user_session_path
+  end
+
   def handle_disabled_provider
     label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
     flash[:alert] = "Signing in using #{label} has been disabled"
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index dbf61a1772425851be6f7a4defd03b1e87a38fe7..ac71f72e624995194a0cf5c7b4ab76ed69daf416 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -51,15 +51,21 @@ class ProfilesController < Profiles::ApplicationController
   end
 
   def update_username
-    result = Users::UpdateService.new(current_user, user: @user, username: user_params[:username]).execute
+    result = Users::UpdateService.new(current_user, user: @user, username: username_param).execute
 
-    options = if result[:status] == :success
-                { notice: "Username successfully changed" }
-              else
-                { alert: "Username change failed - #{result[:message]}" }
-              end
+    respond_to do |format|
+      if result[:status] == :success
+        message = s_("Profiles|Username successfully changed")
+
+        format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
+        format.json { render json: { message: message }, status: :ok }
+      else
+        message = s_("Profiles|Username change failed - %{message}") % { message: result[:message] }
 
-    redirect_back_or_default(default: { action: 'show' }, options: options)
+        format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: message }) }
+        format.json { render json: { message: message }, status: :unprocessable_entity }
+      end
+    end
   end
 
   private
@@ -72,6 +78,10 @@ class ProfilesController < Profiles::ApplicationController
     return render_404 unless @user.can_change_username?
   end
 
+  def username_param
+    @username_param ||= user_params.require(:username)
+  end
+
   def user_params
     @user_params ||= params.require(:user).permit(
       :avatar,
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 6025a40348bbe665e4611308addccd2f91b5be6b..032bb2267e7467b0e03a6efe66bef840e7c874e0 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -1,12 +1,13 @@
 class Projects::ApplicationController < ApplicationController
   include RoutableActions
+  include ChecksCollaboration
 
   skip_before_action :authenticate_user!
   before_action :project
   before_action :repository
   layout 'project'
 
-  helper_method :repository, :can_collaborate_with_project?
+  helper_method :repository, :can_collaborate_with_project?, :user_access
 
   private
 
@@ -31,13 +32,6 @@ class Projects::ApplicationController < ApplicationController
     @repository ||= project.repository
   end
 
-  def can_collaborate_with_project?(project = nil)
-    project ||= @project
-
-    can?(current_user, :push_code, project) ||
-      (current_user && current_user.already_forked?(project))
-  end
-
   def authorize_action!(action)
     unless can?(current_user, action, project)
       return access_denied!
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 0837451cc4900671d1b53c0465bbbb35ad95f3af..abc283d7aa98cabf22c09fa41b916511366c165f 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -1,6 +1,7 @@
 class Projects::ArtifactsController < Projects::ApplicationController
   include ExtractsPath
   include RendersBlob
+  include SendFileUpload
 
   layout 'project'
   before_action :authorize_read_build!
@@ -10,11 +11,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
   before_action :entry, only: [:file]
 
   def download
-    if artifacts_file.file_storage?
-      send_file artifacts_file.path, disposition: 'attachment'
-    else
-      redirect_to artifacts_file.url
-    end
+    send_upload(artifacts_file, attachment: artifacts_file.filename)
   end
 
   def browse
@@ -45,8 +42,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
   end
 
   def raw
-    path = Gitlab::Ci::Build::Artifacts::Path
-      .new(params[:path])
+    path = Gitlab::Ci::Build::Artifacts::Path.new(params[:path])
 
     send_artifacts_entry(build, path)
   end
@@ -75,7 +71,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
   end
 
   def validate_artifacts!
-    render_404 unless build && build.artifacts?
+    render_404 unless build&.artifacts?
   end
 
   def build
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 74c25505e36c07e9d70c8e496ea308a3ceeef7c2..0c1c286a0a45be129bf3a943fb2c37a58acee7d3 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -9,8 +9,12 @@ class Projects::BlobController < Projects::ApplicationController
 
   before_action :require_non_empty_project, except: [:new, :create]
   before_action :authorize_download_code!
-  before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
+
+  # We need to assign the blob vars before `authorize_edit_tree!` so we can
+  # validate access to a specific ref.
   before_action :assign_blob_vars
+  before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
+
   before_action :commit, except: [:new, :create]
   before_action :blob, except: [:new, :create]
   before_action :require_branch_head, only: [:edit, :update]
@@ -38,7 +42,7 @@ class Projects::BlobController < Projects::ApplicationController
       end
 
       format.json do
-        page_title @blob.path, @ref, @project.name_with_namespace
+        page_title @blob.path, @ref, @project.full_name
 
         show_json
       end
@@ -46,7 +50,7 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def edit
-    if can_collaborate_with_project?
+    if can_collaborate_with_project?(project, ref: @ref)
       blob.load_all_data!
     else
       redirect_to action: 'show'
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index cabafe2635726a1ca736098aa5decd34b87e5ea3..b7b36f770f57dd7e5c6932a697a42c1133f7b5e2 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -7,28 +7,36 @@ class Projects::BranchesController < Projects::ApplicationController
   before_action :authorize_download_code!
   before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
 
-  def index
-    @sort = params[:sort].presence || sort_value_recently_updated
-    @branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
-    @branches = Kaminari.paginate_array(@branches).page(params[:page])
+  # Support legacy URLs
+  before_action :redirect_for_legacy_index_sort_or_search, only: [:index]
 
+  def index
     respond_to do |format|
       format.html do
+        @sort = params[:sort].presence || sort_value_recently_updated
+        @mode = params[:state].presence || 'overview'
+        @overview_max_branches = 5
+
+        # Fetch branches for the specified mode
+        fetch_branches_by_mode
+
         @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
-        @merged_branch_names =
-          repository.merged_branch_names(@branches.map(&:name))
-        # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37429
+        @merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
+
+        # n+1: https://gitlab.com/gitlab-org/gitaly/issues/992
         Gitlab::GitalyClient.allow_n_plus_1_calls do
           @max_commits = @branches.reduce(0) do |memo, branch|
             diverging_commit_counts = repository.diverging_commit_counts(branch)
             [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
           end
-
-          render
         end
+
+        render
       end
       format.json do
-        render json: @branches.map(&:name)
+        branches = BranchesFinder.new(@repository, params).execute
+        branches = Kaminari.paginate_array(branches).page(params[:page])
+        render json: branches.map(&:name)
       end
     end
   end
@@ -123,4 +131,27 @@ class Projects::BranchesController < Projects::ApplicationController
       context: 'autodeploy'
     )
   end
+
+  def redirect_for_legacy_index_sort_or_search
+    # Normalize a legacy URL with redirect
+    if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence }
+      redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.'
+    end
+  end
+
+  def fetch_branches_by_mode
+    if @mode == 'overview'
+      # overview mode
+      @active_branches, @stale_branches = BranchesFinder.new(@repository, sort: sort_value_recently_updated).execute.partition(&:active?)
+      # Here we get one more branch to indicate if there are more data we're not showing
+      @active_branches = @active_branches.first(@overview_max_branches + 1)
+      @stale_branches = @stale_branches.first(@overview_max_branches + 1)
+      @branches = @active_branches + @stale_branches
+    else
+      # active/stale/all view mode
+      @branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
+      @branches = @branches.select { |b| b.state.to_s == @mode } if %w[active stale].include?(@mode)
+      @branches = Kaminari.paginate_array(@branches).page(params[:page])
+    end
+  end
 end
diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a2185572a20363bdb0fed771795d5d4ce78e2981
--- /dev/null
+++ b/app/controllers/projects/ci/lints_controller.rb
@@ -0,0 +1,27 @@
+class Projects::Ci::LintsController < Projects::ApplicationController
+  before_action :authorize_create_pipeline!
+
+  def show
+  end
+
+  def create
+    @content = params[:content]
+    @error = Gitlab::Ci::YamlProcessor.validation_message(@content,  yaml_processor_options)
+    @status = @error.blank?
+
+    if @error.blank?
+      @config_processor = Gitlab::Ci::YamlProcessor.new(@content, yaml_processor_options)
+      @stages = @config_processor.stages
+      @builds = @config_processor.builds
+      @jobs = @config_processor.jobs
+    end
+
+    render :show
+  end
+
+  private
+
+  def yaml_processor_options
+    { project: @project, sha: project.repository.commit.sha }
+  end
+end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index effb484ef0fc21994ae8aed7f30fdda64fc6255f..b7f548e0e6371f362dda8274cac5403381ee3653 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -34,6 +34,7 @@ class Projects::CommitController < Projects::ApplicationController
 
   def pipelines
     @pipelines = @commit.pipelines.order(id: :desc)
+    @pipelines = @pipelines.where(ref: params[:ref]) if params[:ref]
 
     respond_to do |format|
       format.html
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 3cb4eb23981267e51891281e768fc29cd2d82ca8..2b0c2ca97c07e2f82866d420cecc6408066873d8 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -17,10 +17,8 @@ class Projects::CompareController < Projects::ApplicationController
 
   def show
     apply_diff_view_cookie!
-    # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430
-    Gitlab::GitalyClient.allow_n_plus_1_calls do
-      render
-    end
+
+    render
   end
 
   def diff_for_path
diff --git a/app/controllers/projects/deploy_tokens_controller.rb b/app/controllers/projects/deploy_tokens_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2f91b8f36de452c9d907e4ac368a57a341a75d99
--- /dev/null
+++ b/app/controllers/projects/deploy_tokens_controller.rb
@@ -0,0 +1,10 @@
+class Projects::DeployTokensController < Projects::ApplicationController
+  before_action :authorize_admin_project!
+
+  def revoke
+    @token = @project.deploy_tokens.find(params[:id])
+    @token.revoke!
+
+    redirect_to project_settings_repository_path(project)
+  end
+end
diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb
index 1a418d0f15a18f7ab154c51671d6549c002b1ed5..b68cdc39cb8d5998628d3b74871cafb276035980 100644
--- a/app/controllers/projects/deployments_controller.rb
+++ b/app/controllers/projects/deployments_controller.rb
@@ -24,7 +24,7 @@ class Projects::DeploymentsController < Projects::ApplicationController
   end
 
   def additional_metrics
-    return render_404 unless deployment.has_additional_metrics?
+    return render_404 unless deployment.has_metrics?
 
     respond_to do |format|
       format.json do
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index ee507009e5028523fd5f46838d9bc4d3e597bf31..8e86af43fee9aaacc3e5c99f37b04e6df81dd499 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -4,8 +4,8 @@ class Projects::DiscussionsController < Projects::ApplicationController
 
   before_action :check_merge_requests_available!
   before_action :merge_request
-  before_action :discussion
-  before_action :authorize_resolve_discussion!
+  before_action :discussion, only: [:resolve, :unresolve]
+  before_action :authorize_resolve_discussion!, only: [:resolve, :unresolve]
 
   def resolve
     Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion)
@@ -19,6 +19,12 @@ class Projects::DiscussionsController < Projects::ApplicationController
     render_discussion
   end
 
+  def show
+    render json: {
+      discussion_html: view_to_html_string('discussions/_diff_with_notes', discussion: discussion, expanded: true)
+    }
+  end
+
   private
 
   def render_discussion
@@ -37,7 +43,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
 
   def render_json_with_discussions_serializer
     render json:
-      DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user)
+      DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user, note_entity:  ProjectNoteEntity)
       .represent(discussion, context: self)
   end
 
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index dd5e66f60e3480d34f2f24d9ef8da93e094877d6..07249fe3182d100e3299c6f0ece899c26b026730 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -7,6 +7,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   attr_reader :authentication_result, :redirected_path
 
   delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
+  delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
 
   alias_method :user, :actor
   alias_method :authenticated_user, :actor
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 45910a9be44ae3891d5e5e6a536362c4b3b2171d..1dcf837f78e07af2942532e68be912886de57640 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -64,7 +64,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
     @access ||= access_klass.new(access_actor, project,
       'http', authentication_abilities: authentication_abilities,
               namespace_path: params[:namespace_id], project_path: project_path,
-              redirected_path: redirected_path)
+              redirected_path: redirected_path, auth_result_type: auth_result_type)
   end
 
   def access_actor
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b14939c4216db9882cccded025cd2745ed7f5fdd..767e492f56635be182ac87a82ebcdf67d619e5bc 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
   before_action :authorize_update_issuable!, only: [:edit, :update, :move]
 
   # Allow create a new branch and empty WIP merge request from current issue
-  before_action :authorize_create_merge_request!, only: [:create_merge_request]
+  before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
 
   respond_to :html
 
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 8b54ba3ad7c9343df6fd8de84f1113bd729c3306..7497b5012ecc0948127495e2b25f811e20a049cf 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -1,6 +1,7 @@
 class Projects::JobsController < Projects::ApplicationController
-  before_action :build, except: [:index, :cancel_all]
+  include SendFileUpload
 
+  before_action :build, except: [:index, :cancel_all]
   before_action :authorize_read_build!,
     only: [:index, :show, :status, :raw, :trace]
   before_action :authorize_update_build!,
@@ -43,8 +44,11 @@ class Projects::JobsController < Projects::ApplicationController
   end
 
   def show
-    @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
-    @builds = @builds.where("id not in (?)", @build.id)
+    @builds = @project.pipelines
+      .find_by_sha(@build.sha)
+      .builds
+      .order('id DESC')
+      .present(current_user: current_user)
     @pipeline = @build.pipeline
 
     respond_to do |format|
@@ -74,6 +78,8 @@ class Projects::JobsController < Projects::ApplicationController
             result.merge!(trace.to_h)
           end
 
+          result[:html] = result[:html].presence || 'No job log'
+
           render json: result
         end
       end
@@ -117,11 +123,17 @@ class Projects::JobsController < Projects::ApplicationController
   end
 
   def raw
-    build.trace.read do |stream|
-      if stream.file?
-        send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
-      else
-        render_404
+    if trace_artifact_file
+      send_upload(trace_artifact_file,
+                  send_params: raw_send_params,
+                  redirect_params: raw_redirect_params)
+    else
+      build.trace.read do |stream|
+        if stream.file?
+          send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
+        else
+          send_data stream.raw, type: 'text/plain; charset=utf-8', disposition: 'inline', filename: 'job.log'
+        end
       end
     end
   end
@@ -136,9 +148,21 @@ class Projects::JobsController < Projects::ApplicationController
     return access_denied! unless can?(current_user, :erase_build, build)
   end
 
+  def raw_send_params
+    { type: 'text/plain; charset=utf-8', disposition: 'inline' }
+  end
+
+  def raw_redirect_params
+    { query: { 'response-content-type' => 'text/plain; charset=utf-8', 'response-content-disposition' => 'inline' } }
+  end
+
+  def trace_artifact_file
+    @trace_artifact_file ||= build.job_artifacts_trace&.file
+  end
+
   def build
     @build ||= project.builds.find(params[:id])
-      .present(current_user: current_user)
+                 .present(current_user: current_user)
   end
 
   def build_path(build)
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index e0f4710175f3220583f6119f6f6651791994d46a..91016f6494e1ed0bdcd2dcc178ce5766ad3cc76a 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -112,12 +112,14 @@ class Projects::LabelsController < Projects::ApplicationController
     begin
       return render_404 unless promote_service.execute(@label)
 
+      flash[:notice] = "#{@label.title} promoted to <a href=\"#{group_labels_path(@project.group)}\">group label</a>.".html_safe
       respond_to do |format|
         format.html do
-          redirect_to(project_labels_path(@project),
-                      notice: 'Label was promoted to a Group Label')
+          redirect_to(project_labels_path(@project), status: 303)
+        end
+        format.json do
+          render json: { url: project_labels_path(@project) }
         end
-        format.js
       end
     rescue ActiveRecord::RecordInvalid => e
       Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label"
@@ -148,7 +150,8 @@ class Projects::LabelsController < Projects::ApplicationController
   end
 
   def find_labels
-    @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
+    @available_labels ||=
+      LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups]).execute
   end
 
   def authorize_admin_labels!
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index c77f10ef1dd73c7890dd7a77f3e2710b66f30ba3..ee4ed6741109b48a18c29053073ee2cc3d6b2bb6 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
 
   def existing_oids
     @existing_oids ||= begin
-      storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
+      project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
     end
   end
 
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
index 941638db42769c912daef4232b3a14226d3c518c..ebde0df1f7b1f073810be5020a7143937f410d5e 100644
--- a/app/controllers/projects/lfs_storage_controller.rb
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -1,6 +1,7 @@
 class Projects::LfsStorageController < Projects::GitHttpClientController
   include LfsRequest
   include WorkhorseRequest
+  include SendFileUpload
 
   skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize]
 
@@ -11,25 +12,30 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
       return
     end
 
-    send_file lfs_object.file.path, content_type: "application/octet-stream"
+    send_upload(lfs_object.file, send_params: { content_type: "application/octet-stream" })
   end
 
   def upload_authorize
     set_workhorse_internal_api_content_type
-    render json: Gitlab::Workhorse.lfs_upload_ok(oid, size)
+
+    authorized = LfsObjectUploader.workhorse_authorize
+    authorized.merge!(LfsOid: oid, LfsSize: size)
+
+    render json: authorized
   end
 
   def upload_finalize
-    unless tmp_filename
-      render_lfs_forbidden
-      return
-    end
-
-    if store_file(oid, size, tmp_filename)
+    if store_file!(oid, size)
       head 200
     else
       render plain: 'Unprocessable entity', status: 422
     end
+  rescue ActiveRecord::RecordInvalid
+    render_lfs_forbidden
+  rescue UploadedFile::InvalidPathError
+    render_lfs_forbidden
+  rescue ObjectStorage::RemoteStoreError
+    render_lfs_forbidden
   end
 
   private
@@ -50,38 +56,29 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
     params[:size].to_i
   end
 
-  def tmp_filename
-    name = request.headers['X-Gitlab-Lfs-Tmp']
-    return if name.include?('/')
-    return unless oid.present? && name.start_with?(oid)
-
-    name
-  end
+  def store_file!(oid, size)
+    object = LfsObject.find_by(oid: oid, size: size)
+    unless object&.file&.exists?
+      object = create_file!(oid, size)
+    end
 
-  def store_file(oid, size, tmp_file)
-    # Define tmp_file_path early because we use it in "ensure"
-    tmp_file_path = File.join(LfsObjectUploader.workhorse_upload_path, tmp_file)
+    return unless object
 
-    object = LfsObject.find_or_create_by(oid: oid, size: size)
-    file_exists = object.file.exists? || move_tmp_file_to_storage(object, tmp_file_path)
-    file_exists && link_to_project(object)
-  ensure
-    FileUtils.rm_f(tmp_file_path)
+    link_to_project!(object)
   end
 
-  def move_tmp_file_to_storage(object, path)
-    File.open(path) do |f|
-      object.file = f
-    end
+  def create_file!(oid, size)
+    uploaded_file = UploadedFile.from_params(
+      params, :file, LfsObjectUploader.workhorse_local_upload_path)
+    return unless uploaded_file
 
-    object.file.store!
-    object.save
+    LfsObject.create!(oid: oid, size: size, file: uploaded_file)
   end
 
-  def link_to_project(object)
+  def link_to_project!(object)
     if object && !object.projects.exists?(storage_project.id)
       object.projects << storage_project
-      object.save
+      object.save!
     end
   end
 end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 793ae03fb889613e63d8e62ee3334b4239775026..67d4ea2ca8f8aa86024eb4a6a74d01d85c4cace3 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -15,6 +15,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
 
   def merge_request_params_attributes
     [
+      :allow_maintainer_to_push,
       :assignee_id,
       :description,
       :force_remove_source_branch,
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index a90030a83123f06efff77b6278508dfdb43c52b6..4a377fefc62bdb69188bb04f42b7f0a6723aa353 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
 
   skip_before_action :merge_request
   before_action :whitelist_query_limiting, only: [:create]
-  before_action :authorize_create_merge_request!
+  before_action :authorize_create_merge_request_from!
   before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
   before_action :build_merge_request, except: [:create]
 
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index a1af125547c7d989f51d4bc45468479f1f79aa14..62b739918e6f6349aa3efeadc51b42b25de80a35 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -60,13 +60,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
       end
 
       format.patch  do
-        return render_404 unless @merge_request.diff_refs
+        break render_404 unless @merge_request.diff_refs
 
         send_git_patch @project.repository, @merge_request.diff_refs
       end
 
       format.diff do
-        return render_404 unless @merge_request.diff_refs
+        break render_404 unless @merge_request.diff_refs
 
         send_git_diff @project.repository, @merge_request.diff_refs
       end
@@ -187,7 +187,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
       begin
         @merge_request.environments_for(current_user).map do |environment|
           project = environment.project
-          deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
+          deployment = environment.first_deployment_for(@merge_request.diff_head_sha)
 
           stop_url =
             if environment.stop_action? && can?(current_user, :create_deployment, environment)
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 75b17d05e2239cc9cb24288a2485c0066f2ba6f2..c5a044541f15efc3b0547659bb3d77fb67692cdc 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController
 
   def index
     @sort = params[:sort] || 'due_date_asc'
-    @milestones = milestones.sort(@sort)
+    @milestones = milestones.sort_by_attribute(@sort)
 
     respond_to do |format|
       format.html do
@@ -42,6 +42,10 @@ class Projects::MilestonesController < Projects::ApplicationController
 
   def show
     @project_namespace = @project.namespace.becomes(Namespace)
+
+    respond_to do |format|
+      format.html
+    end
   end
 
   def create
@@ -71,8 +75,16 @@ class Projects::MilestonesController < Projects::ApplicationController
 
   def promote
     promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone)
-    flash[:notice] = "Milestone has been promoted to group milestone."
-    redirect_to group_milestone_path(project.group, promoted_milestone.iid)
+
+    flash[:notice] = "#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, promoted_milestone.iid)}\">group milestone</a>.".html_safe
+    respond_to do |format|
+      format.html do
+        redirect_to project_milestones_path(project)
+      end
+      format.json do
+        render json: { url: project_milestones_path(project) }
+      end
+    end
   rescue Milestones::PromoteService::PromoteMilestoneError => error
     redirect_to milestone, alert: error.message
   end
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 3b10a93e97fbf2085bf6d3032a10c579bb0351db..35fec229db7fde062ad390e10ec347362fba3445 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -9,25 +9,22 @@ class Projects::NetworkController < Projects::ApplicationController
   before_action :assign_commit
 
   def show
-    # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37602
-    Gitlab::GitalyClient.allow_n_plus_1_calls do
-      @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
-          if @options[:extended_sha1] && !@commit
-            flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
-          end
-        end
+    @url = project_network_path(@project, @ref, @options.merge(format: :json))
+    @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
 
-        format.json do
-          @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
+    respond_to do |format|
+      format.html do
+        if @options[:extended_sha1] && !@commit
+          flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
         end
       end
 
-      render
+      format.json do
+        @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
+      end
     end
+
+    render
   end
 
   def assign_commit
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index dd41b9648e8c95e46d65f2127df53dd0cc7e207a..86c50d88a2a6003f9b734890bed3075a33e15cf3 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
   private
 
   def render_json_with_notes_serializer
-    Notes::RenderService.new(current_user).execute([note], project)
+    Notes::RenderService.new(current_user).execute([note])
 
     render json: note_serializer.represent(note)
   end
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index d421b1a8eb5c729a0040c72faf22c362d9083062..cae6e2c40b861600db3f1d0b5f3f95a6e3ce9f5d 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -21,4 +21,26 @@ class Projects::PagesController < Projects::ApplicationController
       end
     end
   end
+
+  def update
+    result = Projects::UpdateService.new(@project, current_user, project_params).execute
+
+    respond_to do |format|
+      format.html do
+        if result[:status] == :success
+          flash[:notice] = 'Your changes have been saved'
+        else
+          flash[:alert] = 'Something went wrong on our end'
+        end
+
+        redirect_to project_pages_path(@project)
+      end
+    end
+  end
+
+  private
+
+  def project_params
+    params.require(:project).permit(:pages_https_only)
+  end
 end
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index b478e7b5e05b5dd2c808b82a7a685c954f7f2df8..fa258f3d9afc9efb3e6c7ab3a9010eb49ceb16d3 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -92,7 +92,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
   def schedule_params
     params.require(:schedule)
       .permit(:description, :cron, :cron_timezone, :ref, :active,
-        variables_attributes: [:id, :key, :value, :_destroy] )
+        variables_attributes: [:id, :key, :secret_value, :_destroy] )
   end
 
   def authorize_play_pipeline_schedule!
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 06ce7328fb558fa6fc3b8f50dfe05b60a97a2877..73c613b26f3b27a94c559fecfe908f8901c3df2b 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -4,32 +4,4 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
   def show
     redirect_to project_settings_ci_cd_path(@project, params: params)
   end
-
-  def update
-    Projects::UpdateService.new(project, current_user, update_params).tap do |service|
-      if service.execute
-        flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
-
-        if service.run_auto_devops_pipeline?
-          CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
-          flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe
-        end
-
-        redirect_to project_settings_ci_cd_path(@project)
-      else
-        render 'show'
-      end
-    end
-  end
-
-  private
-
-  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, :ci_config_path,
-      auto_devops_attributes: [:id, :domain, :enabled]
-    )
-  end
 end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index e9b4679f94c6b5eb050f5e1ed5eb8114e0edaec3..cfa5e72af645e50d19472c6de843d8aec53369d1 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -21,7 +21,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
       @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
     end
 
-    @project_members = present_members(@project_members.sort(@sort).page(params[:page]))
+    @project_members = present_members(@project_members.sort_by_attribute(@sort).page(params[:page]))
     @requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
     @project_member = @project.project_members.new
   end
diff --git a/app/controllers/projects/prometheus/metrics_controller.rb b/app/controllers/projects/prometheus/metrics_controller.rb
index b739d0f0f9075958a0a66f02705d4239643d068d..1dd886409a57545f4e05e11c7396ee9ee2fc536e 100644
--- a/app/controllers/projects/prometheus/metrics_controller.rb
+++ b/app/controllers/projects/prometheus/metrics_controller.rb
@@ -2,11 +2,12 @@ module Projects
   module Prometheus
     class MetricsController < Projects::ApplicationController
       before_action :authorize_admin_project!
+      before_action :require_prometheus_metrics!
 
       def active_common
         respond_to do |format|
           format.json do
-            matched_metrics = prometheus_service.matched_metrics || {}
+            matched_metrics = prometheus_adapter.query(:matched_metrics) || {}
 
             if matched_metrics.any?
               render json: matched_metrics
@@ -19,8 +20,12 @@ module Projects
 
       private
 
-      def prometheus_service
-        @prometheus_service ||= project.find_or_initialize_service('prometheus')
+      def prometheus_adapter
+        @prometheus_adapter ||= ::Prometheus::AdapterService.new(project).prometheus_adapter
+      end
+
+      def require_prometheus_metrics!
+        render_404 unless prometheus_adapter.can_query?
       end
     end
   end
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index d1719f120728fb80bc4ee124beb5007359d93a0b..64954ac9a42964b0fd812540d21d31f5d577dea9 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -5,12 +5,8 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
     @project.repository.branches
   end
 
-  def create_service_class
-    ::ProtectedBranches::CreateService
-  end
-
-  def update_service_class
-    ::ProtectedBranches::UpdateService
+  def service_namespace
+    ::ProtectedBranches
   end
 
   def load_protected_ref
diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb
index b51bdf7aa783147c1bfabe55d6f94908e0261b16..9e757a8d25f603575c7557ada340e3846c1e9e8d 100644
--- a/app/controllers/projects/protected_refs_controller.rb
+++ b/app/controllers/projects/protected_refs_controller.rb
@@ -37,7 +37,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
   end
 
   def destroy
-    @protected_ref.destroy
+    destroy_service_class.new(@project, current_user).execute(@protected_ref)
 
     respond_to do |format|
       format.html { redirect_to_repository_settings(@project) }
@@ -47,6 +47,18 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
 
   protected
 
+  def create_service_class
+    service_namespace::CreateService
+  end
+
+  def update_service_class
+    service_namespace::UpdateService
+  end
+
+  def destroy_service_class
+    service_namespace::DestroyService
+  end
+
   def access_level_attributes
     %i(access_level id)
   end
diff --git a/app/controllers/projects/protected_tags_controller.rb b/app/controllers/projects/protected_tags_controller.rb
index a5dbd7e46ae87b80bf061e3587c9411f1ec3f57c..198c938ff35ac60dd49b3a7f722672d450bfb678 100644
--- a/app/controllers/projects/protected_tags_controller.rb
+++ b/app/controllers/projects/protected_tags_controller.rb
@@ -5,12 +5,8 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController
     @project.repository.tags
   end
 
-  def create_service_class
-    ::ProtectedTags::CreateService
-  end
-
-  def update_service_class
-    ::ProtectedTags::UpdateService
+  def service_namespace
+    ::ProtectedTags
   end
 
   def load_protected_ref
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index a02cc477e081964766148fa2d519544fd152c418..9bc774b763662716d78318a25244aceaf46d7c96 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -2,6 +2,7 @@
 class Projects::RawController < Projects::ApplicationController
   include ExtractsPath
   include BlobHelper
+  include SendFileUpload
 
   before_action :require_non_empty_project
   before_action :assign_ref_vars
@@ -31,7 +32,7 @@ class Projects::RawController < Projects::ApplicationController
     lfs_object = find_lfs_object
 
     if lfs_object && lfs_object.project_allowed_access?(@project)
-      send_file lfs_object.file.path, filename: @blob.name, disposition: 'attachment'
+      send_upload(lfs_object.file, attachment: @blob.name)
     else
       render_404
     end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 2376f469213de772561f10939c1d5bdf2a815d61..48a09e1ddb8332cca0fb9cd767af13343f2205f3 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController
           when "graphs_commits"
             commits_project_graph_path(@project, @id)
           when "badges"
-            project_pipelines_settings_path(@project, ref: @id)
+            project_settings_ci_cd_path(@project, ref: @id)
           else
             project_commits_path(@project, @id)
           end
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index d5af0341d18fda13ee340f78d1b840ffa6d53186..d01f324e6fdbe8428c9e2f6593e84b247ec2fe71 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -1,6 +1,9 @@
 class Projects::RepositoriesController < Projects::ApplicationController
+  include ExtractsPath
+
   # Authorize
   before_action :require_non_empty_project, except: :create
+  before_action :assign_archive_vars, only: :archive
   before_action :authorize_download_code!
   before_action :authorize_admin_project!, only: :create
 
@@ -11,9 +14,27 @@ class Projects::RepositoriesController < Projects::ApplicationController
   end
 
   def archive
-    send_git_archive @repository, ref: params[:ref], format: params[:format]
+    append_sha = params[:append_sha]
+
+    if @ref
+      shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
+      append_sha = false if @filename == shortname
+    end
+
+    send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha
   rescue => ex
     logger.error("#{self.class.name}: #{ex}")
     return git_not_found!
   end
+
+  def assign_archive_vars
+    if params[:id]
+      @ref, @filename = extract_ref(params[:id])
+    else
+      @ref = params[:ref]
+      @filename = nil
+    end
+  rescue InvalidPathError
+    render_404
+  end
 end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index daa5c88aae04354a10217e673c4488598e44d1f1..a5ea9ff7ed7b767289128b1c36656e1dfa936c1e 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -3,7 +3,8 @@ class Projects::ServicesController < Projects::ApplicationController
 
   # Authorize
   before_action :authorize_admin_project!
-  before_action :service, only: [:edit, :update, :test]
+  before_action :ensure_service_enabled
+  before_action :service
 
   respond_to :html
 
@@ -23,26 +24,32 @@ class Projects::ServicesController < Projects::ApplicationController
   end
 
   def test
-    message = {}
+    if @service.can_test?
+      render json: service_test_response, status: :ok
+    else
+      render json: {}, status: :not_found
+    end
+  end
 
-    if @service.can_test? && @service.update_attributes(service_params[:service])
+  private
+
+  def service_test_response
+    if @service.update_attributes(service_params[:service])
       data = @service.test_data(project, current_user)
       outcome = @service.test(data)
 
-      unless outcome[:success]
-        message = { error: true, message: 'Test failed.', service_response: outcome[:result].to_s }
+      if outcome[:success]
+        {}
+      else
+        { error: true, message: 'Test failed.', service_response: outcome[:result].to_s }
       end
-
-      status = :ok
     else
-      status = :not_found
+      { error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(',') }
     end
-
-    render json: message, status: status
+  rescue Gitlab::HTTP::BlockedUrlError => e
+    { error: true, message: 'Test failed.', service_response: e.message }
   end
 
-  private
-
   def success_message
     if @service.active?
       "#{@service.title} activated."
@@ -54,4 +61,8 @@ class Projects::ServicesController < Projects::ApplicationController
   def service
     @service ||= @project.find_or_initialize_service(params[:id])
   end
+
+  def ensure_service_enabled
+    render_404 unless service
+  end
 end
diff --git a/app/controllers/projects/settings/badges_controller.rb b/app/controllers/projects/settings/badges_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f7b70dd4b7b6ea610ec063951e3e4c892e8e6d7c
--- /dev/null
+++ b/app/controllers/projects/settings/badges_controller.rb
@@ -0,0 +1,13 @@
+module Projects
+  module Settings
+    class BadgesController < Projects::ApplicationController
+      include GrapeRouteHelpers::NamedRouteMatcher
+
+      before_action :authorize_admin_project!
+
+      def index
+        @badge_api_endpoint = api_v4_projects_badges_path(id: @project.id)
+      end
+    end
+  end
+end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 86717bb7242717329e8a36df35279ead54d7cb76..d80ef8113aa96faba8ab7b658df97dfe1bb52ce4 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -2,37 +2,79 @@ module Projects
   module Settings
     class CiCdController < Projects::ApplicationController
       before_action :authorize_admin_pipeline!
+      before_action :define_variables
 
       def show
-        define_runners_variables
-        define_secret_variables
-        define_triggers_variables
-        define_badges_variables
-        define_auto_devops_variables
+      end
+
+      def update
+        Projects::UpdateService.new(project, current_user, update_params).tap do |service|
+          result = service.execute
+          if result[:status] == :success
+            flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
+
+            run_autodevops_pipeline(service)
+
+            redirect_to project_settings_ci_cd_path(@project)
+          else
+            render 'show'
+          end
+        end
       end
 
       def reset_cache
         if ResetProjectCacheService.new(@project, current_user).execute
-          flash[:notice] = _("Project cache successfully reset.")
+          respond_to do |format|
+            format.json { head :ok }
+          end
         else
-          flash[:error] = _("Unable to reset project cache.")
+          respond_to do |format|
+            format.json { head :bad_request }
+          end
         end
-
-        redirect_to project_pipelines_path(@project)
       end
 
       private
 
+      def update_params
+        params.require(:project).permit(
+          :runners_token, :builds_enabled, :build_allow_git_fetch,
+          :build_timeout_human_readable, :build_coverage_regex, :public_builds,
+          :auto_cancel_pending_pipelines, :ci_config_path,
+          auto_devops_attributes: [:id, :domain, :enabled]
+        )
+      end
+
+      def run_autodevops_pipeline(service)
+        return unless service.run_auto_devops_pipeline?
+
+        if @project.empty_repo?
+          flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+          return
+        end
+
+        CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
+        flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe
+      end
+
+      def define_variables
+        define_runners_variables
+        define_secret_variables
+        define_triggers_variables
+        define_badges_variables
+        define_auto_devops_variables
+      end
+
       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)
-        @shared_runners = Ci::Runner.shared.active
+        @shared_runners = ::Ci::Runner.shared.active
         @shared_runners_count = @shared_runners.count(:all)
       end
 
       def define_secret_variables
-        @variable = Ci::Variable.new(project: project)
+        @variable = ::Ci::Variable.new(project: project)
           .present(current_user: current_user)
         @variables = project.variables.order_key_asc
           .map { |variable| variable.present(current_user: current_user) }
@@ -40,7 +82,7 @@ module Projects
 
       def define_triggers_variables
         @triggers = @project.triggers
-        @trigger = Ci::Trigger.new
+        @trigger = ::Ci::Trigger.new
       end
 
       def define_badges_variables
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index d06d18c498b997e94b5f0781b5e574ce0803a8fe..f17056f13e0d80332557d87e23bc605bdd7d154f 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -4,18 +4,40 @@ module Projects
       before_action :authorize_admin_project!
 
       def show
-        @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
+        render_show
+      end
 
-        define_protected_refs
+      def create_deploy_token
+        @new_deploy_token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
+
+        if @new_deploy_token.persisted?
+          flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
+        end
+
+        render_show
       end
 
       private
 
+      def render_show
+        @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
+        @deploy_tokens = @project.deploy_tokens.active
+
+        define_deploy_token
+        define_protected_refs
+
+        render 'show'
+      end
+
       def define_protected_refs
         @protected_branches = @project.protected_branches.order(:name).page(params[:page])
         @protected_tags = @project.protected_tags.order(:name).page(params[:page])
         @protected_branch = @project.protected_branches.new
         @protected_tag = @project.protected_tags.new
+
+        @protected_branches_count = @protected_branches.reduce(0) { |sum, branch| sum + branch.matching(@project.repository.branches).size }
+        @protected_tags_count = @protected_tags.reduce(0) { |sum, tag| sum + tag.matching(@project.repository.tags).size }
+
         load_gon_index
       end
 
@@ -47,6 +69,14 @@ module Projects
         gon.push(protectable_branches_for_dropdown)
         gon.push(access_levels_options)
       end
+
+      def define_deploy_token
+        @new_deploy_token ||= DeployToken.new
+      end
+
+      def deploy_token_params
+        params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry)
+      end
     end
   end
 end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 7c19aa7bb23f21685e2b89be7fb5a49002135009..208a1d198622185a0b317828f5c546c8ada1a7f8 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -5,6 +5,8 @@ class Projects::SnippetsController < Projects::ApplicationController
   include SnippetsActions
   include RendersBlob
 
+  skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
+
   before_action :check_snippets_available!
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
 
@@ -71,6 +73,7 @@ class Projects::SnippetsController < Projects::ApplicationController
       format.json do
         render_blob_json(blob)
       end
+      format.js { render 'shared/snippets/show'}
     end
   end
 
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index f752a46f828ed70f735c95a3a30472ea650bbe3c..ee9b5458282fe33f150c5b684d5df630a3faf241 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -36,7 +36,7 @@ class Projects::TreeController < Projects::ApplicationController
       end
 
       format.json do
-        page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
+        page_title @path.presence || _("Files"), @ref, @project.full_name
 
         # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
         Gitlab::GitalyClient.allow_n_plus_1_calls do
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 7eb509e2e64f024fe0ef1ca47599a43f99e20ee1..bf09ea7e4d8bdd7e609dafe3546df2ea59e06af3 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -12,7 +12,7 @@ class Projects::VariablesController < Projects::ApplicationController
   def update
     if @project.update(variables_params)
       respond_to do |format|
-        format.json { return render_variables }
+        format.json { render_variables }
       end
     else
       respond_to do |format|
@@ -36,6 +36,6 @@ class Projects::VariablesController < Projects::ApplicationController
   end
 
   def variable_params_attributes
-    %i[id key value protected _destroy]
+    %i[id key secret_value protected _destroy]
   end
 end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index c4930d3d18d736d36504bdaf8a0602ddc1662b4e..1b0751f48c57006456e41f1e06ad0dcfe3b83490 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -29,8 +29,7 @@ class Projects::WikisController < Projects::ApplicationController
     else
       return render('empty') unless can?(current_user, :create_wiki, @project)
 
-      @page = WikiPage.new(@project_wiki)
-      @page.title = params[:id]
+      @page = build_page(title: params[:id])
 
       render 'edit'
     end
@@ -54,7 +53,7 @@ class Projects::WikisController < Projects::ApplicationController
     else
       render 'edit'
     end
-  rescue WikiPage::PageChangedError, WikiPage::PageRenameError => e
+  rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e
     @error = e
     render 'edit'
   end
@@ -70,6 +69,11 @@ class Projects::WikisController < Projects::ApplicationController
     else
       render action: "edit"
     end
+  rescue Gitlab::Git::Wiki::OperationError => e
+    @page = build_page(wiki_params)
+    @error = e
+
+    render 'edit'
   end
 
   def history
@@ -94,6 +98,9 @@ class Projects::WikisController < Projects::ApplicationController
     redirect_to project_wiki_path(@project, :home),
                 status: 302,
                 notice: "Page was successfully deleted"
+  rescue Gitlab::Git::Wiki::OperationError => e
+    @error = e
+    render 'edit'
   end
 
   def git_access
@@ -116,4 +123,10 @@ class Projects::WikisController < Projects::ApplicationController
   def wiki_params
     params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
   end
+
+  def build_page(args)
+    WikiPage.new(@project_wiki).tap do |page|
+      page.update_attributes(args)
+    end
+  end
 end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 913689a1e74d6b6a54b79d9eabe384eee2b2d2bd..a93b116c6fe1f9fcace7e5ec65b62f39d5aec27d 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -41,11 +41,11 @@ class ProjectsController < Projects::ApplicationController
       cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) }
 
       redirect_to(
-        project_path(@project),
+        project_path(@project, custom_import_params),
         notice: _("Project '%{project_name}' was successfully created.") % { project_name: @project.name }
       )
     else
-      render 'new', locals: { active_tab: ('import' if project_params[:import_url].present?) }
+      render 'new', locals: { active_tab: active_new_project_tab }
     end
   end
 
@@ -103,7 +103,7 @@ class ProjectsController < Projects::ApplicationController
 
   def show
     if @project.import_in_progress?
-      redirect_to project_import_path(@project)
+      redirect_to project_import_path(@project, custom_import_params)
       return
     end
 
@@ -130,7 +130,7 @@ class ProjectsController < Projects::ApplicationController
     return access_denied! unless can?(current_user, :remove_project, @project)
 
     ::Projects::DestroyService.new(@project, current_user, {}).async_execute
-    flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.name_with_namespace }
+    flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name }
 
     redirect_to dashboard_projects_path, status: 302
   rescue Projects::DestroyService::DestroyError => ex
@@ -324,7 +324,7 @@ class ProjectsController < Projects::ApplicationController
       :avatar,
       :build_allow_git_fetch,
       :build_coverage_regex,
-      :build_timeout_in_minutes,
+      :build_timeout_human_readable,
       :resolve_outdated_diff_discussions,
       :container_registry_enabled,
       :default_branch,
@@ -359,6 +359,14 @@ class ProjectsController < Projects::ApplicationController
     ]
   end
 
+  def custom_import_params
+    {}
+  end
+
+  def active_new_project_tab
+    project_params[:import_url].present? ? 'import' : 'blank'
+  end
+
   def repo_exists?
     project.repository_exists? && !project.empty_repo?
 
@@ -396,7 +404,7 @@ class ProjectsController < Projects::ApplicationController
     params[:namespace_id] = project.namespace.to_param
     params[:id] = project.to_param
 
-    url_for(params)
+    url_for(safe_params)
   end
 
   def project_export_enabled
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 8acefd58e7737e03fadc00f492e53111a60b57f1..651b82f04f435b8d9db1d2673599e251687e8a34 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -42,6 +42,10 @@ class RootController < Dashboard::ProjectsController
       redirect_to(dashboard_groups_path)
     when 'todos'
       redirect_to(dashboard_todos_path)
+    when 'issues'
+      redirect_to(issues_dashboard_path(assignee_id: current_user.id))
+    when 'merge_requests'
+      redirect_to(merge_requests_dashboard_path(assignee_id: current_user.id))
     end
   end
 
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index be2d3f638ffc0a4939f474148beb4dc59e648c86..3d51520ddf422a3bf5e80f68882a2e1d7d4f9b5f 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -6,6 +6,8 @@ class SnippetsController < ApplicationController
   include RendersBlob
   include PreviewMarkdown
 
+  skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
+
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
 
   # Allow read snippet
@@ -77,6 +79,8 @@ class SnippetsController < ApplicationController
       format.json do
         render_blob_json(blob)
       end
+
+      format.js { render 'shared/snippets/show' }
     end
   end
 
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 956df4a0a1687e44bfb714afa97a8992031d07ec..31f47a7aa7c21a3bc12b3d0b1d7a410d24919cc8 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -146,6 +146,6 @@ class UsersController < ApplicationController
   end
 
   def build_canonical_path(user)
-    url_for(params.merge(username: user.to_param))
+    url_for(safe_params.merge(username: user.to_param))
   end
 end
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
index d6bcd93952211e1c77fa3f2daedc32f6d3faf681..53b77f5fed93103246c66e3485aa66a5b9ea9d19 100644
--- a/app/finders/admin/projects_finder.rb
+++ b/app/finders/admin/projects_finder.rb
@@ -16,8 +16,8 @@ class Admin::ProjectsFinder
     items = by_archived(items)
     items = by_personal(items)
     items = by_name(items)
-    items = sort(items)
-    items.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
+    items = items.includes(namespace: [:owner])
+    sort(items).page(params[:page])
   end
 
   private
@@ -62,6 +62,6 @@ class Admin::ProjectsFinder
 
   def sort(items)
     sort = params.fetch(:sort) { 'latest_activity_desc' }
-    items.sort(sort)
+    items.sort_by_attribute(sort)
   end
 end
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 852eac3647d4c41e691f40eaec976cc22f347aaf..8bb1366867ceb70b2e0da764c30f66f32f58dc96 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -1,5 +1,5 @@
 class BranchesFinder
-  def initialize(repository, params)
+  def initialize(repository, params = {})
     @repository = repository
     @params = params
   end
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index e72fd8eb3a514ef614723593adf90d48de667b9d..051ea108e06f3dd621e4495413dbf1ab46f33b7e 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -134,10 +134,8 @@ class GroupDescendantsFinder
   end
 
   def direct_child_projects
-    GroupProjectsFinder.new(group: parent_group,
-                            current_user: current_user,
-                            options: { only_owned: true },
-                            params: params).execute
+    GroupProjectsFinder.new(group: parent_group, current_user: current_user, params: params)
+      .execute
   end
 
   # Finds all projects nested under `parent_group` or any of its descendant
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 9dd6634b38f1b2f0f9708d6d4caee4a26af44947..7ed9b1fc6d01f170253d896ed34de3f9f5a86101 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -19,6 +19,10 @@
 #     non_archived: boolean
 #     iids: integer[]
 #     my_reaction_emoji: string
+#     created_after: datetime
+#     created_before: datetime
+#     updated_after: datetime
+#     updated_before: datetime
 #
 class IssuableFinder
   prepend FinderWithCrossProjectAccess
@@ -79,6 +83,7 @@ class IssuableFinder
   def filter_items(items)
     items = by_scope(items)
     items = by_created_at(items)
+    items = by_updated_at(items)
     items = by_state(items)
     items = by_group(items)
     items = by_search(items)
@@ -154,7 +159,10 @@ class IssuableFinder
         finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
         GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
       else
-        ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
+        opts = { current_user: current_user }
+        opts[:project_ids_relation] = item_project_ids(items) if items
+
+        ProjectsFinder.new(opts).execute
       end
 
     @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
@@ -283,6 +291,13 @@ class IssuableFinder
     end
   end
 
+  def by_updated_at(items)
+    items = items.updated_after(params[:updated_after]) if params[:updated_after].present?
+    items = items.updated_before(params[:updated_before]) if params[:updated_before].present?
+
+    items
+  end
+
   def by_state(items)
     case params[:state].to_s
     when 'closed'
@@ -304,9 +319,9 @@ class IssuableFinder
   def by_project(items)
     items =
       if project?
-        items.of_projects(projects(items)).references_project
-      elsif projects(items)
-        items.merge(projects(items).reorder(nil)).join_project
+        items.of_projects(projects).references_project
+      elsif projects
+        items.merge(projects.reorder(nil)).join_project
       else
         items.none
       end
@@ -325,7 +340,7 @@ class IssuableFinder
   def sort(items)
     # Ensure we always have an explicit sort order (instead of inheriting
     # multiple orders when combining ActiveRecord::Relation objects).
-    params[:sort] ? items.sort(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
+    params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
   end
 
   def by_assignee(items)
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index d65c620e75ac1f012723ad48d3b6bcece089630a..2a27ff0e386066a316b86c7afe2ca0195f56c927 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -17,6 +17,10 @@
 #     my_reaction_emoji: string
 #     public_only: boolean
 #     due_date: date or '0', '', 'overdue', 'week', or 'month'
+#     created_after: datetime
+#     created_before: datetime
+#     updated_after: datetime
+#     updated_before: datetime
 #
 class IssuesFinder < IssuableFinder
   CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 780c0fdb03eb806962c2e1dd1e5fb2e76bdb1169..afd1f824b3251dfdb5831d0f4d613d99ac05182e 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -28,9 +28,10 @@ class LabelsFinder < UnionFinder
       if project
         if project.group.present?
           labels_table = Label.arel_table
+          group_ids = group_ids_for(project.group)
 
           label_ids << Label.where(
-            labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].eq(project.group.id)).or(
+            labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].in(group_ids)).or(
               labels_table[:type].eq('ProjectLabel').and(labels_table[:project_id].eq(project.id))
             )
           )
@@ -38,11 +39,14 @@ class LabelsFinder < UnionFinder
           label_ids << project.labels
         end
       end
-    elsif only_group_labels?
-      label_ids << Label.where(group_id: group_ids)
     else
+      if group?
+        group = Group.find(params[:group_id])
+        label_ids << Label.where(group_id: group_ids_for(group))
+      end
+
       label_ids << Label.where(group_id: projects.group_ids)
-      label_ids << Label.where(project_id: projects.select(:id))
+      label_ids << Label.where(project_id: projects.select(:id)) unless only_group_labels?
     end
 
     label_ids
@@ -59,22 +63,33 @@ class LabelsFinder < UnionFinder
     items.where(title: title)
   end
 
-  def group_ids
+  # Gets redacted array of group ids
+  # which can include the ancestors and descendants of the requested group.
+  def group_ids_for(group)
     strong_memoize(:group_ids) do
-      groups_user_can_read_labels(groups_to_include).map(&:id)
+      groups = groups_to_include(group)
+
+      groups_user_can_read_labels(groups).map(&:id)
     end
   end
 
-  def groups_to_include
-    group = Group.find(params[:group_id])
+  def groups_to_include(group)
     groups = [group]
 
-    groups += group.ancestors if params[:include_ancestor_groups].present?
-    groups += group.descendants if params[:include_descendant_groups].present?
+    groups += group.ancestors if include_ancestor_groups?
+    groups += group.descendants if include_descendant_groups?
 
     groups
   end
 
+  def include_ancestor_groups?
+    params[:include_ancestor_groups]
+  end
+
+  def include_descendant_groups?
+    params[:include_descendant_groups]
+  end
+
   def group?
     params[:group_id].present?
   end
diff --git a/app/finders/merge_request_target_project_finder.rb b/app/finders/merge_request_target_project_finder.rb
index f358938344ed55c3d4398945c0cee940ca7b84ca..188ec447a94e085f21a1200d5c9d6821407f3fa5 100644
--- a/app/finders/merge_request_target_project_finder.rb
+++ b/app/finders/merge_request_target_project_finder.rb
@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
     if @source_project.fork_network
       @source_project.fork_network.projects
         .public_or_visible_to_user(current_user)
+        .non_archived
         .with_feature_available_for_user(:merge_requests, current_user)
     else
       Project.where(id: source_project)
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 068ae7f8c897f366977be57a0bd10fcf72dbf7d6..64dc1e6af0f0a10933fb4222714813dd0e361a30 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -19,6 +19,10 @@
 #     my_reaction_emoji: string
 #     source_branch: string
 #     target_branch: string
+#     created_after: datetime
+#     created_before: datetime
+#     updated_after: datetime
+#     updated_before: datetime
 #
 class MergeRequestsFinder < IssuableFinder
   def klass
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 33ee1e975b95815e9c8d6b297ed705d0ccd3a13e..35f4ff2f62f9cfae3d7ab6f9d0efcf80ab8b56cb 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -48,11 +48,23 @@ class NotesFinder
   def init_collection
     if target
       notes_on_target
+    elsif target_type
+      notes_of_target_type
     else
       notes_of_any_type
     end
   end
 
+  def notes_of_target_type
+    notes = notes_for_type(target_type)
+
+    search(notes)
+  end
+
+  def target_type
+    @params[:target_type]
+  end
+
   def notes_of_any_type
     types = %w(commit issue merge_request snippet)
     note_relations = types.map { |t| notes_for_type(t) }
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 005612ededc8d0120235c3896b767221d6562b39..c7d6bc6cfdcf6ed9e52d4218fb05b9be5eccedb8 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -124,7 +124,7 @@ class ProjectsFinder < UnionFinder
   end
 
   def sort(items)
-    params[:sort].present? ? items.sort(params[:sort]) : items.order_id_desc
+    params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
   end
 
   def by_archived(projects)
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index edb17843002878645027c91b7ab8c4ba3401ad80..09e2c586f2a10053d833a0633e38c5554e05cf41 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -110,10 +110,6 @@ class TodosFinder
     ids
   end
 
-  def projects(items)
-    ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute
-  end
-
   def type?
     type.present? && %w(Issue MergeRequest).include?(type)
   end
@@ -123,7 +119,7 @@ class TodosFinder
   end
 
   def sort(items)
-    params[:sort] ? items.sort(params[:sort]) : items.order_id_desc
+    params[:sort] ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
   end
 
   def by_action(items)
@@ -152,13 +148,12 @@ class TodosFinder
 
   def by_project(items)
     if project?
-      items = items.where(project: project)
+      items.where(project: project)
     else
-      item_projects = projects(items)
-      items = items.merge(item_projects).joins(:project)
-    end
+      projects = Project.public_or_visible_to_user(current_user)
 
-    items
+      items.joins(:project).merge(projects)
+    end
   end
 
   def by_state(items)
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 6f7f7c30d92a211db7bcd3bf5471a461a7782aee..65d6e0197469bf5e0fcc0a5da93e8d8a813298ee 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -12,6 +12,8 @@ class UserRecentEventsFinder
 
   attr_reader :current_user, :target_user, :params
 
+  LIMIT = 20
+
   def initialize(current_user, target_user, params = {})
     @current_user = current_user
     @target_user = target_user
@@ -19,15 +21,44 @@ class UserRecentEventsFinder
   end
 
   def execute
-    target_user
-      .recent_events
-      .merge(projects_for_current_user)
-      .references(:project)
+    recent_events(params[:offset] || 0)
+      .joins(:project)
       .with_associations
-      .limit_recent(20, params[:offset])
+      .limit_recent(LIMIT, params[:offset])
+  end
+
+  private
+
+  def recent_events(offset)
+    sql = <<~SQL
+      (#{projects}) AS projects_for_join
+      JOIN (#{target_events.to_sql}) AS #{Event.table_name}
+        ON #{Event.table_name}.project_id = projects_for_join.id
+    SQL
+
+    # Workaround for https://github.com/rails/rails/issues/24193
+    Event.from([Arel.sql(sql)])
   end
 
-  def projects_for_current_user
-    ProjectsFinder.new(current_user: current_user).execute
+  def target_events
+    Event.where(author: target_user)
+  end
+
+  def projects
+    # Compile a list of projects `current_user` interacted with
+    # and `target_user` is allowed to see.
+
+    authorized = target_user
+      .project_interactions
+      .joins(:project_authorizations)
+      .where(project_authorizations: { user: current_user })
+      .select(:id)
+
+    visible = target_user
+      .project_interactions
+      .where(visibility_level: [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC])
+      .select(:id)
+
+    Gitlab::SQL::Union.new([authorized, visible]).to_sql
   end
 end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index c037de33c22fc27abcf81a1b808854edc2071d71..f48db024e3fdc0f6d054a32056e98b05b4396fd1 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -1,27 +1,27 @@
 module AppearancesHelper
   def brand_title
-    brand_item&.title.presence || 'GitLab Community Edition'
+    current_appearance&.title.presence || 'GitLab Community Edition'
   end
 
   def brand_image
-    image_tag(brand_item.logo) if brand_item&.logo?
+    image_tag(current_appearance.logo) if current_appearance&.logo?
   end
 
   def brand_text
-    markdown_field(brand_item, :description)
+    markdown_field(current_appearance, :description)
   end
 
   def brand_new_project_guidelines
-    markdown_field(brand_item, :new_project_guidelines)
+    markdown_field(current_appearance, :new_project_guidelines)
   end
 
-  def brand_item
+  def current_appearance
     @appearance ||= Appearance.current
   end
 
   def brand_header_logo
-    if brand_item&.header_logo?
-      image_tag brand_item.header_logo
+    if current_appearance&.header_logo?
+      image_tag current_appearance.header_logo
     else
       render 'shared/logo.svg'
     end
@@ -29,7 +29,7 @@ module AppearancesHelper
 
   # Skip the 'GitLab' type logo when custom brand logo is set
   def brand_header_logo_type
-    unless brand_item&.header_logo?
+    unless current_appearance&.header_logo?
       render 'shared/logo_type.svg'
     end
   end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 475341cf9b185a4899a9b1a12701b3f1463704aa..228c8d2e8f980dfe1140d369fb2df9050081dae1 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -228,9 +228,7 @@ module ApplicationHelper
       scope: params[:scope],
       milestone_title: params[:milestone_title],
       assignee_id: params[:assignee_id],
-      assignee_username: params[:assignee_username],
       author_id: params[:author_id],
-      author_username: params[:author_username],
       search: params[:search],
       label_name: params[:label_name]
     }
@@ -285,6 +283,10 @@ module ApplicationHelper
     class_names
   end
 
+  # EE feature: System header and footer, unavailable in CE
+  def system_message_class
+  end
+
   # Returns active css class when condition returns true
   # otherwise returns nil.
   #
@@ -300,7 +302,7 @@ module ApplicationHelper
 
   def linkedin_url(user)
     name = user.linkedin
-    if name =~ %r{\Ahttps?:\/\/(www\.)?linkedin\.com\/in\/}
+    if name =~ %r{\Ahttps?://(www\.)?linkedin\.com/in/}
       name
     else
       "https://www.linkedin.com/in/#{name}"
@@ -309,10 +311,10 @@ module ApplicationHelper
 
   def twitter_url(user)
     name = user.twitter
-    if name =~ %r{\Ahttps?:\/\/(www\.)?twitter\.com\/}
+    if name =~ %r{\Ahttps?://(www\.)?twitter\.com/}
       name
     else
-      "https://www.twitter.com/#{name}"
+      "https://twitter.com/#{name}"
     end
   end
 
@@ -320,11 +322,14 @@ module ApplicationHelper
     cookies["sidebar_collapsed"] == "true"
   end
 
-  def show_new_ide?
-    cookies["new_repo"] == "true" && body_data_page != 'projects:show'
-  end
-
   def locale_path
     asset_path("locale/#{Gitlab::I18n.locale}/app.js")
   end
+
+  # Overridden in EE
+  def read_only_message
+    return unless Gitlab::Database.read_only?
+
+    _('You are on a read-only GitLab instance.')
+  end
 end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 4c4d7cca8a5ad21360ab97f91a67d98abe2fc533..3fbb32c52292286f8cbdc7e5673e56bda30efbac 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -74,10 +74,12 @@ module ApplicationSettingsHelper
       css_class = 'btn'
       css_class << ' active' unless disabled
       checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
+      name = Gitlab::Auth::OAuth::Provider.label_for(source)
 
       label_tag(checkbox_name, class: css_class) do
         check_box_tag(checkbox_name, source, !disabled,
-                      autocomplete: 'off') + Gitlab::Auth::OAuth::Provider.label_for(source)
+                      autocomplete: 'off',
+                      id: name.tr(' ', '_')) + name
       end
     end
   end
@@ -96,7 +98,7 @@ module ApplicationSettingsHelper
 
   def repository_storages_options_for_select(selected)
     options = Gitlab.config.repositories.storages.map do |name, storage|
-      ["#{name} - #{storage['path']}", name]
+      ["#{name} - #{storage['gitaly_address']}", name]
     end
 
     options_for_select(options, selected)
@@ -245,7 +247,8 @@ module ApplicationSettingsHelper
       :usage_ping_enabled,
       :user_default_external,
       :user_oauth_applications,
-      :version_check_enabled
+      :version_check_enabled,
+      :allow_local_requests_from_hooks_and_services
     ]
   end
 end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 0e806d16bc55f27e18723c56b78bcf678389c46a..fef29789832d6d09ee0464ce868e3cdbe4f28991 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -34,13 +34,10 @@ module BlobHelper
   end
 
   def ide_edit_button(project = @project, ref = @ref, path = @path, options = {})
-    return unless show_new_ide?
     return unless blob = readable_blob(options, path, project, ref)
 
-    common_classes = "btn js-edit-ide #{options[:extra_class]}"
-
     edit_button_tag(blob,
-                    common_classes,
+                    'btn btn-default',
                     _('Web IDE'),
                     ide_edit_path(project, ref, path, options),
                     project,
@@ -62,7 +59,7 @@ module BlobHelper
       button_tag label, class: "#{common_classes} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
     elsif can_modify_blob?(blob, project, ref)
       button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
-    elsif can?(current_user, :fork_project, project)
+    elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
       edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
     end
   end
@@ -262,7 +259,7 @@ module BlobHelper
     options = []
 
     if error == :collapsed
-      options << link_to('load it anyway', url_for(params.merge(viewer: viewer.type, expanded: true, format: nil)))
+      options << link_to('load it anyway', url_for(safe_params.merge(viewer: viewer.type, expanded: true, format: nil)))
     end
 
     # If the error is `:server_side_but_stored_externally`, the simple viewer will show the same error,
@@ -283,7 +280,7 @@ module BlobHelper
       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))
+    merge_project = merge_request_source_project_for_project(@project)
     if merge_project
       options << link_to("create a merge request", project_new_merge_request_path(project))
     end
@@ -337,7 +334,7 @@ module BlobHelper
       # Web IDE (Beta) requires the user to have this feature enabled
     elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
       edit_link_tag(text, edit_path, common_classes)
-    elsif current_user && can?(current_user, :fork_project, project)
+    elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
       edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
     end
   end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 12b3d9bac1aabbbbd26c0fe09af18c09840da0eb..af878bcf9a0450a77ca9e6067c6c436dce2605ca 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -17,23 +17,35 @@ module BoardsHelper
   end
 
   def build_issue_link_base
-    project_issues_path(@project)
+    if board.group_board?
+      "#{group_path(@board.group)}/:project_path/issues"
+    else
+      project_issues_path(@project)
+    end
   end
 
   def board_base_url
-    project_boards_path(@project)
+    if board.group_board?
+      group_boards_url(@group)
+    else
+      project_boards_path(@project)
+    end
   end
 
   def multiple_boards_available?
-    current_board_parent.multiple_issue_boards_available?(current_user)
+    current_board_parent.multiple_issue_boards_available?
   end
 
   def current_board_path(board)
-    @current_board_path ||= project_board_path(current_board_parent, board)
+    @current_board_path ||= if board.group_board?
+                              group_board_path(current_board_parent, board)
+                            else
+                              project_board_path(current_board_parent, board)
+                            end
   end
 
   def current_board_parent
-    @current_board_parent ||= @project
+    @current_board_parent ||= @group || @project
   end
 
   def can_admin_issue?
@@ -41,13 +53,16 @@ module BoardsHelper
   end
 
   def board_list_data
+    include_descendant_groups = @group&.present?
+
     {
       toggle: "dropdown",
-      list_labels_path: labels_filter_path(true),
-      labels: labels_filter_path(true),
+      list_labels_path: labels_filter_path(true, include_ancestor_groups: true),
+      labels: labels_filter_path(true, include_descendant_groups: include_descendant_groups),
       labels_endpoint: @labels_endpoint,
       namespace_path: @namespace_path,
-      project_path: @project&.try(:path)
+      project_path: @project&.path,
+      group_path: @group&.path
     }
   end
 
@@ -59,7 +74,8 @@ module BoardsHelper
       field_name: 'issue[assignee_ids][]',
       first_user: current_user&.username,
       current_user: 'true',
-      project_id: @project&.try(:id),
+      project_id: @project&.id,
+      group_id: @group&.id,
       null_user: 'true',
       multi_select: 'true',
       'dropdown-header': dropdown_options[:data][:'dropdown-header'],
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 00b9a0e00ebe1c63de13f5e074ffd46e7367d5cb..07b1fc3d7cfce9a5b4e268be66c0734291cc7344 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -1,15 +1,4 @@
 module BranchesHelper
-  def filter_branches_path(options = {})
-    exist_opts = {
-      search: params[:search],
-      sort: params[:sort]
-    }
-
-    options = exist_opts.merge(options)
-
-    project_branches_path(@project, @id, options)
-  end
-
   def project_branches
     options_for_select(@project.repository.branch_names, @project.default_branch)
   end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 636316da80a2c51f1149da841d614061cdd3e5f5..f0afcac59861676f918d5dc667398a75d872e035 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -94,7 +94,7 @@ module CiStatusHelper
 
   def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
     project = pipeline_status.project
-    path = pipelines_project_commit_path(project, pipeline_status.sha)
+    path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref)
 
     render_status_with_link(
       'commit',
@@ -105,7 +105,7 @@ module CiStatusHelper
 
   def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
     project = commit.project
-    path = pipelines_project_commit_path(project, commit)
+    path = pipelines_project_commit_path(project, commit, ref: ref)
 
     render_status_with_link(
       'commit',
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 0333c29e2fd7dc1cd5e06246174e5f25e95255af..98894b8655168145b21d1d43195690200b9c8f51 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -93,25 +93,18 @@ module CommitsHelper
     return unless current_controller?(:commits)
 
     if @path.blank?
-      return link_to(
-        _("Browse Files"),
-        project_tree_path(project, commit),
-        class: "btn btn-default"
-      )
+      url = project_tree_path(project, commit)
+      tooltip = _("Browse Files")
     elsif @repo.blob_at(commit.id, @path)
-      return link_to(
-        _("Browse File"),
-        project_blob_path(project,
-                                    tree_join(commit.id, @path)),
-        class: "btn btn-default"
-      )
+      url = project_blob_path(project, tree_join(commit.id, @path))
+      tooltip = _("Browse File")
     elsif @path.present?
-      return link_to(
-        _("Browse Directory"),
-        project_tree_path(project,
-                                    tree_join(commit.id, @path)),
-        class: "btn btn-default"
-      )
+      url = project_tree_path(project, tree_join(commit.id, @path))
+      tooltip = _("Browse Directory")
+    end
+
+    link_to url, class: "btn btn-default has-tooltip", title: tooltip, data: { container: "body" } do
+      sprite_icon('folder-open')
     end
   end
 
@@ -170,7 +163,7 @@ module CommitsHelper
     tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
     btn_class = "btn btn-#{btn_class}" unless btn_class.nil?
 
-    if can_collaborate_with_project?
+    if can_collaborate_with_project?(@project)
       link_to action.capitalize, "#modal-#{action}-commit", 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
     elsif can?(current_user, :fork_project, @project)
       continue_params = {
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 8bf96c0905ff61f39415388e4183fbc52df5c904..2df5b5d1695f151e362a6b0aca3df6fb4839de3b 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -3,7 +3,7 @@ module CompareHelper
     from.present? &&
       to.present? &&
       from != to &&
-      can?(current_user, :create_merge_request, project) &&
+      can?(current_user, :create_merge_request_from, project) &&
       project.repository.branch_exists?(from) &&
       project.repository.branch_exists?(to)
   end
diff --git a/app/helpers/deploy_tokens_helper.rb b/app/helpers/deploy_tokens_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bd921322476bcbada18eb5ec8538ea61a565389f
--- /dev/null
+++ b/app/helpers/deploy_tokens_helper.rb
@@ -0,0 +1,12 @@
+module DeployTokensHelper
+  def expand_deploy_tokens_section?(deploy_token)
+    deploy_token.persisted? ||
+      deploy_token.errors.present? ||
+      Rails.env.test?
+  end
+
+  def container_registry_enabled?(project)
+    Gitlab.config.registry.enabled &&
+      can?(current_user, :read_container_image, project)
+  end
+end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index b5ca39711bc3076830542647dfc3d5fbeb9950dc..1bb82fd81503831b212b2e5549f5ebaaa70695f0 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -180,7 +180,7 @@ module DiffHelper
   private
 
   def diff_btn(title, name, selected)
-    params_copy = params.dup
+    params_copy = safe_params.dup
     params_copy[:view] = name
 
     # Always use HTML to handle case where JSON diff rendered this button
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 4ddc1dbed49b30836f3484d844111b07c1520d8c..c86a26ac30ffeec53f87a678a6e009449d5316d5 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -54,9 +54,9 @@ module EmailsHelper
   end
 
   def header_logo
-    if brand_item && brand_item.header_logo?
+    if current_appearance&.header_logo?
       image_tag(
-        brand_item.header_logo,
+        current_appearance.header_logo,
         style: 'height: 50px'
       )
     else
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index e26ce6da03059272b8267b21e652be903a6e6cd7..905e2002592ee5c2166fddc1c3ff171dbceb2d52 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -27,7 +27,7 @@ module FormHelper
         first_user: current_user&.username,
         null_user: true,
         current_user: true,
-        project_id: @project.id,
+        project_id: @project&.id,
         field_name: 'issue[assignee_ids][]',
         default_label: 'Unassigned',
         'max-select': 1,
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 7910de73c52c064cfc0f36abd43b52eedd17f018..95fea2f18d159c9245a05371be98406c5e31adc4 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,6 +1,6 @@
 module GroupsHelper
   def group_nav_link_paths
-    %w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
+    %w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
   end
 
   def group_sidebar_links
@@ -129,7 +129,7 @@ module GroupsHelper
     links = [:overview, :group_members]
 
     if can?(current_user, :read_cross_project)
-      links += [:activity, :issues, :labels, :milestones, :merge_requests]
+      links += [:activity, :issues, :boards, :labels, :milestones, :merge_requests]
     end
 
     if can?(current_user, :admin_group, @group)
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index c5522ff7a69049a5b887bb02ccb2c38c8ffb14ba..2f304b040c7a32409fb09af4483fb5712d07288a 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -43,6 +43,10 @@ module IconsHelper
     content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
   end
 
+  def external_snippet_icon(name)
+    content_tag(:span, "", class: "gl-snippet-icon gl-snippet-icon-#{name}")
+  end
+
   def audit_icon(names, options = {})
     case names
     when "standard"
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index a18ebfb6030b4025031e21bae8d37975d1df39fe..4664b1728c400780f00d1f0dc93ef31d32022822 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -1,29 +1,94 @@
 module ImportHelper
+  include ::Gitlab::Utils::StrongMemoize
+
+  def has_ci_cd_only_params?
+    false
+  end
+
   def import_project_target(owner, name)
     namespace = current_user.can_create_group? ? owner : current_user.namespace_path
     "#{namespace}/#{name}"
   end
 
-  def provider_project_link(provider, path_with_namespace)
-    url = __send__("#{provider}_project_url", path_with_namespace) # rubocop:disable GitlabSecurity/PublicSend
+  def provider_project_link(provider, full_path)
+    url = __send__("#{provider}_project_url", full_path) # rubocop:disable GitlabSecurity/PublicSend
+
+    link_to full_path, url, target: '_blank', rel: 'noopener noreferrer'
+  end
+
+  def import_will_timeout_message(_ci_cd_only)
+    timeout = time_interval_in_words(Gitlab.config.gitlab_shell.git_timeout)
+    _('The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination.') % { timeout: timeout }
+  end
+
+  def import_svn_message(_ci_cd_only)
+    svn_link = link_to _('this document'), help_page_path('user/project/import/svn')
+    _('To import an SVN repository, check out %{svn_link}.').html_safe % { svn_link: svn_link }
+  end
+
+  def import_in_progress_title
+    if @project.forked?
+      _('Forking in progress')
+    else
+      _('Import in progress')
+    end
+  end
+
+  def import_wait_and_refresh_message
+    _('Please wait while we import the repository for you. Refresh at will.')
+  end
+
+  def import_github_title
+    _('Import repositories from GitHub')
+  end
+
+  def import_github_authorize_message
+    _('To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:')
+  end
+
+  def import_github_personal_access_token_message
+    personal_access_token_link = link_to _('Personal Access Token'), 'https://github.com/settings/tokens'
+
+    if github_import_configured?
+      _('Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import.').html_safe % { personal_access_token_link: personal_access_token_link }
+    else
+      _('To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import.').html_safe % { personal_access_token_link: personal_access_token_link }
+    end
+  end
+
+  def import_configure_github_admin_message
+    github_integration_link = link_to 'GitHub integration', help_page_path('integration/github')
+
+    if current_user.admin?
+      _('Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token.').html_safe % { github_integration_link: github_integration_link }
+    else
+      _('Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token.').html_safe % { github_integration_link: github_integration_link }
+    end
+  end
+
+  def import_githubish_choose_repository_message
+    _('Choose which repositories you want to import.')
+  end
 
-    link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer'
+  def import_all_githubish_repositories_button_label
+    _('Import all repositories')
   end
 
   private
 
-  def github_project_url(path_with_namespace)
-    "#{github_root_url}/#{path_with_namespace}"
+  def github_project_url(full_path)
+    URI.join(github_root_url, full_path).to_s
   end
 
   def github_root_url
-    return @github_url if defined?(@github_url)
+    strong_memoize(:github_url) do
+      provider = Gitlab::Auth::OAuth::Provider.config_for('github')
 
-    provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' }
-    @github_url = provider.fetch('url', 'https://github.com') if provider
+      provider&.dig('url').presence || 'https://github.com'
+    end
   end
 
-  def gitea_project_url(path_with_namespace)
-    "#{@gitea_host_url.sub(%r{/+\z}, '')}/#{path_with_namespace}"
+  def gitea_project_url(full_path)
+    URI.join(@gitea_host_url, full_path).to_s
   end
 end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 44ecc2212f2a41103d468812ba57a41049a80364..f39a62bccc8f87c17fb74b0a59f2a36ae3b01c94 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -9,6 +9,32 @@ module IssuablesHelper
     "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
   end
 
+  def sidebar_gutter_tooltip_text
+    sidebar_gutter_collapsed? ? _('Expand sidebar') : _('Collapse sidebar')
+  end
+
+  def sidebar_assignee_tooltip_label(issuable)
+    if issuable.assignee
+      issuable.assignee.name
+    else
+      issuable.allows_multiple_assignees? ? _('Assignee(s)') : _('Assignee')
+    end
+  end
+
+  def sidebar_due_date_tooltip_label(issuable)
+    if issuable.due_date
+      "#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
+    else
+      _('Due date')
+    end
+  end
+
+  def due_date_remaining_days(issuable)
+    remaining_days_in_words = remaining_days_in_words(issuable)
+
+    "#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})"
+  end
+
   def multi_label_name(current_labels, default_label)
     if current_labels && current_labels.any?
       title = current_labels.first.try(:title)
@@ -99,7 +125,7 @@ module IssuablesHelper
     project = Project.find_by(id: project_id)
 
     if project
-      project.name_with_namespace
+      project.full_name
     else
       default_label
     end
@@ -153,22 +179,28 @@ module IssuablesHelper
   def issuable_labels_tooltip(labels, limit: 5)
     first, last = labels.partition.with_index { |_, i| i < limit  }
 
-    label_names = first.collect(&:name)
-    label_names << "and #{last.size} more" unless last.empty?
+    if labels && labels.any?
+      label_names = first.collect(&:name)
+      label_names << "and #{last.size} more" unless last.empty?
 
-    label_names.join(', ')
+      label_names.join(', ')
+    else
+      _("Labels")
+    end
   end
 
-  def issuables_state_counter_text(issuable_type, state)
+  def issuables_state_counter_text(issuable_type, state, display_count)
     titles = {
       opened: "Open"
     }
 
     state_title = titles[state] || state.to_s.humanize
-    count = issuables_count_for_state(issuable_type, state)
-
     html = content_tag(:span, state_title)
-    html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
+
+    if display_count
+      count = issuables_count_for_state(issuable_type, state)
+      html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
+    end
 
     html.html_safe
   end
@@ -191,24 +223,10 @@ module IssuablesHelper
     end
   end
 
-  def issuable_filter_params
-    [
-      :search,
-      :author_id,
-      :assignee_id,
-      :milestone_title,
-      :label_name
-    ]
-  end
-
   def issuable_reference(issuable)
     @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
   end
 
-  def issuable_filter_present?
-    issuable_filter_params.any? { |k| params.key?(k) }
-  end
-
   def issuable_initial_data(issuable)
     data = {
       endpoint: issuable_path(issuable),
@@ -333,7 +351,7 @@ module IssuablesHelper
   def issuable_todo_button_data(issuable, todo, is_collapsed)
     {
       todo_text: "Add todo",
-      mark_text: "Mark done",
+      mark_text: "Mark todo as done",
       todo_icon: (is_collapsed ? icon('plus-square') : nil),
       mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
       issuable_id: issuable.id,
@@ -377,4 +395,11 @@ module IssuablesHelper
   def parent
     @project || @group
   end
+
+  def issuable_milestone_tooltip_title(issuable)
+    if issuable.milestone
+      milestone_tooltip = milestone_tooltip_title(issuable.milestone)
+      _('Milestone') + (milestone_tooltip ? ': ' + milestone_tooltip : '')
+    end
+  end
 end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 0f25d4014060eb3720eb49f68ec3fb7dd2ebefc4..96dc7ae1185c7f4136594f0324eb07fdb399fb1e 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -82,8 +82,8 @@ module IssuesHelper
     names.to_sentence
   end
 
-  def award_state_class(awards, current_user)
-    if !current_user
+  def award_state_class(awardable, awards, current_user)
+    if !can?(current_user, :award_emoji, awardable)
       "disabled"
     elsif current_user && awards.find { |a| a.user_id == current_user.id }
       "active"
@@ -126,6 +126,17 @@ module IssuesHelper
     link_to link_text, path
   end
 
+  def show_new_issue_link?(project)
+    return false unless project
+    return false if project.archived?
+
+    # We want to show the link to users that are not signed in, that way they
+    # get directed to the sign-in/sign-up flow and afterwards to the new issue page.
+    return true unless current_user
+
+    can?(current_user, :create_issue, project)
+  end
+
   # Required for Banzai::Filter::IssueReferenceFilter
   module_function :url_for_issue
   module_function :url_for_internal_issue
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
index d5e77c7e271e315c0d7cea4ce4b6994c5d2a32dc..cd4075b340d77756071ba2a6d873a2dad84eea6e 100644
--- a/app/helpers/javascript_helper.rb
+++ b/app/helpers/javascript_helper.rb
@@ -2,9 +2,4 @@ module JavascriptHelper
   def page_specific_javascript_tag(js)
     javascript_include_tag asset_path(js)
   end
-
-  # deprecated; use webpack_bundle_tag directly instead
-  def page_specific_javascript_bundle_tag(bundle)
-    webpack_bundle_tag(bundle)
-  end
 end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index c1c19062c917718bcaf29f57aa6fd7fef4c49fc5..c4a6a1e4bb3977d4227127f5bccd1e1480f9891f 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -1,4 +1,5 @@
 module LabelsHelper
+  extend self
   include ActionView::Helpers::TagHelper
 
   def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
@@ -128,13 +129,17 @@ module LabelsHelper
     end
   end
 
-  def labels_filter_path(only_group_labels = false)
+  def labels_filter_path(only_group_labels = false, include_ancestor_groups: true, include_descendant_groups: false)
     project = @target_project || @project
 
+    options = {}
+    options[:include_ancestor_groups] = include_ancestor_groups if include_ancestor_groups
+    options[:include_descendant_groups] = include_descendant_groups if include_descendant_groups
+
     if project
-      project_labels_path(project, :json)
+      project_labels_path(project, :json, options)
     elsif @group
-      options = { only_group_labels: only_group_labels } if only_group_labels
+      options[:only_group_labels] = only_group_labels if only_group_labels
       group_labels_path(@group, :json, options)
     else
       dashboard_labels_path(:json)
@@ -173,6 +178,39 @@ module LabelsHelper
     end
   end
 
+  def create_label_title(subject)
+    case subject
+    when Group
+      _('Create group label')
+    when Project
+      _('Create project label')
+    else
+      _('Create new label')
+    end
+  end
+
+  def manage_labels_title(subject)
+    case subject
+    when Group
+      _('Manage group labels')
+    when Project
+      _('Manage project labels')
+    else
+      _('Manage labels')
+    end
+  end
+
+  def view_labels_title(subject)
+    case subject
+    when Group
+      _('View group labels')
+    when Project
+      _('View project labels')
+    else
+      _('View labels')
+    end
+  end
+
   # Required for Banzai::Filter::LabelReferenceFilter
   module_function :render_colored_label, :text_color_for_bg, :escape_once
 end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 2fe1927a1892932fb686292bffbc8701798a0d20..39e7a7fd396576b0f34ece3f27f7f4bbab9c4776 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -256,7 +256,7 @@ module MarkupHelper
     return '' unless html.present?
 
     context.merge!(
-      current_user:   (current_user if defined?(current_user)),
+      current_user: (current_user if defined?(current_user)),
 
       # RelativeLinkFilter
       commit:         @commit,
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index ce57422f45d023d84f75795792ee3f6f4e7dfeb5..c19c5b9cc82f1f38369f18fdda2ed777d9ad3fff 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -125,6 +125,31 @@ module MergeRequestsHelper
     link_to(url[merge_request.project, merge_request], data: data_attrs, &block)
   end
 
+  def allow_maintainer_push_unavailable_reason(merge_request)
+    return if merge_request.can_allow_maintainer_to_push?(current_user)
+
+    minimum_visibility = [merge_request.target_project.visibility_level,
+                          merge_request.source_project.visibility_level].min
+
+    if minimum_visibility < Gitlab::VisibilityLevel::INTERNAL
+      _('Not available for private projects')
+    elsif ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch)
+      _('Not available for protected branches')
+    end
+  end
+
+  def merge_request_source_project_for_project(project = @project)
+    unless can?(current_user, :create_merge_request_in, project)
+      return nil
+    end
+
+    if can?(current_user, :create_merge_request_from, project)
+      project
+    else
+      current_user.fork_of(project)
+    end
+  end
+
   def merge_params_ee(merge_request)
     {}
   end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index be8cb358de2d49e25b61a356744f03a763d23815..e8caab3e50cff0fe4689b94590eaf08dc397b75e 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -1,4 +1,6 @@
 module MilestonesHelper
+  include EntityDateHelper
+
   def milestones_filter_path(opts = {})
     if @project
       project_milestones_path(@project, opts)
@@ -72,6 +74,19 @@ module MilestonesHelper
     end
   end
 
+  def milestone_progress_tooltip_text(milestone)
+    has_issues = milestone.total_issues_count(current_user) > 0
+
+    if has_issues
+      [
+        _('Progress'),
+        _("%{percent}%% complete") % { percent: milestone.percent_complete(current_user) }
+      ].join('<br />')
+    else
+      _('Progress')
+    end
+  end
+
   def milestone_progress_bar(milestone)
     options = {
       class: 'progress-bar progress-bar-success',
@@ -95,27 +110,69 @@ module MilestonesHelper
   end
 
   def milestone_tooltip_title(milestone)
-    if milestone.due_date
-      [milestone.due_date.to_s(:medium), "(#{milestone_remaining_days(milestone)})"].join(' ')
+    if milestone
+      "#{milestone.title}<br />#{milestone_tooltip_due_date(milestone)}"
+    else
+      _('Milestone')
     end
   end
 
-  def milestone_remaining_days(milestone)
-    if milestone.expired?
-      content_tag(:strong, 'Past due')
-    elsif milestone.upcoming?
-      content_tag(:strong, 'Upcoming')
-    elsif milestone.due_date
-      time_ago = time_ago_in_words(milestone.due_date)
-      content = time_ago.gsub(/\d+/) { |match| "<strong>#{match}</strong>" }
-      content.slice!("about ")
-      content << " remaining"
-      content.html_safe
-    elsif milestone.start_date && milestone.start_date.past?
-      days    = milestone.elapsed_days
-      content = content_tag(:strong, days)
-      content << " #{'day'.pluralize(days)} elapsed"
+  def milestone_time_for(date, date_type)
+    title = date_type == :start ? "Start date" : "End date"
+
+    if date
+      time_ago = time_ago_in_words(date)
+      time_ago.slice!("about ")
+
+      time_ago << if date.past?
+                    " ago"
+                  else
+                    " remaining"
+                  end
+
+      content = [
+        title,
+        "<br />",
+        date.to_s(:medium),
+        "(#{time_ago})"
+      ].join(" ")
+
       content.html_safe
+    else
+      title
+    end
+  end
+
+  def milestone_issues_tooltip_text(milestone)
+    issues = milestone.count_issues_by_state(current_user)
+
+    return _("Issues") if issues.empty?
+
+    content = []
+
+    content << n_("1 open issue", "%d open issues", issues["opened"]) % issues["opened"] if issues["opened"]
+    content << n_("1 closed issue", "%d closed issues", issues["closed"]) % issues["closed"] if issues["closed"]
+
+    content.join('<br />').html_safe
+  end
+
+  def milestone_merge_requests_tooltip_text(milestone)
+    merge_requests = milestone.merge_requests
+
+    return _("Merge requests") if merge_requests.empty?
+
+    content = []
+
+    content << n_("1 open merge request", "%d open merge requests", merge_requests.opened.count) % merge_requests.opened.count if merge_requests.opened.any?
+    content << n_("1 closed merge request", "%d closed merge requests", merge_requests.closed.count) % merge_requests.closed.count if merge_requests.closed.any?
+    content << n_("1 merged merge request", "%d merged merge requests", merge_requests.merged.count) % merge_requests.merged.count if merge_requests.merged.any?
+
+    content.join('<br />').html_safe
+  end
+
+  def milestone_tooltip_due_date(milestone)
+    if milestone.due_date
+      "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone)})"
     end
   end
 
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 40ca666f1bf5985e2e31c073a59fd954945455ca..9be93fa69ae41dbc1109932c4a6065736b100719 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -31,7 +31,7 @@ module NamespacesHelper
 
   def namespace_icon(namespace, size = 40)
     if namespace.is_a?(Group)
-      group_icon(namespace)
+      group_icon_url(namespace)
     else
       avatar_icon_for_user(namespace.owner, size)
     end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 56c88e6eab0db7b9a92651faf22cb0a1fe62fdb3..7754c34d6f03c6c8d4931a679d29d7f7332a157d 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -28,7 +28,7 @@ module NavHelper
       end
     elsif current_path?('jobs#show')
       %w[page-gutter build-sidebar right-sidebar-expanded]
-    elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access')
+    elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access', 'destroy')
       %w[page-gutter wiki-sidebar right-sidebar-expanded]
     else
       []
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index e86e43b5ebfb3f5e8a48ef6748d77b347c714aad..7f67574a42872dd863c329b901d2f40f850aaaa5 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -6,12 +6,8 @@ module NotesHelper
     end
   end
 
-  def note_editable?(note)
-    Ability.can_edit_note?(current_user, note)
-  end
-
   def note_supports_quick_actions?(note)
-    Notes::QuickActionsService.supported?(note, current_user)
+    Notes::QuickActionsService.supported?(note)
   end
 
   def noteable_json(noteable)
@@ -151,16 +147,17 @@ module NotesHelper
     }
   end
 
-  def notes_data(issuable)
-    discussions_path =
-      if issuable.is_a?(Issue)
-        discussions_project_issue_path(@project, issuable, format: :json)
-      else
-        discussions_project_merge_request_path(@project, issuable, format: :json)
-      end
+  def discussions_path(issuable)
+    if issuable.is_a?(Issue)
+      discussions_project_issue_path(@project, issuable, format: :json)
+    else
+      discussions_project_merge_request_path(@project, issuable, format: :json)
+    end
+  end
 
+  def notes_data(issuable)
     {
-      discussionsPath: discussions_path,
+      discussionsPath: discussions_path(issuable),
       registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
       newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'),
       markdownDocsPath: help_page_path('user/markdown'),
@@ -169,8 +166,7 @@ module NotesHelper
       reopenPath: reopen_issuable_path(issuable),
       notesPath: notes_url,
       totalNotes: issuable.discussions.length,
-      lastFetchedAt: Time.now
-
+      lastFetchedAt: Time.now.to_i
     }.to_json
   end
 
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 18b9bf214a32e5f5dcf8a972cd6e15d2e1d98671..a8397b03d63dd56bd660d44e7671468ee63dcd90 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -39,7 +39,10 @@ module PageLayoutHelper
   end
 
   def favicon
-    Rails.env.development? ? 'favicon-blue.ico' : 'favicon.ico'
+    return 'favicon-yellow.ico' if Gitlab::Utils.to_boolean(ENV['CANARY'])
+    return 'favicon-blue.ico' if Rails.env.development?
+
+    'favicon.ico'
   end
 
   def page_image
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 373dfd457f72c8a8338a93b2d264b4bf8f3bf9ae..fb523cb865b960162ccd0f14f158b65e3eb5db77 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -9,12 +9,14 @@ module PreferencesHelper
 
   # Maps `dashboard` values to more user-friendly option text
   DASHBOARD_CHOICES = {
-    projects: 'Your Projects (default)',
-    stars:    'Starred Projects',
-    project_activity: "Your Projects' Activity",
-    starred_project_activity: "Starred Projects' Activity",
-    groups: "Your Groups",
-    todos: "Your Todos"
+    projects: _("Your Projects (default)"),
+    stars:    _("Starred Projects"),
+    project_activity: _("Your Projects' Activity"),
+    starred_project_activity: _("Starred Projects' Activity"),
+    groups: _("Your Groups"),
+    todos: _("Your Todos"),
+    issues: _("Assigned Issues"),
+    merge_requests: _("Assigned Merge Requests")
   }.with_indifferent_access.freeze
 
   # Returns an Array usable by a select field for more user-friendly option text
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index cc1c69a1999885ce1a974b4135c9fe7d361583a9..a64b2acdd77b31b7f55bf0a7fa156caff50e0065 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -97,13 +97,13 @@ module ProjectsHelper
   end
 
   def remove_project_message(project)
-    _("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
-      { project_name_with_namespace: project.name_with_namespace }
+    _("You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
+      { project_full_name: project.full_name }
   end
 
   def transfer_project_message(project)
-    _("You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?") %
-      { project_name_with_namespace: project.name_with_namespace }
+    _("You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?") %
+      { project_full_name: project.full_name }
   end
 
   def remove_fork_project_message(project)
@@ -157,40 +157,6 @@ module ProjectsHelper
     current_user&.recent_push(@project)
   end
 
-  def project_feature_access_select(field)
-    # Don't show option "everyone with access" if project is private
-    options = project_feature_options
-
-    level = @project.project_feature.public_send(field) # rubocop:disable GitlabSecurity/PublicSend
-
-    if @project.private?
-      disabled_option = ProjectFeature::ENABLED
-      highest_available_option = ProjectFeature::PRIVATE if level == disabled_option
-    end
-
-    options = options_for_select(
-      options.invert,
-      selected: highest_available_option || level,
-      disabled: disabled_option
-    )
-
-    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
     link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
   end
@@ -274,16 +240,6 @@ module ProjectsHelper
 
   private
 
-  def repo_children_classes(field)
-    needs_repo_check = [:merge_requests_access_level, :builds_access_level]
-    return unless needs_repo_check.include?(field)
-
-    classes = "project-repo-select js-repo-select"
-    classes << " disabled" unless @project.feature_available?(:repository, current_user)
-
-    classes
-  end
-
   def get_project_nav_tabs(project, current_user)
     nav_tabs = [:home]
 
@@ -447,14 +403,6 @@ module ProjectsHelper
     filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
   end
 
-  def project_feature_options
-    {
-      ProjectFeature::DISABLED => s_('ProjectFeature|Disabled'),
-      ProjectFeature::PRIVATE => s_('ProjectFeature|Only team members'),
-      ProjectFeature::ENABLED => s_('ProjectFeature|Everyone with access')
-    }
-  end
-
   def project_child_container_class(view_path)
     view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
   end
@@ -463,20 +411,6 @@ module ProjectsHelper
     IssuesFinder.new(current_user, project_id: project.id).execute
   end
 
-  def visibility_select_options(project, selected_level)
-    level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options|
-      next if restricted_levels.include?(level)
-
-      level_options << [
-        visibility_level_label(level),
-        { data: { description: visibility_level_description(level, project) } },
-        level
-      ]
-    end
-
-    options_for_select(level_options, selected_level)
-  end
-
   def restricted_levels
     return [] if current_user.admin?
 
@@ -531,4 +465,22 @@ module ProjectsHelper
   def can_show_last_commit_in_list?(project)
     can?(current_user, :read_cross_project) && project.commit
   end
+
+  def pages_https_only_disabled?
+    !@project.pages_domains.all?(&:https?)
+  end
+
+  def pages_https_only_title
+    return unless pages_https_only_disabled?
+
+    "You must enable HTTPS for all your domains first"
+  end
+
+  def pages_https_only_label_class
+    if pages_https_only_disabled?
+      "list-label disabled"
+    else
+      "list-label"
+    end
+  end
 end
diff --git a/app/helpers/safe_params_helper.rb b/app/helpers/safe_params_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b568e8810cce6973be7c5c6ecebc272b484bfbf8
--- /dev/null
+++ b/app/helpers/safe_params_helper.rb
@@ -0,0 +1,11 @@
+module SafeParamsHelper
+  # Rails 5.0 requires to permit `params` if they're used in url helpers.
+  # Use this helper when generating links with `params.merge(...)`
+  def safe_params
+    if params.respond_to?(:permit!)
+      params.except(:host, :port, :protocol).permit!
+    else
+      params
+    end
+  end
+end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index e6a6496871ad3d74380d8aaf5b17484f79c68b22..761c1252fc8962fe585e87632bfc965c2fe91181 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -110,7 +110,7 @@ module SearchHelper
         category: "Projects",
         id: p.id,
         value: "#{search_result_sanitize(p.name)}",
-        label: "#{search_result_sanitize(p.name_with_namespace)}",
+        label: "#{search_result_sanitize(p.full_name)}",
         url: project_path(p)
       }
     end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 240783bc7fdfd78fba63e3f3d6b5152de29f8407..f872990122e9c681c871cf53da84a5450baab415 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -7,9 +7,11 @@ module ServicesHelper
       "Event will be triggered when a new tag is pushed to the repository"
     when "note", "note_events"
       "Event will be triggered when someone adds a comment"
+    when "confidential_note", "confidential_note_events"
+      "Event will be triggered when someone adds a comment on a confidential issue"
     when "issue", "issue_events"
       "Event will be triggered when an issue is created/updated/closed"
-    when "confidential_issue", "confidential_issue_events"
+    when "confidential_issue", "confidential_issues_events"
       "Event will be triggered when a confidential issue is created/updated/closed"
     when "merge_request", "merge_request_events"
       "Event will be triggered when a merge request is created/updated/merged"
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 00e7e4230b992a954dc4b4877c01bd509757d8ec..733832c1bbbd930c8e74cc596044e327657356d0 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -101,4 +101,39 @@ module SnippetsHelper
     # Return snippet with chunk array
     { snippet_object: snippet, snippet_chunks: snippet_chunks }
   end
+
+  def snippet_embed
+    "<script src=\"#{url_for(only_path: false, overwrite_params: nil)}.js\"></script>"
+  end
+
+  def embedded_snippet_raw_button
+    blob = @snippet.blob
+    return if blob.empty? || blob.raw_binary? || blob.stored_externally?
+
+    snippet_raw_url = if @snippet.is_a?(PersonalSnippet)
+                        raw_snippet_url(@snippet)
+                      else
+                        raw_project_snippet_url(@snippet.project, @snippet)
+                      end
+
+    link_to external_snippet_icon('doc_code'), snippet_raw_url, class: 'btn', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw'
+  end
+
+  def embedded_snippet_download_button
+    download_url = if @snippet.is_a?(PersonalSnippet)
+                     raw_snippet_url(@snippet, inline: false)
+                   else
+                     raw_project_snippet_url(@snippet.project, @snippet, inline: false)
+                   end
+
+    link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', rel: 'noopener noreferrer'
+  end
+
+  def public_snippet?
+    if @snippet.project_id?
+      can?(nil, :read_project_snippet, @snippet)
+    else
+      can?(nil, :read_personal_snippet, @snippet)
+    end
+  end
 end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index ddb48371c79aec37d7015daebab5696b24688867..f7620e0b6b82a787fe83dc211f0476d732f14a5f 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -114,7 +114,7 @@ module TodosHelper
     projects = current_user.authorized_projects.sorted_by_activity.non_archived.with_route
 
     projects = projects.map do |project|
-      { id: project.id, text: project.name_with_namespace }
+      { id: project.id, text: project.full_name }
     end
 
     projects.unshift({ id: '', text: 'Any Project' }).to_json
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index f6a6d9bebde01451ba753bfc73d29742b56c0fac..dc42caa70e5500695f213401eef008631e4a5c30 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -49,15 +49,13 @@ module TreeHelper
 
     return false unless on_top_of_branch?(project, ref)
 
-    can_collaborate_with_project?(project)
+    can_collaborate_with_project?(project, ref: ref)
   end
 
   def tree_edit_branch(project = @project, ref = @ref)
     return unless can_edit_tree?(project, ref)
 
-    project = project.present(current_user: current_user)
-
-    if project.can_current_user_push_to_branch?(ref)
+    if user_access(project).can_push_to_branch?(ref)
       ref
     else
       project = tree_edit_project(project)
@@ -88,7 +86,16 @@ module TreeHelper
   end
 
   def commit_in_fork_help
-    "A new branch will be created in your fork and a new merge request will be started."
+    _("A new branch will be created in your fork and a new merge request will be started.")
+  end
+
+  def commit_in_single_accessible_branch
+    branch_name = ERB::Util.html_escape(selected_branch)
+
+    message = _("Your changes can be committed to %{branch_name} because a merge "\
+                "request is open.") % { branch_name: "<strong>#{branch_name}</strong>" }
+
+    message.html_safe
   end
 
   def path_breadcrumbs(max_links = 6)
@@ -116,7 +123,7 @@ module TreeHelper
 
   # returns the relative path of the first subdir that doesn't have only one directory descendant
   def flatten_tree(root_path, tree)
-    return tree.flat_path.sub(%r{\A#{root_path}/}, '') if tree.flat_path.present?
+    return tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '') if tree.flat_path.present?
 
     subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
     if subtree.count == 1 && subtree.first.dir?
@@ -125,4 +132,8 @@ module TreeHelper
       return tree.name
     end
   end
+
+  def selected_branch
+    @branch_name || tree_edit_branch
+  end
 end
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index 88f374be1e5c1340e6987c535ea8be7486802466..9f78b80c71df2fab56142180c3cacbaa9bae3c08 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -24,8 +24,8 @@ module WorkhorseHelper
   end
 
   # Archive a Git repository and send it through Workhorse
-  def send_git_archive(repository, ref:, format:)
-    headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
+  def send_git_archive(repository, **kwargs)
+    headers.store(*Gitlab::Workhorse.send_git_archive(repository, **kwargs))
     head :ok
   end
 
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index b33131becd35ad22076f3bb88934d4580b7dcb1b..392cc0bee03dcf8fc69595977d55ef2ce83dabd0 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -6,6 +6,12 @@ module Emails
       mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
     end
 
+    def issue_due_email(recipient_id, issue_id, reason = nil)
+      setup_issue_mail(issue_id, recipient_id)
+
+      mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
+    end
+
     def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
       setup_issue_mail(issue_id, recipient_id)
       mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 5fe09cea83f407d03836c79449c9abb5ad86bd1b..b3f2aeb08ca229cd2f5935862bbf6a66822a6440 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -11,6 +11,15 @@ module Emails
       mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
     end
 
+    def push_to_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil, new_commits: [], existing_commits: [])
+      setup_merge_request_mail(merge_request_id, recipient_id)
+      @new_commits = new_commits
+      @existing_commits = existing_commits
+      @updated_by_user = User.find(updated_by_user_id)
+
+      mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
+    end
+
     def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id, reason = nil)
       setup_merge_request_mail(merge_request_id, recipient_id)
 
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 45d4fb451d8f080f75c615744d3c7a063e04d2a7..e42127759568c8ec63ab20d17c4504fd28d3cd0f 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -117,7 +117,7 @@ class Notify < BaseMailer
 
     if Gitlab::IncomingEmail.enabled? && @sent_notification
       address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
-      address.display_name = @project.name_with_namespace
+      address.display_name = @project.full_name
 
       headers['Reply-To'] = address
 
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 6dae49f38dc799f299c8b5eb83be77f4854c5d09..618d4af4272ce14036f1f2357f8d5e94369b6c12 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -46,10 +46,6 @@ class Ability
       end
     end
 
-    def can_edit_note?(user, note)
-      allowed?(user, :edit_note, note)
-    end
-
     def allowed?(user, action, subject = :global, opts = {})
       if subject.is_a?(Hash)
         opts, subject = subject, :global
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index dcd14c08f3c943a08ca54b4ce2888d405bd5047d..fb66dd0b7668cce2143943356953563407fa9bb8 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -1,5 +1,7 @@
 class Appearance < ActiveRecord::Base
   include CacheMarkdownField
+  include AfterCommitQueue
+  include ObjectStorage::BackgroundMove
 
   cache_markdown_field :description
   cache_markdown_field :new_project_guidelines
@@ -14,7 +16,7 @@ class Appearance < ActiveRecord::Base
 
   has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
 
-  CACHE_KEY = 'current_appearance'.freeze
+  CACHE_KEY = "current_appearance:#{Gitlab::VERSION}".freeze
 
   after_commit :flush_redis_cache
 
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 0dee6df525d5b0a34c2b9159bd9599146b98f423..862933bf1273870e1b173fadf0e5c77adc856d54 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -330,7 +330,8 @@ class ApplicationSetting < ActiveRecord::Base
       usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
       gitaly_timeout_fast: 10,
       gitaly_timeout_medium: 30,
-      gitaly_timeout_default: 55
+      gitaly_timeout_default: 55,
+      allow_local_requests_from_hooks_and_services: false
     }
   end
 
@@ -347,15 +348,15 @@ class ApplicationSetting < ActiveRecord::Base
   end
 
   def home_page_url_column_exists?
-    ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
+    ::Gitlab::Database.cached_column_exists?(:application_settings, :home_page_url)
   end
 
   def help_page_support_url_column_exists?
-    ActiveRecord::Base.connection.column_exists?(:application_settings, :help_page_support_url)
+    ::Gitlab::Database.cached_column_exists?(:application_settings, :help_page_support_url)
   end
 
   def sidekiq_throttling_column_exists?
-    ActiveRecord::Base.connection.column_exists?(:application_settings, :sidekiq_throttling_enabled)
+    ::Gitlab::Database.cached_column_exists?(:application_settings, :sidekiq_throttling_enabled)
   end
 
   def domain_whitelist_raw
diff --git a/app/models/badge.rb b/app/models/badge.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f7e10c2ebfc17fe251fb85c74f275478c512f39e
--- /dev/null
+++ b/app/models/badge.rb
@@ -0,0 +1,51 @@
+class Badge < ActiveRecord::Base
+  # This structure sets the placeholders that the urls
+  # can have. This hash also sets which action to ask when
+  # the placeholder is found.
+  PLACEHOLDERS = {
+    'project_path' => :full_path,
+    'project_id' => :id,
+    'default_branch' => :default_branch,
+    'commit_sha' => ->(project) { project.commit&.sha }
+  }.freeze
+
+  # This regex is built dynamically using the keys from the PLACEHOLDER struct.
+  # So, we can easily add new placeholder just by modifying the PLACEHOLDER hash.
+  # This regex will build the new PLACEHOLDER_REGEX with the new information
+  PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/.freeze
+
+  default_scope { order_created_at_asc }
+
+  scope :order_created_at_asc, -> { reorder(created_at: :asc) }
+
+  validates :link_url, :image_url, url_placeholder: { protocols: %w(http https), placeholder_regex: PLACEHOLDERS_REGEX }
+  validates :type, presence: true
+
+  def rendered_link_url(project = nil)
+    build_rendered_url(link_url, project)
+  end
+
+  def rendered_image_url(project = nil)
+    build_rendered_url(image_url, project)
+  end
+
+  private
+
+  def build_rendered_url(url, project = nil)
+    return url unless valid? && project
+
+    Gitlab::StringPlaceholderReplacer.replace_string_placeholders(url, PLACEHOLDERS_REGEX) do |arg|
+      replace_placeholder_action(PLACEHOLDERS[arg], project)
+    end
+  end
+
+  # The action param represents the :symbol or Proc to call in order
+  # to retrieve the return value from the project.
+  # This method checks if it is a Proc and use the call method, and if it is
+  # a symbol just send the action
+  def replace_placeholder_action(action, project)
+    return unless project
+
+    action.is_a?(Proc) ? action.call(project) : project.public_send(action) # rubocop:disable GitlabSecurity/PublicSend
+  end
+end
diff --git a/app/models/badges/group_badge.rb b/app/models/badges/group_badge.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f4b2bdecdcce834c19f6f76740355c5327863d6d
--- /dev/null
+++ b/app/models/badges/group_badge.rb
@@ -0,0 +1,5 @@
+class GroupBadge < Badge
+  belongs_to :group
+
+  validates :group, presence: true
+end
diff --git a/app/models/badges/project_badge.rb b/app/models/badges/project_badge.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3945b376052d73df27562095c95e7856f6ef441e
--- /dev/null
+++ b/app/models/badges/project_badge.rb
@@ -0,0 +1,15 @@
+class ProjectBadge < Badge
+  belongs_to :project
+
+  validates :project, presence: true
+
+  def rendered_link_url(project = nil)
+    project ||= self.project
+    super
+  end
+
+  def rendered_image_url(project = nil)
+    project ||= self.project
+    super
+  end
+end
diff --git a/app/models/board.rb b/app/models/board.rb
index 5bb7d3d3722becf551a57a62522078fb6ed37cdb..3cede6fc99afffee4fe45cb8480759e4ba8a7fb7 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -1,20 +1,22 @@
 class Board < ActiveRecord::Base
+  belongs_to :group
   belongs_to :project
 
   has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
 
   validates :project, presence: true, if: :project_needed?
+  validates :group, presence: true, unless: :project
 
   def project_needed?
-    true
+    !group
   end
 
   def parent
-    project
+    @parent ||= group || project
   end
 
   def group_board?
-    false
+    group_id.present?
   end
 
   def backlog_list
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 0b5612039141a0386c7dcfb5be0cf9e14fd94990..4aa236555cb6e5c9a53a332092eca893e4e4a4d1 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -19,7 +19,7 @@ class BroadcastMessage < ActiveRecord::Base
   after_commit :flush_redis_cache
 
   def self.current
-    messages = Rails.cache.fetch(CACHE_KEY) { current_and_future_messages.to_a }
+    messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) { current_and_future_messages.to_a }
 
     return messages if messages.empty?
 
@@ -36,6 +36,10 @@ class BroadcastMessage < ActiveRecord::Base
     where('ends_at > :now', now: Time.zone.now).order_id_asc
   end
 
+  def self.cache_expires_in
+    nil
+  end
+
   def active?
     started? && !ended?
   end
diff --git a/app/models/ci/artifact_blob.rb b/app/models/ci/artifact_blob.rb
index ec56cc53aea7bfacf5925202283e4f81e35370b6..760f01f225bffd53187fdbaa122655c8020b7d91 100644
--- a/app/models/ci/artifact_blob.rb
+++ b/app/models/ci/artifact_blob.rb
@@ -36,16 +36,15 @@ module Ci
     def external_url(project, job)
       return unless external_link?(job)
 
-      full_path_parts = project.full_path_components
-      top_level_group = full_path_parts.shift
+      url_project_path = project.full_path.partition('/').last
 
       artifact_path = [
-        '-', *full_path_parts, '-',
+        '-', url_project_path, '-',
         'jobs', job.id,
         'artifacts', path
       ].join('/')
 
-      "#{pages_config.protocol}://#{top_level_group}.#{pages_config.host}/#{artifact_path}"
+      "#{project.pages_group_url}/#{artifact_path}"
     end
 
     def external_link?(job)
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index b230b7f47efe691704e66e741eb99f58a3d2cb2a..b0c02cdeec75bfdeaac73c80712a15907dcd4dbb 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -3,8 +3,10 @@ module Ci
     prepend ArtifactMigratable
     include TokenAuthenticatable
     include AfterCommitQueue
+    include ObjectStorage::BackgroundMove
     include Presentable
     include Importable
+    include Gitlab::Utils::StrongMemoize
 
     MissingDependenciesError = Class.new(StandardError)
 
@@ -18,17 +20,23 @@ module Ci
     has_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment'
     has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
 
-    has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+    has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
     has_one :job_artifacts_archive, -> { where(file_type: Ci::JobArtifact.file_types[:archive]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
     has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
     has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
 
-    # The "environment" field for builds is a String, and is the unexpanded name
+    has_one :metadata, class_name: 'Ci::BuildMetadata'
+    delegate :timeout, to: :metadata, prefix: true, allow_nil: true
+
+    ##
+    # The "environment" field for builds is a String, and is the unexpanded name!
+    #
     def persisted_environment
-      @persisted_environment ||= Environment.find_by(
-        name: expanded_environment_name,
-        project: project
-      )
+      return unless has_environment?
+
+      strong_memoize(:persisted_environment) do
+        Environment.find_by(name: expanded_environment_name, project: project)
+      end
     end
 
     serialize :options # rubocop:disable Cop/ActiveRecordSerialize
@@ -41,12 +49,13 @@ module Ci
 
     scope :unstarted, ->() { where(runner_id: nil) }
     scope :ignore_failures, ->() { where(allow_failure: false) }
-    scope :with_artifacts, ->() do
+    scope :with_artifacts_archive, ->() do
       where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
-        '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id'))
+        '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
     end
-    scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
-    scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
+    scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
+    scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
+    scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
     scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
     scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
     scope :ref_protected, -> { where(protected: true) }
@@ -81,12 +90,13 @@ module Ci
     before_save :ensure_token
     before_destroy { unscoped_project }
 
+    before_create :ensure_metadata
     after_create unless: :importing? do |build|
       run_after_commit { BuildHooksWorker.perform_async(build.id) }
     end
 
-    after_commit :update_project_statistics_after_save, on: [:create, :update]
-    after_commit :update_project_statistics, on: :destroy
+    after_save :update_project_statistics_after_save, if: :artifacts_size_changed?
+    after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
 
     class << self
       # This is needed for url_for to work,
@@ -140,13 +150,25 @@ module Ci
         next if build.retries_max.zero?
 
         if build.retries_count < build.retries_max
-          Ci::Build.retry(build, build.user)
+          begin
+            Ci::Build.retry(build, build.user)
+          rescue Gitlab::Access::AccessDeniedError => ex
+            Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}"
+          end
         end
       end
 
       before_transition any => [:running] do |build|
         build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
       end
+
+      after_transition pending: :running do |build|
+        build.ensure_metadata.update_timeout_state
+      end
+    end
+
+    def ensure_metadata
+      metadata || build_metadata(project: project)
     end
 
     def detailed_status(current_user)
@@ -194,7 +216,11 @@ module Ci
     end
 
     def expanded_environment_name
-      ExpandVariables.expand(environment, simple_variables) if environment
+      return unless has_environment?
+
+      strong_memoize(:expanded_environment_name) do
+        ExpandVariables.expand(environment, simple_variables)
+      end
     end
 
     def has_environment?
@@ -225,10 +251,6 @@ module Ci
       latest_builds.where('stage_idx < ?', stage_idx)
     end
 
-    def timeout
-      project.build_timeout
-    end
-
     def triggered_by?(current_user)
       user == current_user
     end
@@ -244,31 +266,52 @@ module Ci
       Gitlab::Utils.slugify(ref.to_s)
     end
 
-    # Variables whose value does not depend on environment
+    ##
+    # Variables in the environment name scope.
+    #
+    def scoped_variables(environment: expanded_environment_name)
+      Gitlab::Ci::Variables::Collection.new.tap do |variables|
+        variables.concat(predefined_variables)
+        variables.concat(project.predefined_variables)
+        variables.concat(pipeline.predefined_variables)
+        variables.concat(runner.predefined_variables) if runner
+        variables.concat(project.deployment_variables(environment: environment)) if environment
+        variables.concat(yaml_variables)
+        variables.concat(user_variables)
+        variables.concat(secret_group_variables)
+        variables.concat(secret_project_variables(environment: environment))
+        variables.concat(trigger_request.user_variables) if trigger_request
+        variables.concat(pipeline.variables)
+        variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
+      end
+    end
+
+    ##
+    # Variables that do not depend on the environment name.
+    #
     def simple_variables
-      variables(environment: nil)
-    end
-
-    # All variables, including those dependent on environment, which could
-    # contain unexpanded variables.
-    def variables(environment: persisted_environment)
-      variables = predefined_variables
-      variables += project.predefined_variables
-      variables += pipeline.predefined_variables
-      variables += runner.predefined_variables if runner
-      variables += project.container_registry_variables
-      variables += project.deployment_variables if has_environment?
-      variables += project.auto_devops_variables
-      variables += yaml_variables
-      variables += user_variables
-      variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group
-      variables += secret_variables(environment: environment)
-      variables += trigger_request.user_variables if trigger_request
-      variables += pipeline.variables.map(&:to_runner_variable)
-      variables += pipeline.pipeline_schedule.job_variables if pipeline.pipeline_schedule
-      variables += persisted_environment_variables if environment
-
-      variables
+      strong_memoize(:simple_variables) do
+        scoped_variables(environment: nil).to_runner_variables
+      end
+    end
+
+    ##
+    # All variables, including persisted environment variables.
+    #
+    def variables
+      Gitlab::Ci::Variables::Collection.new
+        .concat(persisted_variables)
+        .concat(scoped_variables)
+        .concat(persisted_environment_variables)
+        .to_runner_variables
+    end
+
+    ##
+    # Regular Ruby hash of scoped variables, without duplicates that are
+    # possible to be present in an array of hashes returned from `variables`.
+    #
+    def scoped_variables_hash
+      scoped_variables.to_hash
     end
 
     def features
@@ -328,8 +371,7 @@ module Ci
     end
 
     def erase_old_trace!
-      write_attribute(:trace, nil)
-      save
+      update_column(:trace, nil)
     end
 
     def needs_touch?
@@ -362,13 +404,19 @@ module Ci
       project.running_or_pending_build_count(force: true)
     end
 
+    def browsable_artifacts?
+      artifacts_metadata?
+    end
+
     def artifacts_metadata_entry(path, **options)
-      metadata = Gitlab::Ci::Build::Artifacts::Metadata.new(
-        artifacts_metadata.path,
-        path,
-        **options)
+      artifacts_metadata.use_file do |metadata_path|
+        metadata = Gitlab::Ci::Build::Artifacts::Metadata.new(
+          metadata_path,
+          path,
+          **options)
 
-      metadata.to_entry
+        metadata.to_entry
+      end
     end
 
     def erase_artifacts!
@@ -430,19 +478,24 @@ module Ci
     end
 
     def user_variables
-      return [] if user.blank?
+      Gitlab::Ci::Variables::Collection.new.tap do |variables|
+        break variables if user.blank?
+
+        variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
+        variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
+        variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
+        variables.append(key: 'GITLAB_USER_NAME', value: user.name)
+      end
+    end
+
+    def secret_group_variables
+      return [] unless project.group
 
-      [
-        { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
-        { key: 'GITLAB_USER_EMAIL', value: user.email, public: true },
-        { key: 'GITLAB_USER_LOGIN', value: user.username, public: true },
-        { key: 'GITLAB_USER_NAME', value: user.name, public: true }
-      ]
+      project.group.secret_variables_for(ref, project)
     end
 
-    def secret_variables(environment: persisted_environment)
+    def secret_project_variables(environment: persisted_environment)
       project.secret_variables_for(ref: ref, environment: environment)
-        .map(&:to_runner_variable)
     end
 
     def steps
@@ -539,61 +592,66 @@ module Ci
 
     CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
 
-    def predefined_variables
-      variables = [
-        { key: 'CI', value: 'true', public: true },
-        { key: 'GITLAB_CI', value: 'true', public: true },
-        { key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true },
-        { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
-        { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
-        { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
-        { key: 'CI_JOB_ID', value: id.to_s, public: true },
-        { key: 'CI_JOB_NAME', value: name, public: true },
-        { key: 'CI_JOB_STAGE', value: stage, public: true },
-        { key: 'CI_JOB_TOKEN', value: token, public: false },
-        { key: 'CI_COMMIT_SHA', value: sha, public: true },
-        { key: 'CI_COMMIT_REF_NAME', value: ref, public: true },
-        { key: 'CI_COMMIT_REF_SLUG', value: ref_slug, public: true },
-        { key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER, public: true },
-        { key: 'CI_REGISTRY_PASSWORD', value: token, public: false },
-        { key: 'CI_REPOSITORY_URL', value: repo_url, public: false }
-      ]
-
-      variables << { key: "CI_COMMIT_TAG", value: ref, public: true } if tag?
-      variables << { key: "CI_PIPELINE_TRIGGERED", value: 'true', public: true } if trigger_request
-      variables << { key: "CI_JOB_MANUAL", value: 'true', public: true } if action?
-      variables.concat(legacy_variables)
+    def persisted_variables
+      Gitlab::Ci::Variables::Collection.new.tap do |variables|
+        break variables unless persisted?
+
+        variables
+          .append(key: 'CI_JOB_ID', value: id.to_s)
+          .append(key: 'CI_JOB_TOKEN', value: token, public: false)
+          .append(key: 'CI_BUILD_ID', value: id.to_s)
+          .append(key: 'CI_BUILD_TOKEN', value: token, public: false)
+          .append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
+          .append(key: 'CI_REGISTRY_PASSWORD', value: token, public: false)
+          .append(key: 'CI_REPOSITORY_URL', value: repo_url, public: false)
+      end
     end
 
-    def persisted_environment_variables
-      return [] unless persisted_environment
+    def predefined_variables
+      Gitlab::Ci::Variables::Collection.new.tap do |variables|
+        variables.append(key: 'CI', value: 'true')
+        variables.append(key: 'GITLAB_CI', value: 'true')
+        variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(','))
+        variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
+        variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
+        variables.append(key: 'CI_SERVER_REVISION', value: Gitlab::REVISION)
+        variables.append(key: 'CI_JOB_NAME', value: name)
+        variables.append(key: 'CI_JOB_STAGE', value: stage)
+        variables.append(key: 'CI_COMMIT_SHA', value: sha)
+        variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
+        variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
+        variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
+        variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request
+        variables.append(key: "CI_JOB_MANUAL", value: 'true') if action?
+        variables.concat(legacy_variables)
+      end
+    end
 
-      variables = persisted_environment.predefined_variables
+    def legacy_variables
+      Gitlab::Ci::Variables::Collection.new.tap do |variables|
+        variables.append(key: 'CI_BUILD_REF', value: sha)
+        variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
+        variables.append(key: 'CI_BUILD_REF_NAME', value: ref)
+        variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug)
+        variables.append(key: 'CI_BUILD_NAME', value: name)
+        variables.append(key: 'CI_BUILD_STAGE', value: stage)
+        variables.append(key: "CI_BUILD_TAG", value: ref) if tag?
+        variables.append(key: "CI_BUILD_TRIGGERED", value: 'true') if trigger_request
+        variables.append(key: "CI_BUILD_MANUAL", value: 'true') if action?
+      end
+    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
+    def persisted_environment_variables
+      Gitlab::Ci::Variables::Collection.new.tap do |variables|
+        break variables unless persisted? && persisted_environment.present?
 
-      variables
-    end
+        variables.concat(persisted_environment.predefined_variables)
 
-    def legacy_variables
-      variables = [
-        { key: 'CI_BUILD_ID', value: id.to_s, public: true },
-        { key: 'CI_BUILD_TOKEN', value: token, public: false },
-        { key: 'CI_BUILD_REF', value: sha, public: true },
-        { key: 'CI_BUILD_BEFORE_SHA', value: before_sha, public: true },
-        { key: 'CI_BUILD_REF_NAME', value: ref, public: true },
-        { key: 'CI_BUILD_REF_SLUG', value: ref_slug, public: true },
-        { key: 'CI_BUILD_NAME', value: name, public: true },
-        { key: 'CI_BUILD_STAGE', value: stage, public: true }
-      ]
-
-      variables << { key: "CI_BUILD_TAG", value: ref, public: true } if tag?
-      variables << { key: "CI_BUILD_TRIGGERED", value: 'true', public: true } if trigger_request
-      variables << { key: "CI_BUILD_MANUAL", value: 'true', public: true } if action?
-      variables
+        # 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.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url
+      end
     end
 
     def environment_url
@@ -606,16 +664,20 @@ module Ci
       pipeline.config_processor.build_attributes(name)
     end
 
-    def update_project_statistics
-      return unless project
+    def update_project_statistics_after_save
+      update_project_statistics(read_attribute(:artifacts_size).to_i - artifacts_size_was.to_i)
+    end
 
-      ProjectCacheWorker.perform_async(project_id, [], [:build_artifacts_size])
+    def update_project_statistics_after_destroy
+      update_project_statistics(-artifacts_size)
     end
 
-    def update_project_statistics_after_save
-      if previous_changes.include?('artifacts_size')
-        update_project_statistics
-      end
+    def update_project_statistics(difference)
+      ProjectStatistics.increment_statistic(project_id, :build_artifacts_size, difference)
+    end
+
+    def project_destroyed?
+      project.pending_delete?
     end
   end
 end
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96762f8845ccaa6539f4387325d9da89fbc54da9
--- /dev/null
+++ b/app/models/ci/build_metadata.rb
@@ -0,0 +1,35 @@
+module Ci
+  # The purpose of this class is to store Build related data that can be disposed.
+  # Data that should be persisted forever, should be stored with Ci::Build model.
+  class BuildMetadata < ActiveRecord::Base
+    extend Gitlab::Ci::Model
+    include Presentable
+    include ChronicDurationAttribute
+
+    self.table_name = 'ci_builds_metadata'
+
+    belongs_to :build, class_name: 'Ci::Build'
+    belongs_to :project
+
+    validates :build, presence: true
+    validates :project, presence: true
+
+    chronic_duration_attr_reader :timeout_human_readable, :timeout
+
+    enum timeout_source: {
+        unknown_timeout_source: 1,
+        project_timeout_source: 2,
+        runner_timeout_source: 3
+    }
+
+    def update_timeout_state
+      return unless build.runner.present?
+
+      project_timeout = project&.build_timeout
+      timeout = [project_timeout, build.runner.maximum_timeout].compact.min
+      timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source
+
+      update(timeout: timeout, timeout_source: timeout_source)
+    end
+  end
+end
diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb
index 1dd0e050ba917edf9bd5a4f6e5f249e68097e52e..44cb583e1bdf582c216a225e1fc2fc765f7bfb1a 100644
--- a/app/models/ci/group_variable.rb
+++ b/app/models/ci/group_variable.rb
@@ -4,7 +4,9 @@ module Ci
     include HasVariable
     include Presentable
 
-    belongs_to :group
+    belongs_to :group, class_name: "::Group"
+
+    alias_attribute :secret_value, :value
 
     validates :key, uniqueness: {
       scope: :group_id,
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 0a599f72bc7786f593bef5a9037660dae6e5c1e6..39676efa08c7cd4edf405c167b9dd531c83dcd9c 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -1,15 +1,23 @@
 module Ci
   class JobArtifact < ActiveRecord::Base
+    include AfterCommitQueue
+    include ObjectStorage::BackgroundMove
     extend Gitlab::Ci::Model
 
     belongs_to :project
     belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
 
+    mount_uploader :file, JobArtifactUploader
+
     before_save :set_size, if: :file_changed?
+    after_save :update_project_statistics_after_save, if: :size_changed?
+    after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
 
-    mount_uploader :file, JobArtifactUploader
+    after_save :update_file_store
 
-    delegate :open, :exists?, to: :file
+    scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
+
+    delegate :exists?, :open, to: :file
 
     enum file_type: {
       archive: 1,
@@ -17,12 +25,18 @@ module Ci
       trace: 3
     }
 
+    def update_file_store
+      # The file.object_store is set during `uploader.store!`
+      # which happens after object is inserted/updated
+      self.update_column(:file_store, file.object_store)
+    end
+
     def self.artifacts_size_for(project)
       self.where(project: project).sum(:size)
     end
 
-    def set_size
-      self.size = file.size
+    def local_store?
+      [nil, ::JobArtifactUploader::Store::LOCAL].include?(self.file_store)
     end
 
     def expire_in
@@ -35,5 +49,28 @@ module Ci
           ChronicDuration.parse(value)&.seconds&.from_now
         end
     end
+
+    private
+
+    def set_size
+      self.size = file.size
+    end
+
+    def update_project_statistics_after_save
+      update_project_statistics(size.to_i - size_was.to_i)
+    end
+
+    def update_project_statistics_after_destroy
+      update_project_statistics(-self.size)
+    end
+
+    def update_project_statistics(difference)
+      ProjectStatistics.increment_statistic(project_id, :build_artifacts_size, difference)
+    end
+
+    def project_destroyed?
+      # Use job.project to avoid extra DB query for project
+      job.project.pending_delete?
+    end
   end
 end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index a72a815bfe8d5d23e7bbddb9ced151b3e63518a2..434b9b64c6513a5dc091951cceeede19fb4c1457 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -6,6 +6,7 @@ module Ci
     include AfterCommitQueue
     include Presentable
     include Gitlab::OptimisticLocking
+    include Gitlab::Utils::StrongMemoize
 
     belongs_to :project, inverse_of: :pipelines
     belongs_to :user
@@ -14,7 +15,7 @@ module Ci
 
     has_many :stages
     has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
-    has_many :builds, foreign_key: :commit_id
+    has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
     has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
     has_many :variables, class_name: 'Ci::PipelineVariable'
 
@@ -361,21 +362,23 @@ module Ci
     def stage_seeds
       return [] unless config_processor
 
-      @stage_seeds ||= config_processor.stage_seeds(self)
+      strong_memoize(:stage_seeds) do
+        seeds = config_processor.stages_attributes.map do |attributes|
+          Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes)
+        end
+
+        seeds.select(&:included?)
+      end
     end
 
     def seeds_size
-      @seeds_size ||= stage_seeds.sum(&:size)
+      stage_seeds.sum(&:size)
     end
 
     def has_kubernetes_active?
       project.deployment_platform&.active?
     end
 
-    def has_stage_seeds?
-      stage_seeds.any?
-    end
-
     def has_warnings?
       builds.latest.failed_but_allowed.any?
     end
@@ -388,6 +391,9 @@ module Ci
       end
     end
 
+    ##
+    # TODO, setting yaml_errors should be moved to the pipeline creation chain.
+    #
     def config_processor
       return unless ci_yaml_file
       return @config_processor if defined?(@config_processor)
@@ -472,12 +478,19 @@ module Ci
       end
     end
 
+    def protected_ref?
+      strong_memoize(:protected_ref) { project.protected_for?(ref) }
+    end
+
+    def legacy_trigger
+      strong_memoize(:legacy_trigger) { trigger_requests.first }
+    end
+
     def predefined_variables
-      [
-        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
-        { key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true },
-        { key: 'CI_PIPELINE_SOURCE', value: source.to_s, public: true }
-      ]
+      Gitlab::Ci::Variables::Collection.new
+        .append(key: 'CI_PIPELINE_ID', value: id.to_s)
+        .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
+        .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
     end
 
     def queued_duration
@@ -514,7 +527,7 @@ module Ci
       # We purposely cast the builds to an Array here. Because we always use the
       # rows if there are more than 0 this prevents us from having to run two
       # queries: one to get the count and one to get the rows.
-      @latest_builds_with_artifacts ||= builds.latest.with_artifacts.to_a
+      @latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
     end
 
     private
diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb
index af989fb14b4ce7804fccf9cb42f26662b73ca44a..03df4e3e638fe39ca61030fd373944246effeba9 100644
--- a/app/models/ci/pipeline_schedule_variable.rb
+++ b/app/models/ci/pipeline_schedule_variable.rb
@@ -5,6 +5,8 @@ module Ci
 
     belongs_to :pipeline_schedule
 
+    alias_attribute :secret_value, :value
+
     validates :key, uniqueness: { scope: :pipeline_schedule_id }
   end
 end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 609620a62bb433138e0af16776ba632e1d8cd45d..5a4c56ec0dc5f8fd786a7517d0341eeb55f08d69 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -3,12 +3,13 @@ module Ci
     extend Gitlab::Ci::Model
     include Gitlab::SQL::Pattern
     include RedisCacheable
+    include ChronicDurationAttribute
 
     RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
     ONLINE_CONTACT_TIMEOUT = 1.hour
     UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
     AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
-    FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze
+    FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
 
     has_many :builds
     has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -51,6 +52,12 @@ module Ci
 
     cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address
 
+    chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout
+
+    validates :maximum_timeout, allow_nil: true,
+                                numericality: { greater_than_or_equal_to: 600,
+                                                message: 'needs to be at least 10 minutes' }
+
     # Searches for runners matching the given query.
     #
     # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
@@ -132,11 +139,10 @@ module Ci
     end
 
     def predefined_variables
-      [
-        { key: 'CI_RUNNER_ID', value: id.to_s, public: true },
-        { key: 'CI_RUNNER_DESCRIPTION', value: description, public: true },
-        { key: 'CI_RUNNER_TAGS', value: tag_list.to_s, public: true }
-      ]
+      Gitlab::Ci::Variables::Collection.new
+        .append(key: 'CI_RUNNER_ID', value: id.to_s)
+        .append(key: 'CI_RUNNER_DESCRIPTION', value: description)
+        .append(key: 'CI_RUNNER_TAGS', value: tag_list.to_s)
     end
 
     def tick_runner_queue
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 7c71291de84717c659c9e019b74d84819c3c6a68..452cb910bca0e0cc78fcb0ba17796ab937fea035 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -6,6 +6,8 @@ module Ci
 
     belongs_to :project
 
+    alias_attribute :secret_value, :value
+
     validates :key, uniqueness: {
       scope: [:project_id, :environment_scope],
       message: "(%{value}) has already been taken"
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 89ebd63e605140fc27a058d5e407f674984d5af7..7b25d8c4089dfe121acca46ed5ac20f42d9ab67d 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -1,6 +1,8 @@
 module Clusters
   module Applications
     class Prometheus < ActiveRecord::Base
+      include PrometheusAdapter
+
       VERSION = "2.0.0".freeze
 
       self.table_name = 'clusters_applications_prometheus'
@@ -39,7 +41,7 @@ module Clusters
         )
       end
 
-      def proxy_client
+      def prometheus_client
         return unless kube_client
 
         proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE)
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 7adf1663c352db091fb65ee9a5883466a0467b1c..16efe90fa27ae58cb0385909c6cb45db317d78d2 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -56,12 +56,13 @@ module Clusters
       def specification
         {
           "gitlabUrl" => gitlab_url,
-          "runnerToken" => ensure_runner.token
+          "runnerToken" => ensure_runner.token,
+          "runners" => { "privileged" => privileged }
         }
       end
 
       def content_values
-        specification.merge(YAML.load_file(chart_values_file))
+        YAML.load_file(chart_values_file).deep_merge!(specification)
       end
     end
   end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 1c0046107d78aa6f0f2b24eb2dd019866bb6e9e4..77947d515c1c32468dd55faae93be9b9c3604123 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -10,6 +10,7 @@ module Clusters
       Applications::Prometheus.application_name => Applications::Prometheus,
       Applications::Runner.application_name => Applications::Runner
     }.freeze
+    DEFAULT_ENVIRONMENT = '*'.freeze
 
     belongs_to :user
 
@@ -50,9 +51,11 @@ module Clusters
 
     scope :enabled, -> { where(enabled: true) }
     scope :disabled, -> { where(enabled: false) }
+    scope :user_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:user]) }
+    scope :gcp_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:gcp]) }
+    scope :gcp_installed, -> { gcp_provided.includes(:provider_gcp).where(cluster_providers_gcp: { status: ::Clusters::Providers::Gcp.state_machines[:status].states[:created].value }) }
 
-    scope :for_environment, -> (env) { where(environment_scope: ['*', '', env.slug]) }
-    scope :for_all_environments, -> { where(environment_scope: ['*', '']) }
+    scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
 
     def status_name
       if provider
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 7b7c8eac773cead666a1db90df1062dcbd0ddf41..8f3eb75bfa9d587f03127a6f7fe4ee43641677c6 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -4,6 +4,8 @@ module Clusters
       extend ActiveSupport::Concern
 
       included do
+        scope :installed, -> { where(status: self.state_machines[:status].states[:installed].value) }
+
         state_machine :status, initial: :not_installable do
           state :not_installable, value: -2
           state :errored, value: -1
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 7ce8befeeeb4b3d0b8b9f096c5d46ffbce2e8ba5..ba6552f238f0b0020d9b908c18d9c36083d6763b 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -56,19 +56,19 @@ module Clusters
       def predefined_variables
         config = YAML.dump(kubeconfig)
 
-        variables = [
-          { key: 'KUBE_URL', value: api_url, public: true },
-          { key: 'KUBE_TOKEN', value: token, public: false },
-          { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
-          { key: 'KUBECONFIG', value: config, public: false, file: true }
-        ]
-
-        if ca_pem.present?
-          variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
-          variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
+        Gitlab::Ci::Variables::Collection.new.tap do |variables|
+          variables
+            .append(key: 'KUBE_URL', value: api_url)
+            .append(key: 'KUBE_TOKEN', value: token, public: false)
+            .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
+            .append(key: 'KUBECONFIG', value: config, public: false, file: true)
+
+          if ca_pem.present?
+            variables
+              .append(key: 'KUBE_CA_PEM', value: ca_pem)
+              .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
+          end
         end
-
-        variables
       end
 
       # Constructs a list of terminals from the reactive cache
@@ -134,7 +134,7 @@ module Clusters
         kubeclient = build_kubeclient!
 
         kubeclient.get_pods(namespace: actual_namespace).as_json
-      rescue KubeException => err
+      rescue Kubeclient::HttpError => err
         raise err unless err.error_code == 404
 
         []
diff --git a/app/models/commit.rb b/app/models/commit.rb
index b9106309142e2c23e3492136f79faaeca13681e1..9750e9298ec23eb48103cf552a27abf9c8ed0af8 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -9,6 +9,7 @@ class Commit
   include Mentionable
   include Referable
   include StaticModel
+  include ::Gitlab::Utils::StrongMemoize
 
   attr_mentionable :safe_message, pipeline: :single_line
 
@@ -29,9 +30,12 @@ class Commit
 
   MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
   COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
+  # Used by GFM to match and present link extensions on node texts and hrefs.
+  LINK_EXTENSION_PATTERN = /(patch)/.freeze
 
   def banzai_render_context(field)
-    context = { pipeline: :single_line, project: self.project }
+    pipeline = field == :description ? :commit_description : :single_line
+    context = { pipeline: pipeline, project: self.project }
     context[:author] = self.author if self.author
 
     context
@@ -141,7 +145,8 @@ class Commit
   end
 
   def self.link_reference_pattern
-    @link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/)
+    @link_reference_pattern ||=
+      super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})?(\.(?<extension>#{LINK_EXTENSION_PATTERN}))?/)
   end
 
   def to_reference(from = nil, full: false)
@@ -174,7 +179,7 @@ class Commit
       if safe_message.blank?
         no_commit_message
       else
-        safe_message.split("\n", 2).first
+        safe_message.split(/[\r\n]/, 2).first
       end
   end
 
@@ -225,11 +230,13 @@ class Commit
   end
 
   def parents
-    @parents ||= parent_ids.map { |id| project.commit(id) }
+    @parents ||= parent_ids.map { |oid| Commit.lazy(project, oid) }
   end
 
   def parent
-    @parent ||= project.commit(self.parent_id) if self.parent_id
+    strong_memoize(:parent) do
+      project.commit_by(oid: self.parent_id) if self.parent_id
+    end
   end
 
   def notes
@@ -241,7 +248,7 @@ class Commit
   end
 
   def notes_with_associations
-    notes.includes(:author)
+    notes.includes(:author, :award_emoji)
   end
 
   def merge_requests
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 9fb5b7efec6128201bb222fcd408a024c0d11a61..b6276c2fb50ed41cd9959bad6d0cb57fffe4d6a2 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -87,7 +87,7 @@ class CommitStatus < ActiveRecord::Base
       transition [:created, :pending, :running, :manual] => :canceled
     end
 
-    before_transition created: [:pending, :running] do |commit_status|
+    before_transition [:created, :skipped, :manual] => :pending do |commit_status|
       commit_status.queued_at = Time.now
     end
 
@@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base
   end
 
   def group_name
-    name.to_s.gsub(%r{\d+[\.\s:/\\]+\d+\s*}, '').strip
+    name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
   end
 
   def failed_but_allowed?
diff --git a/app/models/compare.rb b/app/models/compare.rb
index 3a8bbcb1acdf2ac8b24edd9e3dc915c5fe1c0668..feb4b89c7815e95d37d05a85029b83dec2f0a1b7 100644
--- a/app/models/compare.rb
+++ b/app/models/compare.rb
@@ -1,4 +1,6 @@
 class Compare
+  include Gitlab::Utils::StrongMemoize
+
   delegate :same, :head, :base, to: :@compare
 
   attr_reader :project
@@ -11,9 +13,10 @@ class Compare
     end
   end
 
-  def initialize(compare, project, straight: false)
+  def initialize(compare, project, base_sha: nil, straight: false)
     @compare = compare
     @project = project
+    @base_sha = base_sha
     @straight = straight
   end
 
@@ -22,40 +25,36 @@ class Compare
   end
 
   def start_commit
-    return @start_commit if defined?(@start_commit)
+    strong_memoize(:start_commit) do
+      commit = @compare.base
 
-    commit = @compare.base
-    @start_commit = commit ? ::Commit.new(commit, project) : nil
+      ::Commit.new(commit, project) if commit
+    end
   end
 
   def head_commit
-    return @head_commit if defined?(@head_commit)
+    strong_memoize(:head_commit) do
+      commit = @compare.head
 
-    commit = @compare.head
-    @head_commit = commit ? ::Commit.new(commit, project) : nil
+      ::Commit.new(commit, project) if commit
+    end
   end
   alias_method :commit, :head_commit
 
-  def base_commit
-    return @base_commit if defined?(@base_commit)
-
-    @base_commit = if start_commit && head_commit
-                     project.merge_base_commit(start_commit.id, head_commit.id)
-                   else
-                     nil
-                   end
-  end
-
   def start_commit_sha
-    start_commit.try(:sha)
+    start_commit&.sha
   end
 
   def base_commit_sha
-    base_commit.try(:sha)
+    strong_memoize(:base_commit) do
+      next unless start_commit && head_commit
+
+      @base_sha || project.merge_base_commit(start_commit.id, head_commit.id)&.sha
+    end
   end
 
   def head_commit_sha
-    commit.try(:sha)
+    commit&.sha
   end
 
   def raw_diffs(*args)
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4b66725a3e6a1c644cbdc823415e2e3b2173bf1f
--- /dev/null
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -0,0 +1,46 @@
+# Include atomic internal id generation scheme for a model
+#
+# This allows us to atomically generate internal ids that are
+# unique within a given scope.
+#
+# For example, let's generate internal ids for Issue per Project:
+# ```
+# class Issue < ActiveRecord::Base
+#   has_internal_id :iid, scope: :project, init: ->(s) { s.project.issues.maximum(:iid) }
+# end
+# ```
+#
+# This generates unique internal ids per project for newly created issues.
+# The generated internal id is saved in the `iid` attribute of `Issue`.
+#
+# This concern uses InternalId records to facilitate atomicity.
+# In the absence of a record for the given scope, one will be created automatically.
+# In this situation, the `init` block is called to calculate the initial value.
+# In the example above, we calculate the maximum `iid` of all issues
+# within the given project.
+#
+# Note that a model may have more than one internal id associated with possibly
+# different scopes.
+module AtomicInternalId
+  extend ActiveSupport::Concern
+
+  module ClassMethods
+    def has_internal_id(column, scope:, init:) # rubocop:disable Naming/PredicateName
+      before_validation(on: :create) do
+        if read_attribute(column).blank?
+          scope_attrs = { scope => association(scope).reader }
+          usage = self.class.table_name.to_sym
+
+          new_iid = InternalId.generate_next(self, scope_attrs, usage, init)
+          write_attribute(column, new_iid)
+        end
+      end
+
+      validates column, presence: true, numericality: true
+    end
+  end
+
+  def to_param
+    iid.to_s
+  end
+end
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index d35e37935fb0bb7f4634ad5d390625ded08a1c9f..7677891b9cef760a6e3b48398ef23415bb92ab72 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -3,6 +3,7 @@ module Avatarable
 
   included do
     prepend ShadowMethods
+    include ObjectStorage::BackgroundMove
 
     validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
     validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
@@ -21,7 +22,7 @@ module Avatarable
 
   def avatar_type
     unless self.avatar.image?
-      self.errors.add :avatar, "only images allowed"
+      errors.add :avatar, "file format is not supported. Please try one of the following supported formats: #{AvatarUploader::IMAGE_EXT.join(', ')}"
     end
   end
 
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index d83944153625a62aef535d31538d0e66606c5d84..fce37e7f78ee7b3862151ef62c023ea4cad92b93 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -79,11 +79,7 @@ module Awardable
   end
 
   def user_can_award?(current_user, name)
-    if user_authored?(current_user)
-      !awardable_votes?(normalize_name(name))
-    else
-      true
-    end
+    awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
   end
 
   def user_authored?(current_user)
@@ -119,4 +115,12 @@ module Awardable
   def normalize_name(name)
     Gitlab::Emoji.normalize_emoji_name(name)
   end
+
+  def awardable_by_user?(current_user, name)
+    if user_authored?(current_user)
+      !awardable_votes?(normalize_name(name))
+    else
+      true
+    end
+  end
 end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 4ae5dd8c67796dc1b458023c376b5b90e647ced6..db8cf322ef7f702688ea97184b281dfd763d58ca 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -11,7 +11,9 @@ module CacheMarkdownField
   extend ActiveSupport::Concern
 
   # Increment this number every time the renderer changes its output
-  CACHE_VERSION = 3
+  CACHE_REDCARPET_VERSION         = 3
+  CACHE_COMMONMARK_VERSION_START  = 10
+  CACHE_COMMONMARK_VERSION        = 11
 
   # changes to these attributes cause the cache to be invalidates
   INVALIDATED_BY = %w[author project].freeze
@@ -49,12 +51,14 @@ module CacheMarkdownField
 
     # Always include a project key, or Banzai complains
     project = self.project if self.respond_to?(:project)
-    group = self.group if self.respond_to?(:group)
+    group   = self.group if self.respond_to?(:group)
     context = cached_markdown_fields[field].merge(project: project, group: group)
 
     # Banzai is less strict about authors, so don't always have an author key
     context[:author] = self.author if self.respond_to?(:author)
 
+    context[:markdown_engine] = markdown_engine
+
     context
   end
 
@@ -69,7 +73,7 @@ module CacheMarkdownField
         Banzai::Renderer.cacheless_render_field(self, markdown_field, options)
       ]
     end.to_h
-    updates['cached_markdown_version'] = CacheMarkdownField::CACHE_VERSION
+    updates['cached_markdown_version'] = latest_cached_markdown_version
 
     updates.each {|html_field, data| write_attribute(html_field, data) }
   end
@@ -90,7 +94,7 @@ module CacheMarkdownField
     markdown_changed = attribute_changed?(markdown_field) || false
     html_changed = attribute_changed?(html_field) || false
 
-    CacheMarkdownField::CACHE_VERSION == cached_markdown_version &&
+    latest_cached_markdown_version == cached_markdown_version &&
       (html_changed || markdown_changed == html_changed)
   end
 
@@ -109,6 +113,24 @@ module CacheMarkdownField
     __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
   end
 
+  def latest_cached_markdown_version
+    return CacheMarkdownField::CACHE_REDCARPET_VERSION unless cached_markdown_version
+
+    if cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
+      CacheMarkdownField::CACHE_REDCARPET_VERSION
+    else
+      CacheMarkdownField::CACHE_COMMONMARK_VERSION
+    end
+  end
+
+  def markdown_engine
+    if latest_cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
+      :redcarpet
+    else
+      :common_mark
+    end
+  end
+
   included do
     cattr_reader :cached_markdown_fields do
       FieldData.new
diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb
new file mode 100644
index 0000000000000000000000000000000000000000..593a9b3d71d3678adda57b19cf65bd0f49cfc581
--- /dev/null
+++ b/app/models/concerns/chronic_duration_attribute.rb
@@ -0,0 +1,39 @@
+module ChronicDurationAttribute
+  extend ActiveSupport::Concern
+
+  class_methods do
+    def chronic_duration_attr_reader(virtual_attribute, source_attribute)
+      define_method(virtual_attribute) do
+        chronic_duration_attributes[virtual_attribute] || output_chronic_duration_attribute(source_attribute)
+      end
+    end
+
+    def chronic_duration_attr_writer(virtual_attribute, source_attribute, parameters = {})
+      chronic_duration_attr_reader(virtual_attribute, source_attribute)
+
+      define_method("#{virtual_attribute}=") do |value|
+        chronic_duration_attributes[virtual_attribute] = value.presence || parameters[:default].presence.to_s
+
+        begin
+          new_value = value.present? ? ChronicDuration.parse(value).to_i : parameters[:default].presence
+          assign_attributes(source_attribute => new_value)
+        rescue ChronicDuration::DurationParseError
+          # ignore error as it will be caught by validation
+        end
+      end
+
+      validates virtual_attribute, allow_nil: true, duration: true
+    end
+
+    alias_method :chronic_duration_attr, :chronic_duration_attr_writer
+  end
+
+  def chronic_duration_attributes
+    @chronic_duration_attributes ||= {}
+  end
+
+  def output_chronic_duration_attribute(source_attribute)
+    value = attributes[source_attribute.to_s]
+    ChronicDuration.output(value, format: :short) if value
+  end
+end
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 89d0474a59680573c3990a57afc4e145fcd8fc4a..52851b3d0b2146b759ef49141552bea3f087ef46 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -1,15 +1,24 @@
 module DeploymentPlatform
-  def deployment_platform
-    @deployment_platform ||=
-      find_cluster_platform_kubernetes ||
-      find_kubernetes_service_integration ||
-      build_cluster_and_deployment_platform
+  # EE would override this and utilize environment argument
+  # rubocop:disable Gitlab/ModuleWithInstanceVariables
+  def deployment_platform(environment: nil)
+    @deployment_platform ||= {}
+
+    @deployment_platform[environment] ||= find_deployment_platform(environment)
   end
 
   private
 
-  def find_cluster_platform_kubernetes
-    clusters.find_by(enabled: true)&.platform_kubernetes
+  def find_deployment_platform(environment)
+    find_cluster_platform_kubernetes(environment: environment) ||
+      find_kubernetes_service_integration ||
+      build_cluster_and_deployment_platform
+  end
+
+  # EE would override this and utilize environment argument
+  def find_cluster_platform_kubernetes(environment: nil)
+    clusters.enabled.default_environment
+      .last&.platform_kubernetes
   end
 
   def find_kubernetes_service_integration
diff --git a/app/models/concerns/group_descendant.rb b/app/models/concerns/group_descendant.rb
index 01957da0bf3e61ad0ceb1d51023e21c4851bf72d..261ace57a17745229cdec5635ced52dd2d33a5fa 100644
--- a/app/models/concerns/group_descendant.rb
+++ b/app/models/concerns/group_descendant.rb
@@ -37,7 +37,20 @@ module GroupDescendant
     parent ||= preloaded.detect { |possible_parent| possible_parent.is_a?(Group) && possible_parent.id == child.parent_id }
 
     if parent.nil? && !child.parent_id.nil?
-      raise ArgumentError.new('parent was not preloaded')
+      parent = child.parent
+
+      exception = ArgumentError.new <<~MSG
+        parent: [GroupDescendant: #{parent.inspect}] was not preloaded for [#{child.inspect}]")
+        This error is not user facing, but causes a +1 query.
+      MSG
+      extras = {
+        parent: parent,
+        child: child,
+        preloaded: preloaded.map(&:full_path)
+      }
+      issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/40785'
+
+      Gitlab::Sentry.track_exception(exception, issue_url: issue_url, extra: extras)
     end
 
     if parent.nil? && hierarchy_top.present?
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
deleted file mode 100644
index 01079fb8bd6aceb6db26b83c557207639d2f58e7..0000000000000000000000000000000000000000
--- a/app/models/concerns/internal_id.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module InternalId
-  extend ActiveSupport::Concern
-
-  included do
-    validate :set_iid, on: :create
-    validates :iid, presence: true, numericality: true
-  end
-
-  def set_iid
-    if iid.blank?
-      parent = project || group
-      records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
-      max_iid = records.maximum(:iid)
-
-      self.iid = max_iid.to_i + 1
-    end
-  end
-
-  def to_param
-    iid.to_s
-  end
-end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 7049f340c9d41cbb99545ef7e1e1cb247fce557b..b45395343cce032f223c0f6ad0f44f12bae73b46 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -19,6 +19,7 @@ module Issuable
   include AfterCommitQueue
   include Sortable
   include CreatedAtFilterable
+  include UpdatedAtFilterable
 
   # This object is used to gather issuable meta data for displaying
   # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
@@ -136,7 +137,7 @@ module Issuable
       fuzzy_search(query, [:title, :description])
     end
 
-    def sort(method, excluded_labels: [])
+    def sort_by_attribute(method, excluded_labels: [])
       sorted =
         case method.to_s
         when 'downvotes_desc'     then order_downvotes_desc
@@ -222,6 +223,10 @@ module Issuable
     def to_ability_name
       model_name.singular
     end
+
+    def parent_class
+      ::Project
+    end
   end
 
   def today?
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index caf8afa97f93c48fc9bcc03d21792443a73093b5..967fd9c5eea5031b5bbfc4c6bcaf4041c7707336 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -45,11 +45,11 @@ module Milestoneish
   end
 
   def sorted_issues(user)
-    issues_visible_to_user(user).preload_associations.sort('label_priority')
+    issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority')
   end
 
   def sorted_merge_requests
-    merge_requests.sort('label_priority')
+    merge_requests.sort_by_attribute('label_priority')
   end
 
   def upcoming?
@@ -102,14 +102,14 @@ module Milestoneish
     Gitlab::TimeTrackingFormatter.output(total_issue_time_estimate)
   end
 
-  private
-
   def count_issues_by_state(user)
     memoize_per_user(user, :count_issues_by_state) do
       issues_visible_to_user(user).reorder(nil).group(:state).count
     end
   end
 
+  private
+
   def memoize_per_user(user, method_name)
     memoized_users[method_name][user&.id] ||= yield
   end
diff --git a/app/models/concerns/nonatomic_internal_id.rb b/app/models/concerns/nonatomic_internal_id.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9d0c9b8512fbd1e85809fc98246ce96578191328
--- /dev/null
+++ b/app/models/concerns/nonatomic_internal_id.rb
@@ -0,0 +1,22 @@
+module NonatomicInternalId
+  extend ActiveSupport::Concern
+
+  included do
+    validate :set_iid, on: :create
+    validates :iid, presence: true, numericality: true
+  end
+
+  def set_iid
+    if iid.blank?
+      parent = project || group
+      records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
+      max_iid = records.maximum(:iid)
+
+      self.iid = max_iid.to_i + 1
+    end
+  end
+
+  def to_param
+    iid.to_s
+  end
+end
diff --git a/app/models/concerns/presentable.rb b/app/models/concerns/presentable.rb
index 7b33b8370043cd9e7b66c72bd31fa672966b93bc..bc4fbd19a0224498036d2de8ccc2fd326817f749 100644
--- a/app/models/concerns/presentable.rb
+++ b/app/models/concerns/presentable.rb
@@ -1,4 +1,12 @@
 module Presentable
+  extend ActiveSupport::Concern
+
+  class_methods do
+    def present(attributes)
+      all.map { |klass_object| klass_object.present(attributes) }
+    end
+  end
+
   def present(**attributes)
     Gitlab::View::Presenter::Factory
       .new(self, attributes)
diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..18cbbd871a11cfef85c90a3949dabaf57151a642
--- /dev/null
+++ b/app/models/concerns/prometheus_adapter.rb
@@ -0,0 +1,48 @@
+module PrometheusAdapter
+  extend ActiveSupport::Concern
+
+  included do
+    include ReactiveCaching
+
+    self.reactive_cache_key = ->(adapter) { [adapter.class.model_name.singular, adapter.id] }
+    self.reactive_cache_lease_timeout = 30.seconds
+    self.reactive_cache_refresh_interval = 30.seconds
+    self.reactive_cache_lifetime = 1.minute
+
+    def prometheus_client
+      raise NotImplementedError
+    end
+
+    def prometheus_client_wrapper
+      Gitlab::PrometheusClient.new(prometheus_client)
+    end
+
+    def can_query?
+      prometheus_client.present?
+    end
+
+    def query(query_name, *args)
+      return unless can_query?
+
+      query_class = Gitlab::Prometheus::Queries.const_get("#{query_name.to_s.classify}Query")
+
+      args.map!(&:id)
+
+      with_reactive_cache(query_class.name, *args, &query_class.method(:transform_reactive_result))
+    end
+
+    # Cache metrics for specific environment
+    def calculate_reactive_cache(query_class_name, *args)
+      return unless prometheus_client
+
+      data = Kernel.const_get(query_class_name).new(prometheus_client_wrapper).query(*args)
+      {
+        success: true,
+        data: data,
+        last_update: Time.now.utc
+      }
+    rescue Gitlab::PrometheusClient::Error => err
+      { success: false, result: err.message }
+    end
+  end
+end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 67a988addbe7ca941d789f68dc7f157b2d8b47da..f05e606995dedbbd8c6c0d78f867a6682df1b0da 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -7,29 +7,24 @@ module Storage
         raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
       end
 
-      expires_full_path_cache
-
-      # Move the namespace directory in all storage paths used by member projects
-      repository_storage_paths.each do |repository_storage_path|
-        # Ensure old directory exists before moving it
-        gitlab_shell.add_namespace(repository_storage_path, full_path_was)
-
-        # Ensure new directory exists before moving it (if there's a parent)
-        gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
+      parent_was = if parent_changed? && parent_id_was.present?
+                     Namespace.find(parent_id_was) # raise NotFound early if needed
+                   end
 
-        unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
+      expires_full_path_cache
 
-          Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
+      move_repositories
 
-          # if we cannot move namespace directory we should rollback
-          # db changes in order to prevent out of sync between db and fs
-          raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
-        end
+      if parent_changed?
+        former_parent_full_path = parent_was&.full_path
+        parent_full_path = parent&.full_path
+        Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
+        Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
+      else
+        Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
+        Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
       end
 
-      Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
-      Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
-
       remove_exports!
 
       # If repositories moved successfully we need to
@@ -57,6 +52,26 @@ module Storage
 
     private
 
+    def move_repositories
+      # Move the namespace directory in all storage paths used by member projects
+      repository_storage_paths.each do |repository_storage_path|
+        # Ensure old directory exists before moving it
+        gitlab_shell.add_namespace(repository_storage_path, full_path_was)
+
+        # Ensure new directory exists before moving it (if there's a parent)
+        gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
+
+        unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
+
+          Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
+
+          # if we cannot move namespace directory we should rollback
+          # db changes in order to prevent out of sync between db and fs
+          raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
+        end
+      end
+    end
+
     def old_repository_storage_paths
       @old_repository_storage_paths ||= repository_storage_paths
     end
diff --git a/app/models/concerns/updated_at_filterable.rb b/app/models/concerns/updated_at_filterable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..edb423b7828cd594da103ae4c0515965cf4545da
--- /dev/null
+++ b/app/models/concerns/updated_at_filterable.rb
@@ -0,0 +1,12 @@
+module UpdatedAtFilterable
+  extend ActiveSupport::Concern
+
+  included do
+    scope :updated_before, ->(date) { where(scoped_table[:updated_at].lteq(date)) }
+    scope :updated_after, ->(date) { where(scoped_table[:updated_at].gteq(date)) }
+
+    def self.scoped_table
+      arel_table.alias(table_name)
+    end
+  end
+end
diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index c2e0a5fa12688a219bc5c6b1ad0a94dbe435b912..89a74b7dcb1d94a8fb612121454d1ce1698b4219 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -27,6 +27,10 @@ class DeployKey < Key
     self.private?
   end
 
+  def user
+    super || User.ghost
+  end
+
   def has_access_to?(project)
     deploy_keys_project_for(project).present?
   end
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
new file mode 100644
index 0000000000000000000000000000000000000000..979e9232fdaa11380f69eaa43ae2e63ed4652219
--- /dev/null
+++ b/app/models/deploy_token.rb
@@ -0,0 +1,61 @@
+class DeployToken < ActiveRecord::Base
+  include Expirable
+  include TokenAuthenticatable
+  add_authentication_token_field :token
+
+  AVAILABLE_SCOPES = %i(read_repository read_registry).freeze
+
+  default_value_for(:expires_at) { Forever.date }
+
+  has_many :project_deploy_tokens, inverse_of: :deploy_token
+  has_many :projects, through: :project_deploy_tokens
+
+  validate :ensure_at_least_one_scope
+  before_save :ensure_token
+
+  accepts_nested_attributes_for :project_deploy_tokens
+
+  scope :active, -> { where("revoked = false AND expires_at >= NOW()") }
+
+  def revoke!
+    update!(revoked: true)
+  end
+
+  def active?
+    !revoked
+  end
+
+  def scopes
+    AVAILABLE_SCOPES.select { |token_scope| read_attribute(token_scope) }
+  end
+
+  def username
+    "gitlab+deploy-token-#{id}"
+  end
+
+  def has_access_to?(requested_project)
+    active? && project == requested_project
+  end
+
+  # This is temporal. Currently we limit DeployToken
+  # to a single project, later we're going to extend
+  # that to be for multiple projects and namespaces.
+  def project
+    projects.first
+  end
+
+  def expires_at
+    expires_at = read_attribute(:expires_at)
+    expires_at != Forever.date ? expires_at : nil
+  end
+
+  def expires_at=(value)
+    write_attribute(:expires_at, value.presence || Forever.date)
+  end
+
+  private
+
+  def ensure_at_least_one_scope
+    errors.add(:base, "Scopes can't be blank") unless read_repository || read_registry
+  end
+end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index b6cf168d60e3052e92c7c64f71c8484be9f3b40b..e18ea8bfea43be236596a316d390f6ea6f7ddf55 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -1,5 +1,5 @@
 class Deployment < ActiveRecord::Base
-  include InternalId
+  include NonatomicInternalId
 
   belongs_to :project, required: true
   belongs_to :environment, required: true
@@ -98,28 +98,29 @@ class Deployment < ActiveRecord::Base
   end
 
   def has_metrics?
-    project.monitoring_service.present?
+    prometheus_adapter&.can_query?
   end
 
   def metrics
     return {} unless has_metrics?
 
-    project.monitoring_service.deployment_metrics(self)
-  end
-
-  def has_additional_metrics?
-    project.prometheus_service.present?
+    metrics = prometheus_adapter.query(:deployment, self)
+    metrics&.merge(deployment_time: created_at.to_i) || {}
   end
 
   def additional_metrics
-    return {} unless project.prometheus_service.present?
+    return {} unless has_metrics?
 
-    metrics = project.prometheus_service.additional_deployment_metrics(self)
+    metrics = prometheus_adapter.query(:additional_metrics_deployment, self)
     metrics&.merge(deployment_time: created_at.to_i) || {}
   end
 
   private
 
+  def prometheus_adapter
+    environment.prometheus_adapter
+  end
+
   def ref_path
     File.join(environment.ref_path, 'deployments', iid.to_s)
   end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index f78c21aebe5bc7046ae18825330ce0f69ebdd9a3..fddb269af4baa7a70a43cb4558c82ce64532e945 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -65,10 +65,9 @@ class Environment < ActiveRecord::Base
   end
 
   def predefined_variables
-    [
-      { key: 'CI_ENVIRONMENT_NAME', value: name, public: true },
-      { key: 'CI_ENVIRONMENT_SLUG', value: slug, public: true }
-    ]
+    Gitlab::Ci::Variables::Collection.new
+      .append(key: 'CI_ENVIRONMENT_NAME', value: name)
+      .append(key: 'CI_ENVIRONMENT_SLUG', value: slug)
   end
 
   def recently_updated_on_branch?(ref)
@@ -99,8 +98,8 @@ class Environment < ActiveRecord::Base
     folder_name == "production"
   end
 
-  def first_deployment_for(commit)
-    ref = project.repository.ref_name_for_sha(ref_path, commit.sha)
+  def first_deployment_for(commit_sha)
+    ref = project.repository.ref_name_for_sha(ref_path, commit_sha)
 
     return nil unless ref
 
@@ -146,21 +145,19 @@ class Environment < ActiveRecord::Base
   end
 
   def has_metrics?
-    project.monitoring_service.present? && available? && last_deployment.present?
+    prometheus_adapter&.can_query? && available? && last_deployment.present?
   end
 
   def metrics
-    project.monitoring_service.environment_metrics(self) if has_metrics?
+    prometheus_adapter.query(:environment, self) if has_metrics?
   end
 
-  def has_additional_metrics?
-    project.prometheus_service.present? && available? && last_deployment.present?
+  def additional_metrics
+    prometheus_adapter.query(:additional_metrics_environment, self) if has_metrics?
   end
 
-  def additional_metrics
-    if has_additional_metrics?
-      project.prometheus_service.additional_environment_metrics(self)
-    end
+  def prometheus_adapter
+    @prometheus_adapter ||= Prometheus::AdapterService.new(project, deployment_platform).prometheus_adapter
   end
 
   def slug
@@ -226,6 +223,10 @@ class Environment < ActiveRecord::Base
     self.environment_type || self.name
   end
 
+  def deployment_platform
+    project.deployment_platform(environment: self.name)
+  end
+
   private
 
   # Slugifying a name may remove the uniqueness guarantee afforded by it being
diff --git a/app/models/event.rb b/app/models/event.rb
index 75538ba196c049c920f96cd48a7c1a1c3002c6fe..741a84194e2aa571a01d29c8a0604af86d99aa5b 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -52,12 +52,12 @@ class Event < ActiveRecord::Base
   belongs_to :target, -> {
     # If the association for "target" defines an "author" association we want to
     # eager-load this so Banzai & friends don't end up performing N+1 queries to
-    # get the authors of notes, issues, etc.
-    if reflections['events'].active_record.reflect_on_association(:author)
-      includes(:author)
-    else
-      self
+    # get the authors of notes, issues, etc. (likewise for "noteable").
+    incs = %i(author noteable).select do |a|
+      reflections['events'].active_record.reflect_on_association(a)
     end
+
+    incs.reduce(self) { |obj, a| obj.includes(a) }
   }, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
 
   has_one :push_event_payload
@@ -65,6 +65,7 @@ class Event < ActiveRecord::Base
   # Callbacks
   after_create :reset_project_activity
   after_create :set_last_repository_updated_at, if: :push?
+  after_create :track_user_interacted_projects
 
   # Scopes
   scope :recent, -> { reorder(id: :desc) }
@@ -109,7 +110,10 @@ class Event < ActiveRecord::Base
       end
     end
 
+    # Remove this method when removing Gitlab.rails5? code.
     def subclass_from_attributes(attrs)
+      return super if Gitlab.rails5?
+
       # Without this Rails will keep calling this method on the returned class,
       # resulting in an infinite loop.
       return unless self == Event
@@ -158,7 +162,7 @@ class Event < ActiveRecord::Base
 
   def project_name
     if project
-      project.name_with_namespace
+      project.full_name
     else
       "(deleted project)"
     end
@@ -389,4 +393,11 @@ class Event < ActiveRecord::Base
     Project.unscoped.where(id: project_id)
       .update_all(last_repository_updated_at: created_at)
   end
+
+  def track_user_interacted_projects
+    # Note the call to .available? is due to earlier migrations
+    # that would otherwise conflict with the call to .track
+    # (because the table does not exist yet).
+    UserInteractedProject.track(self) if UserInteractedProject.available?
+  end
 end
diff --git a/app/models/group.rb b/app/models/group.rb
index 75bf013ecd2721da56a5d4a4103714e0ec2057ee..8ff781059cc01592a0c726031713b81bd7e5dc60 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -31,6 +31,9 @@ class Group < Namespace
 
   has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
 
+  has_many :boards
+  has_many :badges, class_name: 'GroupBadge'
+
   accepts_nested_attributes_for :variables, allow_destroy: true
 
   validate :visibility_level_allowed_by_projects
@@ -50,7 +53,7 @@ class Group < Namespace
       Gitlab::Database.postgresql?
     end
 
-    def sort(method)
+    def sort_by_attribute(method)
       if method == 'storage_size_desc'
         # storage_size is a virtual column so we need to
         # pass a string to avoid AR adding the table name
@@ -186,12 +189,6 @@ class Group < Namespace
     owners.include?(user) && owners.size == 1
   end
 
-  def avatar_type
-    unless self.avatar.image?
-      self.errors.add :avatar, "only images allowed"
-    end
-  end
-
   def post_create_hook
     Gitlab::AppLogger.info("Group \"#{name}\" was created")
 
@@ -227,13 +224,13 @@ class Group < Namespace
       end
 
     GroupMember
-      .active_without_invites
+      .active_without_invites_and_requests
       .where(source_id: source_ids)
   end
 
   def members_with_descendants
     GroupMember
-      .active_without_invites
+      .active_without_invites_and_requests
       .where(source_id: self_and_descendants.reorder(nil).select(:id))
   end
 
@@ -289,6 +286,10 @@ class Group < Namespace
     false
   end
 
+  def refresh_project_authorizations
+    refresh_members_authorized_projects(blocking: false)
+  end
+
   private
 
   def update_two_factor_requirement
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index b6dd39b860bcc045c251384ae11919e9ae8de25a..ec072882cc9a580ca93c1153835ca6f8180d65da 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -7,6 +7,7 @@ class ProjectHook < WebHook
     :issue_hooks,
     :confidential_issue_hooks,
     :note_hooks,
+    :confidential_note_hooks,
     :merge_request_hooks,
     :job_hooks,
     :pipeline_hooks,
diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96a430066423b4bad017814f2299051bae6c0734
--- /dev/null
+++ b/app/models/internal_id.rb
@@ -0,0 +1,141 @@
+# An InternalId is a strictly monotone sequence of integers
+# generated for a given scope and usage.
+#
+# For example, issues use their project to scope internal ids:
+# In that sense, scope is "project" and usage is "issues".
+# Generated internal ids for an issue are unique per project.
+#
+# See InternalId#usage enum for available usages.
+#
+# In order to leverage InternalId for other usages, the idea is to
+# * Add `usage` value to enum
+# * (Optionally) add columns to `internal_ids` if needed for scope.
+class InternalId < ActiveRecord::Base
+  belongs_to :project
+
+  enum usage: { issues: 0 }
+
+  validates :usage, presence: true
+
+  REQUIRED_SCHEMA_VERSION = 20180305095250
+
+  # Increments #last_value and saves the record
+  #
+  # The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL).
+  # As such, the increment is atomic and safe to be called concurrently.
+  #
+  # If a `maximum_iid` is passed in, this overrides the incremented value if it's
+  # greater than that. This can be used to correct the increment value if necessary.
+  def increment_and_save!(maximum_iid)
+    lock!
+    self.last_value = [(last_value || 0) + 1, (maximum_iid || 0) + 1].max
+    save!
+    last_value
+  end
+
+  class << self
+    def generate_next(subject, scope, usage, init)
+      # Shortcut if `internal_ids` table is not available (yet)
+      # This can be the case in other (unrelated) migration specs
+      return (init.call(subject) || 0) + 1 unless available?
+
+      InternalIdGenerator.new(subject, scope, usage, init).generate
+    end
+
+    def available?
+      @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION # rubocop:disable Gitlab/PredicateMemoization
+    end
+
+    # Flushes cached information about schema
+    def reset_column_information
+      @available_flag = nil
+      super
+    end
+  end
+
+  class InternalIdGenerator
+    # Generate next internal id for a given scope and usage.
+    #
+    # For currently supported usages, see #usage enum.
+    #
+    # The method implements a locking scheme that has the following properties:
+    # 1) Generated sequence of internal ids is unique per (scope and usage)
+    # 2) The method is thread-safe and may be used in concurrent threads/processes.
+    # 3) The generated sequence is gapless.
+    # 4) In the absence of a record in the internal_ids table, one will be created
+    #    and last_value will be calculated on the fly.
+    #
+    # subject: The instance we're generating an internal id for. Gets passed to init if called.
+    # scope: Attributes that define the scope for id generation.
+    # usage: Symbol to define the usage of the internal id, see InternalId.usages
+    # init: Block that gets called to initialize InternalId record if not present
+    #       Make sure to not throw exceptions in the absence of records (if this is expected).
+    attr_reader :subject, :scope, :init, :scope_attrs, :usage
+
+    def initialize(subject, scope, usage, init)
+      @subject = subject
+      @scope = scope
+      @init = init
+      @usage = usage
+
+      raise ArgumentError, 'Scope is not well-defined, need at least one column for scope (given: 0)' if scope.empty?
+
+      unless InternalId.usages.has_key?(usage.to_s)
+        raise ArgumentError, "Usage '#{usage}' is unknown. Supported values are #{InternalId.usages.keys} from InternalId.usages"
+      end
+    end
+
+    # Generates next internal id and returns it
+    def generate
+      subject.transaction do
+        # Create a record in internal_ids if one does not yet exist
+        # and increment its last value
+        #
+        # Note this will acquire a ROW SHARE lock on the InternalId record
+
+        # Note we always calculate the maximum iid present here and
+        # pass it in to correct the InternalId entry if it's last_value is off.
+        #
+        # This can happen in a transition phase where both `AtomicInternalId` and
+        # `NonatomicInternalId` code runs (e.g. during a deploy).
+        #
+        # This is subject to be cleaned up with the 10.8 release:
+        # https://gitlab.com/gitlab-org/gitlab-ce/issues/45389.
+        (lookup || create_record).increment_and_save!(maximum_iid)
+      end
+    end
+
+    private
+
+    # Retrieve InternalId record for (project, usage) combination, if it exists
+    def lookup
+      InternalId.find_by(**scope, usage: usage_value)
+    end
+
+    def usage_value
+      @usage_value ||= InternalId.usages[usage.to_s]
+    end
+
+    # Create InternalId record for (scope, usage) combination, if it doesn't exist
+    #
+    # We blindly insert without synchronization. If another process
+    # was faster in doing this, we'll realize once we hit the unique key constraint
+    # violation. We can safely roll-back the nested transaction and perform
+    # a lookup instead to retrieve the record.
+    def create_record
+      subject.transaction(requires_new: true) do
+        InternalId.create!(
+          **scope,
+          usage: usage_value,
+          last_value: maximum_iid
+        )
+      end
+    rescue ActiveRecord::RecordNotUnique
+      lookup
+    end
+
+    def maximum_iid
+      @maximum_iid ||= init.call(subject) || 0
+    end
+  end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index c81f7e52bb1ee392013ac935a67fc482e53e6ef1..7611e83647cdddc4db17898dc0e9e862a49e747f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -1,7 +1,7 @@
 require 'carrierwave/orm/activerecord'
 
 class Issue < ActiveRecord::Base
-  include InternalId
+  include AtomicInternalId
   include Issuable
   include Noteable
   include Referable
@@ -23,6 +23,9 @@ class Issue < ActiveRecord::Base
 
   belongs_to :project
   belongs_to :moved_to, class_name: 'Issue'
+  belongs_to :closed_by, class_name: 'User'
+
+  has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.issues&.maximum(:iid) }
 
   has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
 
@@ -46,6 +49,7 @@ class Issue < ActiveRecord::Base
   scope :without_due_date, -> { where(due_date: nil) }
   scope :due_before, ->(date) { where('issues.due_date < ?', date) }
   scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
+  scope :due_tomorrow, -> { where(due_date: Date.tomorrow) }
 
   scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
   scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
@@ -76,6 +80,11 @@ class Issue < ActiveRecord::Base
     before_transition any => :closed do |issue|
       issue.closed_at = Time.zone.now
     end
+
+    before_transition closed: :opened do |issue|
+      issue.closed_at = nil
+      issue.closed_by = nil
+    end
   end
 
   class << self
@@ -108,7 +117,7 @@ class Issue < ActiveRecord::Base
     'project_id'
   end
 
-  def self.sort(method, excluded_labels: [])
+  def self.sort_by_attribute(method, excluded_labels: [])
     case method.to_s
     when 'due_date'      then order_due_date_asc
     when 'due_date_asc'  then order_due_date_asc
@@ -264,11 +273,17 @@ class Issue < ActiveRecord::Base
 
   def as_json(options = {})
     super(options).tap do |json|
-      if options.key?(:sidebar_endpoints) && project
+      if options.key?(:issue_endpoints) && project
         url_helper = Gitlab::Routing.url_helpers
 
-        json.merge!(issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
-                    toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self))
+        issue_reference = options[:include_full_project_path] ? to_reference(full: true) : to_reference
+
+        json.merge!(
+          reference_path: issue_reference,
+          real_path: url_helper.project_issue_path(project, self),
+          issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
+          toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self)
+        )
       end
 
       if options.key?(:labels)
diff --git a/app/models/label.rb b/app/models/label.rb
index 7538f2d8718b5d086378a7532e9efee8473b3095..de7f1d56c6458800a3e713f90930aae95f13aad7 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -35,6 +35,7 @@ class Label < ActiveRecord::Base
   scope :templates, -> { where(template: true) }
   scope :with_title, ->(title) { where(title: title) }
   scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
+  scope :on_group_boards, ->(group_id) { with_lists_and_board.where(boards: { group_id: group_id }) }
   scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
 
   def self.prioritized(project)
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index fc586fa216e5006fdea9694dca28b520a91df7ed..6b7f280fb70c69d2e2b4ae58df820365cb7a8b87 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,18 +1,39 @@
 class LfsObject < ActiveRecord::Base
+  include AfterCommitQueue
+  include ObjectStorage::BackgroundMove
+
   has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
   has_many :projects, through: :lfs_objects_projects
 
+  scope :with_files_stored_locally, -> { where(file_store: [nil, LfsObjectUploader::Store::LOCAL]) }
+
   validates :oid, presence: true, uniqueness: true
 
   mount_uploader :file, LfsObjectUploader
 
+  after_save :update_file_store
+
+  def update_file_store
+    # The file.object_store is set during `uploader.store!`
+    # which happens after object is inserted/updated
+    self.update_column(:file_store, file.object_store)
+  end
+
   def project_allowed_access?(project)
     projects.exists?(project.lfs_storage_project.id)
   end
 
+  def local_store?
+    [nil, LfsObjectUploader::Store::LOCAL].include?(self.file_store)
+  end
+
   def self.destroy_unreferenced
     joins("LEFT JOIN lfs_objects_projects ON lfs_objects_projects.lfs_object_id = #{table_name}.id")
         .where(lfs_objects_projects: { id: nil })
         .destroy_all
   end
+
+  def self.calculate_oid(path)
+    Digest::SHA256.file(path).hexdigest
+  end
 end
diff --git a/app/models/member.rb b/app/models/member.rb
index 408e8b2d7041ef7e36a3471c686dead3c2ef86d2..eac4a22a03f6af4fed0943430fa2f9aca5b7a918 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -52,10 +52,10 @@ class Member < ActiveRecord::Base
   end
 
   # Like active, but without invites. For when a User is required.
-  scope :active_without_invites, -> do
+  scope :active_without_invites_and_requests, -> do
     left_join_users
       .where(users: { state: 'active' })
-      .where(requested_at: nil)
+      .non_request
       .reorder(nil)
   end
 
@@ -85,6 +85,7 @@ class Member < ActiveRecord::Base
   after_create :create_notification_setting, unless: [:pending?, :importing?]
   after_create :post_create_hook, unless: [:pending?, :importing?]
   after_update :post_update_hook, unless: [:pending?, :importing?]
+  after_destroy :destroy_notification_setting
   after_destroy :post_destroy_hook, unless: :pending?
   after_commit :refresh_member_authorized_projects
 
@@ -95,7 +96,7 @@ class Member < ActiveRecord::Base
       joins(:user).merge(User.search(query))
     end
 
-    def sort(method)
+    def sort_by_attribute(method)
       case method.to_s
       when 'access_level_asc' then reorder(access_level: :asc)
       when 'access_level_desc' then reorder(access_level: :desc)
@@ -315,6 +316,10 @@ class Member < ActiveRecord::Base
     user.notification_settings.find_or_create_for(source)
   end
 
+  def destroy_notification_setting
+    notification_setting&.destroy
+  end
+
   def notification_setting
     @notification_setting ||= user&.notification_settings_for(source)
   end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index b6f1dd272cd10ab1010df332048cd3e7d2d165b2..1c7ed4a96dfa0323fd8b27625f99be8c08a4564b 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -13,8 +13,6 @@ class ProjectMember < Member
 
   scope :in_project, ->(project) { where(source_id: project.id) }
 
-  before_destroy :delete_member_todos
-
   class << self
     # Add users to projects with passed access option
     #
@@ -93,10 +91,6 @@ class ProjectMember < Member
 
   private
 
-  def delete_member_todos
-    user.todos.where(project_id: source_id).destroy_all if user
-  end
-
   def send_invite
     notification_service.invite_project_member(self, @raw_invite_token)
 
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5bec68ce4f619eb706994f2778702e461e552607..91d8be5559b1de9463b930b4b45fe3e3b73c2a03 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1,5 +1,5 @@
 class MergeRequest < ActiveRecord::Base
-  include InternalId
+  include NonatomicInternalId
   include Issuable
   include Noteable
   include Referable
@@ -375,15 +375,27 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def diff_start_sha
-    diff_start_commit.try(:sha)
+    if persisted?
+      merge_request_diff.start_commit_sha
+    else
+      target_branch_head.try(:sha)
+    end
   end
 
   def diff_base_sha
-    diff_base_commit.try(:sha)
+    if persisted?
+      merge_request_diff.base_commit_sha
+    else
+      branch_merge_base_commit.try(:sha)
+    end
   end
 
   def diff_head_sha
-    diff_head_commit.try(:sha)
+    if persisted?
+      merge_request_diff.head_commit_sha
+    else
+      source_branch_head.try(:sha)
+    end
   end
 
   # When importing a pull request from GitHub, the old and new branches may no
@@ -524,18 +536,25 @@ class MergeRequest < ActiveRecord::Base
     merge_request_diff(true)
   end
 
+  def viewable_diffs
+    @viewable_diffs ||= merge_request_diffs.viewable.to_a
+  end
+
   def merge_request_diff_for(diff_refs_or_sha)
-    @merge_request_diffs_by_diff_refs_or_sha ||= Hash.new do |h, diff_refs_or_sha|
-      diffs = merge_request_diffs.viewable
-      h[diff_refs_or_sha] =
-        if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
-          diffs.find_by_diff_refs(diff_refs_or_sha)
-        else
-          diffs.find_by(head_commit_sha: diff_refs_or_sha)
-        end
-    end
+    matcher =
+      if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
+        {
+          'start_commit_sha' => diff_refs_or_sha.start_sha,
+          'head_commit_sha' => diff_refs_or_sha.head_sha,
+          'base_commit_sha' => diff_refs_or_sha.base_sha
+        }
+      else
+        { 'head_commit_sha' => diff_refs_or_sha }
+      end
 
-    @merge_request_diffs_by_diff_refs_or_sha[diff_refs_or_sha]
+    viewable_diffs.find do |diff|
+      diff.attributes.slice(*matcher.keys) == matcher
+    end
   end
 
   def version_params_for(diff_refs)
@@ -567,9 +586,10 @@ class MergeRequest < ActiveRecord::Base
     return unless open?
 
     old_diff_refs = self.diff_refs
+    new_diff = create_merge_request_diff
+
+    MergeRequests::MergeRequestDiffCacheService.new.execute(self, new_diff)
 
-    create_merge_request_diff
-    MergeRequests::MergeRequestDiffCacheService.new.execute(self)
     new_diff_refs = self.diff_refs
 
     update_diff_discussion_positions(
@@ -646,7 +666,7 @@ class MergeRequest < ActiveRecord::Base
     !ProtectedBranch.protected?(source_project, source_branch) &&
       !source_project.root_ref?(source_branch) &&
       Ability.allowed?(current_user, :push_code, source_project) &&
-      diff_head_commit == source_branch_head
+      diff_head_sha == source_branch_head.try(:sha)
   end
 
   def should_remove_source_branch?
@@ -853,7 +873,7 @@ class MergeRequest < ActiveRecord::Base
 
   def can_be_merged_by?(user)
     access = ::Gitlab::UserAccess.new(user, project: project)
-    access.can_push_to_branch?(target_branch) || access.can_merge_to_branch?(target_branch)
+    access.can_update_branch?(target_branch)
   end
 
   def can_be_merged_via_command_line_by?(user)
@@ -1075,4 +1095,22 @@ class MergeRequest < ActiveRecord::Base
 
     project.merge_requests.merged.where(author_id: author_id).empty?
   end
+
+  def allow_maintainer_to_push
+    maintainer_push_possible? && super
+  end
+
+  alias_method :allow_maintainer_to_push?, :allow_maintainer_to_push
+
+  def maintainer_push_possible?
+    source_project.present? && for_fork? &&
+      target_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
+      source_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
+      !ProtectedBranch.protected?(source_project, source_branch)
+  end
+
+  def can_allow_maintainer_to_push?(user)
+    maintainer_push_possible? &&
+      Ability.allowed?(user, :push_code, source_project)
+  end
 end
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
index b75387e236e402b03adaa6610a914ded3d4b09fd..1c2e57bb01fdb9fd14dddca4c5c19d4164a73acd 100644
--- a/app/models/merge_request_diff_commit.rb
+++ b/app/models/merge_request_diff_commit.rb
@@ -17,7 +17,7 @@ class MergeRequestDiffCommit < ActiveRecord::Base
       commit_hash.merge(
         merge_request_diff_id: merge_request_diff_id,
         relative_order: index,
-        sha: sha_attribute.type_cast_for_database(sha),
+        sha: sha_attribute.serialize(sha), # rubocop:disable Cop/ActiveRecordSerialize
         authored_date: Gitlab::Database.sanitize_timestamp(commit_hash[:authored_date]),
         committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date])
       )
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 77c19380e6697f7e55eea380ea384b086a89457d..a66a0015827c0b61ecbb60169ad30c849e06c6c0 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -8,7 +8,7 @@ class Milestone < ActiveRecord::Base
   Started = MilestoneStruct.new('Started', '#started', -3)
 
   include CacheMarkdownField
-  include InternalId
+  include NonatomicInternalId
   include Sortable
   include Referable
   include StripAttribute
@@ -34,8 +34,8 @@ class Milestone < ActiveRecord::Base
 
   scope :for_projects_and_groups, -> (project_ids, group_ids) do
     conditions = []
-    conditions << arel_table[:project_id].in(project_ids) if project_ids.compact.any?
-    conditions << arel_table[:group_id].in(group_ids) if group_ids.compact.any?
+    conditions << arel_table[:project_id].in(project_ids) if project_ids&.compact&.any?
+    conditions << arel_table[:group_id].in(group_ids) if group_ids&.compact&.any?
 
     where(conditions.reduce(:or))
   end
@@ -138,7 +138,7 @@ class Milestone < ActiveRecord::Base
     User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).uniq
   end
 
-  def self.sort(method)
+  def self.sort_by_attribute(method)
     case method.to_s
     when 'due_date_asc'
       reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index db274ea81720bf5a1a6ac31f231a8157cefe7ddb..c29a53e5ce7bb49f714be68c4551e1bc853d9d9c 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -222,6 +222,11 @@ class Namespace < ActiveRecord::Base
     has_parent?
   end
 
+  # Overridden on EE module
+  def multiple_issue_boards_available?
+    false
+  end
+
   def full_path_was
     if parent_id_was.nil?
       path_was
@@ -243,8 +248,8 @@ class Namespace < ActiveRecord::Base
     all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
   end
 
-  def features
-    []
+  def refresh_project_authorizations
+    owner.refresh_authorized_projects
   end
 
   private
diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb
index 9357e55b41951343f59761a04ef431ba2722aa1f..22d48c9e6613e999a2a13c9ff0a75f019450fefc 100644
--- a/app/models/network/commit.rb
+++ b/app/models/network/commit.rb
@@ -24,12 +24,7 @@ module Network
     end
 
     def parents(map)
-      @commit.parents.map do |p|
-        if map.include?(p.id)
-          map[p.id]
-        end
-      end
-      .compact
+      map.values_at(*@commit.parent_ids).compact
     end
   end
 end
diff --git a/app/models/note.rb b/app/models/note.rb
index d7a67ec277cdea363a68ede55319cae36996fc3c..e426f84832bbdced9dcdb437591c74f02bd1f834 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -81,7 +81,7 @@ class Note < ActiveRecord::Base
   validates :author, presence: true
   validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ }
 
-  validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note|
+  validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note|
     unless note.noteable.try(:project) == note.project
       errors.add(:project, 'does not match noteable project')
     end
@@ -228,7 +228,7 @@ class Note < ActiveRecord::Base
   end
 
   def skip_project_check?
-    for_personal_snippet?
+    !for_project_noteable?
   end
 
   def commit
@@ -268,6 +268,10 @@ class Note < ActiveRecord::Base
     self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR
   end
 
+  def confidential?
+    noteable.try(:confidential?)
+  end
+
   def editable?
     !system?
   end
@@ -308,6 +312,15 @@ class Note < ActiveRecord::Base
     self.noteable.supports_discussions? && !part_of_discussion?
   end
 
+  def can_create_todo?
+    # Skip system notes, and notes on project snippet
+    !system? && !for_snippet?
+  end
+
+  def can_create_notification?
+    true
+  end
+
   def discussion_class(noteable = nil)
     # When commit notes are rendered on an MR's Discussion page, they are
     # displayed in one discussion instead of individually.
@@ -374,12 +387,15 @@ class Note < ActiveRecord::Base
   def expire_etag_cache
     return unless noteable&.discussions_rendered_on_frontend?
 
-    key = Gitlab::Routing.url_helpers.project_noteable_notes_path(
+    Gitlab::EtagCaching::Store.new.touch(etag_key)
+  end
+
+  def etag_key
+    Gitlab::Routing.url_helpers.project_noteable_notes_path(
       project,
       target_type: noteable_type.underscore,
       target_id: noteable_id
     )
-    Gitlab::EtagCaching::Store.new.touch(key)
   end
 
   def touch(*args)
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index fd70e920c7ec771c00df726ac0e1eaf4f9f33862..2c3580bbdc6fc674c77f9e0527531875eb93f8c1 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -35,7 +35,8 @@ class NotificationRecipient
 
     # check this last because it's expensive
     # nobody should receive notifications if they've specifically unsubscribed
-    return false if unsubscribed?
+    # except if they were mentioned.
+    return false if @type != :mention && unsubscribed?
 
     true
   end
@@ -47,7 +48,7 @@ class NotificationRecipient
     when :custom
       custom_enabled? || %i[participating mention].include?(@type)
     when :watch, :participating
-      !excluded_watcher_action?
+      !action_excluded?
     when :mention
       @type == :mention
     else
@@ -82,26 +83,35 @@ class NotificationRecipient
 
   def has_access?
     DeclarativePolicy.subject_scope do
-      return false unless user.can?(:receive_notifications)
-      return true if @skip_read_ability
+      break false unless user.can?(:receive_notifications)
+      break true if @skip_read_ability
 
-      return false if @target && !user.can?(:read_cross_project)
-      return false if @project && !user.can?(:read_project, @project)
+      break false if @target && !user.can?(:read_cross_project)
+      break false if @project && !user.can?(:read_project, @project)
 
-      return true unless read_ability
-      return true unless DeclarativePolicy.has_policy?(@target)
+      break true unless read_ability
+      break true unless DeclarativePolicy.has_policy?(@target)
 
       user.can?(read_ability, @target)
     end
   end
 
+  def action_excluded?
+    excluded_watcher_action? || excluded_participating_action?
+  end
+
   def excluded_watcher_action?
-    return false unless @custom_action
-    return false if notification_level == :custom
+    return false unless @custom_action && notification_level == :watch
 
     NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
   end
 
+  def excluded_participating_action?
+    return false unless @custom_action && notification_level == :participating
+
+    NotificationSetting::EXCLUDED_PARTICIPATING_EVENTS.include?(@custom_action)
+  end
+
   private
 
   def read_ability
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 245f8dddcf91fe0bac59ece194e13fe01d8e402c..9195408551f1c3497eac1a4963c368ec487933a9 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -33,6 +33,7 @@ class NotificationSetting < ActiveRecord::Base
     :close_issue,
     :reassign_issue,
     :new_merge_request,
+    :push_to_merge_request,
     :reopen_merge_request,
     :close_merge_request,
     :reassign_merge_request,
@@ -41,10 +42,15 @@ class NotificationSetting < ActiveRecord::Base
     :success_pipeline
   ].freeze
 
-  EXCLUDED_WATCHER_EVENTS = [
+  EXCLUDED_PARTICIPATING_EVENTS = [
     :success_pipeline
   ].freeze
 
+  EXCLUDED_WATCHER_EVENTS = [
+    :push_to_merge_request,
+    :issue_due
+  ].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze
+
   def self.find_or_create_for(source)
     setting = find_or_initialize_by(source: source)
 
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 588bd50ed779521080b2f1e20ab3fceaf7750b29..2e478a247789b0c037ae73a30998b8de955e4576 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -6,8 +6,10 @@ class PagesDomain < ActiveRecord::Base
 
   validates :domain, hostname: { allow_numeric_hostname: true }
   validates :domain, uniqueness: { case_sensitive: false }
-  validates :certificate, certificate: true, allow_nil: true, allow_blank: true
-  validates :key, certificate_key: true, allow_nil: true, allow_blank: true
+  validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? }
+  validates :certificate, certificate: true, if: ->(domain) { domain.certificate.present? }
+  validates :key, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? }
+  validates :key, certificate_key: true, if: ->(domain) { domain.key.present? }
   validates :verification_code, presence: true, allow_blank: false
 
   validate :validate_pages_domain
@@ -46,6 +48,10 @@ class PagesDomain < ActiveRecord::Base
     !Gitlab::CurrentSettings.pages_domain_verification_enabled? || enabled_until.present?
   end
 
+  def https?
+    certificate.present?
+  end
+
   def to_param
     domain
   end
diff --git a/app/models/project.rb b/app/models/project.rb
index 5b1f8b2658b72afc8688dc988de3eca6e4353722..cec1e705aa8f90759d6314740dfe3ac35ef9f0ab 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,6 +21,7 @@ class Project < ActiveRecord::Base
   include Gitlab::SQL::Pattern
   include DeploymentPlatform
   include ::Gitlab::Utils::StrongMemoize
+  include ChronicDurationAttribute
 
   extend Gitlab::ConfigHelper
 
@@ -38,6 +39,9 @@ class Project < ActiveRecord::Base
     attachments: 2
   }.freeze
 
+  # Valids ports to import from
+  VALID_IMPORT_PORTS = [22, 80, 443].freeze
+
   cache_markdown_field :description, pipeline: :description
 
   delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
@@ -150,6 +154,7 @@ class Project < ActiveRecord::Base
 
   # Merge Requests for target project should be removed with it
   has_many :merge_requests, foreign_key: 'target_project_id'
+  has_many :source_of_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest'
   has_many :issues
   has_many :labels, class_name: 'ProjectLabel'
   has_many :services
@@ -187,6 +192,8 @@ class Project < ActiveRecord::Base
   has_many :todos
   has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
 
+  has_many :internal_ids
+
   has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
   has_one :project_feature, inverse_of: :project
   has_one :statistics, class_name: 'ProjectStatistics'
@@ -215,12 +222,16 @@ class Project < ActiveRecord::Base
   has_many :environments
   has_many :deployments
   has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
+  has_many :project_deploy_tokens
+  has_many :deploy_tokens, through: :project_deploy_tokens
 
   has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
 
   has_one :auto_devops, class_name: 'ProjectAutoDevops'
   has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
 
+  has_many :project_badges, class_name: 'ProjectBadge'
+
   accepts_nested_attributes_for :variables, allow_destroy: true
   accepts_nested_attributes_for :project_feature, update_only: true
   accepts_nested_attributes_for :import_data
@@ -259,6 +270,7 @@ class Project < ActiveRecord::Base
   validate :visibility_level_allowed_by_group
   validate :visibility_level_allowed_as_fork
   validate :check_wiki_path_conflict
+  validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
   validates :repository_storage,
     presence: true,
     inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
@@ -274,7 +286,8 @@ class Project < ActiveRecord::Base
   scope :without_storage_feature, ->(feature) { where('storage_version < :version OR storage_version IS NULL', version: HASHED_STORAGE_FEATURES[feature]) }
   scope :with_unmigrated_storage, -> { where('storage_version < :version OR storage_version IS NULL', version: LATEST_STORAGE_VERSION) }
 
-  scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
+  # last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
+  scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") }
   scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
 
   scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
@@ -315,6 +328,12 @@ class Project < ActiveRecord::Base
 
   enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
 
+  chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
+
+  validates :build_timeout, allow_nil: true,
+                            numericality: { greater_than_or_equal_to: 600,
+                                            message: 'needs to be at least 10 minutes' }
+
   # 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)
@@ -426,7 +445,7 @@ class Project < ActiveRecord::Base
       Gitlab::VisibilityLevel.options
     end
 
-    def sort(method)
+    def sort_by_attribute(method)
       case method.to_s
       when 'storage_size_desc'
         # storage_size is a joined column so we need to
@@ -494,7 +513,7 @@ class Project < ActiveRecord::Base
   end
 
   def repository_storage_path
-    Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
+    Gitlab.config.repositories.storages[repository_storage]&.legacy_disk_path
   end
 
   def team
@@ -538,7 +557,7 @@ class Project < ActiveRecord::Base
     latest_pipeline = pipelines.latest_successful_for(ref)
 
     if latest_pipeline
-      latest_pipeline.builds.latest.with_artifacts
+      latest_pipeline.builds.latest.with_artifacts_archive
     else
       builds.none
     end
@@ -556,9 +575,7 @@ class Project < ActiveRecord::Base
   def add_import_job
     job_id =
       if forked?
-        RepositoryForkWorker.perform_async(id,
-                                           forked_from_project.repository_storage_path,
-                                           forked_from_project.disk_path)
+        RepositoryForkWorker.perform_async(id)
       elsif gitlab_project_import?
         # Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-ce/issues/26189 is solved.
         RepositoryImportWorker.set(retry: false).perform_async(self.id)
@@ -622,7 +639,7 @@ class Project < ActiveRecord::Base
   end
 
   def create_or_update_import_data(data: nil, credentials: nil)
-    return unless import_url.present? && valid_import_url?
+    return if data.nil? && credentials.nil?
 
     project_import_data = import_data || build_import_data
     if data
@@ -728,6 +745,26 @@ class Project < ActiveRecord::Base
     end
   end
 
+  def pages_https_only
+    return false unless Gitlab.config.pages.external_https
+
+    super
+  end
+
+  def pages_https_only?
+    return false unless Gitlab.config.pages.external_https
+
+    super
+  end
+
+  def validate_pages_https_only
+    return unless pages_https_only?
+
+    unless pages_domains.all?(&:https?)
+      errors.add(:pages_https_only, "cannot be enabled unless all domains have TLS certificates")
+    end
+  end
+
   def to_param
     if persisted? && errors.include?(:path)
       path_was
@@ -776,7 +813,7 @@ class Project < ActiveRecord::Base
   end
 
   def last_activity_date
-    last_repository_updated_at || last_activity_at || updated_at
+    [last_activity_at, last_repository_updated_at, updated_at].compact.max
   end
 
   def project_id
@@ -1038,6 +1075,16 @@ class Project < ActiveRecord::Base
     end
   end
 
+  # This will return all `lfs_objects` that are accessible to the project.
+  # So this might be `self.lfs_objects` if the project is not part of a fork
+  # network, or it is the base of the fork network.
+  #
+  # TODO: refactor this to get the correct lfs objects when implementing
+  #       https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
+  def all_lfs_objects
+    lfs_storage_project.lfs_objects
+  end
+
   def personal?
     !group
   end
@@ -1079,7 +1126,7 @@ class Project < ActiveRecord::Base
     # Forked import is handled asynchronously
     return if forked? && !force
 
-    if gitlab_shell.add_repository(repository_storage, disk_path)
+    if gitlab_shell.create_repository(repository_storage, disk_path)
       repository.after_create
       true
     else
@@ -1271,14 +1318,6 @@ class Project < ActiveRecord::Base
     self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
   end
 
-  def build_timeout_in_minutes
-    build_timeout / 60
-  end
-
-  def build_timeout_in_minutes=(value)
-    self.build_timeout = value.to_i * 60
-  end
-
   def open_issues_count
     Projects::OpenIssuesCountService.new(self).count
   end
@@ -1316,20 +1355,19 @@ class Project < ActiveRecord::Base
     Dir.exist?(public_pages_path)
   end
 
-  def pages_url
-    subdomain, _, url_path = full_path.partition('/')
-
-    # The hostname always needs to be in downcased
-    # All web servers convert hostname to lowercase
-    host = "#{subdomain}.#{Settings.pages.host}".downcase
-
+  def pages_group_url
     # The host in URL always needs to be downcased
-    url = Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix|
-      "#{prefix}#{subdomain}."
+    Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix|
+      "#{prefix}#{pages_subdomain}."
     end.downcase
+  end
+
+  def pages_url
+    url = pages_group_url
+    url_path = full_path.partition('/').last
 
     # If the project path is the same as host, we serve it as group page
-    return url if host == url_path
+    return url if url == "#{Settings.pages.protocol}://#{url_path}"
 
     "#{url}/#{url_path}"
   end
@@ -1436,7 +1474,9 @@ class Project < ActiveRecord::Base
   end
 
   def rename_repo_notify!
-    send_move_instructions(full_path_was)
+    # When we import a project overwriting the original project, there
+    # is a move operation. In that case we don't want to send the instructions.
+    send_move_instructions(full_path_was) unless started?
     expires_full_path_cache
 
     self.old_path_with_namespace = full_path_was
@@ -1451,6 +1491,7 @@ class Project < ActiveRecord::Base
     remove_import_jid
     update_project_counter_caches
     after_create_default_branch
+    refresh_markdown_cache!
   end
 
   def update_project_counter_caches
@@ -1515,8 +1556,8 @@ class Project < ActiveRecord::Base
     @errors = original_errors
   end
 
-  def add_export_job(current_user:)
-    job_id = ProjectExportWorker.perform_async(current_user.id, self.id)
+  def add_export_job(current_user:, after_export_strategy: nil, params: {})
+    job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params)
 
     if job_id
       Rails.logger.info "Export job started for project ID #{self.id} with job ID #{job_id}"
@@ -1525,22 +1566,52 @@ class Project < ActiveRecord::Base
     end
   end
 
+  def import_export_shared
+    @import_export_shared ||= Gitlab::ImportExport::Shared.new(self)
+  end
+
   def export_path
     return nil unless namespace.present? || hashed_storage?(:repository)
 
-    File.join(Gitlab::ImportExport.storage_path, disk_path)
+    import_export_shared.archive_path
   end
 
   def export_project_path
     Dir.glob("#{export_path}/*export.tar.gz").max_by { |f| File.ctime(f) }
   end
 
+  def export_status
+    if export_in_progress?
+      :started
+    elsif after_export_in_progress?
+      :after_export_action
+    elsif export_project_path
+      :finished
+    else
+      :none
+    end
+  end
+
+  def export_in_progress?
+    import_export_shared.active_export_count > 0
+  end
+
+  def after_export_in_progress?
+    import_export_shared.after_export_in_progress?
+  end
+
   def remove_exports
     return nil unless export_path.present?
 
     FileUtils.rm_rf(export_path)
   end
 
+  def remove_exported_project_file
+    return unless export_project_path.present?
+
+    FileUtils.rm_f(export_project_path)
+  end
+
   def full_path_slug
     Gitlab::Utils.slugify(full_path.to_s)
   end
@@ -1550,29 +1621,30 @@ class Project < ActiveRecord::Base
   end
 
   def predefined_variables
-    [
-      { key: 'CI_PROJECT_ID', value: id.to_s, public: true },
-      { key: 'CI_PROJECT_NAME', value: path, public: true },
-      { key: 'CI_PROJECT_PATH', value: full_path, public: true },
-      { key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug, public: true },
-      { key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
-      { key: 'CI_PROJECT_URL', value: web_url, public: true },
-      { key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level), public: true }
-    ]
+    visibility = Gitlab::VisibilityLevel.string_level(visibility_level)
+
+    Gitlab::Ci::Variables::Collection.new
+      .append(key: 'CI_PROJECT_ID', value: id.to_s)
+      .append(key: 'CI_PROJECT_NAME', value: path)
+      .append(key: 'CI_PROJECT_PATH', value: full_path)
+      .append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug)
+      .append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path)
+      .append(key: 'CI_PROJECT_URL', value: web_url)
+      .append(key: 'CI_PROJECT_VISIBILITY', value: visibility)
+      .concat(container_registry_variables)
+      .concat(auto_devops_variables)
   end
 
   def container_registry_variables
-    return [] unless Gitlab.config.registry.enabled
+    Gitlab::Ci::Variables::Collection.new.tap do |variables|
+      break variables unless Gitlab.config.registry.enabled
 
-    variables = [
-      { key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port, public: true }
-    ]
+      variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port)
 
-    if container_registry_enabled?
-      variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_url, public: true }
+      if container_registry_enabled?
+        variables.append(key: 'CI_REGISTRY_IMAGE', value: container_registry_url)
+      end
     end
-
-    variables
   end
 
   def secret_variables_for(ref:, environment: nil)
@@ -1592,16 +1664,14 @@ class Project < ActiveRecord::Base
     end
   end
 
-  def deployment_variables
-    return [] unless deployment_platform
-
-    deployment_platform.predefined_variables
+  def deployment_variables(environment: nil)
+    deployment_platform(environment: environment)&.predefined_variables || []
   end
 
   def auto_devops_variables
     return [] unless auto_devops_enabled?
 
-    (auto_devops || build_auto_devops)&.variables
+    (auto_devops || build_auto_devops)&.predefined_variables
   end
 
   def append_or_update_attribute(name, value)
@@ -1663,8 +1733,9 @@ class Project < ActiveRecord::Base
     end
   end
 
-  def multiple_issue_boards_available?(user)
-    feature_available?(:multiple_issue_boards, user)
+  # Overridden on EE module
+  def multiple_issue_boards_available?
+    false
   end
 
   def issue_board_milestone_available?(user = nil)
@@ -1766,6 +1837,48 @@ class Project < ActiveRecord::Base
       .set(import_jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
   end
 
+  def badges
+    return project_badges unless group
+
+    group_badges_rel = GroupBadge.where(group: group.self_and_ancestors)
+
+    union = Gitlab::SQL::Union.new([project_badges.select(:id),
+                                    group_badges_rel.select(:id)])
+
+    Badge.where("id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
+  end
+
+  def merge_requests_allowing_push_to_user(user)
+    return MergeRequest.none unless user
+
+    developer_access_exists = user.project_authorizations
+                                .where('access_level >= ? ', Gitlab::Access::DEVELOPER)
+                                .where('project_authorizations.project_id = merge_requests.target_project_id')
+                                .limit(1)
+                                .select(1)
+    source_of_merge_requests.opened
+      .where(allow_maintainer_to_push: true)
+      .where('EXISTS (?)', developer_access_exists)
+  end
+
+  def branch_allows_maintainer_push?(user, branch_name)
+    return false unless user
+
+    cache_key = "user:#{user.id}:#{branch_name}:branch_allows_push"
+
+    memoized_results = strong_memoize(:branch_allows_maintainer_push) do
+      Hash.new do |result, cache_key|
+        result[cache_key] = fetch_branch_allows_maintainer_push?(user, branch_name)
+      end
+    end
+
+    memoized_results[cache_key]
+  end
+
+  def licensed_features
+    []
+  end
+
   private
 
   def storage
@@ -1888,4 +2001,22 @@ class Project < ActiveRecord::Base
 
     raise ex
   end
+
+  def fetch_branch_allows_maintainer_push?(user, branch_name)
+    check_access = -> do
+      merge_request = source_of_merge_requests.opened
+                        .where(allow_maintainer_to_push: true)
+                        .find_by(source_branch: branch_name)
+
+      merge_request&.can_be_merged_by?(user)
+    end
+
+    if RequestStore.active?
+      RequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_maintainer_push") do
+        check_access.call
+      end
+    else
+      check_access.call
+    end
+  end
 end
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 112ed7ed434f96d56ac43520803e63f3d7971c77..ed6c1eddbc15305a97b31eac58b474829058f40e 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -14,9 +14,12 @@ class ProjectAutoDevops < ActiveRecord::Base
     domain.present? || instance_domain.present?
   end
 
-  def variables
-    variables = []
-    variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain.presence || instance_domain, public: true } if has_domain?
-    variables
+  def predefined_variables
+    Gitlab::Ci::Variables::Collection.new.tap do |variables|
+      if has_domain?
+        variables.append(key: 'AUTO_DEVOPS_DOMAIN',
+                         value: domain.presence || instance_domain)
+      end
+    end
   end
 end
diff --git a/app/models/project_deploy_token.rb b/app/models/project_deploy_token.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab4482f0c0bee72cb6b624623d8c19ec175aa126
--- /dev/null
+++ b/app/models/project_deploy_token.rb
@@ -0,0 +1,8 @@
+class ProjectDeployToken < ActiveRecord::Base
+  belongs_to :project
+  belongs_to :deploy_token, inverse_of: :project_deploy_tokens
+
+  validates :deploy_token, presence: true
+  validates :project, presence: true
+  validates :deploy_token_id, uniqueness: { scope: [:project_id] }
+end
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 109258d1eb78b1e34b7c4598029871c8d4d33272..4f289e6e215fa34577edeb46ea8caf2024916d8d 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -68,7 +68,7 @@ http://app.asana.com/-/account_api'
     end
 
     user = data[:user_name]
-    project_name = project.name_with_namespace
+    project_name = project.full_name
 
     data[:commits].each do |commit|
       push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):"
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index ae6af732ed403369e114afdd1a597c7800284a08..4234b8044e549b7aa6b4126d099710fbf0319822 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -1,6 +1,4 @@
 class AssemblaService < Service
-  include HTTParty
-
   prop_accessor :token, :subdomain
   validates :token, presence: true, if: :activated?
 
@@ -31,6 +29,6 @@ class AssemblaService < Service
     return unless supported_events.include?(data[:object_kind])
 
     url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}"
-    AssemblaService.post(url, body: { payload: data }.to_json, headers: { 'Content-Type' => 'application/json' })
+    Gitlab::HTTP.post(url, body: { payload: data }.to_json, headers: { 'Content-Type' => 'application/json' })
   end
 end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 42939ea0ec87df373aabbaab8b97c9dd77420404..54e4b3278db293845663ac4f614d0e6d7e68b3c3 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -117,14 +117,14 @@ class BambooService < CiService
     url = build_url(path)
 
     if username.blank? && password.blank?
-      HTTParty.get(url, verify: false)
+      Gitlab::HTTP.get(url, verify: false)
     else
       url << '&os_authType=basic'
-      HTTParty.get(url, verify: false,
-                        basic_auth: {
-                          username: username,
-                          password: password
-                        })
+      Gitlab::HTTP.get(url, verify: false,
+                            basic_auth: {
+                              username: username,
+                              password: password
+                            })
     end
   end
 end
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index fc30f6e3365ff937fff31d79b8c529c9ad4bc01f..d2aaff8817ab1178fd9bab149b7f1d3537d3038c 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -71,7 +71,7 @@ class BuildkiteService < CiService
   end
 
   def calculate_reactive_cache(sha, ref)
-    response = HTTParty.get(commit_status_path(sha), verify: false)
+    response = Gitlab::HTTP.get(commit_status_path(sha), verify: false)
 
     status =
       if response.code == 200 && response['status']
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index c3f5b3106197f7726a1cde07ab6e16c759c05bda..cb4af73807b9eb826d4dcea63a643547aeca2681 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -1,6 +1,4 @@
 class CampfireService < Service
-  include HTTParty
-
   prop_accessor :token, :subdomain, :room
   validates :token, presence: true, if: :activated?
 
@@ -31,7 +29,6 @@ class CampfireService < Service
   def execute(data)
     return unless supported_events.include?(data[:object_kind])
 
-    self.class.base_uri base_uri
     message = build_message(data)
     speak(self.room, message, auth)
   end
@@ -69,14 +66,14 @@ class CampfireService < Service
         }
       }
     }
-    res = self.class.post(path, auth.merge(body))
+    res = Gitlab::HTTP.post(path, base_uri: base_uri, **auth.merge(body))
     res.code == 201 ? res : nil
   end
 
   # Returns a list of rooms, or [].
   # https://github.com/basecamp/campfire-api/blob/master/sections/rooms.md#get-rooms
   def rooms(auth)
-    res = self.class.get("/rooms.json", auth)
+    res = Gitlab::HTTP.get("/rooms.json", base_uri: base_uri, **auth)
     res.code == 200 ? res["rooms"] : []
   end
 
@@ -86,7 +83,7 @@ class CampfireService < Service
     after = push[:after]
 
     message = ""
-    message << "[#{project.name_with_namespace}] "
+    message << "[#{project.full_name}] "
     message << "#{push[:user_name]} "
 
     if Gitlab::Git.blank_ref?(before)
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index e2ffdf72201629ac3236d109b933168228539b4a..7591ab4f47888e2e4d135cab4f6aa2e538c366ec 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -21,8 +21,16 @@ class ChatNotificationService < Service
     end
   end
 
+  def confidential_issue_channel
+    properties['confidential_issue_channel'].presence || properties['issue_channel']
+  end
+
+  def confidential_note_channel
+    properties['confidential_note_channel'].presence || properties['note_channel']
+  end
+
   def self.supported_events
-    %w[push issue confidential_issue merge_request note tag_push
+    %w[push issue confidential_issue merge_request note confidential_note tag_push
        pipeline wiki_page]
   end
 
@@ -55,7 +63,9 @@ class ChatNotificationService < Service
 
     return false unless message
 
-    channel_name = get_channel_field(object_kind).presence || channel
+    event_type = data[:event_type] || object_kind
+
+    channel_name = get_channel_field(event_type).presence || channel
 
     opts = {}
     opts[:channel] = channel_name if channel_name
@@ -129,7 +139,7 @@ class ChatNotificationService < Service
   end
 
   def project_name
-    project.name_with_namespace.gsub(/\s/, '')
+    project.full_name.gsub(/\s/, '')
   end
 
   def project_url
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index c93f16326522b11c1529621a2be76e4bd4c13172..71b10fc6bc16428e02997ca4c173c2b133223ff8 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -49,7 +49,7 @@ class DroneCiService < CiService
   end
 
   def calculate_reactive_cache(sha, ref)
-    response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
+    response = Gitlab::HTTP.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
 
     status =
       if response.code == 200 && response['status']
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 720ad61162e882a0bdf8e9fe257913622e668c08..1553f169827d5027beac30b50811bdfb78d3e131 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -1,6 +1,4 @@
 class ExternalWikiService < Service
-  include HTTParty
-
   prop_accessor :external_wiki_url
 
   validates :external_wiki_url, presence: true, url: true, if: :activated?
@@ -24,7 +22,7 @@ class ExternalWikiService < Service
   end
 
   def execute(_data)
-    @response = HTTParty.get(properties['external_wiki_url'], verify: true) rescue nil
+    @response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) rescue nil
     if @response != 200
       nil
     end
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 017a9b2df6e9fd700f028822975b6f3cea91c770..26cbfd784ad4f579f82246d5579ab6b84300d846 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -36,7 +36,7 @@ class GemnasiumService < Service
       after: data[:after],
       token: token,
       api_key: api_key,
-      repo: project.repository.path_to_repo
+      repo: project.repository.path_to_repo # Gitaly: fixed by https://gitlab.com/gitlab-org/security-products/gemnasium-migration/issues/9
     )
   end
 end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index bfe7ac29c18637bdca620f2dea38752530ea0821..dce878e485f1d1d560b02d2a92f6d21b3c0991be 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -46,7 +46,7 @@ class HipchatService < Service
   end
 
   def self.supported_events
-    %w(push issue confidential_issue merge_request note tag_push pipeline)
+    %w(push issue confidential_issue merge_request note confidential_note tag_push pipeline)
   end
 
   def execute(data)
@@ -120,7 +120,7 @@ class HipchatService < Service
     else
       message << "pushed to #{ref_type} <a href=\""\
                   "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
-      message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/, '')}</a> "
+      message << "of <a href=\"#{project.web_url}\">#{project.full_name.gsub!(/\s/, '')}</a> "
       message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
 
       push[:commits].take(MAX_COMMITS).each do |commit|
@@ -274,7 +274,7 @@ class HipchatService < Service
   end
 
   def project_name
-    project.name_with_namespace.gsub(/\s/, '')
+    project.full_name.gsub(/\s/, '')
   end
 
   def project_url
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 5fb15c383cab1751fcf8aa28ea3b0b33692f08be..df6dcd90985f00a72a9263bc3d89531991f393a7 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -77,13 +77,13 @@ class IssueTrackerService < Service
     result = false
 
     begin
-      response = HTTParty.head(self.project_url, verify: true)
+      response = Gitlab::HTTP.head(self.project_url, verify: true)
 
       if response
         message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
         result = true
       end
-    rescue HTTParty::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => error
+    rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => error
       message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
     end
     Rails.logger.info(message)
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 436a870b0c4b425f9bc77cefcdaae6d600960788..ed4bbfb6cfcb550ea297d43c2165f72becad3c8b 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,5 +1,7 @@
 class JiraService < IssueTrackerService
   include Gitlab::Routing
+  include ApplicationHelper
+  include ActionView::Helpers::AssetUrlHelper
 
   validates :url, url: true, presence: true, if: :activated?
   validates :api_url, url: true, allow_blank: true
@@ -12,9 +14,8 @@ class JiraService < IssueTrackerService
 
   alias_method :project_url, :url
 
-  # This is confusing, but JiraService does not really support these events.
-  # The values here are required to display correct options in the service
-  # configuration screen.
+  # When these are false GitLab does not create cross reference
+  # comments on JIRA except when an issue gets transitioned.
   def self.supported_events
     %w(commit merge_request)
   end
@@ -159,11 +160,6 @@ class JiraService < IssueTrackerService
     add_comment(data, jira_issue)
   end
 
-  # reason why service cannot be tested
-  def disabled_title
-    "Please fill in Password and Username."
-  end
-
   def test(_)
     result = test_settings
     success = result.present?
@@ -268,7 +264,9 @@ class JiraService < IssueTrackerService
         url: url,
         title: title,
         status: status,
-        icon: { title: 'GitLab', url16x16: 'https://gitlab.com/favicon.ico' }
+        icon: {
+          title: 'GitLab', url16x16: asset_url('favicon.ico', host: gitlab_config.url)
+        }
       }
     }
   end
@@ -319,4 +317,13 @@ class JiraService < IssueTrackerService
 
     url_changed?
   end
+
+  def self.event_description(event)
+    case event
+    when "merge_request", "merge_request_events"
+      "JIRA comments will be created when an issue gets referenced in a merge request."
+    when "commit", "commit_events"
+      "JIRA comments will be created when an issue gets referenced in a commit."
+    end
+  end
 end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index ad4ad7903adb540a3fb026c6540585ab639e9049..20fed432e557d7341c494b37e3b7721606227563 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -105,19 +105,19 @@ class KubernetesService < DeploymentService
   def predefined_variables
     config = YAML.dump(kubeconfig)
 
-    variables = [
-      { key: 'KUBE_URL', value: api_url, public: true },
-      { key: 'KUBE_TOKEN', value: token, public: false },
-      { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
-      { key: 'KUBECONFIG', value: config, public: false, file: true }
-    ]
-
-    if ca_pem.present?
-      variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
-      variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
+    Gitlab::Ci::Variables::Collection.new.tap do |variables|
+      variables
+        .append(key: 'KUBE_URL', value: api_url)
+        .append(key: 'KUBE_TOKEN', value: token, public: false)
+        .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
+        .append(key: 'KUBECONFIG', value: config, public: false, file: true)
+
+      if ca_pem.present?
+        variables
+          .append(key: 'KUBE_CA_PEM', value: ca_pem)
+          .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
+      end
     end
-
-    variables
   end
 
   # Constructs a list of terminals from the reactive cache
@@ -197,7 +197,7 @@ class KubernetesService < DeploymentService
     kubeclient = build_kubeclient!
 
     kubeclient.get_pods(namespace: actual_namespace).as_json
-  rescue KubeException => err
+  rescue Kubeclient::HttpError => err
     raise err unless err.error_code == 404
 
     []
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 4d2037286a2e15f443a7e6851065e4a478549b22..227d430083d8233c9aec2a163ff7dcca0fd2cb13 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -37,7 +37,7 @@ class MattermostSlashCommandsService < SlashCommandsService
   private
 
   def command(params)
-    pretty_project_name = project.name_with_namespace
+    pretty_project_name = project.full_name
 
     params.merge(
       auto_complete: true,
diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb
index 72ddf9a4be30f96079e52f29f7a36bf6655ef71a..2221459c90b82b364831f507565e66f91bcfb4a3 100644
--- a/app/models/project_services/mock_ci_service.rb
+++ b/app/models/project_services/mock_ci_service.rb
@@ -52,7 +52,7 @@ class MockCiService < CiService
   #
   #
   def commit_status(sha, ref)
-    response = HTTParty.get(commit_status_path(sha), verify: false)
+    response = Gitlab::HTTP.get(commit_status_path(sha), verify: false)
     read_commit_status(response)
   rescue Errno::ECONNREFUSED
     :error
diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb
index ee9cd78327a87c49a66102e8789887c563c24f57..9af68b4e8210f500ba31e133941b860ff332160e 100644
--- a/app/models/project_services/monitoring_service.rb
+++ b/app/models/project_services/monitoring_service.rb
@@ -9,11 +9,11 @@ class MonitoringService < Service
     %w()
   end
 
-  def environment_metrics(environment)
+  def can_query?
     raise NotImplementedError
   end
 
-  def deployment_metrics(deployment)
+  def query(_, *_)
     raise NotImplementedError
   end
 end
diff --git a/app/models/project_services/packagist_service.rb b/app/models/project_services/packagist_service.rb
index f68a0c1a3c38dff80910c03aff2f9de6f5b51014..ba62a5b7ac0faafe6fe95b478df1843fa7b62ba2 100644
--- a/app/models/project_services/packagist_service.rb
+++ b/app/models/project_services/packagist_service.rb
@@ -1,6 +1,4 @@
 class PackagistService < Service
-  include HTTParty
-
   prop_accessor :username, :token, :server
 
   validates :username, presence: true, if: :activated?
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 9c7b58dead52c4f90550e798f3b624ab9d3c68a0..4cf149ac0445bc6c6aa5f7adadf3041d3413dcb3 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -39,10 +39,6 @@ class PipelinesEmailService < Service
     project.pipelines.any?
   end
 
-  def disabled_title
-    'Please setup a pipeline on your repository.'
-  end
-
   def test_data(project, user)
     data = Gitlab::DataBuilder::Pipeline.build(project.pipelines.last)
     data[:user] = user.hook_attrs
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index f9dfa2e91c315eab3881c447c055e270e674d3a0..3476e7d22839cd0e415376b68ccbee5d607a0add 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,6 +1,4 @@
 class PivotaltrackerService < Service
-  include HTTParty
-
   API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
 
   prop_accessor :token, :restrict_to_branch
@@ -52,7 +50,7 @@ class PivotaltrackerService < Service
           'message' => commit[:message]
         }
       }
-      PivotaltrackerService.post(
+      Gitlab::HTTP.post(
         API_ENDPOINT,
         body: message.to_json,
         headers: {
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 58731451429d803199b200186acd26a365c8c153..dcaeb65dc32bca1c43d330b543be90f36de914d0 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -1,9 +1,5 @@
 class PrometheusService < MonitoringService
-  include ReactiveService
-
-  self.reactive_cache_lease_timeout = 30.seconds
-  self.reactive_cache_refresh_interval = 30.seconds
-  self.reactive_cache_lifetime = 1.minute
+  include PrometheusAdapter
 
   #  Access to prometheus is directly through the API
   prop_accessor :api_url
@@ -13,7 +9,7 @@ class PrometheusService < MonitoringService
     validates :api_url, url: true
   end
 
-  before_save :synchronize_service_state!
+  before_save :synchronize_service_state
 
   after_save :clear_reactive_cache!
 
@@ -66,63 +62,15 @@ class PrometheusService < MonitoringService
 
   # Check we can connect to the Prometheus API
   def test(*args)
-    client.ping
+    Gitlab::PrometheusClient.new(prometheus_client).ping
 
     { success: true, result: 'Checked API endpoint' }
   rescue Gitlab::PrometheusClient::Error => err
     { success: false, result: err }
   end
 
-  def environment_metrics(environment)
-    with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &rename_field(:data, :metrics))
-  end
-
-  def deployment_metrics(deployment)
-    metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &rename_field(:data, :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.environment.id, 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?
-
-    environment_id = args.first
-    client = client(environment_id)
-
-    data = Kernel.const_get(query_class_name).new(client).query(*args)
-    {
-      success: true,
-      data: data,
-      last_update: Time.now.utc
-    }
-  rescue Gitlab::PrometheusClient::Error => err
-    { success: false, result: err.message }
-  end
-
-  def client(environment_id = nil)
-    if manual_configuration?
-      Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url))
-    else
-      cluster = cluster_with_prometheus(environment_id)
-      raise Gitlab::PrometheusClient::Error, "couldn't find cluster with Prometheus installed" unless cluster
-
-      rest_client = client_from_cluster(cluster)
-      raise Gitlab::PrometheusClient::Error, "couldn't create proxy Prometheus client" unless rest_client
-
-      Gitlab::PrometheusClient.new(rest_client)
-    end
+  def prometheus_client
+    RestClient::Resource.new(api_url) if api_url && manual_configuration? && active?
   end
 
   def prometheus_installed?
@@ -134,32 +82,7 @@ class PrometheusService < MonitoringService
 
   private
 
-  def cluster_with_prometheus(environment_id = nil)
-    clusters = if environment_id
-                 ::Environment.find_by(id: environment_id).try do |env|
-                   # sort results by descending order based on environment_scope being longer
-                   # thus more closely matching environment slug
-                   project.clusters.enabled.for_environment(env).sort_by { |c| c.environment_scope&.length }.reverse!
-                 end
-               else
-                 project.clusters.enabled.for_all_environments
-               end
-
-    clusters&.detect { |cluster| cluster.application_prometheus&.installed? }
-  end
-
-  def client_from_cluster(cluster)
-    cluster.application_prometheus.proxy_client
-  end
-
-  def rename_field(old_field, new_field)
-    -> (metrics) do
-      metrics[new_field] = metrics.delete(old_field)
-      metrics
-    end
-  end
-
-  def synchronize_service_state!
+  def synchronize_service_state
     self.active = prometheus_installed? || manual_configuration?
 
     true
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index aa7bd4c3c842c77102e2e349bcd13934c7f5548b..8777a44b72f738e533f81c24c697e43b417f892b 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -1,6 +1,5 @@
 class PushoverService < Service
-  include HTTParty
-  base_uri 'https://api.pushover.net/1'
+  BASE_URI = 'https://api.pushover.net/1'.freeze
 
   prop_accessor :api_key, :user_key, :device, :priority, :sound
   validates :api_key, :user_key, :priority, presence: true, if: :activated?
@@ -88,10 +87,10 @@ class PushoverService < Service
       user: user_key,
       device: device,
       priority: priority,
-      title: "#{project.name_with_namespace}",
+      title: "#{project.full_name}",
       message: message,
       url: data[:project][:web_url],
-      url_title: "See project #{project.name_with_namespace}"
+      url_title: "See project #{project.full_name}"
     }
 
     # Sound parameter MUST NOT be sent to API if not selected
@@ -99,6 +98,6 @@ class PushoverService < Service
       pushover_data[:sound] = sound
     end
 
-    PushoverService.post('/messages.json', body: pushover_data)
+    Gitlab::HTTP.post('/messages.json', base_uri: BASE_URI, body: pushover_data)
   end
 end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index cbe137452bd3f1aa3012febaa05283100813e22b..145313b8e711bead2df463743284ef0d286e8ae7 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -83,7 +83,7 @@ class TeamcityService < CiService
 
     branch = Gitlab::Git.ref_name(data[:ref])
 
-    HTTParty.post(
+    Gitlab::HTTP.post(
       build_url('httpAuth/app/rest/buildQueue'),
       body: "<build branchName=\"#{branch}\">"\
             "<buildType id=\"#{build_type}\"/>"\
@@ -134,10 +134,10 @@ class TeamcityService < CiService
   end
 
   def get_path(path)
-    HTTParty.get(build_url(path), verify: false,
-                                  basic_auth: {
-                                    username: username,
-                                    password: password
-                                  })
+    Gitlab::HTTP.get(build_url(path), verify: false,
+                                      basic_auth: {
+                                        username: username,
+                                        password: password
+                                      })
   end
 end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 87a4350f0228809ef8a66794031dde8f406844aa..5d4e3c34b397fd053706aca7062b2a9fcd7fde01 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -4,15 +4,15 @@ class ProjectStatistics < ActiveRecord::Base
 
   before_save :update_storage_size
 
-  STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size].freeze
-  STATISTICS_COLUMNS = [:commit_count] + STORAGE_COLUMNS
+  COLUMNS_TO_REFRESH = [:repository_size, :lfs_objects_size, :commit_count].freeze
+  INCREMENTABLE_COLUMNS = [:build_artifacts_size].freeze
 
   def total_repository_size
     repository_size + lfs_objects_size
   end
 
   def refresh!(only: nil)
-    STATISTICS_COLUMNS.each do |column, generator|
+    COLUMNS_TO_REFRESH.each do |column, generator|
       if only.blank? || only.include?(column)
         public_send("update_#{column}") # rubocop:disable GitlabSecurity/PublicSend
       end
@@ -34,13 +34,15 @@ class ProjectStatistics < ActiveRecord::Base
     self.lfs_objects_size = project.lfs_objects.sum(:size)
   end
 
-  def update_build_artifacts_size
-    self.build_artifacts_size =
-      project.builds.sum(:artifacts_size) +
-      Ci::JobArtifact.artifacts_size_for(self.project)
+  def update_storage_size
+    self.storage_size = repository_size + lfs_objects_size + build_artifacts_size
   end
 
-  def update_storage_size
-    self.storage_size = STORAGE_COLUMNS.sum(&method(:read_attribute))
+  def self.increment_statistic(project_id, key, amount)
+    raise ArgumentError, "Cannot increment attribute: #{key}" unless key.in?(INCREMENTABLE_COLUMNS)
+    return if amount == 0
+
+    where(project_id: project_id)
+      .update_all(["#{key} = COALESCE(#{key}, 0) + (?)", amount])
   end
 end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index a9e5cfb8240774d5042098428b2e6983c0a1bc1a..33280eda0b970f98fb2c4c9bc36477f9743c1f55 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -85,6 +85,15 @@ class ProjectTeam
     @masters ||= fetch_members(Gitlab::Access::MASTER)
   end
 
+  def owners
+    @owners ||=
+      if group
+        group.owners
+      else
+        [project.owner]
+      end
+  end
+
   def import(source_project, current_user = nil)
     target_project = project
 
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f6041da986ce21905cd9e9438f8b7d3c516900b0..b7e38ceb5021a0dfdab7269914b528b9396166af 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -169,7 +169,7 @@ class ProjectWiki
   private
 
   def create_repo!(raw_repository)
-    gitlab_shell.add_repository(project.repository_storage, disk_path)
+    gitlab_shell.create_repository(project.repository_storage, disk_path)
 
     raise CouldNotCreateWikiError unless raw_repository.exists?
 
@@ -179,7 +179,11 @@ class ProjectWiki
   def commit_details(action, message = nil, title = nil)
     commit_message = message || default_message(action, title)
 
-    Gitlab::Git::Wiki::CommitDetails.new(@user.name, @user.email, commit_message)
+    Gitlab::Git::Wiki::CommitDetails.new(@user.id,
+                                         @user.username,
+                                         @user.name,
+                                         @user.email,
+                                         commit_message)
   end
 
   def default_message(action, title)
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 2053252734647d8a1fa5714d97bb4ed096c3ca0f..31de204d824f6b1e7a9a5670737ce10104b61e51 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -17,32 +17,4 @@ class RedirectRoute < ActiveRecord::Base
 
     where(wheres, path, "#{sanitize_sql_like(path)}/%")
   end
-
-  scope :permanent, -> do
-    if column_permanent_exists?
-      where(permanent: true)
-    else
-      none
-    end
-  end
-
-  scope :temporary, -> do
-    if column_permanent_exists?
-      where(permanent: [false, nil])
-    else
-      all
-    end
-  end
-
-  default_value_for :permanent, false
-
-  def permanent=(value)
-    if self.class.column_permanent_exists?
-      super
-    end
-  end
-
-  def self.column_permanent_exists?
-    ActiveRecord::Base.connection.column_exists?(:redirect_routes, :permanent)
-  end
 end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 242d9d5f1256b213fadc044bb7fa00fe1ec96599..5bdaa7f07207eb828e7440cbd5f62ded5d08219f 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -16,6 +16,7 @@ class Repository
   ].freeze
 
   include Gitlab::ShellAdapter
+  include Gitlab::RepositoryCacheAdapter
 
   attr_accessor :full_path, :disk_path, :project, :is_wiki
 
@@ -35,7 +36,7 @@ class Repository
   CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
                       changelog license_blob license_key gitignore koding_yml
                       gitlab_ci_yml branch_names tag_names branch_count
-                      tag_count avatar exists? empty? root_ref has_visible_content?
+                      tag_count avatar exists? root_ref has_visible_content?
                       issue_template_names merge_request_template_names).freeze
 
   # Methods that use cache_method but only memoize the value
@@ -57,22 +58,6 @@ class Repository
     merge_request_template: :merge_request_template_names
   }.freeze
 
-  # Wraps around the given method and caches its output in Redis and an instance
-  # variable.
-  #
-  # This only works for methods that do not take any arguments.
-  def self.cache_method(name, fallback: nil, memoize_only: false)
-    original = :"_uncached_#{name}"
-
-    alias_method(original, name)
-
-    define_method(name) do
-      cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
-        __send__(original) # rubocop:disable GitlabSecurity/PublicSend
-      end
-    end
-  end
-
   def initialize(full_path, project, disk_path: nil, is_wiki: false)
     @full_path = full_path
     @disk_path = disk_path || full_path
@@ -108,10 +93,6 @@ class Repository
     "#<#{self.class.name}:#{@disk_path}>"
   end
 
-  def create_hooks
-    Gitlab::Git::Repository.create_hooks(path_to_repo, Gitlab.config.gitlab_shell.hooks_path)
-  end
-
   def commit(ref = 'HEAD')
     return nil unless exists?
     return ref if ref.is_a?(::Commit)
@@ -253,7 +234,7 @@ class Repository
   # branches or tags, but we want to keep some of these commits around, for
   # example if they have comments or CI builds.
   def keep_around(sha)
-    return unless sha && commit_by(oid: sha)
+    return unless sha.present? && commit_by(oid: sha)
 
     return if kept_around?(sha)
 
@@ -268,13 +249,13 @@ class Repository
   end
 
   def diverging_commit_counts(branch)
-    root_ref_hash = raw_repository.commit(root_ref).id
+    @root_ref_hash ||= raw_repository.commit(root_ref).id
     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, number_commits_ahead =
         raw_repository.count_commits_between(
-          root_ref_hash,
+          @root_ref_hash,
           branch.dereferenced_target.sha,
           left_right: true,
           max_count: MAX_DIVERGING_COUNT)
@@ -302,17 +283,6 @@ class Repository
     expire_method_caches(CACHED_METHODS)
   end
 
-  # Expires the caches of a specific set of methods
-  def expire_method_caches(methods)
-    methods.each do |key|
-      cache.expire(key)
-
-      ivar = cache_instance_variable_name(key)
-
-      remove_instance_variable(ivar) if instance_variable_defined?(ivar)
-    end
-  end
-
   def expire_avatar_cache
     expire_method_caches(%i(avatar))
   end
@@ -360,7 +330,8 @@ class Repository
   def expire_emptiness_caches
     return unless empty?
 
-    expire_method_caches(%i(empty? has_visible_content?))
+    expire_method_caches(%i(has_visible_content?))
+    raw_repository.expire_has_local_branches_cache
   end
 
   def lookup_cache
@@ -506,12 +477,14 @@ class Repository
   end
   cache_method :exists?
 
+  # We don't need to cache the output of this method because both exists? and
+  # has_visible_content? are already memoized and cached. There's no guarantee
+  # that the values are expired and loaded atomically.
   def empty?
     return true unless exists?
 
     !has_visible_content?
   end
-  cache_method :empty?
 
   # The size of this repository in megabytes.
   def size
@@ -651,14 +624,15 @@ class Repository
   end
 
   def last_commit_for_path(sha, path)
-    commit_by(oid: last_commit_id_for_path(sha, path))
+    commit = raw_repository.last_commit_for_path(sha, path)
+    ::Commit.new(commit, @project) if commit
   end
 
   def last_commit_id_for_path(sha, path)
     key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
 
     cache.fetch(key) do
-      raw_repository.last_commit_id_for_path(sha, path)
+      last_commit_for_path(sha, path)&.id
     end
   end
 
@@ -866,20 +840,20 @@ class Repository
     raw_repository.ancestor?(ancestor_id, descendant_id)
   end
 
-  def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil)
+  def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil, prune: true)
     unless remote_name
       remote_name = "tmp-#{SecureRandom.hex}"
       tmp_remote_name = true
     end
 
     add_remote(remote_name, url, mirror_refmap: refmap)
-    fetch_remote(remote_name, forced: forced)
+    fetch_remote(remote_name, forced: forced, prune: prune)
   ensure
     remove_remote(remote_name) if tmp_remote_name
   end
 
-  def fetch_remote(remote, forced: false, ssh_auth: nil, no_tags: false)
-    gitlab_shell.fetch_remote(raw_repository, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
+  def fetch_remote(remote, forced: false, ssh_auth: nil, no_tags: false, prune: true)
+    gitlab_shell.fetch_remote(raw_repository, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, prune: prune)
   end
 
   def fetch_source_branch!(source_repository, source_branch, local_ref)
@@ -921,49 +895,6 @@ class Repository
     end
   end
 
-  # Caches the supplied block both in a cache and in an instance variable.
-  #
-  # The cache key and instance variable are named the same way as the value of
-  # the `key` argument.
-  #
-  # This method will return `nil` if the corresponding instance variable is also
-  # set to `nil`. This ensures we don't keep yielding the block when it returns
-  # `nil`.
-  #
-  # key - The name of the key to cache the data in.
-  # fallback - A value to fall back to in the event of a Git error.
-  def cache_method_output(key, fallback: nil, memoize_only: false, &block)
-    ivar = cache_instance_variable_name(key)
-
-    if instance_variable_defined?(ivar)
-      instance_variable_get(ivar)
-    else
-      # If the repository doesn't exist and a fallback was specified we return
-      # that value inmediately. This saves us Rugged/gRPC invocations.
-      return fallback unless fallback.nil? || exists?
-
-      begin
-        value =
-          if memoize_only
-            yield
-          else
-            cache.fetch(key, &block)
-          end
-
-        instance_variable_set(ivar, value)
-      rescue Gitlab::Git::Repository::NoRepository
-        # Even if the above `#exists?` check passes these errors might still
-        # occur (for example because of a non-existing HEAD). We want to
-        # gracefully handle this and not cache anything
-        fallback
-      end
-    end
-  end
-
-  def cache_instance_variable_name(key)
-    :"@#{key.to_s.tr('?!', '')}"
-  end
-
   def file_on_head(type)
     if head = tree(:head)
       head.blobs.find do |blob|
@@ -1018,8 +949,7 @@ class Repository
   end
 
   def cache
-    # TODO: should we use UUIDs here? We could move repositories without clearing this cache
-    @cache ||= RepositoryCache.new(full_path, @project.id)
+    @cache ||= Gitlab::RepositoryCache.new(self)
   end
 
   def tags_sorted_by_committed_date
diff --git a/app/models/route.rb b/app/models/route.rb
index 07d96c21cf1f7ef0b54adc3deef44ae74000c519..2d60992005108b42cb1aed9d6785287f7864a9d6 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -10,8 +10,6 @@ class Route < ActiveRecord::Base
     presence: true,
     uniqueness: { case_sensitive: false }
 
-  validate :ensure_permanent_paths, if: :path_changed?
-
   before_validation :delete_conflicting_orphaned_routes
   after_create :delete_conflicting_redirects
   after_update :delete_conflicting_redirects, if: :path_changed?
@@ -45,7 +43,7 @@ class Route < ActiveRecord::Base
         # We are not calling route.delete_conflicting_redirects here, in hopes
         # of avoiding deadlocks. The parent (self, in this method) already
         # called it, which deletes conflicts for all descendants.
-        route.create_redirect(old_path, permanent: permanent_redirect?) if attributes[:path]
+        route.create_redirect(old_path) if attributes[:path]
       end
     end
   end
@@ -55,31 +53,17 @@ class Route < ActiveRecord::Base
   end
 
   def conflicting_redirects
-    RedirectRoute.temporary.matching_path_and_descendants(path)
+    RedirectRoute.matching_path_and_descendants(path)
   end
 
-  def create_redirect(path, permanent: false)
-    RedirectRoute.create(source: source, path: path, permanent: permanent)
+  def create_redirect(path)
+    RedirectRoute.create(source: source, path: path)
   end
 
   private
 
   def create_redirect_for_old_path
-    create_redirect(path_was, permanent: permanent_redirect?) if path_changed?
-  end
-
-  def permanent_redirect?
-    source_type != "Project"
-  end
-
-  def ensure_permanent_paths
-    return if path.nil?
-
-    errors.add(:path, "has been taken before") if conflicting_redirect_exists?
-  end
-
-  def conflicting_redirect_exists?
-    RedirectRoute.permanent.matching_path_and_descendants(path).exists?
+    create_redirect(path_was) if path_changed?
   end
 
   def delete_conflicting_orphaned_routes
diff --git a/app/models/service.rb b/app/models/service.rb
index 369cae2e85f011788c2785541f2e6cda0f3156c6..f7e3f7590ada339bf3b27e4c01cb76d132a3048b 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -14,6 +14,7 @@ class Service < ActiveRecord::Base
   default_value_for :merge_requests_events, true
   default_value_for :tag_push_events, true
   default_value_for :note_events, true
+  default_value_for :confidential_note_events, true
   default_value_for :job_events, true
   default_value_for :pipeline_events, true
   default_value_for :wiki_page_events, true
@@ -42,6 +43,7 @@ class Service < ActiveRecord::Base
   scope :confidential_issue_hooks, -> { where(confidential_issues_events: true, active: true) }
   scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
   scope :note_hooks, -> { where(note_events: true, active: true) }
+  scope :confidential_note_hooks, -> { where(confidential_note_events: true, active: true) }
   scope :job_hooks, -> { where(job_events: true, active: true) }
   scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
   scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
@@ -129,6 +131,17 @@ class Service < ActiveRecord::Base
     fields
   end
 
+  def configurable_events
+    events = self.class.supported_events
+
+    # No need to disable individual triggers when there is only one
+    if events.count == 1
+      []
+    else
+      events
+    end
+  end
+
   def supported_events
     self.class.supported_events
   end
@@ -151,19 +164,16 @@ class Service < ActiveRecord::Base
     true
   end
 
-  # reason why service cannot be tested
-  def disabled_title
-    "Please setup a project repository."
-  end
-
   # Provide convenient accessor methods
   # for each serialized property.
   # Also keep track of updated properties in a similar way as ActiveModel::Dirty
   def self.prop_accessor(*args)
     args.each do |arg|
       class_eval %{
-        def #{arg}
-          properties['#{arg}']
+        unless method_defined?(arg)
+          def #{arg}
+            properties['#{arg}']
+          end
         end
 
         def #{arg}=(value)
@@ -196,7 +206,11 @@ class Service < ActiveRecord::Base
     args.each do |arg|
       class_eval %{
         def #{arg}?
-          ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
+          if Gitlab.rails5?
+            !ActiveModel::Type::Boolean::FALSE_VALUES.include?(#{arg})
+          else
+            ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
+          end
         end
       }
     end
@@ -267,6 +281,7 @@ class Service < ActiveRecord::Base
 
   def self.build_from_template(project_id, template)
     service = template.dup
+    service.active = false unless service.valid?
     service.template = false
     service.project_id = project_id
     service
@@ -298,6 +313,29 @@ class Service < ActiveRecord::Base
     end
   end
 
+  def self.event_description(event)
+    case event
+    when "push", "push_events"
+      "Event will be triggered by a push to the repository"
+    when "tag_push", "tag_push_events"
+      "Event will be triggered when a new tag is pushed to the repository"
+    when "note", "note_events"
+      "Event will be triggered when someone adds a comment"
+    when "issue", "issue_events"
+      "Event will be triggered when an issue is created/updated/closed"
+    when "confidential_issue", "confidential_issue_events"
+      "Event will be triggered when a confidential issue is created/updated/closed"
+    when "merge_request", "merge_request_events"
+      "Event will be triggered when a merge request is created/updated/merged"
+    when "pipeline", "pipeline_events"
+      "Event will be triggered when a pipeline status changes"
+    when "wiki_page", "wiki_page_events"
+      "Event will be triggered when a wiki page is created/updated"
+    when "commit", "commit_events"
+      "Event will be triggered when a commit is created/updated"
+    end
+  end
+
   def valid_recipients?
     activated? && !importing?
   end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index a58c208279efe3087942ea2d271b06b3dec6808f..644120453cfd2b93a10d0359a3f81882d1018582 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -168,5 +168,9 @@ class Snippet < ActiveRecord::Base
     def search_code(query)
       fuzzy_search(query, [:content])
     end
+
+    def parent_class
+      ::Project
+    end
   end
 end
diff --git a/app/models/todo.rb b/app/models/todo.rb
index bb5965e20eb2841fa0851b9c32b46e8070eb6848..a2ab405fdbec7d42675b92bf525e11b8c6632d14 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -32,8 +32,6 @@ class Todo < ActiveRecord::Base
   validates :target_id, presence: true, unless: :for_commit?
   validates :commit_id, presence: true, if: :for_commit?
 
-  default_scope { reorder(id: :desc) }
-
   scope :pending, -> { with_state(:pending) }
   scope :done, -> { with_state(:done) }
 
@@ -52,11 +50,15 @@ class Todo < ActiveRecord::Base
     # Priority sorting isn't displayed in the dropdown, because we don't show
     # milestones, but still show something if the user has a URL with that
     # selected.
-    def sort(method)
-      case method.to_s
-      when 'priority', 'label_priority' then order_by_labels_priority
-      else order_by(method)
-      end
+    def sort_by_attribute(method)
+      sorted =
+        case method.to_s
+        when 'priority', 'label_priority' then order_by_labels_priority
+        else order_by(method)
+        end
+
+      # Break ties with the ID column for pagination
+      sorted.order(id: :desc)
     end
 
     # Order by priority depending on which issue/merge request the Todo belongs to
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 99ad37dc89207067fa2a7229fd68b7b1f5803674..cf71a7b76fc524d5504483d9d1b5d4a783a3d327 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -9,6 +9,8 @@ class Upload < ActiveRecord::Base
   validates :model, presence: true
   validates :uploader, presence: true
 
+  scope :with_files_stored_locally, -> { where(store: [nil, ObjectStorage::Store::LOCAL]) }
+
   before_save  :calculate_checksum!, if: :foreground_checksummable?
   after_commit :schedule_checksum,   if: :checksummable?
 
@@ -21,6 +23,7 @@ class Upload < ActiveRecord::Base
   end
 
   def absolute_path
+    raise ObjectStorage::RemoteStoreError, "Remote object has no absolute path." unless local?
     return path unless relative_path?
 
     uploader_class.absolute_path(self)
@@ -30,11 +33,11 @@ class Upload < ActiveRecord::Base
     self.checksum = nil
     return unless checksummable?
 
-    self.checksum = self.class.hexdigest(absolute_path)
+    self.checksum = Digest::SHA256.file(absolute_path).hexdigest
   end
 
-  def build_uploader
-    uploader_class.new(model, mount_point, **uploader_context).tap do |uploader|
+  def build_uploader(mounted_as = nil)
+    uploader_class.new(model, mounted_as || mount_point).tap do |uploader|
       uploader.upload = self
       uploader.retrieve_from_store!(identifier)
     end
@@ -51,6 +54,12 @@ class Upload < ActiveRecord::Base
     }.compact
   end
 
+  def local?
+    return true if store.nil?
+
+    store == ObjectStorage::Store::LOCAL
+  end
+
   private
 
   def delete_file!
@@ -61,10 +70,6 @@ class Upload < ActiveRecord::Base
     checksum.nil? && local? && exist?
   end
 
-  def local?
-    true
-  end
-
   def foreground_checksummable?
     checksummable? && size <= CHECKSUM_THRESHOLD
   end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9c60adf0c90d1d4c636afe3212a607e62f795030..b06681489720ac6af76e3a7b13993250a2dad1d9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -82,11 +82,8 @@ class User < ActiveRecord::Base
   has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent
 
   # Profile
-  has_many :keys, -> do
-    type = Key.arel_table[:type]
-    where(type.not_eq('DeployKey').or(type.eq(nil)))
-  end, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
-  has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+  has_many :keys, -> { where(type: ['Key', nil]) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+  has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
   has_many :gpg_keys
 
   has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -100,8 +97,8 @@ class User < ActiveRecord::Base
   has_many :members
   has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember'
   has_many :groups, through: :group_members
-  has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
-  has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
+  has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
+  has_many :masters_groups, -> { where(members: { access_level: Gitlab::Access::MASTER }) }, through: :group_members, source: :group
 
   # Projects
   has_many :groups_projects,          through: :groups, source: :projects
@@ -114,13 +111,15 @@ class User < ActiveRecord::Base
   has_many :project_authorizations
   has_many :authorized_projects, through: :project_authorizations, source: :project
 
+  has_many :user_interacted_projects
+  has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project'
+
   has_many :snippets,                 dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
   has_many :notes,                    dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
   has_many :issues,                   dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
   has_many :merge_requests,           dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
   has_many :events,                   dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
   has_many :subscriptions,            dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
-  has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id,   class_name: "Event"
   has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
   has_one  :abuse_report,             dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent
   has_many :reported_abuse_reports,   dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent
@@ -165,12 +164,15 @@ class User < ActiveRecord::Base
 
   before_validation :sanitize_attrs
   before_validation :set_notification_email, if: :email_changed?
+  before_save :set_notification_email, if: :email_changed? # in case validation is skipped
   before_validation :set_public_email, if: :public_email_changed?
+  before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
   before_save :ensure_incoming_email_token
   before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
   before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
   before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
   before_validation :ensure_namespace_correct
+  before_save :ensure_namespace_correct # in case validation is skipped
   after_validation :set_username_errors
   after_update :username_changed_hook, if: :username_changed?
   after_destroy :post_destroy_hook
@@ -185,7 +187,7 @@ class User < ActiveRecord::Base
 
   # User's Dashboard preference
   # Note: When adding an option, it MUST go on the end of the array.
-  enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos]
+  enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests]
 
   # User's Project preference
   # Note: When adding an option, it MUST go on the end of the array.
@@ -257,7 +259,7 @@ class User < ActiveRecord::Base
       end
     end
 
-    def sort(method)
+    def sort_by_attribute(method)
       order_method = method || 'id_desc'
 
       case order_method.to_s
@@ -409,7 +411,6 @@ class User < ActiveRecord::Base
       unique_internal(where(ghost: true), 'ghost', email) do |u|
         u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
         u.name = 'Ghost User'
-        u.notification_email = email
       end
     end
   end
@@ -621,9 +622,7 @@ class User < ActiveRecord::Base
   end
 
   def owned_projects
-    @owned_projects ||=
-      Project.where('namespace_id IN (?) OR namespace_id = ?',
-                    owned_groups.select(:id), namespace.id).joins(:namespace)
+    @owned_projects ||= Project.from("(#{owned_projects_union.to_sql}) AS projects")
   end
 
   # Returns projects which user can admin issues on (for example to move an issue to that project).
@@ -701,10 +700,6 @@ class User < ActiveRecord::Base
     projects_limit - personal_projects_count
   end
 
-  def personal_projects_count
-    @personal_projects_count ||= personal_projects.count
-  end
-
   def recent_push(project = nil)
     service = Users::LastPushEventService.new(self)
 
@@ -952,10 +947,13 @@ class User < ActiveRecord::Base
   end
 
   def manageable_groups
-    union = Gitlab::SQL::Union.new([owned_groups.select(:id),
-                                    masters_groups.select(:id)])
-    arel_union = Arel::Nodes::SqlLiteral.new(union.to_sql)
-    owned_and_master_groups = Group.where(Group.arel_table[:id].in(arel_union))
+    union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), masters_groups.select(:id)]).to_sql
+
+    # Update this line to not use raw SQL when migrated to Rails 5.2.
+    # Either ActiveRecord or Arel constructions are fine.
+    # This was replaced with the raw SQL construction because of bugs in the arel gem.
+    # Bugs were fixed in arel 9.0.0 (Rails 5.2).
+    owned_and_master_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection
 
     Gitlab::GroupHierarchy.new(owned_and_master_groups).base_and_descendants
   end
@@ -998,7 +996,7 @@ 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})") # rubocop:disable GitlabSecurity/SqlInjection
+        .where(project: authorized_projects(Gitlab::Access::MASTER))
         .select(:runner_id)
       Ci::Runner.specific.where(id: runner_ids)
     end
@@ -1035,14 +1033,35 @@ class User < ActiveRecord::Base
     end
   end
 
-  def update_cache_counts
-    assigned_open_merge_requests_count(force: true)
-    assigned_open_issues_count(force: true)
+  def todos_done_count(force: false)
+    Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
+      TodosFinder.new(self, state: :done).execute.count
+    end
+  end
+
+  def todos_pending_count(force: false)
+    Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
+      TodosFinder.new(self, state: :pending).execute.count
+    end
+  end
+
+  def personal_projects_count(force: false)
+    Rails.cache.fetch(['users', id, 'personal_projects_count'], force: force, expires_in: 24.hours, raw: true) do
+      personal_projects.count
+    end.to_i
+  end
+
+  def update_todos_count_cache
+    todos_done_count(force: true)
+    todos_pending_count(force: true)
   end
 
   def invalidate_cache_counts
     invalidate_issue_cache_counts
     invalidate_merge_request_cache_counts
+    invalidate_todos_done_count
+    invalidate_todos_pending_count
+    invalidate_personal_projects_count
   end
 
   def invalidate_issue_cache_counts
@@ -1053,21 +1072,16 @@ class User < ActiveRecord::Base
     Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
   end
 
-  def todos_done_count(force: false)
-    Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
-      TodosFinder.new(self, state: :done).execute.count
-    end
+  def invalidate_todos_done_count
+    Rails.cache.delete(['users', id, 'todos_done_count'])
   end
 
-  def todos_pending_count(force: false)
-    Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
-      TodosFinder.new(self, state: :pending).execute.count
-    end
+  def invalidate_todos_pending_count
+    Rails.cache.delete(['users', id, 'todos_pending_count'])
   end
 
-  def update_todos_count_cache
-    todos_done_count(force: true)
-    todos_pending_count(force: true)
+  def invalidate_personal_projects_count
+    Rails.cache.delete(['users', id, 'personal_projects_count'])
   end
 
   # This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth
@@ -1184,13 +1198,13 @@ class User < ActiveRecord::Base
 
   private
 
-  def ci_projects_union
-    scope  = { access_level: [Gitlab::Access::MASTER, Gitlab::Access::OWNER] }
-    groups = groups_projects.where(members: scope)
-    other  = projects.where(members: scope)
-
-    Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
-                            other.select(:id)])
+  def owned_projects_union
+    Gitlab::SQL::Union.new([
+      Project.where(namespace: namespace),
+      Project.joins(:project_authorizations)
+        .where("projects.namespace_id <> ?", namespace.id)
+        .where(project_authorizations: { user_id: id, access_level: Gitlab::Access::OWNER })
+    ], remove_duplicates: false)
   end
 
   # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dd55a6acb79116b3e2d37fe5e8458a7e00e5d801
--- /dev/null
+++ b/app/models/user_interacted_project.rb
@@ -0,0 +1,59 @@
+class UserInteractedProject < ActiveRecord::Base
+  belongs_to :user
+  belongs_to :project
+
+  validates :project_id, presence: true
+  validates :user_id, presence: true
+
+  CACHE_EXPIRY_TIME = 1.day
+
+  # Schema version required for this model
+  REQUIRED_SCHEMA_VERSION = 20180223120443
+
+  class << self
+    def track(event)
+      # For events without a project, we simply don't care.
+      # An example of this is the creation of a snippet (which
+      # is not related to any project).
+      return unless event.project_id
+
+      attributes = {
+        project_id: event.project_id,
+        user_id: event.author_id
+      }
+
+      cached_exists?(attributes) do
+        transaction(requires_new: true) do
+          begin
+            where(attributes).select(1).first || create!(attributes)
+            true # not caching the whole record here for now
+          rescue ActiveRecord::RecordNotUnique
+            # Note, above queries are not atomic and prone
+            # to race conditions (similar like #find_or_create!).
+            # In the case where we hit this, the record we want
+            # already exists - shortcut and return.
+            true
+          end
+        end
+      end
+    end
+
+    # Check if we can safely call .track (table exists)
+    def available?
+      @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION # rubocop:disable Gitlab/PredicateMemoization
+    end
+
+    # Flushes cached information about schema
+    def reset_column_information
+      @available_flag = nil
+      super
+    end
+
+    private
+
+    def cached_exists?(project_id:, user_id:, &block)
+      cache_key = "user_interacted_projects:#{project_id}:#{user_id}"
+      Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRY_TIME, &block)
+    end
+  end
+end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 0f5536415f79ceea274c468cc0da5f14694d2799..cde79b95062373e177ccd37503267477bb1e9930 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -265,6 +265,15 @@ class WikiPage
     title.present? && self.class.unhyphenize(@page.url_path) != title
   end
 
+  # Updates the current @attributes hash by merging a hash of params
+  def update_attributes(attrs)
+    attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
+
+    attrs.slice!(:content, :format, :message, :title)
+
+    @attributes.merge!(attrs)
+  end
+
   private
 
   # Process and format the title based on the user input.
@@ -290,15 +299,6 @@ class WikiPage
     File.join(components)
   end
 
-  # Updates the current @attributes hash by merging a hash of params
-  def update_attributes(attrs)
-    attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
-
-    attrs.slice!(:content, :format, :message, :title)
-
-    @attributes.merge!(attrs)
-  end
-
   def set_attributes
     attributes[:slug] = @page.url_path
     attributes[:title] = @page.title
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 1ab391a5a9d3b6350e92c7155d3ddd25783047bb..808a81cbbf928053244975999129c317d5160312 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -11,7 +11,7 @@ module Ci
     end
 
     condition(:owner_of_job) do
-      can?(:developer_access) && @subject.triggered_by?(@user)
+      @subject.triggered_by?(@user)
     end
 
     rule { protected_ref }.policy do
@@ -19,6 +19,6 @@ module Ci
       prevent :erase_build
     end
 
-    rule { can?(:master_access) | owner_of_job }.enable :erase_build
+    rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
   end
 end
diff --git a/app/policies/ci/pipeline_schedule_policy.rb b/app/policies/ci/pipeline_schedule_policy.rb
index dc7a4aed5774c5548e8f89c4dfdc5681c3d9b57b..ecba0488d3c44f3c7e787910f01252a14ce044bf 100644
--- a/app/policies/ci/pipeline_schedule_policy.rb
+++ b/app/policies/ci/pipeline_schedule_policy.rb
@@ -7,23 +7,17 @@ module Ci
     end
 
     condition(:owner_of_schedule) do
-      can?(:developer_access) && pipeline_schedule.owned_by?(@user)
+      pipeline_schedule.owned_by?(@user)
     end
 
-    condition(:non_owner_of_schedule) do
-      !pipeline_schedule.owned_by?(@user)
-    end
-
-    rule { can?(:developer_access) }.policy do
-      enable :play_pipeline_schedule
-    end
+    rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
 
-    rule { can?(:master_access) | owner_of_schedule }.policy do
+    rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
       enable :update_pipeline_schedule
       enable :admin_pipeline_schedule
     end
 
-    rule { can?(:master_access) & non_owner_of_schedule }.policy do
+    rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do
       enable :take_ownership_pipeline_schedule
     end
 
diff --git a/app/policies/deploy_token_policy.rb b/app/policies/deploy_token_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7aa9106e8b1d58002f2486919e68fbe9d71f3dcf
--- /dev/null
+++ b/app/policies/deploy_token_policy.rb
@@ -0,0 +1,11 @@
+class DeployTokenPolicy < BasePolicy
+  with_options scope: :subject, score: 0
+  condition(:master) { @subject.project.team.master?(@user) }
+
+  rule { anonymous }.prevent_all
+
+  rule { master }.policy do
+    enable :create_deploy_token
+    enable :update_deploy_token
+  end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index f0bcba588a220658ccb4ca19b3e042829f374d41..c9cb730c4e9ef675e1ecfc19e2a293aedeb5efae 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -48,7 +48,12 @@ class GroupPolicy < BasePolicy
   rule { has_access }.enable :read_namespace
 
   rule { developer }.enable :admin_milestones
-  rule { reporter }.enable :admin_label
+
+  rule { reporter }.policy do
+    enable :admin_label
+    enable :admin_list
+    enable :admin_issue
+  end
 
   rule { master }.policy do
     enable :create_projects
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 3f6d7d046672b3e9e8d689528145a9d6aa95f01d..b431d376e3dd8a2d5c957349d25c0a56bddfc7f2 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -2,20 +2,6 @@ class IssuablePolicy < BasePolicy
   delegate { @subject.project }
 
   condition(:locked, scope: :subject, score: 0) { @subject.discussion_locked? }
-
-  # We aren't checking `:read_issue` or `:read_merge_request` in this case
-  # because it could be possible for a user to see an issuable-iid
-  # (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be allowed
-  # to read the actual issue after a more expensive `:read_issue` check.
-  #
-  # `:read_issue` & `:read_issue_iid` could diverge in gitlab-ee.
-  condition(:visible_to_user, score: 4) do
-    Project.where(id: @subject.project)
-      .public_or_visible_to_user(@user)
-      .with_feature_available_for_user(@subject, @user)
-      .any?
-  end
-
   condition(:is_project_member) { @user && @subject.project && @subject.project.team.member?(@user) }
 
   desc "User is the assignee or author"
@@ -32,9 +18,7 @@ class IssuablePolicy < BasePolicy
 
   rule { locked & ~is_project_member }.policy do
     prevent :create_note
-    prevent :update_note
     prevent :admin_note
     prevent :resolve_note
-    prevent :edit_note
   end
 end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index ed4995119994031b4288b2ed8cffc39d663fb31e..263c6e3039c2576b25b70c46368aed6005bbdd8a 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -17,6 +17,4 @@ class IssuePolicy < IssuablePolicy
     prevent :update_issue
     prevent :admin_issue
   end
-
-  rule { can?(:read_issue) | visible_to_user }.enable :read_issue_iid
 end
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index e003376d219d8050b24a7971fb0606f3743cd678..c3fe857f8a2dd85fc490e603335f27d25e83dc89 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -1,3 +1,2 @@
 class MergeRequestPolicy < IssuablePolicy
-  rule { can?(:read_merge_request) | visible_to_user }.enable :read_merge_request_iid
 end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index d4cb5a77e639a2e6f2f7bcf4056c05987596ae8f..077a6761ee6c400858c9280d13948e497babe9b1 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -1,26 +1,21 @@
 class NotePolicy < BasePolicy
   delegate { @subject.project }
-  delegate { @subject.noteable if @subject.noteable.lockable? }
+  delegate { @subject.noteable if DeclarativePolicy.has_policy?(@subject.noteable) }
 
   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 }
 
   condition(:editable, scope: :subject) { @subject.editable? }
 
-  rule { ~editable | anonymous }.prevent :edit_note
-
-  rule { is_author | admin }.enable :edit_note
-  rule { can?(:master_access) }.enable :edit_note
+  rule { ~editable }.prevent :admin_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
+  rule { 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 cac0530b9f71b696f18402a64a11553549d98cd1..c1a84727cfa5489fd8714921ecdbd61d04b98e17 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
   end
 
   rule { anonymous }.prevent :comment_personal_snippet
+
+  rule { can?(:comment_personal_snippet) }.enable :award_emoji
 end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 3b0550b4dd63f4da571b0b6f6373f986a8268764..3529d0aa60c128e63d06c803f988fd7cd71508eb 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -1,12 +1,26 @@
 class ProjectPolicy < BasePolicy
-  def self.create_read_update_admin(name)
-    [
-      :"create_#{name}",
-      :"read_#{name}",
-      :"update_#{name}",
-      :"admin_#{name}"
-    ]
-  end
+  extend ClassMethods
+
+  READONLY_FEATURES_WHEN_ARCHIVED = %i[
+    issue
+    list
+    merge_request
+    label
+    milestone
+    project_snippet
+    wiki
+    note
+    pipeline
+    pipeline_schedule
+    build
+    trigger
+    environment
+    deployment
+    commit_status
+    container_image
+    pages
+    cluster
+  ].freeze
 
   desc "User is a project owner"
   condition :owner do
@@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy
   end
 
   desc "Project has public builds enabled"
-  condition(:public_builds, scope: :subject) { project.public_builds? }
+  condition(:public_builds, scope: :subject, score: 0) { project.public_builds? }
 
   # For guest access we use #team_member? so we can use
   # project.members, which gets cached in subject scope.
@@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy
   condition(:master) { team_access_level >= Gitlab::Access::MASTER }
 
   desc "Project is public"
-  condition(:public_project, scope: :subject) { project.public? }
+  condition(:public_project, scope: :subject, score: 0) { project.public? }
 
   desc "Project is visible to internal users"
   condition(:internal_access) do
@@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy
   condition(:group_member, scope: :subject) { project_group_member? }
 
   desc "Project is archived"
-  condition(:archived, scope: :subject) { project.archived? }
+  condition(:archived, scope: :subject, score: 0) { project.archived? }
 
   condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
 
@@ -56,10 +70,31 @@ class ProjectPolicy < BasePolicy
   end
 
   desc "Project has an external wiki"
-  condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
+  condition(:has_external_wiki, scope: :subject, score: 0) { project.has_external_wiki? }
 
   desc "Project has request access enabled"
-  condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
+  condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled }
+
+  desc "Has merge requests allowing pushes to user"
+  condition(:has_merge_requests_allowing_pushes, scope: :subject) do
+    project.merge_requests_allowing_push_to_user(user).any?
+  end
+
+  # We aren't checking `:read_issue` or `:read_merge_request` in this case
+  # because it could be possible for a user to see an issuable-iid
+  # (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
+  # allowed to read the actual issue after a more expensive `:read_issue`
+  # check. These checks are intended to be used alongside
+  # `:read_project_for_iids`.
+  #
+  # `:read_issue` & `:read_issue_iid` could diverge in gitlab-ee.
+  condition(:issues_visible_to_user, score: 4) do
+    @subject.feature_available?(:issues, @user)
+  end
+
+  condition(:merge_requests_visible_to_user, score: 4) do
+    @subject.feature_available?(:merge_requests, @user)
+  end
 
   features = %w[
     merge_requests
@@ -76,6 +111,10 @@ class ProjectPolicy < BasePolicy
     condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
   end
 
+  # `:read_project` may be prevented in EE, but `:read_project_for_iids` should
+  # not.
+  rule { guest | admin }.enable :read_project_for_iids
+
   rule { guest }.enable :guest_access
   rule { reporter }.enable :reporter_access
   rule { developer }.enable :developer_access
@@ -101,6 +140,7 @@ class ProjectPolicy < BasePolicy
 
   rule { can?(:guest_access) }.policy do
     enable :read_project
+    enable :create_merge_request_in
     enable :read_board
     enable :read_list
     enable :read_wiki
@@ -115,10 +155,11 @@ class ProjectPolicy < BasePolicy
     enable :create_note
     enable :upload_file
     enable :read_cycle_analytics
+    enable :award_emoji
   end
 
   # These abilities are not allowed to admins that are not members of the project,
-  # that's why they are defined separatly.
+  # that's why they are defined separately.
   rule { guest & can?(:download_code) }.enable :build_download_code
   rule { guest & can?(:read_container_image) }.enable :build_read_container_image
 
@@ -145,6 +186,7 @@ class ProjectPolicy < BasePolicy
   # where we enable or prevent it based on other coditions.
   rule { (~anonymous & public_project) | internal_access }.policy do
     enable :public_user_access
+    enable :read_project_for_iids
   end
 
   rule { can?(:public_user_access) }.policy do
@@ -171,7 +213,7 @@ class ProjectPolicy < BasePolicy
     enable :create_pipeline
     enable :update_pipeline
     enable :create_pipeline_schedule
-    enable :create_merge_request
+    enable :create_merge_request_from
     enable :create_wiki
     enable :push_code
     enable :resolve_note
@@ -182,7 +224,7 @@ class ProjectPolicy < BasePolicy
   end
 
   rule { can?(:master_access) }.policy do
-    enable :delete_protected_branch
+    enable :push_to_delete_protected_branch
     enable :update_project_snippet
     enable :update_environment
     enable :update_deployment
@@ -205,37 +247,50 @@ class ProjectPolicy < BasePolicy
   end
 
   rule { archived }.policy do
-    prevent :create_merge_request
     prevent :push_code
-    prevent :delete_protected_branch
-    prevent :update_merge_request
-    prevent :admin_merge_request
+    prevent :push_to_delete_protected_branch
+    prevent :request_access
+    prevent :upload_file
+    prevent :resolve_note
+    prevent :create_merge_request_from
+    prevent :create_merge_request_in
+    prevent :award_emoji
+
+    READONLY_FEATURES_WHEN_ARCHIVED.each do |feature|
+      prevent(*create_update_admin_destroy(feature))
+    end
+  end
+
+  rule { issues_disabled }.policy do
+    prevent(*create_read_update_admin_destroy(:issue))
   end
 
   rule { merge_requests_disabled | repository_disabled }.policy do
-    prevent(*create_read_update_admin(:merge_request))
+    prevent :create_merge_request_in
+    prevent :create_merge_request_from
+    prevent(*create_read_update_admin_destroy(:merge_request))
   end
 
   rule { issues_disabled & merge_requests_disabled }.policy do
-    prevent(*create_read_update_admin(:label))
-    prevent(*create_read_update_admin(:milestone))
+    prevent(*create_read_update_admin_destroy(:label))
+    prevent(*create_read_update_admin_destroy(:milestone))
   end
 
   rule { snippets_disabled }.policy do
-    prevent(*create_read_update_admin(:project_snippet))
+    prevent(*create_read_update_admin_destroy(:project_snippet))
   end
 
   rule { wiki_disabled & ~has_external_wiki }.policy do
-    prevent(*create_read_update_admin(:wiki))
+    prevent(*create_read_update_admin_destroy(: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))
+    prevent(*create_update_admin_destroy(:pipeline))
+    prevent(*create_read_update_admin_destroy(:build))
+    prevent(*create_read_update_admin_destroy(:pipeline_schedule))
+    prevent(*create_read_update_admin_destroy(:environment))
+    prevent(*create_read_update_admin_destroy(:deployment))
   end
 
   rule { repository_disabled }.policy do
@@ -246,11 +301,15 @@ class ProjectPolicy < BasePolicy
   end
 
   rule { container_registry_disabled }.policy do
-    prevent(*create_read_update_admin(:container_image))
+    prevent(*create_read_update_admin_destroy(:container_image))
   end
 
   rule { anonymous & ~public_project }.prevent_all
-  rule { public_project }.enable(:public_access)
+
+  rule { public_project }.policy do
+    enable :public_access
+    enable :read_project_for_iids
+  end
 
   rule { can?(:public_access) }.policy do
     enable :read_project
@@ -284,13 +343,23 @@ class ProjectPolicy < BasePolicy
     enable :read_pipeline_schedule
   end
 
-  rule { issues_disabled }.policy do
-    prevent :create_issue
-    prevent :update_issue
-    prevent :admin_issue
-    prevent :read_issue
+  # These rules are included to allow maintainers of projects to push to certain
+  # to run pipelines for the branches they have access to.
+  rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
+    enable :create_build
+    enable :update_build
+    enable :create_pipeline
+    enable :update_pipeline
   end
 
+  rule do
+    (can?(:read_project_for_iids) & issues_visible_to_user) | can?(:read_issue)
+  end.enable :read_issue_iid
+
+  rule do
+    (can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
+  end.enable :read_merge_request_iid
+
   private
 
   def team_member?
diff --git a/app/policies/project_policy/class_methods.rb b/app/policies/project_policy/class_methods.rb
new file mode 100644
index 0000000000000000000000000000000000000000..60e5aba00baa68df5e7f9e60f798dca58d7f53e5
--- /dev/null
+++ b/app/policies/project_policy/class_methods.rb
@@ -0,0 +1,19 @@
+class ProjectPolicy
+  module ClassMethods
+    def create_read_update_admin_destroy(name)
+      [
+        :"read_#{name}",
+        *create_update_admin_destroy(name)
+      ]
+    end
+
+    def create_update_admin_destroy(name)
+      [
+        :"create_#{name}",
+        :"update_#{name}",
+        :"admin_#{name}",
+        :"destroy_#{name}"
+      ]
+    end
+  end
+end
diff --git a/app/policies/protected_branch_policy.rb b/app/policies/protected_branch_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1a7faa4db4047607905ecf1a6b2638e93e32d4d6
--- /dev/null
+++ b/app/policies/protected_branch_policy.rb
@@ -0,0 +1,9 @@
+class ProtectedBranchPolicy < BasePolicy
+  delegate { @subject.project }
+
+  rule { can?(:admin_project) }.policy do
+    enable :create_protected_branch
+    enable :update_protected_branch
+    enable :destroy_protected_branch
+  end
+end
diff --git a/app/presenters/ci/build_metadata_presenter.rb b/app/presenters/ci/build_metadata_presenter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5048f967ea862cafce4653c82904a64708b7dce2
--- /dev/null
+++ b/app/presenters/ci/build_metadata_presenter.rb
@@ -0,0 +1,18 @@
+module Ci
+  class BuildMetadataPresenter < Gitlab::View::Presenter::Delegated
+    TIMEOUT_SOURCES = {
+        unknown_timeout_source: nil,
+        project_timeout_source: 'project',
+        runner_timeout_source: 'runner'
+    }.freeze
+
+    presents :metadata
+
+    def timeout_source
+      return unless metadata.timeout_source?
+
+      TIMEOUT_SOURCES[metadata.timeout_source.to_sym] ||
+        metadata.timeout_source
+    end
+  end
+end
diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb
index 255475e1fe65d4b8e402d03a7617af2423d789aa..4873d7ce6627c57dcfaa4d8c762f2003cc8c0fc5 100644
--- a/app/presenters/ci/build_presenter.rb
+++ b/app/presenters/ci/build_presenter.rb
@@ -1,5 +1,14 @@
 module Ci
   class BuildPresenter < Gitlab::View::Presenter::Delegated
+    CALLOUT_FAILURE_MESSAGES = {
+      unknown_failure: 'There is an unknown failure, please try again',
+      script_failure: 'There has been a script failure. Check the job log for more information',
+      api_failure: 'There has been an API failure, please try again',
+      stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again',
+      runner_system_failure: 'There has been a runner system failure, please try again',
+      missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information'
+    }.freeze
+
     presents :build
 
     def erased_by_user?
@@ -15,6 +24,8 @@ module Ci
     def status_title
       if auto_canceled?
         "Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
+      else
+        tooltip_for_badge
       end
     end
 
@@ -28,5 +39,31 @@ module Ci
           trigger_request.user_variables
         end
     end
+
+    def tooltip_message
+      "#{subject.name} - #{detailed_status.status_tooltip}"
+    end
+
+    def callout_failure_message
+      CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym]
+    end
+
+    def recoverable?
+      failed? && !unrecoverable?
+    end
+
+    private
+
+    def tooltip_for_badge
+      detailed_status.badge_tooltip.capitalize
+    end
+
+    def detailed_status
+      @detailed_status ||= subject.detailed_status(user)
+    end
+
+    def unrecoverable?
+      script_failure? || missing_dependency_failure?
+    end
   end
 end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 08ae49562c7f418d99d637da6de72b9deea73eef..4b4132af2d064f1261d90b3a2e6a4b38ea3fb962 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
   include GitlabRoutingHelper
   include MarkupHelper
   include TreeHelper
+  include ChecksCollaboration
   include Gitlab::Utils::StrongMemoize
 
   presents :merge_request
@@ -78,7 +79,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
   end
 
   def rebase_path
-    if !rebase_in_progress? && should_be_rebased? && user_can_push_to_source_branch?
+    if !rebase_in_progress? && should_be_rebased? && can_push_to_source_branch?
       rebase_project_merge_request_path(project, merge_request)
     end
   end
@@ -152,15 +153,19 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
   end
 
   def can_revert_on_current_merge_request?
-    user_can_collaborate_with_project? && cached_can_be_reverted?
+    can_collaborate_with_project?(project) && cached_can_be_reverted?
   end
 
   def can_cherry_pick_on_current_merge_request?
-    user_can_collaborate_with_project? && can_be_cherry_picked?
+    can_collaborate_with_project?(project) && can_be_cherry_picked?
   end
 
   def can_push_to_source_branch?
-    source_branch_exists? && user_can_push_to_source_branch?
+    return false unless source_branch_exists?
+
+    !!::Gitlab::UserAccess
+      .new(current_user, project: source_project)
+      .can_push_to_branch?(source_branch)
   end
 
   private
@@ -191,19 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
     end.sort.to_sentence
   end
 
-  def user_can_push_to_source_branch?
-    return false unless source_branch_exists?
-
-    ::Gitlab::UserAccess
-      .new(current_user, project: source_project)
-      .can_push_to_branch?(source_branch)
-  end
-
-  def user_can_collaborate_with_project?
-    can?(current_user, :push_code, project) ||
-      (current_user && current_user.already_forked?(project))
-  end
-
   def user_can_fork_project?
     can?(current_user, :fork_project, project)
   end
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 69d46f5ec14d91387e3c96e10fcc6234f8dc3cf0..ca4480fe2b18de06ea8550d6f99d5f5b3b57e076 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -5,6 +5,8 @@ class BuildDetailsEntity < JobEntity
   expose :runner, using: RunnerEntity
   expose :pipeline, using: PipelineEntity
 
+  expose :metadata, using: BuildMetadataEntity
+
   expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
   expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build|
     erase_project_job_path(project, build)
diff --git a/app/serializers/build_metadata_entity.rb b/app/serializers/build_metadata_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f16f3badffa0a8cfaec0c3417c6f10e15fcfd4cb
--- /dev/null
+++ b/app/serializers/build_metadata_entity.rb
@@ -0,0 +1,6 @@
+class BuildMetadataEntity < Grape::Entity
+  expose :timeout_human_readable
+  expose :timeout_source do |metadata|
+    metadata.present.timeout_source
+  end
+end
diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb
index bbbcf6a97c189e8ca70a92f3126d7989d303c46b..718fb35e62d0cfa21c549fade0495b9aa1e55227 100644
--- a/app/serializers/discussion_entity.rb
+++ b/app/serializers/discussion_entity.rb
@@ -4,7 +4,9 @@ class DiscussionEntity < Grape::Entity
   expose :id, :reply_id
   expose :expanded?, as: :expanded
 
-  expose :notes, using: NoteEntity
+  expose :notes do |discussion, opts|
+    request.note_entity.represent(discussion.notes, opts)
+  end
 
   expose :individual_note?, as: :individual_note
   expose :resolvable?, as: :resolvable
@@ -12,7 +14,7 @@ class DiscussionEntity < Grape::Entity
   expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion|
     resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id)
   end
-  expose :resolve_with_issue_path do |discussion|
+  expose :resolve_with_issue_path, if: -> (d, _) { d.resolvable? } do |discussion|
     new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id)
   end
 
diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb
index 71d9a65fb58c98335ccfbcae8863df218afdd907..464217123b4f10af415955f2c64c14eb1e405bc6 100644
--- a/app/serializers/entity_date_helper.rb
+++ b/app/serializers/entity_date_helper.rb
@@ -1,5 +1,6 @@
 module EntityDateHelper
   include ActionView::Helpers::DateHelper
+  include ActionView::Helpers::TagHelper
 
   def interval_in_words(diff)
     return 'Not started' unless diff
@@ -34,4 +35,30 @@ module EntityDateHelper
 
     duration_hash
   end
+
+  # Generates an HTML-formatted string for remaining dates based on start_date and due_date
+  #
+  # It returns "Past due" for expired entities
+  # It returns "Upcoming" for upcoming entities
+  # If due date is provided, it returns "# days|weeks|months remaining|ago"
+  # If start date is provided and elapsed, with no due date, it returns "# days elapsed"
+  def remaining_days_in_words(entity)
+    if entity.try(:expired?)
+      content_tag(:strong, 'Past due')
+    elsif entity.try(:upcoming?)
+      content_tag(:strong, 'Upcoming')
+    elsif entity.due_date
+      is_upcoming = (entity.due_date - Date.today).to_i > 0
+      time_ago = time_ago_in_words(entity.due_date)
+      content = time_ago.gsub(/\d+/) { |match| "<strong>#{match}</strong>" }
+      content.slice!("about ")
+      content << " " + (is_upcoming ? _("remaining") : _("ago"))
+      content.html_safe
+    elsif entity.start_date && entity.start_date.past?
+      days    = entity.elapsed_days
+      content = content_tag(:strong, days)
+      content << " #{'day'.pluralize(days)} elapsed"
+      content.html_safe
+    end
+  end
 end
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index b5e2334b6e3e903ff3a5cdc7122ffe9d155ddd7f..840fdbcbf1409446bc567aead151abf9c6077fe8 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity
     expose :can_update do |issue|
       can?(request.current_user, :update_issue, issue)
     end
+
+    expose :can_award_emoji do |issue|
+      can?(request.current_user, :award_emoji, issue)
+    end
   end
 
   expose :create_note_path do |issue|
diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb
index 523b522d44900337ea56a1c44544a5f38afcf8b3..3076fed1674cb3412b466cda918d1abaecfcc983 100644
--- a/app/serializers/job_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -26,6 +26,8 @@ class JobEntity < Grape::Entity
   expose :created_at
   expose :updated_at
   expose :detailed_status, as: :status, with: StatusEntity
+  expose :callout_message, if: -> (*) { failed? }
+  expose :recoverable, if: -> (*) { failed? }
 
   private
 
@@ -50,4 +52,20 @@ class JobEntity < Grape::Entity
   def path_to(route, build)
     send("#{route}_path", build.project.namespace, build.project, build) # rubocop:disable GitlabSecurity/PublicSend
   end
+
+  def failed?
+    build.failed?
+  end
+
+  def callout_message
+    build_presenter.callout_failure_message
+  end
+
+  def recoverable
+    build_presenter.recoverable?
+  end
+
+  def build_presenter
+    @build_presenter ||= build.present
+  end
 end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 4e8ef320af2e0463c92a50af4aa5fe8bfb34340e..4a812e39ee139bc488aec29dc897e0c0654d3b28 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -11,6 +11,7 @@ class MergeRequestWidgetEntity < IssuableEntity
   expose :source_project_id
   expose :target_branch
   expose :target_project_id
+  expose :allow_maintainer_to_push
 
   expose :should_be_rebased?, as: :should_be_rebased
   expose :ff_only_enabled do |merge_request|
@@ -29,6 +30,7 @@ class MergeRequestWidgetEntity < IssuableEntity
   expose :can_push_to_source_branch do |merge_request|
     presenter(merge_request).can_push_to_source_branch?
   end
+
   expose :rebase_path do |merge_request|
     presenter(merge_request).rebase_path
   end
@@ -38,7 +40,7 @@ class MergeRequestWidgetEntity < IssuableEntity
 
   # Diff sha's
   expose :diff_head_sha do |merge_request|
-    merge_request.diff_head_sha if merge_request.diff_head_commit
+    merge_request.diff_head_sha.presence
   end
 
   expose :merge_commit_message
@@ -136,8 +138,8 @@ class MergeRequestWidgetEntity < IssuableEntity
   end
 
   expose :new_blob_path do |merge_request|
-    if can?(current_user, :push_code, merge_request.project)
-      project_new_blob_path(merge_request.project, merge_request.source_branch)
+    if presenter(merge_request).can_push_to_source_branch?
+      project_new_blob_path(merge_request.source_project, merge_request.source_branch)
     end
   end
 
diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb
index 4ccf0bca476084a938161bd4a053b851634025aa..06d603b277e6a2959baf4c053289da90c21ecd15 100644
--- a/app/serializers/note_entity.rb
+++ b/app/serializers/note_entity.rb
@@ -5,10 +5,6 @@ class NoteEntity < API::Entities::Note
 
   expose :author, using: NoteUserEntity
 
-  expose :human_access do |note|
-    note.project.team.human_max_access(note.author_id)
-  end
-
   unexpose :note, as: :body
   expose :note
 
@@ -19,7 +15,11 @@ class NoteEntity < API::Entities::Note
 
   expose :current_user do
     expose :can_edit do |note|
-      Ability.can_edit_note?(request.current_user, note)
+      Ability.allowed?(request.current_user, :admin_note, note)
+    end
+
+    expose :can_award_emoji do |note|
+      Ability.allowed?(request.current_user, :award_emoji, note)
     end
   end
 
@@ -37,36 +37,10 @@ class NoteEntity < API::Entities::Note
 
   expose :emoji_awardable?, as: :emoji_awardable
   expose :award_emoji, if: -> (note, _) { note.emoji_awardable? }, using: AwardEmojiEntity
-  expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note|
-    if note.for_personal_snippet?
-      toggle_award_emoji_snippet_note_path(note.noteable, note)
-    else
-      toggle_award_emoji_project_note_path(note.project, note.id)
-    end
-  end
 
   expose :report_abuse_path do |note|
     new_abuse_report_path(user_id: note.author.id, ref_url: Gitlab::UrlBuilder.build(note))
   end
 
-  expose :path do |note|
-    if note.for_personal_snippet?
-      snippet_note_path(note.noteable, note)
-    else
-      project_note_path(note.project, note)
-    end
-  end
-
-  expose :resolve_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
-    resolve_project_merge_request_discussion_path(note.project, note.noteable, note.discussion_id)
-  end
-
-  expose :resolve_with_issue_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
-    new_project_issue_path(note.project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id)
-  end
-
   expose :attachment, using: NoteAttachmentEntity, if: -> (note, _) { note.attachment? }
-  expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note|
-    delete_attachment_project_note_path(note.project, note)
-  end
 end
diff --git a/app/serializers/note_serializer.rb b/app/serializers/note_serializer.rb
deleted file mode 100644
index 2afe40d7a34bf67e3de870a6f16f30ff64b2d5d4..0000000000000000000000000000000000000000
--- a/app/serializers/note_serializer.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class NoteSerializer < BaseSerializer
-  entity NoteEntity
-end
diff --git a/app/serializers/project_note_entity.rb b/app/serializers/project_note_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e541bfbee8d11bc6fb4c5af678ad79c06d3702d6
--- /dev/null
+++ b/app/serializers/project_note_entity.rb
@@ -0,0 +1,25 @@
+class ProjectNoteEntity < NoteEntity
+  expose :human_access do |note|
+    note.project.team.human_max_access(note.author_id)
+  end
+
+  expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note|
+    toggle_award_emoji_project_note_path(note.project, note.id)
+  end
+
+  expose :path do |note|
+    project_note_path(note.project, note)
+  end
+
+  expose :resolve_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
+    resolve_project_merge_request_discussion_path(note.project, note.noteable, note.discussion_id)
+  end
+
+  expose :resolve_with_issue_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
+    new_project_issue_path(note.project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id)
+  end
+
+  expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note|
+    delete_attachment_project_note_path(note.project, note)
+  end
+end
diff --git a/app/serializers/project_note_serializer.rb b/app/serializers/project_note_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..763ad0bdb3fab85f167044f907e2b9b267d8895e
--- /dev/null
+++ b/app/serializers/project_note_serializer.rb
@@ -0,0 +1,3 @@
+class ProjectNoteSerializer < BaseSerializer
+  entity ProjectNoteEntity
+end
diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb
index 3e40ecf1c1c092cba15f3d3c4606c044cbb570a1..8e8bda2f9df5639391141524a249c37425fb2d8c 100644
--- a/app/serializers/status_entity.rb
+++ b/app/serializers/status_entity.rb
@@ -2,13 +2,19 @@ class StatusEntity < Grape::Entity
   include RequestAwareEntity
 
   expose :icon, :text, :label, :group
-
+  expose :status_tooltip, as: :tooltip
   expose :has_details?, as: :has_details
   expose :details_path
 
   expose :favicon do |status|
-    dir = 'ci_favicons'
-    dir = File.join(dir, 'dev') if Rails.env.development?
+    dir =
+      if Gitlab::Utils.to_boolean(ENV['CANARY'])
+        File.join('ci_favicons', 'canary')
+      elsif Rails.env.development?
+        File.join('ci_favicons', 'dev')
+      else
+        'ci_favicons'
+      end
 
     ActionController::Base.helpers.image_path(File.join(dir, "#{status.favicon}.ico"))
   end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 2b77f6be72a1941f5c6d232d7ff1329313ae9e75..f28cddb2af34ead13b7bc51bf4721fff04d3c4e4 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -109,7 +109,7 @@ module Auth
 
       case requested_action
       when 'pull'
-        build_can_pull?(requested_project) || user_can_pull?(requested_project)
+        build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
       when 'push'
         build_can_push?(requested_project) || user_can_push?(requested_project)
       when '*'
@@ -123,22 +123,34 @@ module Auth
       Gitlab.config.registry
     end
 
+    def can_user?(ability, project)
+      user = current_user.is_a?(User) ? current_user : nil
+      can?(user, ability, project)
+    end
+
     def build_can_pull?(requested_project)
       # Build can:
       # 1. pull from its own project (for ex. a build)
       # 2. read images from dependent projects if creator of build is a team member
       has_authentication_ability?(:build_read_container_image) &&
-        (requested_project == project || can?(current_user, :build_read_container_image, requested_project))
+        (requested_project == project || can_user?(:build_read_container_image, requested_project))
     end
 
     def user_can_admin?(requested_project)
       has_authentication_ability?(:admin_container_image) &&
-        can?(current_user, :admin_container_image, requested_project)
+        can_user?(:admin_container_image, requested_project)
     end
 
     def user_can_pull?(requested_project)
       has_authentication_ability?(:read_container_image) &&
-        can?(current_user, :read_container_image, requested_project)
+        can_user?(:read_container_image, requested_project)
+    end
+
+    def deploy_token_can_pull?(requested_project)
+      has_authentication_ability?(:read_container_image) &&
+        current_user.is_a?(DeployToken) &&
+        current_user.has_access_to?(requested_project) &&
+        current_user.read_registry?
     end
 
     ##
@@ -154,7 +166,7 @@ module Auth
 
     def user_can_push?(requested_project)
       has_authentication_ability?(:create_container_image) &&
-        can?(current_user, :create_container_image, requested_project)
+        can_user?(:create_container_image, requested_project)
     end
 
     def error(code, status:, message: '')
diff --git a/app/services/badges/base_service.rb b/app/services/badges/base_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f87426bd38e678b3e5ce741513bda38370f10a4
--- /dev/null
+++ b/app/services/badges/base_service.rb
@@ -0,0 +1,11 @@
+module Badges
+  class BaseService
+    protected
+
+    attr_accessor :params
+
+    def initialize(params = {})
+      @params = params.dup
+    end
+  end
+end
diff --git a/app/services/badges/build_service.rb b/app/services/badges/build_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6267e57183889cc6305d1ef3977dc514ef43e217
--- /dev/null
+++ b/app/services/badges/build_service.rb
@@ -0,0 +1,12 @@
+module Badges
+  class BuildService < Badges::BaseService
+    # returns the created badge
+    def execute(source)
+      if source.is_a?(Group)
+        GroupBadge.new(params.merge(group: source))
+      else
+        ProjectBadge.new(params.merge(project: source))
+      end
+    end
+  end
+end
diff --git a/app/services/badges/create_service.rb b/app/services/badges/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aafb87f7dcd33ed183999d0c21f1feaeb38f5aa7
--- /dev/null
+++ b/app/services/badges/create_service.rb
@@ -0,0 +1,10 @@
+module Badges
+  class CreateService < Badges::BaseService
+    # returns the created badge
+    def execute(source)
+      badge = Badges::BuildService.new(params).execute(source)
+
+      badge.tap { |b| b.save }
+    end
+  end
+end
diff --git a/app/services/badges/update_service.rb b/app/services/badges/update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7ca84b5df310988ac15b3f7aa15f66c7526a37e6
--- /dev/null
+++ b/app/services/badges/update_service.rb
@@ -0,0 +1,12 @@
+module Badges
+  class UpdateService < Badges::BaseService
+    # returns the updated badge
+    def execute(badge)
+      if params.present?
+        badge.update_attributes(params)
+      end
+
+      badge
+    end
+  end
+end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 6078fe38064dae9ccfed07fd8e58fc9dc0c732d0..ac70a99c2c587257b476c0d80cc5c61e42c97354 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -35,18 +35,27 @@ module Boards
       def filter_params
         set_parent
         set_state
+        set_scope
 
         params
       end
 
       def set_parent
-        params[:project_id] = parent.id
+        if parent.is_a?(Group)
+          params[:group_id] = parent.id
+        else
+          params[:project_id] = parent.id
+        end
       end
 
       def set_state
         params[:state] = list && list.closed? ? 'closed' : 'opened'
       end
 
+      def set_scope
+        params[:include_subgroups] = board.group_board?
+      end
+
       def board_label_ids
         @board_label_ids ||= board.lists.movable.pluck(:label_id)
       end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 797d6df7c1a9cdd7f1badc0b2abb193ec88257e2..3ceab209f3feb1b0b4366c15d6f4e63aa536324d 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -42,7 +42,10 @@ module Boards
           )
         end
 
-        attrs[:move_between_ids] = move_between_ids if move_between_ids
+        if move_between_ids
+          attrs[:move_between_ids] = move_between_ids
+          attrs[:board_group_id] =  board.group&.id
+        end
 
         attrs
       end
@@ -60,8 +63,10 @@ module Boards
         label_ids =
           if moving_to_list.movable?
             moving_from_list.label_id
+          elsif board.group_board?
+            ::Label.on_group_boards(parent.id).pluck(:label_id)
           else
-            Label.on_project_boards(parent.id).pluck(:label_id)
+            ::Label.on_project_boards(parent.id).pluck(:label_id)
           end
 
         Array(label_ids).compact
diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb
index 6d0dd0a9f99559b107e516b322692c0ac5dedcf1..9269b8d26202b7bb6ced46831de1c47346168085 100644
--- a/app/services/boards/list_service.rb
+++ b/app/services/boards/list_service.rb
@@ -2,11 +2,15 @@ module Boards
   class ListService < Boards::BaseService
     def execute
       create_board! if parent.boards.empty?
-      parent.boards
+      boards
     end
 
     private
 
+    def boards
+      parent.boards
+    end
+
     def create_board!
       Boards::CreateService.new(parent, current_user).execute
     end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index 183556a1d6b57a5a6365b970bd199ab34b208f62..02f1c709374b947526b213d664c8ad5b66d8704b 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -12,7 +12,15 @@ module Boards
       private
 
       def available_labels_for(board)
-        LabelsFinder.new(current_user, project_id: parent.id).execute
+        options = { include_ancestor_groups: true }
+
+        if board.group_board?
+          options.merge!(group_id: parent.id, only_group_labels: true)
+        else
+          options[:project_id] = parent.id
+        end
+
+        LabelsFinder.new(current_user, options).execute
       end
 
       def next_position(board)
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index c8b112132b3d3bacd0541c86d87b233993922769..6ce86983287978bd08841619c724285a7f0ff871 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -7,6 +7,7 @@ module Ci
                 Gitlab::Ci::Pipeline::Chain::Validate::Repository,
                 Gitlab::Ci::Pipeline::Chain::Validate::Config,
                 Gitlab::Ci::Pipeline::Chain::Skip,
+                Gitlab::Ci::Pipeline::Chain::Populate,
                 Gitlab::Ci::Pipeline::Chain::Create].freeze
 
     def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block)
@@ -65,7 +66,7 @@ module Ci
       project.pipelines
         .where(ref: pipeline.ref)
         .where.not(id: pipeline.id)
-        .where.not(sha: project.repository.sha_from_ref(pipeline.ref))
+        .where.not(sha: project.commit(pipeline.ref).try(:id))
         .created_or_pending
     end
 
@@ -81,7 +82,7 @@ module Ci
     end
 
     def related_merge_requests
-      MergeRequest.opened.where(source_project: pipeline.project, source_branch: pipeline.ref)
+      pipeline.project.source_of_merge_requests.opened.where(source_branch: pipeline.ref)
     end
   end
 end
diff --git a/app/services/ci/create_pipeline_stages_service.rb b/app/services/ci/create_pipeline_stages_service.rb
deleted file mode 100644
index f2c175adee67d07e9375d8fb969471476c5918fc..0000000000000000000000000000000000000000
--- a/app/services/ci/create_pipeline_stages_service.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module Ci
-  class CreatePipelineStagesService < BaseService
-    def execute(pipeline)
-      pipeline.stage_seeds.each do |seed|
-        seed.user = current_user
-
-        seed.create! do |build|
-          ##
-          # Create the environment before the build starts. This sets its slug and
-          # makes it available as an environment variable
-          #
-          if build.has_environment?
-            environment_name = build.expanded_environment_name
-            project.environments.find_or_create_by(name: environment_name)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/app/services/ci/create_trace_artifact_service.rb b/app/services/ci/create_trace_artifact_service.rb
deleted file mode 100644
index ffde824972c7a820e1e90fdb7552781f68d23860..0000000000000000000000000000000000000000
--- a/app/services/ci/create_trace_artifact_service.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Ci
-  class CreateTraceArtifactService < BaseService
-    def execute(job)
-      return if job.job_artifacts_trace
-
-      job.trace.read do |stream|
-        break unless stream.file?
-
-        clone_file!(stream.path, JobArtifactUploader.workhorse_upload_path) do |clone_path|
-          create_job_trace!(job, clone_path)
-          FileUtils.rm(stream.path)
-        end
-      end
-    end
-
-    private
-
-    def create_job_trace!(job, path)
-      File.open(path) do |stream|
-        job.create_job_artifacts_trace!(
-          project: job.project,
-          file_type: :trace,
-          file: stream)
-      end
-    end
-
-    def clone_file!(src_path, temp_dir)
-      FileUtils.mkdir_p(temp_dir)
-      Dir.mktmpdir('tmp-trace', temp_dir) do |dir_path|
-        temp_path = File.join(dir_path, "job.log")
-        FileUtils.copy(src_path, temp_path)
-        yield(temp_path)
-      end
-    end
-  end
-end
diff --git a/app/services/ci/fetch_kubernetes_token_service.rb b/app/services/ci/fetch_kubernetes_token_service.rb
index e73c6ad678078d3449211257dc5fcd00fda3d775..bca883ec0a05bb0e6a8643f6407fd89a54279147 100644
--- a/app/services/ci/fetch_kubernetes_token_service.rb
+++ b/app/services/ci/fetch_kubernetes_token_service.rb
@@ -32,7 +32,7 @@ module Ci
       kubeclient = build_kubeclient!
 
       kubeclient.get_secrets.as_json
-    rescue KubeException => err
+    rescue Kubeclient::HttpError => err
       raise err unless err.error_code == 404
 
       []
diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb
index a9813d774bb411b40e265242ffa9c3ac0b64a15e..85533a1cbdbe20e03a0acc0ba83a5759c0a1bd10 100644
--- a/app/services/ci/pipeline_trigger_service.rb
+++ b/app/services/ci/pipeline_trigger_service.rb
@@ -16,8 +16,8 @@ module Ci
 
       pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: params[:ref])
         .execute(:trigger, ignore_skip_ci: true) do |pipeline|
-          pipeline.trigger_requests.create!(trigger: trigger)
-          create_pipeline_variables!(pipeline)
+          pipeline.trigger_requests.build(trigger: trigger)
+          pipeline.variables.build(variables)
         end
 
       if pipeline.persisted?
@@ -33,14 +33,10 @@ module Ci
       end
     end
 
-    def create_pipeline_variables!(pipeline)
-      return unless params[:variables]
-
-      variables = params[:variables].map do |key, value|
+    def variables
+      params[:variables].to_h.map do |key, value|
         { key: key, value: value }
       end
-
-      pipeline.variables.create!(variables)
     end
   end
 end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index e09b445636f1ebc89fdc8d7f84edacc8a99c0d6e..0b087ad73da14f836fe3160ea50fabfe23f5b367 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -4,6 +4,9 @@ module Ci
   class RegisterJobService
     attr_reader :runner
 
+    JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
+    JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
+
     Result = Struct.new(:build, :valid?)
 
     def initialize(runner)
@@ -41,7 +44,7 @@ module Ci
             build.run!
             register_success(build)
 
-            return Result.new(build, true)
+            return Result.new(build, true) # rubocop:disable Cop/AvoidReturnFromBlocks
           rescue Ci::Build::MissingDependenciesError
             build.drop!(:missing_dependency_failure)
           end
@@ -104,10 +107,22 @@ module Ci
     end
 
     def register_success(job)
-      job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
+      labels = { shared_runner: runner.shared?,
+                 jobs_running_for_project: jobs_running_for_project(job) }
+
+      job_queue_duration_seconds.observe(labels, Time.now - job.queued_at) unless job.queued_at.nil?
       attempt_counter.increment
     end
 
+    def jobs_running_for_project(job)
+      return '+Inf' unless runner.shared?
+
+      # excluding currently started job
+      running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.shared)
+                              .limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
+      running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
+    end
+
     def failed_attempt_counter
       @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
     end
@@ -117,7 +132,7 @@ module Ci
     end
 
     def job_queue_duration_seconds
-      @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time')
+      @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS)
     end
   end
 end
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index bde090eaeec70a30efe3e04d97e2bc7fb0acb97b..90393e951a48398b69799a9639f279242857f84e 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -12,7 +12,7 @@ module Clusters
         else
           check_timeout
         end
-      rescue KubeException => ke
+      rescue Kubeclient::HttpError => ke
         app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored?
       end
 
diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb
index 8ceeec687cd3d14c5fd825d29a13a93f8f1269b9..4c25a09814bcccc51e0153f4a552e0e426580d70 100644
--- a/app/services/clusters/applications/install_service.rb
+++ b/app/services/clusters/applications/install_service.rb
@@ -10,7 +10,7 @@ module Clusters
 
           ClusterWaitForAppInstallationWorker.perform_in(
             ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
-        rescue KubeException => ke
+        rescue Kubeclient::HttpError => ke
           app.make_errored!("Kubernetes error: #{ke.message}")
         rescue StandardError
           app.make_errored!("Can't start installation process")
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 15ab2d544043b4cfb58ca0798b0026971f4c4e11..84944e955427361eca1f09f71a9a246d8b3fcb74 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -13,7 +13,7 @@ module Clusters
       rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
         provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
       rescue ActiveRecord::RecordInvalid => e
-        provider.make_errored!("Failed to configure GKE Cluster: #{e.message}")
+        provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}")
       end
 
       private
diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb
index f994aacd086564f28154bada29cc4d7b66e41cec..7cc4324677e1b0b04abc6a554c0dea1e029cd612 100644
--- a/app/services/clusters/gcp/verify_provision_status_service.rb
+++ b/app/services/clusters/gcp/verify_provision_status_service.rb
@@ -17,7 +17,7 @@ module Clusters
           when 'DONE'
             finalize_creation
           else
-            return provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
+            provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
           end
         end
       end
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 1db91c3c90ca694408f0e0350b7e60fb276d7ac0..2a69a205629d6ed55038be89853bbfd3f95da660 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -10,9 +10,14 @@ class CompareService
     @start_ref_name = new_start_ref_name
   end
 
-  def execute(target_project, target_ref, straight: false)
+  def execute(target_project, target_ref, base_sha: nil, straight: false)
     raw_compare = target_project.repository.compare_source_branch(target_ref, start_project.repository, start_ref_name, straight: straight)
 
-    Compare.new(raw_compare, target_project, straight: straight) if raw_compare
+    return unless raw_compare
+
+    Compare.new(raw_compare,
+                target_project,
+                base_sha: base_sha,
+                straight: straight)
   end
 end
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index 88dfb7a4a904d258b2ca1b870e3e7e44914c287d..7e5a77fb056727b21d456c5271eb161bcfc95bd1 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -19,8 +19,8 @@ class CreateDeploymentService
 
       environment.fire_state_event(action)
 
-      return unless environment.save
-      return if environment.stopped?
+      break unless environment.save
+      break if environment.stopped?
 
       deploy.tap(&:update_merge_request_metrics!)
     end
diff --git a/app/services/deploy_tokens/create_service.rb b/app/services/deploy_tokens/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..52f545947af3acbda666121f60770a828af63267
--- /dev/null
+++ b/app/services/deploy_tokens/create_service.rb
@@ -0,0 +1,7 @@
+module DeployTokens
+  class CreateService < BaseService
+    def execute
+      @project.deploy_tokens.create(params)
+    end
+  end
+end
diff --git a/app/services/events/render_service.rb b/app/services/events/render_service.rb
index 0b62d8aedf110fc3afbb4ced7c8216dc5dbebe9d..bb72d7685dd1477027216b63a3249cd730f4d179 100644
--- a/app/services/events/render_service.rb
+++ b/app/services/events/render_service.rb
@@ -1,15 +1,17 @@
 module Events
   class RenderService < BaseRenderer
     def execute(events, atom_request: false)
-      events.map(&:note).compact.group_by(&:project).each do |project, notes|
-        render_notes(notes, project, atom_request)
-      end
+      notes = events.map(&:note).compact
+
+      render_notes(notes, atom_request)
     end
 
     private
 
-    def render_notes(notes, project, atom_request)
-      Notes::RenderService.new(current_user).execute(notes, project, render_options(atom_request))
+    def render_notes(notes, atom_request)
+      Notes::RenderService
+        .new(current_user)
+        .execute(notes, render_options(atom_request))
     end
 
     def render_options(atom_request)
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 46acdc5406c2d032a439aefebed158bdc76127bc..a954564946b467d72c3f5dedcbeb303541b22b40 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,11 +1,11 @@
 module Files
   class CreateService < Files::BaseService
     def create_commit!
-      handler = Lfs::FileModificationHandler.new(project, @branch_name)
+      transformer = Lfs::FileTransformer.new(project, @branch_name)
 
-      handler.new_file(@file_path, @file_content) do |content_or_lfs_pointer|
-        create_transformed_commit(content_or_lfs_pointer)
-      end
+      result = transformer.new_file(@file_path, @file_content)
+
+      create_transformed_commit(result.content)
     end
 
     private
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index a03c59f569df3f0a22d6c5535990ada1f98c5f2f..13a1dee41739e81a41c66e149cb34cf4d3ca2c4e 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -3,11 +3,33 @@ module Files
     UPDATE_FILE_ACTIONS = %w(update move delete).freeze
 
     def create_commit!
+      transformer = Lfs::FileTransformer.new(project, @branch_name)
+
+      actions = actions_after_lfs_transformation(transformer, params[:actions])
+
+      commit_actions!(actions)
+    end
+
+    private
+
+    def actions_after_lfs_transformation(transformer, actions)
+      actions.map do |action|
+        if action[:action] == 'create'
+          result = transformer.new_file(action[:file_path], action[:content], encoding: action[:encoding])
+          action[:content] = result.content
+          action[:encoding] = result.encoding
+        end
+
+        action
+      end
+    end
+
+    def commit_actions!(actions)
       repository.multi_action(
         current_user,
         message: @commit_message,
         branch_name: @branch_name,
-        actions: params[:actions],
+        actions: actions,
         author_email: @author_email,
         author_name: @author_name,
         start_project: @start_project,
@@ -17,8 +39,6 @@ module Files
       raise_error(e)
     end
 
-    private
-
     def validate!
       super
 
diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb
index 6442406d77ee99535e117219f661dd11c66221a7..74088b970c922496328337a4bc64ac1096278b51 100644
--- a/app/services/import_export_clean_up_service.rb
+++ b/app/services/import_export_clean_up_service.rb
@@ -10,7 +10,7 @@ class ImportExportCleanUpService
 
   def execute
     Gitlab::Metrics.measure(:import_export_clean_up) do
-      return unless File.directory?(path)
+      next unless File.directory?(path)
 
       clean_up_export_files
     end
diff --git a/app/services/issuable/destroy_service.rb b/app/services/issuable/destroy_service.rb
index 7197a426a726f76542264b2e0e32a044a74e87b2..0b1a33518c6e88499168b1c9baa78fe4e63e4016 100644
--- a/app/services/issuable/destroy_service.rb
+++ b/app/services/issuable/destroy_service.rb
@@ -4,6 +4,7 @@ module Issuable
       TodoService.new.destroy_target(issuable) do |issuable|
         if issuable.destroy
           issuable.update_project_counter_caches
+          issuable.assignees.each(&:invalidate_cache_counts)
         end
       end
     end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e87fd49d1935481ce01921ff2fdd2bce2010cc17..1f67e3ecf9dfda5dd013d28a635917a9d705dfb0 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -51,9 +51,10 @@ class IssuableBaseService < BaseService
     return unless milestone_id
 
     params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
+    group_ids = project.group&.self_and_ancestors&.pluck(:id)
 
     milestone =
-      Milestone.for_projects_and_groups([project.id], [project.group&.id]).find_by_id(milestone_id)
+      Milestone.for_projects_and_groups([project.id], group_ids).find_by_id(milestone_id)
 
     params[:milestone_id] = '' unless milestone
   end
@@ -106,7 +107,11 @@ class IssuableBaseService < BaseService
   end
 
   def available_labels
-    @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
+    @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: true).execute
+  end
+
+  def handle_quick_actions_on_create(issuable)
+    merge_quick_actions_into_params!(issuable)
   end
 
   def merge_quick_actions_into_params!(issuable)
@@ -131,7 +136,7 @@ class IssuableBaseService < BaseService
   end
 
   def create(issuable)
-    merge_quick_actions_into_params!(issuable)
+    handle_quick_actions_on_create(issuable)
     filter_params(issuable)
 
     params.delete(:state_event)
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 0c5cf2c62ade5809f8d39f62c53e7dcf1c4569f4..fee5bc38f7b4bb3fc4313b50aebd76e6be5d42d2 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -23,6 +23,7 @@ module Issues
       end
 
       if project.issues_enabled? && issue.close
+        issue.update(closed_by: current_user)
         event_service.close_issue(issue, current_user)
         create_note(issue, commit) if system_note
         notification_service.close_issue(issue, current_user) if notifications
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index d7aa7e2347ea1dad62541463714dd1fbe462d578..1374f10c586fbcffb9206624b83b9ce8fcd153e7 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -55,9 +55,10 @@ module Issues
       return unless params[:move_between_ids]
 
       after_id, before_id = params.delete(:move_between_ids)
+      board_group_id = params.delete(:board_group_id)
 
-      issue_before = get_issue_if_allowed(issue.project, before_id) if before_id
-      issue_after = get_issue_if_allowed(issue.project, after_id) if after_id
+      issue_before = get_issue_if_allowed(before_id, board_group_id)
+      issue_after = get_issue_if_allowed(after_id, board_group_id)
 
       issue.move_between(issue_before, issue_after)
     end
@@ -84,8 +85,16 @@ module Issues
 
     private
 
-    def get_issue_if_allowed(project, id)
-      issue = project.issues.find(id)
+    def get_issue_if_allowed(id, board_group_id = nil)
+      return unless id
+
+      issue =
+        if board_group_id
+          IssuesFinder.new(current_user, group_id: board_group_id, include_subgroups: true).find_by(id: id)
+        else
+          project.issues.find(id)
+        end
+
       issue if can?(current_user, :update_issue, issue)
     end
 
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
index 775efed48eb5ae9d7913d128a2d1fe35b89b401f..9b7486cf53b1fb716b2fbd35743c6d45693ca517 100644
--- a/app/services/labels/transfer_service.rb
+++ b/app/services/labels/transfer_service.rb
@@ -64,9 +64,14 @@ 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)
+      # use 'labels' relation to get label_link ids only of issues/MRs
+      # in the project being transferred.
+      # IDs are fetched in a separate query because MySQL doesn't
+      # allow referring of 'label_links' table in UPDATE query:
+      # https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/62435068
+      link_ids = labels.pluck('label_links.id')
+
+      LabelLink.where(id: link_ids, label_id: old_label_id)
         .update_all(label_id: new_label_id)
     end
 
diff --git a/app/services/lfs/file_modification_handler.rb b/app/services/lfs/file_modification_handler.rb
deleted file mode 100644
index fe9091a6e5d697d190879b0719ceb8710b6702f5..0000000000000000000000000000000000000000
--- a/app/services/lfs/file_modification_handler.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module Lfs
-  class FileModificationHandler
-    attr_reader :project, :branch_name
-
-    delegate :repository, to: :project
-
-    def initialize(project, branch_name)
-      @project = project
-      @branch_name = branch_name
-    end
-
-    def new_file(file_path, file_content)
-      if project.lfs_enabled? && lfs_file?(file_path)
-        lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
-        lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
-        content = lfs_pointer_file.pointer
-
-        success = yield(content)
-
-        link_lfs_object!(lfs_object) if success
-      else
-        yield(file_content)
-      end
-    end
-
-    private
-
-    def lfs_file?(file_path)
-      repository.attributes_at(branch_name, file_path)['filter'] == 'lfs'
-    end
-
-    def create_lfs_object!(lfs_pointer_file, file_content)
-      LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
-        lfs_object.file = CarrierWaveStringFile.new(file_content)
-      end
-    end
-
-    def link_lfs_object!(lfs_object)
-      project.lfs_objects << lfs_object
-    end
-  end
-end
diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..69281ee3137caab67f0a3d3535c69be62d933854
--- /dev/null
+++ b/app/services/lfs/file_transformer.rb
@@ -0,0 +1,66 @@
+module Lfs
+  # Usage: Calling `new_file` check to see if a file should be in LFS and
+  #        return a transformed result with `content` and `encoding` to commit.
+  #
+  #        For LFS an LfsObject linked to the project is stored and an LFS
+  #        pointer returned. If the file isn't in LFS the untransformed content
+  #        is returned to save in the commit.
+  #
+  # transformer = Lfs::FileTransformer.new(project, @branch_name)
+  # content_or_lfs_pointer = transformer.new_file(file_path, content).content
+  # create_transformed_commit(content_or_lfs_pointer)
+  #
+  class FileTransformer
+    attr_reader :project, :branch_name
+
+    delegate :repository, to: :project
+
+    def initialize(project, branch_name)
+      @project = project
+      @branch_name = branch_name
+    end
+
+    def new_file(file_path, file_content, encoding: nil)
+      if project.lfs_enabled? && lfs_file?(file_path)
+        file_content = Base64.decode64(file_content) if encoding == 'base64'
+        lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
+        lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
+
+        link_lfs_object!(lfs_object)
+
+        Result.new(content: lfs_pointer_file.pointer, encoding: 'text')
+      else
+        Result.new(content: file_content, encoding: encoding)
+      end
+    end
+
+    class Result
+      attr_reader :content, :encoding
+
+      def initialize(content:, encoding:)
+        @content = content
+        @encoding = encoding
+      end
+    end
+
+    private
+
+    def lfs_file?(file_path)
+      cached_attributes.attributes(file_path)['filter'] == 'lfs'
+    end
+
+    def cached_attributes
+      @cached_attributes ||= Gitlab::Git::AttributesAtRefParser.new(repository, branch_name)
+    end
+
+    def create_lfs_object!(lfs_pointer_file, file_content)
+      LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
+        lfs_object.file = CarrierWaveStringFile.new(file_content)
+      end
+    end
+
+    def link_lfs_object!(lfs_object)
+      project.lfs_objects << lfs_object
+    end
+  end
+end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index b141bfd5fbc6e6a1d0f45287f559a92f027d0f22..5b51e1982f1a84208efc524f7a12f6d21ed55608 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -5,12 +5,9 @@ module Members
 
       return member if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
 
-      Member.transaction do
-        unassign_issues_and_merge_requests(member) unless member.invite?
-        member.notification_setting&.destroy
+      member.destroy
 
-        member.destroy
-      end
+      member.user&.invalidate_cache_counts
 
       if member.request? && member.user != current_user
         notification_service.decline_access_request(member)
@@ -37,38 +34,5 @@ module Members
         raise "Unknown member type: #{member}!"
       end
     end
-
-    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)
-
-        # 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
-
-        MergeRequestsFinder.new(current_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)
-
-        # 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
-
-        project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil)
-      end
-
-      member.user.invalidate_cache_counts
-    end
   end
 end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 20a2b50d3de8012b33c990351b14519c4b6db14e..231ab76fde48931c57a5ce85eb365c4dc6c08d72 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -24,6 +24,25 @@ module MergeRequests
 
     private
 
+    def handle_wip_event(merge_request)
+      if wip_event = params.delete(:wip_event)
+        # We update the title that is provided in the params or we use the mr title
+        title = params[:title] || merge_request.title
+        params[:title] = case wip_event
+                         when 'wip' then MergeRequest.wip_title(title)
+                         when 'unwip' then MergeRequest.wipless_title(title)
+                         end
+      end
+    end
+
+    def filter_params(merge_request)
+      super
+
+      unless merge_request.can_allow_maintainer_to_push?(current_user)
+        params.delete(:allow_maintainer_to_push)
+      end
+    end
+
     def merge_request_metrics_service(merge_request)
       MergeRequestMetricsService.new(merge_request.metrics)
     end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 4b186d93772d30c0a01f0a897ac05225cbe03ddf..a98bbdf74ddfb4a7b4ff9e9860ac72311b0ffb7e 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -6,6 +6,7 @@ module MergeRequests
       @params_issue_iid = params.delete(:issue_iid)
 
       self.merge_request = MergeRequest.new(params)
+      merge_request.author = current_user
       merge_request.compare_commits = []
       merge_request.source_project  = find_source_project
       merge_request.target_project  = find_target_project
diff --git a/app/services/merge_requests/conflicts/list_service.rb b/app/services/merge_requests/conflicts/list_service.rb
index ca9a33678e46db25b459cb577e7179a91c38a5f5..72cbc49adb2057ae0ddcd16e14d12f2ab274fca1 100644
--- a/app/services/merge_requests/conflicts/list_service.rb
+++ b/app/services/merge_requests/conflicts/list_service.rb
@@ -17,15 +17,7 @@ module MergeRequests
         return @conflicts_can_be_resolved_in_ui = false unless merge_request.has_complete_diff_refs?
         return @conflicts_can_be_resolved_in_ui = false if merge_request.branch_missing?
 
-        begin
-          # Try to parse each conflict. If the MR's mergeable status hasn't been
-          # updated, ensure that we don't say there are conflicts to resolve
-          # when there are no conflict files.
-          conflicts.files.each(&:lines)
-          @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
-        rescue Gitlab::Git::CommandError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing
-          @conflicts_can_be_resolved_in_ui = false
-        end
+        @conflicts_can_be_resolved_in_ui = conflicts.can_be_resolved_in_ui?
       end
 
       def conflicts
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index a18b1c90765b07d65b39b27ea07884acafc68d65..fe1ac70781e4c33fb112d26ca9409875e363b666 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -34,6 +34,12 @@ module MergeRequests
       super
     end
 
+    # Override from IssuableBaseService
+    def handle_quick_actions_on_create(merge_request)
+      super
+      handle_wip_event(merge_request)
+    end
+
     private
 
     def update_merge_requests_head_pipeline(merge_request)
@@ -65,8 +71,8 @@ module MergeRequests
       params.delete(:source_project_id)
       params.delete(:target_project_id)
 
-      unless can?(current_user, :read_project, @source_project) &&
-          can?(current_user, :read_project, @project)
+      unless can?(current_user, :create_merge_request_from, @source_project) &&
+          can?(current_user, :create_merge_request_in, @project)
 
         raise Gitlab::Access::AccessDeniedError
       end
diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb
index 2945a7fd4e4042f811cc1c1783bbf2b0d5503700..10aa9ae609cdb46d897509224ff6e86085c78edb 100644
--- a/app/services/merge_requests/merge_request_diff_cache_service.rb
+++ b/app/services/merge_requests/merge_request_diff_cache_service.rb
@@ -1,8 +1,17 @@
 module MergeRequests
   class MergeRequestDiffCacheService
-    def execute(merge_request)
+    def execute(merge_request, new_diff)
       # Executing the iteration we cache all the highlighted diff information
       merge_request.diffs.diff_files.to_a
+
+      # Remove cache for all diffs on this MR. Do not use the association on the
+      # model, as that will interfere with other actions happening when
+      # reloading the diff.
+      MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
+        next if merge_request_diff == new_diff
+
+        merge_request_diff.diffs.clear_cache!
+      end
     end
   end
 end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 18c40ce8992a7770e7437729e9e3fccd9df66b6a..1fb1796b56c06c4eb5af74b858825359af85738d 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -21,7 +21,7 @@ module MergeRequests
         comment_mr_branch_presence_changed
       end
 
-      comment_mr_with_commits
+      notify_about_push
       mark_mr_as_wip_from_commits
       execute_mr_web_hooks
 
@@ -141,8 +141,8 @@ module MergeRequests
       end
     end
 
-    # Add comment about pushing new commits to merge requests
-    def comment_mr_with_commits
+    # Add comment about pushing new commits to merge requests and send nofitication emails
+    def notify_about_push
       return unless @commits.present?
 
       merge_requests_for_source_branch.each do |merge_request|
@@ -155,6 +155,8 @@ module MergeRequests
         SystemNoteService.add_commits(merge_request, merge_request.project,
                                       @current_user, new_commits,
                                       existing_commits, @oldrev)
+
+        notification_service.push_to_merge_request(merge_request, @current_user, new_commits: new_commits, existing_commits: existing_commits)
       end
     end
 
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index c153872c87442a4bd89d1ccfb604f8405241e2a5..8a40ad88182422d91ad27457d6d67dafa3c21439 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -98,17 +98,6 @@ module MergeRequests
 
     private
 
-    def handle_wip_event(merge_request)
-      if wip_event = params.delete(:wip_event)
-        # We update the title that is provided in the params or we use the mr title
-        title = params[:title] || merge_request.title
-        params[:title] = case wip_event
-                         when 'wip' then MergeRequest.wip_title(title)
-                         when 'unwip' then MergeRequest.wipless_title(title)
-                         end
-      end
-    end
-
     def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
       SystemNoteService.change_branch(
         issuable, issuable.project, current_user, branch_type,
diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb
index abf25bb778bba88d1dc4ca41e3dd0148d7d9b1fb..77e7b8a5ea79c2813a48e07afc4b0cca1ece7c3f 100644
--- a/app/services/notes/build_service.rb
+++ b/app/services/notes/build_service.rb
@@ -26,14 +26,19 @@ module Notes
       if project
         project.notes.find_discussion(discussion_id)
       else
-        # only PersonalSnippets can have discussions without project association
         discussion = Note.find_discussion(discussion_id)
         noteable = discussion.noteable
 
-        return nil unless noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
+        return nil unless noteable_without_project?(noteable)
 
         discussion
       end
     end
+
+    def noteable_without_project?(noteable)
+      return true if noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
+
+      false
+    end
   end
 end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index 6a10e17248333fab9e6b3805cc38061af735874c..199b8028dbc7ba705454d3f5b2cb01083c16d28a 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -23,9 +23,13 @@ module Notes
     end
 
     def execute_note_hooks
+      return unless @note.project
+
       note_data = hook_data
-      @note.project.execute_hooks(note_data, :note_hooks)
-      @note.project.execute_services(note_data, :note_hooks)
+      hooks_scope = @note.confidential? ? :confidential_note_hooks : :note_hooks
+
+      @note.project.execute_hooks(note_data, hooks_scope)
+      @note.project.execute_services(note_data, hooks_scope)
     end
   end
 end
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index a8d0cc15527694c7cf3bc1b58b4a233e08a8d568..0a33d5f3f3dfb4fae4da12dd568700404251e7e0 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -9,14 +9,12 @@ module Notes
       UPDATE_SERVICES[note.noteable_type]
     end
 
-    def self.supported?(note, current_user)
-      noteable_update_service(note) &&
-        current_user &&
-        current_user.can?(:"update_#{note.to_ability_name}", note.noteable)
+    def self.supported?(note)
+      !!noteable_update_service(note)
     end
 
     def supported?(note)
-      self.class.supported?(note, current_user)
+      self.class.supported?(note)
     end
 
     def extract_commands(note, options = {})
diff --git a/app/services/notes/render_service.rb b/app/services/notes/render_service.rb
index a77e98c2b07717f28dbc21d3eecb561cfdf63945..efc9d6da2aa6f6102dc6969d7986511785a497ca 100644
--- a/app/services/notes/render_service.rb
+++ b/app/services/notes/render_service.rb
@@ -3,19 +3,18 @@ module Notes
     # Renders a collection of Note instances.
     #
     # notes - The notes to render.
-    # project - The project to use for redacting.
-    # user - The user viewing the notes.
-
+    #
     # Possible options:
+    #
     # requested_path - The request path.
     # project_wiki - The project's wiki.
     # ref - The current Git reference.
     # only_path - flag to turn relative paths into absolute ones.
     # xhtml - flag to save the html in XHTML
-    def execute(notes, project, **opts)
-      renderer = Banzai::ObjectRenderer.new(project, current_user, **opts)
-
-      renderer.render(notes, :note)
+    def execute(notes, options = {})
+      Banzai::ObjectRenderer
+        .new(user: current_user, redaction_context: options)
+        .render(notes, :note)
     end
   end
 end
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 6835b14648b1aa3810c03886265c61a053215a54..83e59a649b65d1f1d123b1c8cfc07ac853a0630b 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -54,8 +54,7 @@ module NotificationRecipientService
           users = users.includes(:notification_settings)
         end
 
-        users = Array(users)
-        users.compact!
+        users = Array(users).compact
         recipients.concat(users.map { |u| make_recipient(u, type, reason) })
       end
 
@@ -204,10 +203,11 @@ module NotificationRecipientService
       attr_reader :action
       attr_reader :previous_assignee
       attr_reader :skip_current_user
-      def initialize(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
+      def initialize(target, current_user, action:, custom_action: nil, previous_assignee: nil, skip_current_user: true)
         @target = target
         @current_user = current_user
         @action = action
+        @custom_action = custom_action
         @previous_assignee = previous_assignee
         @skip_current_user = skip_current_user
       end
@@ -237,7 +237,13 @@ module NotificationRecipientService
           add_mentions(current_user, target: target)
 
           # Add the assigned users, if any
-          assignees = custom_action == :new_issue ? target.assignees : target.assignee
+          assignees = case custom_action
+                      when :new_issue
+                        target.assignees
+                      else
+                        target.assignee
+                      end
+
           # We use the `:participating` notification level in order to match existing legacy behavior as captured
           # in existing specs (notification_service_spec.rb ~ line 507)
           add_recipients(assignees, :participating, NotificationReason::ASSIGNED) if assignees
@@ -280,7 +286,7 @@ module NotificationRecipientService
         add_participants(note.author)
         add_mentions(note.author, target: note)
 
-        unless note.for_personal_snippet?
+        if note.for_project_noteable?
           # Merge project watchers
           add_project_watchers
 
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index e07ecda27b55fd6c9bc1a7284e449040ccb16e74..274161df9460890b79c43defa642b69586b4bc1f 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -113,6 +113,16 @@ class NotificationService
     new_resource_email(merge_request, :new_merge_request_email)
   end
 
+  def push_to_merge_request(merge_request, current_user, new_commits: [], existing_commits: [])
+    new_commits = new_commits.map { |c| { short_id: c.short_id, title: c.title } }
+    existing_commits = existing_commits.map { |c| { short_id: c.short_id, title: c.title } }
+    recipients = NotificationRecipientService.build_recipients(merge_request, current_user, action: "push_to")
+
+    recipients.each do |recipient|
+      mailer.send(:push_to_merge_request_email, recipient.user.id, merge_request.id, current_user.id, recipient.reason, new_commits: new_commits, existing_commits: existing_commits).deliver_later
+    end
+  end
+
   # When merge request text is updated, we should send an email to:
   #
   #  * newly mentioned project team members with notification level higher than Participating
@@ -208,9 +218,9 @@ class NotificationService
   def new_access_request(member)
     return true unless member.notifiable?(:subscription)
 
-    recipients = member.source.members.owners_and_masters
+    recipients = member.source.members.active_without_invites_and_requests.owners_and_masters
     if fallback_to_group_owners_masters?(recipients, member)
-      recipients = member.source.group.members.owners_and_masters
+      recipients = member.source.group.members.active_without_invites_and_requests.owners_and_masters
     end
 
     recipients.each { |recipient| deliver_access_request_email(recipient, member) }
@@ -363,6 +373,20 @@ class NotificationService
     end
   end
 
+  def issue_due(issue)
+    recipients = NotificationRecipientService.build_recipients(
+      issue,
+      issue.author,
+      action: 'due',
+      custom_action: :issue_due,
+      skip_current_user: false
+    )
+
+    recipients.each do |recipient|
+      mailer.send(:issue_due_email, recipient.user.id, issue.id, recipient.reason).deliver_later
+    end
+  end
+
   protected
 
   def new_resource_email(target, method)
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index e61ecb696d0a9ba26a5938213c8f2dc053e22eed..346971138b16e2967242f1a43f73c50cfddd0f5f 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -21,7 +21,8 @@ module Projects
     end
 
     def labels(target = nil)
-      labels = LabelsFinder.new(current_user, project_id: project.id).execute.select([:color, :title])
+      labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true)
+        .execute.select([:color, :title])
 
       return labels unless target&.respond_to?(:labels)
 
diff --git a/app/services/projects/base_move_relations_service.rb b/app/services/projects/base_move_relations_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e8fd3ef57e5c4858019503adac2fa5cda62ff2e7
--- /dev/null
+++ b/app/services/projects/base_move_relations_service.rb
@@ -0,0 +1,22 @@
+module Projects
+  class BaseMoveRelationsService < BaseService
+    attr_reader :source_project
+    def execute(source_project, remove_remaining_elements: true)
+      return if source_project.blank?
+
+      @source_project = source_project
+
+      true
+    end
+
+    private
+
+    def prepare_relation(relation, id_param = :id)
+      if Gitlab::Database.postgresql?
+        relation
+      else
+        relation.model.where("#{id_param}": relation.pluck(id_param))
+      end
+    end
+  end
+end
diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb
index a549cfbabeab80e4901773e1725553ee017291e4..29b133cc466def906bd9d8136f6a36ea799e16cc 100644
--- a/app/services/projects/create_from_template_service.rb
+++ b/app/services/projects/create_from_template_service.rb
@@ -8,9 +8,10 @@ module Projects
       template_name = params.delete(:template_name)
       file = Gitlab::ProjectTemplate.find(template_name).file
 
+      override_params = params.dup
       params[:file] = file
 
-      GitlabProjectsImportService.new(current_user, params).execute
+      GitlabProjectsImportService.new(current_user, params, override_params).execute
 
     ensure
       file&.close
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 01838ec6b5ddca1de6a99e5541506ed84071f297..d361d07099385498403cbb93ab14f620aa7c9121 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -85,20 +85,19 @@ module Projects
     end
 
     def after_create_actions
-      log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
+      log_info("#{@project.owner.name} created a new project \"#{@project.full_name}\"")
 
       unless @project.gitlab_project_import?
         @project.write_repository_config
         @project.create_wiki unless skip_wiki?
-        create_services_from_active_templates(@project)
-
-        @project.create_labels
       end
 
       event_service.create_project(@project, current_user)
       system_hook_service.execute_hooks_for(@project, :create)
 
       setup_authorizations
+
+      current_user.invalidate_personal_projects_count
     end
 
     # Refresh the current user's authorizations inline (so they can access the
@@ -121,21 +120,29 @@ module Projects
       Project.transaction do
         @project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
 
-        if @project.save && !@project.import?
-          raise 'Failed to create repository' unless @project.create_repository
+        if @project.save
+          unless @project.gitlab_project_import?
+            create_services_from_active_templates(@project)
+            @project.create_labels
+          end
+
+          unless @project.import?
+            raise 'Failed to create repository' unless @project.create_repository
+          end
         end
       end
     end
 
     def fail(error:)
       message = "Unable to save project. Error: #{error}"
-      message << "Project ID: #{@project.id}" if @project && @project.id
+      log_message = message.dup
 
-      Rails.logger.error(message)
+      log_message << " Project ID: #{@project.id}" if @project&.id
+      Rails.logger.error(log_message)
 
-      if @project && @project.import?
+      if @project
         @project.errors.add(:base, message)
-        @project.mark_import_as_failed(message)
+        @project.mark_import_as_failed(message) if @project.import?
       end
 
       @project
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 81972df9b3cdd4b364b352c9d28ad8093028faf7..44e869851caafb164631fd12f3cc2a9bb598c4a0 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -34,6 +34,8 @@ module Projects
       system_hook_service.execute_hooks_for(project, :destroy)
       log_info("Project \"#{project.full_path}\" was removed")
 
+      current_user.invalidate_personal_projects_count
+
       true
     rescue => error
       attempt_rollback(project, error.message)
@@ -44,6 +46,20 @@ module Projects
       raise
     end
 
+    def attempt_repositories_rollback
+      return unless @project
+
+      flush_caches(@project)
+
+      unless mv_repository(removal_path(repo_path), repo_path)
+        raise_error('Failed to restore project repository. Please contact the administrator.')
+      end
+
+      unless mv_repository(removal_path(wiki_path), wiki_path)
+        raise_error('Failed to restore wiki repository. Please contact the administrator.')
+      end
+    end
+
     private
 
     def repo_path
@@ -68,12 +84,9 @@ module Projects
       # Skip repository removal. We use this flag when remove user or group
       return true if params[:skip_repo] == true
 
-      # There is a possibility project does not have repository or wiki
-      return true unless gitlab_shell.exists?(project.repository_storage_path, path + '.git')
-
       new_path = removal_path(path)
 
-      if gitlab_shell.mv_repository(project.repository_storage_path, path, new_path)
+      if mv_repository(path, new_path)
         log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
 
         project.run_after_commit do
@@ -85,10 +98,21 @@ module Projects
       end
     end
 
+    def mv_repository(from_path, to_path)
+      # There is a possibility project does not have repository or wiki
+      return true unless gitlab_shell.exists?(project.repository_storage_path, from_path + '.git')
+
+      gitlab_shell.mv_repository(project.repository_storage_path, from_path, to_path)
+    end
+
     def attempt_rollback(project, message)
       return unless project
 
-      project.update_attributes(delete_error: message, pending_delete: false)
+      # It's possible that the project was destroyed, but some after_commit
+      # hook failed and caused us to end up here. A destroyed model will be a frozen hash,
+      # which cannot be altered.
+      project.update_attributes(delete_error: message, pending_delete: false) unless project.destroyed?
+
       log_error("Deletion failed on #{project.full_path} with the following message: #{message}")
     end
 
@@ -113,7 +137,7 @@ module Projects
       return true unless Gitlab.config.registry.enabled
 
       ContainerRepository.build_root_repository(project).tap do |repository|
-        return repository.has_tags? ? repository.delete_tags! : true
+        break repository.has_tags? ? repository.delete_tags! : true
       end
     end
 
diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb
index a68ecb4abe1fa99937d9ddb5c91c0b37db456ab8..a16268f4fd2883553a7682c13c77168f4500f7f5 100644
--- a/app/services/projects/gitlab_projects_import_service.rb
+++ b/app/services/projects/gitlab_projects_import_service.rb
@@ -5,8 +5,8 @@ module Projects
   class GitlabProjectsImportService
     attr_reader :current_user, :params
 
-    def initialize(user, params)
-      @current_user, @params = user, params.dup
+    def initialize(user, import_params, override_params = nil)
+      @current_user, @params, @override_params = user, import_params.dup, override_params
     end
 
     def execute
@@ -15,8 +15,18 @@ module Projects
       file = params.delete(:file)
       FileUtils.copy_entry(file.path, import_upload_path)
 
+      @overwrite = params.delete(:overwrite)
+      data = {}
+      data[:override_params] = @override_params if @override_params
+
+      if overwrite_project?
+        data[:original_path] = params[:path]
+        params[:path] += "-#{tmp_filename}"
+      end
+
       params[:import_type] = 'gitlab_project'
       params[:import_source] = import_upload_path
+      params[:import_data] = { data: data } if data.present?
 
       ::Projects::CreateService.new(current_user, params).execute
     end
@@ -30,5 +40,17 @@ module Projects
     def tmp_filename
       SecureRandom.hex
     end
+
+    def overwrite_project?
+      @overwrite && project_with_same_full_path?
+    end
+
+    def project_with_same_full_path?
+      Project.find_by_full_path("#{current_namespace.full_path}/#{params[:path]}").present?
+    end
+
+    def current_namespace
+      @current_namespace ||= Namespace.find_by(id: params[:namespace_id])
+    end
   end
 end
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index fe4e8ea10bfacf46169ab047193d271f3968a1f0..7bf0b90b49178c89c50239fccf59adadab170c7d 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -1,22 +1,36 @@
 module Projects
   module ImportExport
     class ExportService < BaseService
-      def execute(_options = {})
-        @shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.disk_path, 'work'))
-        save_all
+      def execute(after_export_strategy = nil, options = {})
+        @shared = project.import_export_shared
+
+        save_all!
+        execute_after_export_action(after_export_strategy)
       end
 
       private
 
-      def save_all
-        if [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
+      def execute_after_export_action(after_export_strategy)
+        return unless after_export_strategy
+
+        unless after_export_strategy.execute(current_user, project)
+          cleanup_and_notify_error
+        end
+      end
+
+      def save_all!
+        if save_services
           Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
           notify_success
         else
-          cleanup_and_notify
+          cleanup_and_notify_error!
         end
       end
 
+      def save_services
+        [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver].all?(&:save)
+      end
+
       def version_saver
         Gitlab::ImportExport::VersionSaver.new(shared: @shared)
       end
@@ -26,7 +40,7 @@ module Projects
       end
 
       def project_tree_saver
-        Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared)
+        Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared, params: @params)
       end
 
       def uploads_saver
@@ -41,19 +55,26 @@ module Projects
         Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared)
       end
 
-      def cleanup_and_notify
+      def lfs_saver
+        Gitlab::ImportExport::LfsSaver.new(project: project, shared: @shared)
+      end
+
+      def cleanup_and_notify_error
         Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
 
         FileUtils.rm_rf(@shared.export_path)
 
         notify_error
+      end
+
+      def cleanup_and_notify_error!
+        cleanup_and_notify_error
+
         raise Gitlab::ImportExport::Error.new(@shared.errors.join(', '))
       end
 
       def notify_success
         Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported")
-
-        notification_service.project_exported(@project, @current_user)
       end
 
       def notify_error
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index f2d676af5c30d92ee8849126c35cf4dcb181618e..bdd9598f85ac65686f6a38a2b4cb00a6e9848ee0 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -28,7 +28,11 @@ module Projects
 
     def add_repository_to_project
       if project.external_import? && !unknown_url?
-        raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url)
+        begin
+          Gitlab::UrlBlocker.validate!(project.import_url, valid_ports: Project::VALID_IMPORT_PORTS)
+        rescue Gitlab::UrlBlocker::BlockedUrlError => e
+          raise Error, "Blocked import URL: #{e.message}"
+        end
       end
 
       # We should skip the repository for a GitHub import or GitLab project import,
@@ -57,7 +61,7 @@ module Projects
           project.ensure_repository
           project.repository.fetch_as_mirror(project.import_url, refmap: refmap)
         else
-          gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, project.import_url)
+          gitlab_shell.import_repository(project.repository_storage, project.disk_path, project.import_url)
         end
       rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e
         # Expire cache to prevent scenarios such as:
diff --git a/app/services/projects/move_access_service.rb b/app/services/projects/move_access_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3af3a22d486bc70b9b52a58e9dbe442e625f9a23
--- /dev/null
+++ b/app/services/projects/move_access_service.rb
@@ -0,0 +1,25 @@
+module Projects
+  class MoveAccessService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      @project.with_transaction_returning_status do
+        if @project.namespace != source_project.namespace
+          @project.run_after_commit do
+            source_project.namespace.refresh_project_authorizations
+            self.namespace.refresh_project_authorizations
+          end
+        end
+
+        ::Projects::MoveProjectMembersService.new(@project, @current_user)
+          .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+        ::Projects::MoveProjectGroupLinksService.new(@project, @current_user)
+          .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+        ::Projects::MoveProjectAuthorizationsService.new(@project, @current_user)
+          .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+
+        success
+      end
+    end
+  end
+end
diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dde420655b035e0e05178c22c9dca8922bac9259
--- /dev/null
+++ b/app/services/projects/move_deploy_keys_projects_service.rb
@@ -0,0 +1,31 @@
+module Projects
+  class MoveDeployKeysProjectsService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      Project.transaction(requires_new: true) do
+        move_deploy_keys_projects
+        remove_remaining_deploy_keys_projects if remove_remaining_elements
+
+        success
+      end
+    end
+
+    private
+
+    def move_deploy_keys_projects
+      prepare_relation(non_existent_deploy_keys_projects)
+        .update_all(project_id: @project.id)
+    end
+
+    def non_existent_deploy_keys_projects
+      source_project.deploy_keys_projects
+                    .joins(:deploy_key)
+                    .where.not(keys: { fingerprint: @project.deploy_keys.select(:fingerprint) })
+    end
+
+    def remove_remaining_deploy_keys_projects
+      source_project.deploy_keys_projects.destroy_all
+    end
+  end
+end
diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d2901ea1457142ca41703b39a8faf9dcc2aca7b3
--- /dev/null
+++ b/app/services/projects/move_forks_service.rb
@@ -0,0 +1,42 @@
+module Projects
+  class MoveForksService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super && source_project.fork_network
+
+      Project.transaction(requires_new: true) do
+        move_forked_project_links
+        move_fork_network_members
+        update_root_project
+        refresh_forks_count
+
+        success
+      end
+    end
+
+    private
+
+    def move_forked_project_links
+      # Update ancestor
+      ForkedProjectLink.where(forked_to_project: source_project)
+                       .update_all(forked_to_project_id: @project.id)
+
+      # Update the descendants
+      ForkedProjectLink.where(forked_from_project: source_project)
+                       .update_all(forked_from_project_id: @project.id)
+    end
+
+    def move_fork_network_members
+      ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
+      ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
+    end
+
+    def update_root_project
+      # Update root network project
+      ForkNetwork.where(root_project: source_project).update_all(root_project_id: @project.id)
+    end
+
+    def refresh_forks_count
+      Projects::ForksCountService.new(@project).refresh_cache
+    end
+  end
+end
diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..298da5f1a8255b07244a882ec69184c5b20d05c8
--- /dev/null
+++ b/app/services/projects/move_lfs_objects_projects_service.rb
@@ -0,0 +1,29 @@
+module Projects
+  class MoveLfsObjectsProjectsService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      Project.transaction(requires_new: true) do
+        move_lfs_objects_projects
+        remove_remaining_lfs_objects_project if remove_remaining_elements
+
+        success
+      end
+    end
+
+    private
+
+    def move_lfs_objects_projects
+      prepare_relation(non_existent_lfs_objects_projects)
+        .update_all(project_id: @project.lfs_storage_project.id)
+    end
+
+    def remove_remaining_lfs_objects_project
+      source_project.lfs_objects_projects.destroy_all
+    end
+
+    def non_existent_lfs_objects_projects
+      source_project.lfs_objects_projects.where.not(lfs_object: @project.lfs_objects)
+    end
+  end
+end
diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f7be461a5dae6a492a98fad61554d1be7d746b7e
--- /dev/null
+++ b/app/services/projects/move_notification_settings_service.rb
@@ -0,0 +1,38 @@
+module Projects
+  class MoveNotificationSettingsService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      Project.transaction(requires_new: true) do
+        move_notification_settings
+        remove_remaining_notification_settings if remove_remaining_elements
+
+        success
+      end
+    end
+
+    private
+
+    def move_notification_settings
+      prepare_relation(non_existent_notifications)
+        .update_all(source_id: @project.id)
+    end
+
+    # Remove remaining notification settings from source_project
+    def remove_remaining_notification_settings
+      source_project.notification_settings.destroy_all
+    end
+
+    # Get users of current notification_settings
+    def users_in_target_project
+      @project.notification_settings.select(:user_id)
+    end
+
+    # Look for notification_settings in source_project that are not in the target project
+    def non_existent_notifications
+      source_project.notification_settings
+        .select(:id)
+        .where.not(user_id: users_in_target_project)
+    end
+  end
+end
diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5ef12fc49e5f87b9793d0d6b54a52f98f99f2f83
--- /dev/null
+++ b/app/services/projects/move_project_authorizations_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+  class MoveProjectAuthorizationsService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      Project.transaction(requires_new: true) do
+        move_project_authorizations
+
+        remove_remaining_authorizations if remove_remaining_elements
+
+        success
+      end
+    end
+
+    private
+
+    def move_project_authorizations
+      prepare_relation(non_existent_authorization, :user_id)
+        .update_all(project_id: @project.id)
+    end
+
+    def remove_remaining_authorizations
+      # I think because the Project Authorization table does not have a primary key
+      # it brings a lot a problems/bugs. First, Rails raises PG::SyntaxException if we use
+      # destroy_all instead of delete_all.
+      source_project.project_authorizations.delete_all(:delete_all)
+    end
+
+    # Look for authorizations in source_project that are not in the target project
+    def non_existent_authorization
+      source_project.project_authorizations
+                    .select(:user_id)
+                    .where.not(user: @project.authorized_users)
+    end
+  end
+end
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dbeffd7dae9ae7e70d1fb89b8e54a2479119b50a
--- /dev/null
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+  class MoveProjectGroupLinksService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      Project.transaction(requires_new: true) do
+        move_group_links
+        remove_remaining_project_group_links if remove_remaining_elements
+
+        success
+      end
+    end
+
+    private
+
+    def move_group_links
+      prepare_relation(non_existent_group_links)
+        .update_all(project_id: @project.id)
+    end
+
+    # Remove remaining project group links from source_project
+    def remove_remaining_project_group_links
+      source_project.reload.project_group_links.destroy_all
+    end
+
+    def group_links_in_target_project
+      @project.project_group_links.select(:group_id)
+    end
+
+    # Look for groups in source_project that are not in the target project
+    def non_existent_group_links
+      source_project.project_group_links
+                    .where.not(group_id: group_links_in_target_project)
+    end
+  end
+end
diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..22a5f0a3fe62dca988c50f290267d704c6a24c5f
--- /dev/null
+++ b/app/services/projects/move_project_members_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+  class MoveProjectMembersService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      Project.transaction(requires_new: true) do
+        move_project_members
+        remove_remaining_members if remove_remaining_elements
+
+        success
+      end
+    end
+
+    private
+
+    def move_project_members
+      prepare_relation(non_existent_members).update_all(source_id: @project.id)
+    end
+
+    def remove_remaining_members
+      # Remove remaining members and authorizations from source_project
+      source_project.project_members.destroy_all
+    end
+
+    def project_members_in_target_project
+      @project.project_members.select(:user_id)
+    end
+
+    # Look for members in source_project that are not in the target project
+    def non_existent_members
+      source_project.members
+                    .select(:id)
+                    .where.not(user_id: @project.project_members.select(:user_id))
+    end
+  end
+end
diff --git a/app/services/projects/move_users_star_projects_service.rb b/app/services/projects/move_users_star_projects_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..079fd5b968585322698633e908375329b6dce587
--- /dev/null
+++ b/app/services/projects/move_users_star_projects_service.rb
@@ -0,0 +1,20 @@
+module Projects
+  class MoveUsersStarProjectsService < BaseMoveRelationsService
+    def execute(source_project, remove_remaining_elements: true)
+      return unless super
+
+      user_stars = source_project.users_star_projects
+
+      return unless user_stars.any?
+
+      Project.transaction(requires_new: true) do
+        user_stars.update_all(project_id: @project.id)
+
+        Project.reset_counters @project.id, :users_star_projects
+        Project.reset_counters source_project.id, :users_star_projects
+
+        success
+      end
+    end
+  end
+end
diff --git a/app/services/projects/overwrite_project_service.rb b/app/services/projects/overwrite_project_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ce94f147aa941d0f47c2888a33ef79579642078b
--- /dev/null
+++ b/app/services/projects/overwrite_project_service.rb
@@ -0,0 +1,69 @@
+module Projects
+  class OverwriteProjectService < BaseService
+    def execute(source_project)
+      return unless source_project && source_project.namespace == @project.namespace
+
+      Project.transaction do
+        move_before_destroy_relationships(source_project)
+        destroy_old_project(source_project)
+        rename_project(source_project.name, source_project.path)
+
+        @project
+      end
+    # Projects::DestroyService can raise Exceptions, but we don't want
+    # to pass that kind of exception to the caller. Instead, we change it
+    # for a StandardError exception
+    rescue Exception => e # rubocop:disable Lint/RescueException
+      attempt_restore_repositories(source_project)
+
+      if e.class == Exception
+        raise StandardError, e.message
+      else
+        raise
+      end
+    end
+
+    private
+
+    def move_before_destroy_relationships(source_project)
+      options = { remove_remaining_elements: false }
+
+      ::Projects::MoveUsersStarProjectsService.new(@project, @current_user).execute(source_project, options)
+      ::Projects::MoveAccessService.new(@project, @current_user).execute(source_project, options)
+      ::Projects::MoveDeployKeysProjectsService.new(@project, @current_user).execute(source_project, options)
+      ::Projects::MoveNotificationSettingsService.new(@project, @current_user).execute(source_project, options)
+      ::Projects::MoveForksService.new(@project, @current_user).execute(source_project, options)
+      ::Projects::MoveLfsObjectsProjectsService.new(@project, @current_user).execute(source_project, options)
+      add_source_project_to_fork_network(source_project)
+    end
+
+    def destroy_old_project(source_project)
+      # Delete previous project (synchronously) and unlink relations
+      ::Projects::DestroyService.new(source_project, @current_user).execute
+    end
+
+    def rename_project(name, path)
+      # Update de project's name and path to the original name/path
+      ::Projects::UpdateService.new(@project,
+                                    @current_user,
+                                    { name: name, path: path })
+                               .execute
+    end
+
+    def attempt_restore_repositories(project)
+      ::Projects::DestroyService.new(project, @current_user).attempt_repositories_rollback
+    end
+
+    def add_source_project_to_fork_network(source_project)
+      return unless @project.fork_network
+
+      # Because he have moved all references in the fork network from the source_project
+      # we won't be able to query the database (only through its cached data),
+      # for its former relationships. That's why we're adding it to the network
+      # as a fork of the target project
+      ForkNetworkMember.create!(fork_network: @project.fork_network,
+                                project: source_project,
+                                forked_from_project: @project)
+    end
+  end
+end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 26765e5c3f358ce12ab7837258a2334375c3fc9a..5a23f0f0a627362a0a4a104697027add143bcae5 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -24,6 +24,8 @@ module Projects
 
       transfer(project)
 
+      current_user.invalidate_personal_projects_count
+
       true
     rescue Projects::TransferService::TransferError => ex
       project.reload
diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb
index 52ff64cc938918d8cd8f02e03adce3fa2fac74cc..25017c5cbe312f47927b5f498305d3ec0af8f9b9 100644
--- a/app/services/projects/update_pages_configuration_service.rb
+++ b/app/services/projects/update_pages_configuration_service.rb
@@ -18,7 +18,8 @@ module Projects
 
     def pages_config
       {
-        domains: pages_domains_config
+        domains: pages_domains_config,
+        https_only: project.pages_https_only?
       }
     end
 
@@ -27,7 +28,8 @@ module Projects
         {
           domain: domain.domain,
           certificate: domain.certificate,
-          key: domain.key
+          key: domain.key,
+          https_only: project.pages_https_only? && domain.https?
         }
       end
     end
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index c760bd3b6269bf947c0502ea86d04e8d97617766..de77f6bf585bd4b19e91de66601f9f9ee1cc755d 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -1,5 +1,8 @@
 module Projects
   class UpdatePagesService < BaseService
+    InvaildStateError = Class.new(StandardError)
+    FailedToExtractError = Class.new(StandardError)
+
     BLOCK_SIZE = 32.kilobytes
     MAX_SIZE = 1.terabyte
     SITE_PATH = 'public/'.freeze
@@ -11,13 +14,15 @@ module Projects
     end
 
     def execute
+      register_attempt
+
       # Create status notifying the deployment of pages
       @status = create_status
       @status.enqueue!
       @status.run!
 
-      raise 'missing pages artifacts' unless build.artifacts?
-      raise 'pages are outdated' unless latest?
+      raise InvaildStateError, 'missing pages artifacts' unless build.artifacts?
+      raise InvaildStateError, 'pages are outdated' unless latest?
 
       # Create temporary directory in which we will extract the artifacts
       FileUtils.mkdir_p(tmp_path)
@@ -26,32 +31,34 @@ module Projects
 
         # Check if we did extract public directory
         archive_public_path = File.join(archive_path, 'public')
-        raise 'pages miss the public folder' unless Dir.exist?(archive_public_path)
-        raise 'pages are outdated' unless latest?
+        raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
+        raise InvaildStateError, 'pages are outdated' unless latest?
 
         deploy_page!(archive_public_path)
         success
       end
-    rescue => e
-      register_failure
+    rescue InvaildStateError => e
       error(e.message)
-    ensure
-      register_attempt
-      build.erase_artifacts! unless build.has_expiring_artifacts?
+    rescue => e
+      error(e.message, false)
+      raise e
     end
 
     private
 
     def success
       @status.success
+      delete_artifact!
       super
     end
 
-    def error(message, http_status = nil)
+    def error(message, allow_delete_artifact = true)
+      register_failure
       log_error("Projects::UpdatePagesService: #{message}")
       @status.allow_failure = !latest?
       @status.description = message
       @status.drop(:script_failure)
+      delete_artifact! if allow_delete_artifact
       super
     end
 
@@ -67,31 +74,21 @@ module Projects
     end
 
     def extract_archive!(temp_path)
-      if artifacts.ends_with?('.tar.gz') || artifacts.ends_with?('.tgz')
-        extract_tar_archive!(temp_path)
-      elsif artifacts.ends_with?('.zip')
+      if artifacts.ends_with?('.zip')
         extract_zip_archive!(temp_path)
       else
-        raise 'unsupported artifacts format'
+        raise InvaildStateError, 'unsupported artifacts format'
       end
     end
 
-    def extract_tar_archive!(temp_path)
-      results = Open3.pipeline(%W(gunzip -c #{artifacts}),
-                               %W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
-                               %W(tar -x -C #{temp_path} #{SITE_PATH}),
-                               err: '/dev/null')
-      raise 'pages failed to extract' unless results.compact.all?(&:success?)
-    end
-
     def extract_zip_archive!(temp_path)
-      raise 'missing artifacts metadata' unless build.artifacts_metadata?
+      raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata?
 
       # Calculate page size after extract
       public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
 
       if public_entry.total_size > max_size
-        raise "artifacts for pages are too large: #{public_entry.total_size}"
+        raise InvaildStateError, "artifacts for pages are too large: #{public_entry.total_size}"
       end
 
       # Requires UnZip at least 6.00 Info-ZIP.
@@ -99,8 +96,10 @@ module Projects
       # -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 -qq -n #{artifacts} #{site_path} -d #{temp_path}))
-        raise 'pages failed to extract'
+      build.artifacts_file.use_file do |artifacts_path|
+        unless system(*%W(unzip -n #{artifacts_path} #{site_path} -d #{temp_path}))
+          raise FailedToExtractError, 'pages failed to extract'
+        end
       end
     end
 
@@ -163,8 +162,16 @@ module Projects
       build.artifacts_file.path
     end
 
+    def delete_artifact!
+      build.reload # Reload stable object to prevent erase artifacts with old state
+      build.erase_artifacts! unless build.has_expiring_artifacts?
+    end
+
     def latest_sha
       project.commit(build.ref).try(:sha).to_s
+    ensure
+      # Close any file descriptors that were opened and free libgit2 buffers
+      project.cleanup
     end
 
     def sha
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 379a8068023647fc955aaa6cef00a3c7eafa1895..679f4a9cb62bfd111db6b2402f49a6a39476c87d 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -24,6 +24,8 @@ module Projects
           system_hook_service.execute_hooks_for(project, :update)
         end
 
+        update_pages_config if changing_pages_https_only?
+
         success
       else
         model_errors = project.errors.full_messages.to_sentence
@@ -58,7 +60,7 @@ module Projects
     def enabling_wiki?
       return false if @project.wiki_enabled?
 
-      params[:project_feature_attributes][:wiki_access_level].to_i > ProjectFeature::DISABLED
+      params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED
     end
 
     def ensure_wiki_exists
@@ -67,5 +69,13 @@ module Projects
       log_error("Could not create wiki for #{project.full_name}")
       Gitlab::Metrics.counter(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki')
     end
+
+    def update_pages_config
+      Projects::UpdatePagesConfigurationService.new(project).execute
+    end
+
+    def changing_pages_https_only?
+      project.previous_changes.include?(:pages_https_only)
+    end
   end
 end
diff --git a/app/services/prometheus/adapter_service.rb b/app/services/prometheus/adapter_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4504d2ccfe6c15c5a36d4834781967feee8afd15
--- /dev/null
+++ b/app/services/prometheus/adapter_service.rb
@@ -0,0 +1,36 @@
+module Prometheus
+  class AdapterService
+    def initialize(project, deployment_platform = nil)
+      @project = project
+
+      @deployment_platform = if deployment_platform
+                               deployment_platform
+                             else
+                               project.deployment_platform
+                             end
+    end
+
+    attr_reader :deployment_platform, :project
+
+    def prometheus_adapter
+      @prometheus_adapter ||= if service_prometheus_adapter.can_query?
+                                service_prometheus_adapter
+                              else
+                                cluster_prometheus_adapter
+                              end
+    end
+
+    def service_prometheus_adapter
+      project.find_or_initialize_service('prometheus')
+    end
+
+    def cluster_prometheus_adapter
+      return unless deployment_platform.respond_to?(:cluster)
+
+      cluster = deployment_platform.cluster
+      return unless cluster.application_prometheus&.installed?
+
+      cluster.application_prometheus
+    end
+  end
+end
diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb
index 6212fd69077b98de59f3ed6a7c990ffd2d440de1..9d947f73af180847161363efdad50756d7607883 100644
--- a/app/services/protected_branches/create_service.rb
+++ b/app/services/protected_branches/create_service.rb
@@ -1,11 +1,20 @@
 module ProtectedBranches
   class CreateService < BaseService
-    attr_reader :protected_branch
-
     def execute(skip_authorization: false)
-      raise Gitlab::Access::AccessDeniedError unless skip_authorization || can?(current_user, :admin_project, project)
+      raise Gitlab::Access::AccessDeniedError unless skip_authorization || authorized?
+
+      protected_branch.save
+      protected_branch
+    end
+
+    def authorized?
+      can?(current_user, :create_protected_branch, protected_branch)
+    end
+
+    private
 
-      project.protected_branches.create(params)
+    def protected_branch
+      @protected_branch ||= project.protected_branches.new(params)
     end
   end
 end
diff --git a/app/services/protected_branches/destroy_service.rb b/app/services/protected_branches/destroy_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8172c896e76ba91119470f837a36ebf2a0b2b41f
--- /dev/null
+++ b/app/services/protected_branches/destroy_service.rb
@@ -0,0 +1,9 @@
+module ProtectedBranches
+  class DestroyService < BaseService
+    def execute(protected_branch)
+      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_protected_branch, protected_branch)
+
+      protected_branch.destroy
+    end
+  end
+end
diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb
index 4b3337a5c9d940becec71b3ce7879c0125a09381..95e46645374d550aea7a967484c7dbfe4438845b 100644
--- a/app/services/protected_branches/update_service.rb
+++ b/app/services/protected_branches/update_service.rb
@@ -1,7 +1,7 @@
 module ProtectedBranches
   class UpdateService < BaseService
     def execute(protected_branch)
-      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
+      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :update_protected_branch, protected_branch)
 
       protected_branch.update(params)
       protected_branch
diff --git a/app/services/protected_tags/destroy_service.rb b/app/services/protected_tags/destroy_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c868d7ad8e6017769eddcf3ce38388e2f29a80da
--- /dev/null
+++ b/app/services/protected_tags/destroy_service.rb
@@ -0,0 +1,7 @@
+module ProtectedTags
+  class DestroyService < BaseService
+    def execute(protected_tag)
+      protected_tag.destroy
+    end
+  end
+end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 1a0e27d551ae8a3ea696ed7efa6e913cf5e60348..c1bcff34b6f1f7a094715dd71ad4f5b6d7b9022e 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -200,7 +200,7 @@ module QuickActions
     end
     params '~label1 ~"label 2"'
     condition do
-      available_labels = LabelsFinder.new(current_user, project_id: project.id).execute
+      available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute
 
       current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
         available_labels.any?
@@ -367,9 +367,9 @@ module QuickActions
       "#{verb} this #{noun} as Work In Progress."
     end
     condition do
-      issuable.persisted? &&
-        issuable.respond_to?(:work_in_progress?) &&
-        current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
+      issuable.respond_to?(:work_in_progress?) &&
+        # Allow it to mark as WIP on MR creation page _or_ through MR notes.
+        (issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable))
     end
     command :wip do
       @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
@@ -582,7 +582,7 @@ module QuickActions
 
     def find_labels(labels_param)
       extract_references(labels_param, :label) |
-        LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute
+        LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split, include_ancestor_groups: true).execute
     end
 
     def find_label_references(labels_param)
@@ -613,6 +613,7 @@ module QuickActions
 
     def extract_references(arg, type)
       ext = Gitlab::ReferenceExtractor.new(project, current_user)
+
       ext.analyze(arg, author: current_user)
 
       ext.references(type)
diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb
index aa84d36a206f9752a7dd42691665e8bf0870122f..9a88459b5116f59b26361b746623c749a0015414 100644
--- a/app/services/repository_archive_clean_up_service.rb
+++ b/app/services/repository_archive_clean_up_service.rb
@@ -10,7 +10,7 @@ class RepositoryArchiveCleanUpService
 
   def execute
     Gitlab::Metrics.measure(:repository_archive_clean_up) do
-      return unless File.directory?(path)
+      next unless File.directory?(path)
 
       clean_up_old_archives
       clean_up_empty_directories
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 2623f253d98d24cf631582ae0eee04750957afd1..ac029fad7ea195535bc426546fdbacef67b8e4a0 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -14,16 +14,17 @@ class SubmitUsagePingService
   def execute
     return false unless Gitlab::CurrentSettings.usage_ping_enabled?
 
-    response = HTTParty.post(
+    response = Gitlab::HTTP.post(
       URL,
       body: Gitlab::UsageData.to_json(force_refresh: true),
+      allow_local_requests: true,
       headers: { 'Content-type' => 'application/json' }
     )
 
     store_metrics(response)
 
     true
-  rescue HTTParty::Error => e
+  rescue Gitlab::HTTP::Error => e
     Rails.logger.info "Unable to contact GitLab, Inc.: #{e}"
 
     false
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index af8c02a10b7ffd0f6e4817af117619d826c967f3..ba7946fd23ce568a1c19e7a94b17e17255291528 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -20,8 +20,8 @@ class SystemHooksService
   def build_event_data(model, event)
     data = {
       event_name: build_event_name(model, event),
-      created_at: model.created_at.xmlschema,
-      updated_at: model.updated_at.xmlschema
+      created_at: model.created_at&.xmlschema,
+      updated_at: model.updated_at&.xmlschema
     }
 
     case model
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 2253d638e93b3400c2af2b20403e6d8a0f817291..958ef06501262f6ca11015e7898b3ef2fc8bc31e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -159,7 +159,7 @@ module SystemNoteService
     body = if noteable.time_estimate == 0
              "removed time estimate"
            else
-             "changed time estimate to #{parsed_time}"
+             "changed time estimate to #{parsed_time},"
            end
 
     create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
@@ -429,7 +429,7 @@ module SystemNoteService
   def cross_reference(noteable, mentioner, author)
     return if cross_reference_disallowed?(noteable, mentioner)
 
-    gfm_reference = mentioner.gfm_reference(noteable.project)
+    gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
     body = cross_reference_note_content(gfm_reference)
 
     if noteable.is_a?(ExternalIssue)
@@ -582,7 +582,7 @@ module SystemNoteService
       text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}"
       notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize)
     else
-      gfm_reference = mentioner.gfm_reference(noteable.project)
+      gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
       text = cross_reference_note_content(gfm_reference)
       notes.where(note: [text, text.capitalize])
     end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index e9aefb1fb754f4e134b18bbdbf26b27559ce575f..aadc1ea644b3941260dfb223c9ae983cb9a9b1cc 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -19,7 +19,7 @@ module TestHooks
       error_message = catch(:validation_error) do
         sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend
 
-        return hook.execute(sample_data, trigger_key)
+        return hook.execute(sample_data, trigger_key) # rubocop:disable Cop/AvoidReturnFromBlocks
       end
 
       error(error_message)
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index c2ca404b179e1b95926f0b7c0f4ac3ad15435311..ffd48e842c2c758a63dfd7be77ad5d342328c1ff 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -241,8 +241,7 @@ class TodoService
   end
 
   def handle_note(note, author, skip_users = [])
-    # Skip system notes, and notes on project snippet
-    return if note.system? || note.for_snippet?
+    return unless note.can_create_todo?
 
     project = note.project
     target  = note.noteable
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index b71002433d6701f352403c406d39b7e85b2a8705..06b604dad4de44e88139fa82ebcd549adf3393a4 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -49,6 +49,8 @@ module Users
         ::Projects::DestroyService.new(project, current_user, skip_repo: project.legacy_storage?).execute
       end
 
+      yield(user) if block_given?
+
       MigrateToGhostUserService.new(user).execute unless options[:hard_delete]
 
       # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
diff --git a/app/services/verify_pages_domain_service.rb b/app/services/verify_pages_domain_service.rb
index 86166047302d045d2f48c96485de7410689adc29..13cb53dee01e48082d93783ac50644322f70f577 100644
--- a/app/services/verify_pages_domain_service.rb
+++ b/app/services/verify_pages_domain_service.rb
@@ -34,7 +34,8 @@ class VerifyPagesDomainService < BaseService
     # Prevent any pre-existing grace period from being truncated
     reverify = [domain.enabled_until, VERIFICATION_PERIOD.from_now].compact.max
 
-    domain.update!(verified_at: Time.now, enabled_until: reverify)
+    domain.assign_attributes(verified_at: Time.now, enabled_until: reverify)
+    domain.save!(validate: false)
 
     if was_disabled
       notify(:enabled)
@@ -47,7 +48,9 @@ class VerifyPagesDomainService < BaseService
 
   def unverify_domain!
     if domain.verified?
-      domain.update!(verified_at: nil)
+      domain.assign_attributes(verified_at: nil)
+      domain.save!(validate: false)
+
       notify(:verification_failed)
     end
 
@@ -55,7 +58,8 @@ class VerifyPagesDomainService < BaseService
   end
 
   def disable_domain!
-    domain.update!(verified_at: nil, enabled_until: nil)
+    domain.assign_attributes(verified_at: nil, enabled_until: nil)
+    domain.save!(validate: false)
 
     notify(:disabled)
 
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 36e589d5aa87e9badbda088685737401927cc488..809ce1303d801912f3bb405af5d3e35496fdac8b 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -3,23 +3,20 @@ class WebHookService
     attr_reader :body, :headers, :code
 
     def initialize
-      @headers = HTTParty::Response::Headers.new({})
+      @headers = Gitlab::HTTP::Response::Headers.new({})
       @body = ''
       @code = 'internal error'
     end
   end
 
-  include HTTParty
-
-  # HTTParty timeout
-  default_timeout Gitlab.config.gitlab.webhook_timeout
-
-  attr_accessor :hook, :data, :hook_name
+  attr_accessor :hook, :data, :hook_name, :request_options
 
   def initialize(hook, data, hook_name)
     @hook = hook
     @data = data
     @hook_name = hook_name.to_s
+    @request_options = { timeout: Gitlab.config.gitlab.webhook_timeout }
+    @request_options.merge!(allow_local_requests: true) if @hook.is_a?(SystemHook)
   end
 
   def execute
@@ -73,11 +70,12 @@ class WebHookService
   end
 
   def make_request(url, basic_auth = false)
-    self.class.post(url,
+    Gitlab::HTTP.post(url,
       body: data.to_json,
       headers: build_headers(hook_name),
       verify: hook.enable_ssl_verification,
-      basic_auth: basic_auth)
+      basic_auth: basic_auth,
+      **request_options)
   end
 
   def make_request_with_auth
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index 4930fb2fca72185e7eb2817950e599c301b1134b..cd819dc9bff660a84564ac42c1f7d61cf25edc91 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -1,8 +1,8 @@
 class AttachmentUploader < GitlabUploader
-  include UploaderHelper
   include RecordsUploads::Concern
-
-  storage :file
+  include ObjectStorage::Concern
+  prepend ObjectStorage::Extension::RecordsUploads
+  include UploaderHelper
 
   private
 
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 5c8e1cea62e03fd20943772785727438bc6755b4..5848e6c6994eb0bf65c14307f233743ef2b4e7c8 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -1,18 +1,18 @@
 class AvatarUploader < GitlabUploader
   include UploaderHelper
   include RecordsUploads::Concern
-
-  storage :file
+  include ObjectStorage::Concern
+  prepend ObjectStorage::Extension::RecordsUploads
 
   def exists?
     model.avatar.file && model.avatar.file.present?
   end
 
-  def move_to_cache
+  def move_to_store
     false
   end
 
-  def move_to_store
+  def move_to_cache
     false
   end
 
diff --git a/app/uploaders/file_mover.rb b/app/uploaders/file_mover.rb
index 8f56f09c9f72cde5c1ac678d7df8c28fea1f5633..bd7736ad74e787a2650e98687608371127938668 100644
--- a/app/uploaders/file_mover.rb
+++ b/app/uploaders/file_mover.rb
@@ -10,7 +10,11 @@ class FileMover
 
   def execute
     move
-    uploader.record_upload if update_markdown
+
+    if update_markdown
+      uploader.record_upload
+      uploader.schedule_background_upload
+    end
   end
 
   private
@@ -24,11 +28,8 @@ class FileMover
     updated_text = model.read_attribute(update_field)
                         .gsub(temp_file_uploader.markdown_link, uploader.markdown_link)
     model.update_attribute(update_field, updated_text)
-
-    true
   rescue
     revert
-
     false
   end
 
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index bde1161dfa8cfa0934183cb61734028307111ca8..133fdf6684d32df27363e6762306b5404fe59230 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -9,14 +9,18 @@
 class FileUploader < GitlabUploader
   include UploaderHelper
   include RecordsUploads::Concern
+  include ObjectStorage::Concern
+  prepend ObjectStorage::Extension::RecordsUploads
 
   MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
   DYNAMIC_PATH_PATTERN = %r{(?<secret>\h{32})/(?<identifier>.*)}
 
-  storage :file
-
   after :remove, :prune_store_dir
 
+  # FileUploader do not run in a model transaction, so we can simply
+  # enqueue a job after the :store hook.
+  after :store, :schedule_background_upload
+
   def self.root
     File.join(options.storage_path, 'uploads')
   end
@@ -28,8 +32,11 @@ class FileUploader < GitlabUploader
     )
   end
 
-  def self.base_dir(model)
-    model_path_segment(model)
+  def self.base_dir(model, store = Store::LOCAL)
+    decorated_model = model
+    decorated_model = Storage::HashedProject.new(model) if store == Store::REMOTE
+
+    model_path_segment(decorated_model)
   end
 
   # used in migrations and import/exports
@@ -47,21 +54,24 @@ class FileUploader < GitlabUploader
   #
   # Returns a String without a trailing slash
   def self.model_path_segment(model)
-    if model.hashed_storage?(:attachments)
-      model.disk_path
+    case model
+    when Storage::HashedProject then model.disk_path
     else
-      model.full_path
+      model.hashed_storage?(:attachments) ? model.disk_path : model.full_path
     end
   end
 
-  def self.upload_path(secret, identifier)
-    File.join(secret, identifier)
-  end
-
   def self.generate_secret
     SecureRandom.hex
   end
 
+  def upload_paths(filename)
+    [
+      File.join(secret, filename),
+      File.join(base_dir(Store::REMOTE), secret, filename)
+    ]
+  end
+
   attr_accessor :model
 
   def initialize(model, mounted_as = nil, **uploader_context)
@@ -71,8 +81,10 @@ class FileUploader < GitlabUploader
     apply_context!(uploader_context)
   end
 
-  def base_dir
-    self.class.base_dir(@model)
+  # enforce the usage of Hashed storage when storing to
+  # remote store as the FileMover doesn't support OS
+  def base_dir(store = nil)
+    self.class.base_dir(@model, store || object_store)
   end
 
   # we don't need to know the actual path, an uploader instance should be
@@ -82,15 +94,19 @@ class FileUploader < GitlabUploader
   end
 
   def upload_path
-    self.class.upload_path(dynamic_segment, identifier)
-  end
-
-  def model_path_segment
-    self.class.model_path_segment(@model)
+    if file_storage?
+      # Legacy path relative to project.full_path
+      File.join(dynamic_segment, identifier)
+    else
+      File.join(store_dir, identifier)
+    end
   end
 
-  def store_dir
-    File.join(base_dir, dynamic_segment)
+  def store_dirs
+    {
+      Store::LOCAL => File.join(base_dir, dynamic_segment),
+      Store::REMOTE => File.join(base_dir(ObjectStorage::Store::REMOTE), dynamic_segment)
+    }
   end
 
   def markdown_link
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 010100f2da1c3b31531d76ff643b5271a133291e..f12f0466a1d0f371505c33d6a93664b2bb40e9a7 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -37,12 +37,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
     cache_storage.is_a?(CarrierWave::Storage::File)
   end
 
-  # Reduce disk IO
   def move_to_cache
     file_storage?
   end
 
-  # Reduce disk IO
   def move_to_store
     file_storage?
   end
@@ -51,10 +49,6 @@ class GitlabUploader < CarrierWave::Uploader::Base
     file.present?
   end
 
-  def store_dir
-    File.join(base_dir, dynamic_segment)
-  end
-
   def cache_dir
     File.join(root, base_dir, 'tmp/cache')
   end
@@ -76,6 +70,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
   # Designed to be overridden by child uploaders that have a dynamic path
   # segment -- that is, a path that changes based on mutable attributes of its
   # associated model
+  #
+  # For example, `FileUploader` builds the storage path based on the associated
+  # project model's `path_with_namespace` value, which can change when the
+  # project or its containing namespace is moved or renamed.
   def dynamic_segment
     raise(NotImplementedError)
   end
diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb
index ad5385f45a45c149bafb83037669601bd467e1b0..2a5a830ce4f653a212beae3000e8432f2f82fc1c 100644
--- a/app/uploaders/job_artifact_uploader.rb
+++ b/app/uploaders/job_artifact_uploader.rb
@@ -1,12 +1,15 @@
 class JobArtifactUploader < GitlabUploader
   extend Workhorse::UploadPath
+  include ObjectStorage::Concern
+
+  ObjectNotReadyError = Class.new(StandardError)
 
   storage_options Gitlab.config.artifacts
 
-  def size
-    return super if model.size.nil?
+  def cached_size
+    return model.size if model.size.present? && !model.file_changed?
 
-    model.size
+    size
   end
 
   def store_dir
@@ -14,14 +17,18 @@ class JobArtifactUploader < GitlabUploader
   end
 
   def open
-    raise 'Only File System is supported' unless file_storage?
-
-    File.open(path, "rb") if path
+    if file_storage?
+      File.open(path, "rb") if path
+    else
+      ::Gitlab::Ci::Trace::HttpIO.new(url, cached_size) if url
+    end
   end
 
   private
 
   def dynamic_segment
+    raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id
+
     creation_date = model.created_at.utc.strftime('%Y_%m_%d')
 
     File.join(disk_hash[0..1], disk_hash[2..3], disk_hash,
diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb
index 28c458d3ff17cbcbc16fb2ccbd19db773344a7b6..efb7893d153967eaa66890d0287cd7e6ab1de641 100644
--- a/app/uploaders/legacy_artifact_uploader.rb
+++ b/app/uploaders/legacy_artifact_uploader.rb
@@ -1,5 +1,8 @@
 class LegacyArtifactUploader < GitlabUploader
   extend Workhorse::UploadPath
+  include ObjectStorage::Concern
+
+  ObjectNotReadyError = Class.new(StandardError)
 
   storage_options Gitlab.config.artifacts
 
@@ -10,6 +13,8 @@ class LegacyArtifactUploader < GitlabUploader
   private
 
   def dynamic_segment
+    raise ObjectNotReadyError, 'Build is not ready' unless model.id
+
     File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s)
   end
 end
diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb
index e04c97ce1791c74f162c5c26dff82308afa1c3ed..eb521a22ebc3b52bc60ac5d861e2c843e09c096d 100644
--- a/app/uploaders/lfs_object_uploader.rb
+++ b/app/uploaders/lfs_object_uploader.rb
@@ -1,10 +1,6 @@
 class LfsObjectUploader < GitlabUploader
   extend Workhorse::UploadPath
-
-  # LfsObject are in `tmp/upload` instead of `tmp/uploads`
-  def self.workhorse_upload_path
-    File.join(root, 'tmp/upload')
-  end
+  include ObjectStorage::Concern
 
   storage_options Gitlab.config.lfs
 
diff --git a/app/uploaders/namespace_file_uploader.rb b/app/uploaders/namespace_file_uploader.rb
index 993e85fbc1325388a0fe54ce040704451f2289a9..1085ecb1700a4741e417debdb5e13608cc98ae91 100644
--- a/app/uploaders/namespace_file_uploader.rb
+++ b/app/uploaders/namespace_file_uploader.rb
@@ -4,7 +4,7 @@ class NamespaceFileUploader < FileUploader
     options.storage_path
   end
 
-  def self.base_dir(model)
+  def self.base_dir(model, _store = nil)
     File.join(options.base_dir, 'namespace', model_path_segment(model))
   end
 
@@ -14,6 +14,13 @@ class NamespaceFileUploader < FileUploader
 
   # Re-Override
   def store_dir
-    File.join(base_dir, dynamic_segment)
+    store_dirs[object_store]
+  end
+
+  def store_dirs
+    {
+      Store::LOCAL => File.join(base_dir, dynamic_segment),
+      Store::REMOTE => File.join('namespace', self.class.model_path_segment(model), dynamic_segment)
+    }
   end
 end
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a3549cada95f6046053c7522237ad977ce4cbbf3
--- /dev/null
+++ b/app/uploaders/object_storage.rb
@@ -0,0 +1,434 @@
+require 'fog/aws'
+require 'carrierwave/storage/fog'
+
+#
+# This concern should add object storage support
+# to the GitlabUploader class
+#
+module ObjectStorage
+  RemoteStoreError = Class.new(StandardError)
+  UnknownStoreError = Class.new(StandardError)
+  ObjectStorageUnavailable = Class.new(StandardError)
+
+  DIRECT_UPLOAD_TIMEOUT = 4.hours
+  TMP_UPLOAD_PATH = 'tmp/upload'.freeze
+
+  module Store
+    LOCAL = 1
+    REMOTE = 2
+  end
+
+  module Extension
+    # this extension is the glue between the ObjectStorage::Concern and RecordsUploads::Concern
+    module RecordsUploads
+      extend ActiveSupport::Concern
+
+      def prepended(base)
+        raise "#{base} must include ObjectStorage::Concern to use extensions." unless base < Concern
+
+        base.include(RecordsUploads::Concern)
+      end
+
+      def retrieve_from_store!(identifier)
+        paths = store_dirs.map { |store, path| File.join(path, identifier) }
+
+        unless current_upload_satisfies?(paths, model)
+          # the upload we already have isn't right, find the correct one
+          self.upload = uploads.find_by(model: model, path: paths)
+        end
+
+        super
+      end
+
+      def build_upload
+        super.tap do |upload|
+          upload.store = object_store
+        end
+      end
+
+      def upload=(upload)
+        return unless upload
+
+        self.object_store = upload.store
+        super
+      end
+
+      def schedule_background_upload(*args)
+        return unless schedule_background_upload?
+        return unless upload
+
+        ObjectStorage::BackgroundMoveWorker.perform_async(self.class.name,
+                                                upload.class.to_s,
+                                                mounted_as,
+                                                upload.id)
+      end
+
+      private
+
+      def current_upload_satisfies?(paths, model)
+        return false unless upload
+        return false unless model
+
+        paths.include?(upload.path) &&
+          upload.model_id == model.id &&
+          upload.model_type == model.class.base_class.sti_name
+      end
+    end
+  end
+
+  # Add support for automatic background uploading after the file is stored.
+  #
+  module BackgroundMove
+    extend ActiveSupport::Concern
+
+    def background_upload(mount_points = [])
+      return unless mount_points.any?
+
+      run_after_commit do
+        mount_points.each { |mount| send(mount).schedule_background_upload } # rubocop:disable GitlabSecurity/PublicSend
+      end
+    end
+
+    def changed_mounts
+      self.class.uploaders.select do |mount, uploader_class|
+        mounted_as = uploader_class.serialization_column(self.class, mount)
+        uploader = send(:"#{mounted_as}") # rubocop:disable GitlabSecurity/PublicSend
+
+        next unless uploader
+        next unless uploader.exists?
+        next unless send(:"#{mounted_as}_changed?") # rubocop:disable GitlabSecurity/PublicSend
+
+        mount
+      end.keys
+    end
+
+    included do
+      after_save on: [:create, :update] do
+        background_upload(changed_mounts)
+      end
+    end
+  end
+
+  module Concern
+    extend ActiveSupport::Concern
+
+    included do |base|
+      base.include(ObjectStorage)
+
+      after :migrate, :delete_migrated_file
+    end
+
+    class_methods do
+      def object_store_options
+        options.object_store
+      end
+
+      def object_store_enabled?
+        object_store_options.enabled
+      end
+
+      def direct_upload_enabled?
+        object_store_options&.direct_upload
+      end
+
+      def background_upload_enabled?
+        object_store_options.background_upload
+      end
+
+      def proxy_download_enabled?
+        object_store_options.proxy_download
+      end
+
+      def direct_download_enabled?
+        !proxy_download_enabled?
+      end
+
+      def object_store_credentials
+        object_store_options.connection.to_hash.deep_symbolize_keys
+      end
+
+      def remote_store_path
+        object_store_options.remote_directory
+      end
+
+      def serialization_column(model_class, mount_point)
+        model_class.uploader_options.dig(mount_point, :mount_on) || mount_point
+      end
+
+      def workhorse_authorize
+        {
+          RemoteObject: workhorse_remote_upload_options,
+          TempPath: workhorse_local_upload_path
+        }.compact
+      end
+
+      def workhorse_local_upload_path
+        File.join(self.root, TMP_UPLOAD_PATH)
+      end
+
+      def workhorse_remote_upload_options
+        return unless self.object_store_enabled?
+        return unless self.direct_upload_enabled?
+
+        id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-')
+        upload_path = File.join(TMP_UPLOAD_PATH, id)
+        connection = ::Fog::Storage.new(self.object_store_credentials)
+        expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT
+        options = { 'Content-Type' => 'application/octet-stream' }
+
+        {
+          ID: id,
+          GetURL: connection.get_object_url(remote_store_path, upload_path, expire_at),
+          DeleteURL: connection.delete_object_url(remote_store_path, upload_path, expire_at),
+          StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options)
+        }
+      end
+    end
+
+    # allow to configure and overwrite the filename
+    def filename
+      @filename || super || file&.filename # rubocop:disable Gitlab/ModuleWithInstanceVariables
+    end
+
+    def filename=(filename)
+      @filename = filename # rubocop:disable Gitlab/ModuleWithInstanceVariables
+    end
+
+    def file_storage?
+      storage.is_a?(CarrierWave::Storage::File)
+    end
+
+    def file_cache_storage?
+      cache_storage.is_a?(CarrierWave::Storage::File)
+    end
+
+    def object_store
+      # We use Store::LOCAL as null value indicates the local storage
+      @object_store ||= model.try(store_serialization_column) || Store::LOCAL
+    end
+
+    # rubocop:disable Gitlab/ModuleWithInstanceVariables
+    def object_store=(value)
+      @object_store = value || Store::LOCAL
+      @storage = storage_for(object_store)
+    end
+    # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+    # Return true if the current file is part or the model (i.e. is mounted in the model)
+    #
+    def persist_object_store?
+      model.respond_to?(:"#{store_serialization_column}=")
+    end
+
+    # Save the current @object_store to the model <mounted_as>_store column
+    def persist_object_store!
+      return unless persist_object_store?
+
+      updated = model.update_column(store_serialization_column, object_store)
+      raise 'Failed to update object store' unless updated
+    end
+
+    def use_file(&blk)
+      with_exclusive_lease do
+        unsafe_use_file(&blk)
+      end
+    end
+
+    #
+    # Move the file to another store
+    #
+    #   new_store: Enum (Store::LOCAL, Store::REMOTE)
+    #
+    def migrate!(new_store)
+      with_exclusive_lease do
+        unsafe_migrate!(new_store)
+      end
+    end
+
+    def schedule_background_upload(*args)
+      return unless schedule_background_upload?
+
+      ObjectStorage::BackgroundMoveWorker.perform_async(self.class.name,
+                                                          model.class.name,
+                                                          mounted_as,
+                                                          model.id)
+    end
+
+    def fog_directory
+      self.class.remote_store_path
+    end
+
+    def fog_credentials
+      self.class.object_store_credentials
+    end
+
+    def fog_public
+      false
+    end
+
+    def delete_migrated_file(migrated_file)
+      migrated_file.delete if exists?
+    end
+
+    def exists?
+      file.present?
+    end
+
+    def store_dir(store = nil)
+      store_dirs[store || object_store]
+    end
+
+    def store_dirs
+      {
+        Store::LOCAL => File.join(base_dir, dynamic_segment),
+        Store::REMOTE => File.join(dynamic_segment)
+      }
+    end
+
+    def cache!(new_file = sanitized_file)
+      # We intercept ::UploadedFile which might be stored on remote storage
+      # We use that for "accelerated" uploads, where we store result on remote storage
+      if new_file.is_a?(::UploadedFile) && new_file.remote_id
+        return cache_remote_file!(new_file.remote_id, new_file.original_filename)
+      end
+
+      super
+    end
+
+    def store!(new_file = nil)
+      # when direct upload is enabled, always store on remote storage
+      if self.class.object_store_enabled? && self.class.direct_upload_enabled?
+        self.object_store = Store::REMOTE
+      end
+
+      super
+    end
+
+    private
+
+    def schedule_background_upload?
+      self.class.object_store_enabled? &&
+        self.class.background_upload_enabled? &&
+        self.file_storage?
+    end
+
+    def cache_remote_file!(remote_object_id, original_filename)
+      file_path = File.join(TMP_UPLOAD_PATH, remote_object_id)
+      file_path = Pathname.new(file_path).cleanpath.to_s
+      raise RemoteStoreError, 'Bad file path' unless file_path.start_with?(TMP_UPLOAD_PATH + '/')
+
+      # TODO:
+      # This should be changed to make use of `tmp/cache` mechanism
+      # instead of using custom upload directory,
+      # using tmp/cache makes this implementation way easier than it is today
+      CarrierWave::Storage::Fog::File.new(self, storage_for(Store::REMOTE), file_path).tap do |file|
+        raise RemoteStoreError, 'Missing file' unless file.exists?
+
+        # Remote stored file, we force to store on remote storage
+        self.object_store = Store::REMOTE
+
+        # TODO:
+        # We store file internally and force it to be considered as `cached`
+        # This makes CarrierWave to store file in permament location (copy/delete)
+        # once this object is saved, but not sooner
+        @cache_id = "force-to-use-cache" # rubocop:disable Gitlab/ModuleWithInstanceVariables
+        @file = file # rubocop:disable Gitlab/ModuleWithInstanceVariables
+        @filename = original_filename # rubocop:disable Gitlab/ModuleWithInstanceVariables
+      end
+    end
+
+    # this is a hack around CarrierWave. The #migrate method needs to be
+    # able to force the current file to the migrated file upon success.
+    def file=(file)
+      @file = file # rubocop:disable Gitlab/ModuleWithInstanceVariables
+    end
+
+    def serialization_column
+      self.class.serialization_column(model.class, mounted_as)
+    end
+
+    # Returns the column where the 'store' is saved
+    #   defaults to 'store'
+    def store_serialization_column
+      [serialization_column, 'store'].compact.join('_').to_sym
+    end
+
+    def storage
+      @storage ||= storage_for(object_store)
+    end
+
+    def storage_for(store)
+      case store
+      when Store::REMOTE
+        raise 'Object Storage is not enabled' unless self.class.object_store_enabled?
+
+        CarrierWave::Storage::Fog.new(self)
+      when Store::LOCAL
+        CarrierWave::Storage::File.new(self)
+      else
+        raise UnknownStoreError
+      end
+    end
+
+    def exclusive_lease_key
+      "object_storage_migrate:#{model.class}:#{model.id}"
+    end
+
+    def with_exclusive_lease
+      uuid = Gitlab::ExclusiveLease.new(exclusive_lease_key, timeout: 1.hour.to_i).try_obtain
+      raise 'exclusive lease already taken' unless uuid
+
+      yield uuid
+    ensure
+      Gitlab::ExclusiveLease.cancel(exclusive_lease_key, uuid)
+    end
+
+    #
+    # Move the file to another store
+    #
+    #   new_store: Enum (Store::LOCAL, Store::REMOTE)
+    #
+    def unsafe_migrate!(new_store)
+      return unless object_store != new_store
+      return unless file
+
+      new_file = nil
+      file_to_delete = file
+      from_object_store = object_store
+      self.object_store = new_store # changes the storage and file
+
+      cache_stored_file! if file_storage?
+
+      with_callbacks(:migrate, file_to_delete) do
+        with_callbacks(:store, file_to_delete) do # for #store_versions!
+          new_file = storage.store!(file)
+          persist_object_store!
+          self.file = new_file
+        end
+      end
+
+      file
+    rescue => e
+      # in case of failure delete new file
+      new_file.delete unless new_file.nil?
+      # revert back to the old file
+      self.object_store = from_object_store
+      self.file = file_to_delete
+      raise e
+    end
+  end
+
+  def unsafe_use_file
+    if file_storage?
+      return yield path
+    end
+
+    begin
+      cache_stored_file!
+      yield cache_path
+    ensure
+      FileUtils.rm_f(cache_path)
+      cache_storage.delete_dir!(cache_path(nil))
+    end
+  end
+end
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index f2ad0badd53d8f9e4acb305dbd81e71258e1624c..e3898b07730e999b2f163bfed8d34e9d2c221d14 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -4,7 +4,7 @@ class PersonalFileUploader < FileUploader
     options.storage_path
   end
 
-  def self.base_dir(model)
+  def self.base_dir(model, _store = nil)
     File.join(options.base_dir, model_path_segment(model))
   end
 
@@ -14,6 +14,12 @@ class PersonalFileUploader < FileUploader
     File.join(model.class.to_s.underscore, model.id.to_s)
   end
 
+  def object_store
+    return Store::LOCAL unless model
+
+    super
+  end
+
   # model_path_segment does not require a model to be passed, so we can always
   # generate a path, even when there's no model.
   def model_valid?
@@ -22,7 +28,14 @@ class PersonalFileUploader < FileUploader
 
   # Revert-Override
   def store_dir
-    File.join(base_dir, dynamic_segment)
+    store_dirs[object_store]
+  end
+
+  def store_dirs
+    {
+      Store::LOCAL => File.join(base_dir, dynamic_segment),
+      Store::REMOTE => File.join(self.class.model_path_segment(model), dynamic_segment)
+    }
   end
 
   private
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 458928bc0672a0c40f5cbb0984526afa38f1de67..89c74a7883567ce72a1106457c4655d0815a9a79 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -24,8 +24,7 @@ module RecordsUploads
         uploads.where(path: upload_path).delete_all
         upload.destroy! if upload
 
-        self.upload = build_upload
-        upload.save!
+        self.upload = build_upload.tap(&:save!)
       end
     end
 
diff --git a/app/validators/certificate_fingerprint_validator.rb b/app/validators/certificate_fingerprint_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..17df756183a994b413aa3bfbfa20ba367f95370e
--- /dev/null
+++ b/app/validators/certificate_fingerprint_validator.rb
@@ -0,0 +1,9 @@
+class CertificateFingerprintValidator < ActiveModel::EachValidator
+  FINGERPRINT_PATTERN = /\A([a-zA-Z0-9]{2}[\s\-:]?){16,}\z/.freeze
+
+  def validate_each(record, attribute, value)
+    unless value.try(:match, FINGERPRINT_PATTERN)
+      record.errors.add(attribute, "must be a hash containing only letters, numbers, spaces, : and -")
+    end
+  end
+end
diff --git a/app/validators/certificate_validator.rb b/app/validators/certificate_validator.rb
index 5239e70a3267b360868fbebf9d1fa66b161b6e04..b0c9a1b92a4b0a84a12ba6c748ec37604b86e6f5 100644
--- a/app/validators/certificate_validator.rb
+++ b/app/validators/certificate_validator.rb
@@ -16,8 +16,6 @@ class CertificateValidator < ActiveModel::EachValidator
   private
 
   def valid_certificate_pem?(value)
-    return false unless value
-
     OpenSSL::X509::Certificate.new(value).present?
   rescue OpenSSL::X509::CertificateError
     false
diff --git a/app/validators/importable_url_validator.rb b/app/validators/importable_url_validator.rb
index 37a314adee6c4207ebec153816683c06fe31715b..612d3c71913d6497e27075d82436209b87c9bc81 100644
--- a/app/validators/importable_url_validator.rb
+++ b/app/validators/importable_url_validator.rb
@@ -4,8 +4,8 @@
 # protect against Server-side Request Forgery (SSRF).
 class ImportableUrlValidator < ActiveModel::EachValidator
   def validate_each(record, attribute, value)
-    if Gitlab::UrlBlocker.blocked_url?(value)
-      record.errors.add(attribute, "imports are not allowed from that URL")
-    end
+    Gitlab::UrlBlocker.validate!(value, valid_ports: Project::VALID_IMPORT_PORTS)
+  rescue Gitlab::UrlBlocker::BlockedUrlError => e
+    record.errors.add(attribute, "is blocked: #{e.message}")
   end
 end
diff --git a/app/validators/top_level_group_validator.rb b/app/validators/top_level_group_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e2e735e0cf605dc5e94b2ddb8472cb40ee3e4c7
--- /dev/null
+++ b/app/validators/top_level_group_validator.rb
@@ -0,0 +1,7 @@
+class TopLevelGroupValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    if value&.subgroup?
+      record.errors.add(attribute, "must be a top level Group")
+    end
+  end
+end
diff --git a/app/validators/url_placeholder_validator.rb b/app/validators/url_placeholder_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dd681218b6bfc61a80047b287e05a460f05d366a
--- /dev/null
+++ b/app/validators/url_placeholder_validator.rb
@@ -0,0 +1,32 @@
+# UrlValidator
+#
+# Custom validator for URLs.
+#
+# By default, only URLs for the HTTP(S) protocols will be considered valid.
+# Provide a `:protocols` option to configure accepted protocols.
+#
+# Also, this validator can help you validate urls with placeholders inside.
+# Usually, if you have a url like 'http://www.example.com/%{project_path}' the
+# URI parser will reject that URL format. Provide a `:placeholder_regex` option
+# to configure accepted placeholders.
+#
+# Example:
+#
+#   class User < ActiveRecord::Base
+#     validates :personal_url, url: true
+#
+#     validates :ftp_url, url: { protocols: %w(ftp) }
+#
+#     validates :git_url, url: { protocols: %w(http https ssh git) }
+#
+#     validates :placeholder_url, url: { placeholder_regex: /(project_path|project_id|default_branch)/ }
+#   end
+#
+class UrlPlaceholderValidator < UrlValidator
+  def validate_each(record, attribute, value)
+    placeholder_regex = self.options[:placeholder_regex]
+    value = value.gsub(/%{#{placeholder_regex}}/, 'foo') if placeholder_regex && value
+
+    super(record, attribute, value)
+  end
+end
diff --git a/app/views/admin/application_settings/_abuse.html.haml b/app/views/admin/application_settings/_abuse.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..bb3fa26a33e3ac3d9cebaaca776d20676b1c766e
--- /dev/null
+++ b/app/views/admin/application_settings/_abuse.html.haml
@@ -0,0 +1,12 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :admin_notification_email, class: 'form-control'
+        .help-block
+          Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..dd86c9ed2ebd694e10e5527e015dc3612ed3ee2b
--- /dev/null
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -0,0 +1,39 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :gravatar_enabled do
+            = f.check_box :gravatar_enabled
+            Gravatar enabled
+    .form-group
+      = f.label :default_projects_limit, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :default_projects_limit, class: 'form-control'
+    .form-group
+      = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :max_attachment_size, class: 'form-control'
+    .form-group
+      = f.label :session_expire_delay, 'Session duration (minutes)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :session_expire_delay, class: 'form-control'
+        %span.help-block#session_expire_delay_help_block GitLab restart is required to apply changes
+    .form-group
+      = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2'
+      .col-sm-10
+        .checkbox
+          = f.label :user_oauth_applications do
+            = f.check_box :user_oauth_applications
+            Allow users to register any application to use GitLab as an OAuth provider
+    .form-group
+      = f.label :user_default_external, 'New users set to external', class: 'control-label col-sm-2'
+      .col-sm-10
+        .checkbox
+          = f.label :user_default_external do
+            = f.check_box :user_default_external
+            Newly registered users will by default be external
+
+  = f.submit 'Save changes', class: 'btn btn-success'
diff --git a/app/views/admin/application_settings/_background_jobs.html.haml b/app/views/admin/application_settings/_background_jobs.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..8198a822a10321fd090c797ef06e943a4bd9e064
--- /dev/null
+++ b/app/views/admin/application_settings/_background_jobs.html.haml
@@ -0,0 +1,30 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    %p
+      These settings require a
+      = link_to 'restart', help_page_path('administration/restart_gitlab')
+      to take effect.
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :sidekiq_throttling_enabled do
+            = f.check_box :sidekiq_throttling_enabled
+            Enable Sidekiq Job Throttling
+          .help-block
+            Limit the amount of resources slow running jobs are assigned.
+    .form-group
+      = f.label :sidekiq_throttling_queues, 'Sidekiq queues to throttle', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.select :sidekiq_throttling_queues, sidekiq_queue_options_for_select, { include_hidden: false }, multiple: true, class: 'select2 select-wide', data: { field: 'sidekiq_throttling_queues' }
+        .help-block
+          Choose which queues you wish to throttle.
+    .form-group
+      = f.label :sidekiq_throttling_factor, 'Throttling Factor', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :sidekiq_throttling_factor, class: 'form-control', min: '0.01', max: '0.99', step: '0.01'
+        .help-block
+          The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b4d2a789df0af225060af596467369fc18d7b10c
--- /dev/null
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -0,0 +1,47 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :auto_devops_enabled do
+            = f.check_box :auto_devops_enabled
+            Enabled Auto DevOps (Beta) for projects by default
+        .help-block
+          It will automatically build, test, and deploy applications based on a predefined CI/CD configuration
+          = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md')
+    .form-group
+      = f.label :auto_devops_domain, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :auto_devops_domain, class: 'form-control', placeholder: 'domain.com'
+        .help-block
+          = s_("AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages.")
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :shared_runners_enabled do
+            = f.check_box :shared_runners_enabled
+            Enable shared runners for new projects
+    .form-group
+      = f.label :shared_runners_text, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :shared_runners_text, class: 'form-control', rows: 4
+        .help-block Markdown enabled
+    .form-group
+      = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :max_artifacts_size, class: 'form-control'
+        .help-block
+          Set the maximum file size for each job's artifacts
+          = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
+    .form-group
+      = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :default_artifacts_expire_in, class: 'form-control'
+        .help-block
+          Set the default expiration time for each job's artifacts.
+          0 for unlimited.
+          = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6c89f1c4e98312bba1bfff94e754f7a0061660a7
--- /dev/null
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -0,0 +1,26 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :email_author_in_body do
+            = f.check_box :email_author_in_body
+            Include author name in notification email body
+          .help-block
+            Some email servers do not support overriding the email sender name.
+            Enable this option to include the name of the author of the issue,
+            merge request or comment in the email body instead.
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :html_emails_enabled do
+            = f.check_box :html_emails_enabled
+            Enable HTML emails
+          .help-block
+            By default GitLab sends emails in HTML and plain text formats so mail
+            clients can choose what format to use. Disable this option if you only
+            want to send emails in plain text format.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
deleted file mode 100644
index 68788134b8eaaf57e5097f560bf3572ea629322a..0000000000000000000000000000000000000000
--- a/app/views/admin/application_settings/_form.html.haml
+++ /dev/null
@@ -1,862 +0,0 @@
-= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
-  = form_errors(@application_setting)
-
-  %fieldset
-    %legend Visibility and Access Controls
-    .form-group
-      = f.label :default_branch_protection, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
-    .form-group.visibility-level-setting
-      = f.label :default_project_visibility, class: 'control-label col-sm-2'
-      .col-sm-10
-        = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
-    .form-group.visibility-level-setting
-      = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
-      .col-sm-10
-        = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
-    .form-group.visibility-level-setting
-      = f.label :default_group_visibility, class: 'control-label col-sm-2'
-      .col-sm-10
-        = render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
-    .form-group
-      = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
-      .col-sm-10
-        - 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
-          Selected levels cannot be used by non-admin users for projects or snippets.
-          If the public level is restricted, user profiles are only visible to logged in users.
-    .form-group
-      = f.label :import_sources, class: 'control-label col-sm-2'
-      .col-sm-10
-        - import_sources_checkboxes('import-sources-help').each do |source|
-          .checkbox= source
-        %span.help-block#import-sources-help
-          Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
-          = link_to "(?)", help_page_path("integration/github")
-          , Bitbucket
-          = link_to "(?)", help_page_path("integration/bitbucket")
-          and GitLab.com
-          = link_to "(?)", help_page_path("integration/gitlab")
-
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :project_export_enabled do
-            = f.check_box :project_export_enabled
-            Project export enabled
-
-    .form-group
-      %label.control-label.col-sm-2 Enabled Git access protocols
-      .col-sm-10
-        = select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
-        %span.help-block#clone-protocol-help
-          Allow only the selected protocols to be used for Git access.
-
-    - ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
-      - field_name = :"#{type}_key_restriction"
-      .form-group
-        = f.label field_name, "#{type.upcase} SSH keys", class: 'control-label col-sm-2'
-        .col-sm-10
-          = f.select field_name, key_restriction_options_for_select(type), {}, class: 'form-control'
-
-  %fieldset
-    %legend Account and Limit Settings
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :gravatar_enabled do
-            = f.check_box :gravatar_enabled
-            Gravatar enabled
-    .form-group
-      = f.label :default_projects_limit, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :default_projects_limit, class: 'form-control'
-    .form-group
-      = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :max_attachment_size, class: 'form-control'
-    .form-group
-      = f.label :session_expire_delay, 'Session duration (minutes)', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :session_expire_delay, class: 'form-control'
-        %span.help-block#session_expire_delay_help_block GitLab restart is required to apply changes
-    .form-group
-      = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2'
-      .col-sm-10
-        .checkbox
-          = f.label :user_oauth_applications do
-            = f.check_box :user_oauth_applications
-            Allow users to register any application to use GitLab as an OAuth provider
-    .form-group
-      = f.label :user_default_external, 'New users set to external', class: 'control-label col-sm-2'
-      .col-sm-10
-        .checkbox
-          = f.label :user_default_external do
-            = f.check_box :user_default_external
-            Newly registered users will by default be external
-
-  %fieldset
-    %legend Sign-up Restrictions
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :signup_enabled do
-            = f.check_box :signup_enabled
-            Sign-up enabled
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :send_user_confirmation_email do
-            = f.check_box :send_user_confirmation_email
-            Send confirmation email on sign-up
-    .form-group
-      = f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :domain_whitelist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
-        .help-block ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
-    .form-group
-      = f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'control-label col-sm-2'
-      .col-sm-10
-        .checkbox
-          = f.label :domain_blacklist_enabled do
-            = f.check_box :domain_blacklist_enabled
-            Enable domain blacklist for sign ups
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .radio
-          = label_tag :blacklist_type_file do
-            = radio_button_tag :blacklist_type, :file
-            .option-title
-              Upload blacklist file
-        .radio
-          = label_tag :blacklist_type_raw do
-            = radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank?
-            .option-title
-              Enter blacklist manually
-    .form-group.blacklist-file
-      = f.label :domain_blacklist_file, 'Blacklist file', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.file_field :domain_blacklist_file, class: 'form-control', accept: '.txt,.conf'
-        .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.
-    .form-group.blacklist-raw
-      = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
-        .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
-
-    .form-group
-      = f.label :after_sign_up_text, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :after_sign_up_text, class: 'form-control', rows: 4
-        .help-block Markdown enabled
-
-  %fieldset
-    %legend Sign-in Restrictions
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :password_authentication_enabled_for_web do
-            = f.check_box :password_authentication_enabled_for_web
-            Password authentication enabled for web interface
-            .help-block
-              When disabled, an external authentication provider must be used.
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :password_authentication_enabled_for_git do
-            = f.check_box :password_authentication_enabled_for_git
-            Password authentication enabled for Git over HTTP(S)
-            .help-block
-              When disabled, a Personal Access Token
-              - if Gitlab::Auth::LDAP::Config.enabled?
-                or LDAP password
-              must be used to authenticate.
-    - if omniauth_enabled? && button_based_providers.any?
-      .form-group
-        = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
-        .col-sm-10
-          .btn-group{ data: { toggle: 'buttons' } }
-            - oauth_providers_checkboxes.each do |source|
-              = source
-    .form-group
-      = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
-      .col-sm-10
-        .checkbox
-          = f.label :require_two_factor_authentication do
-            = f.check_box :require_two_factor_authentication
-            Require all users to setup Two-factor authentication
-    .form-group
-      = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
-        .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
-    .form-group
-      = f.label :home_page_url, 'Home page URL', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block'
-        %span.help-block#home_help_block We will redirect non-logged in users to this page
-    .form-group
-      = f.label :after_sign_out_path, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :after_sign_out_path, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'after_sign_out_path_help_block'
-        %span.help-block#after_sign_out_path_help_block We will redirect users to this page after they sign out
-    .form-group
-      = f.label :sign_in_text, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :sign_in_text, class: 'form-control', rows: 4
-        .help-block Markdown enabled
-
-  %fieldset
-    %legend Help Page
-    .form-group
-      = f.label :help_page_text, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :help_page_text, class: 'form-control', rows: 4
-        .help-block Markdown enabled
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :help_page_hide_commercial_content do
-            = f.check_box :help_page_hide_commercial_content
-            Hide marketing-related entries from help
-    .form-group
-      = f.label :help_page_support_url, 'Support page URL', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block'
-        %span.help-block#support_help_block Alternate support URL for help page
-
-  %fieldset
-    %legend Pages
-    .form-group
-      = f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :max_pages_size, class: 'form-control'
-        .help-block 0 for unlimited
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :pages_domain_verification_enabled do
-            = f.check_box :pages_domain_verification_enabled
-            Require users to prove ownership of custom domains
-        .help-block
-          Domain verification is an essential security measure for public GitLab
-          sites. Users are required to demonstrate they control a domain before
-          it is enabled
-          = link_to icon('question-circle'), help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
-
-  %fieldset
-    %legend Continuous Integration and Deployment
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :auto_devops_enabled do
-            = f.check_box :auto_devops_enabled
-            Enabled Auto DevOps (Beta) for projects by default
-        .help-block
-          It will automatically build, test, and deploy applications based on a predefined CI/CD configuration
-          = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md')
-    .form-group
-      = f.label :auto_devops_domain, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :auto_devops_domain, class: 'form-control', placeholder: 'domain.com'
-        .help-block
-          = s_("AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages.")
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :shared_runners_enabled do
-            = f.check_box :shared_runners_enabled
-            Enable shared runners for new projects
-    .form-group
-      = f.label :shared_runners_text, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :shared_runners_text, class: 'form-control', rows: 4
-        .help-block Markdown enabled
-    .form-group
-      = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :max_artifacts_size, class: 'form-control'
-        .help-block
-          Set the maximum file size for each job's artifacts
-          = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
-    .form-group
-      = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :default_artifacts_expire_in, class: 'form-control'
-        .help-block
-          Set the default expiration time for each job's artifacts.
-          0 for unlimited.
-          = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
-
-  - if Gitlab.config.registry.enabled
-    %fieldset
-      %legend Container Registry
-      .form-group
-        = f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2'
-        .col-sm-10
-          = f.number_field :container_registry_token_expire_delay, class: 'form-control'
-
-  %fieldset
-    %legend Metrics - Influx
-    %p
-      Setup InfluxDB to measure a wide variety of statistics like the time spent
-      in running SQL queries. These settings require a
-      = link_to 'restart', help_page_path('administration/restart_gitlab')
-      to take effect.
-      = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :metrics_enabled do
-            = f.check_box :metrics_enabled
-            Enable InfluxDB Metrics
-    .form-group
-      = f.label :metrics_host, 'InfluxDB host', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :metrics_host, class: 'form-control', placeholder: 'influxdb.example.com'
-    .form-group
-      = f.label :metrics_port, 'InfluxDB port', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :metrics_port, class: 'form-control', placeholder: '8089'
-        .help-block
-          The UDP port to use for connecting to InfluxDB. InfluxDB requires that
-          your server configuration specifies a database to store data in when
-          sending messages to this port, without it metrics data will not be
-          saved.
-    .form-group
-      = f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :metrics_pool_size, class: 'form-control'
-        .help-block
-          The amount of InfluxDB connections to open. Connections are opened
-          lazily. Users using multi-threaded application servers should ensure
-          enough connections are available (at minimum the amount of application
-          server threads).
-    .form-group
-      = f.label :metrics_timeout, 'Connection timeout', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :metrics_timeout, class: 'form-control'
-        .help-block
-          The amount of seconds after which an InfluxDB connection will time
-          out.
-    .form-group
-      = f.label :metrics_method_call_threshold, 'Method Call Threshold (ms)', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :metrics_method_call_threshold, class: 'form-control'
-        .help-block
-          A method call is only tracked when it takes longer to complete than
-          the given amount of milliseconds.
-    .form-group
-      = f.label :metrics_sample_interval, 'Sampler Interval (sec)', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :metrics_sample_interval, class: 'form-control'
-        .help-block
-          The sampling interval in seconds. Sampled data includes memory usage,
-          retained Ruby objects, file descriptors and so on.
-    .form-group
-      = f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :metrics_packet_size, class: 'form-control'
-        .help-block
-          The amount of points to store in a single UDP packet. More points
-          results in fewer but larger UDP packets being sent.
-
-  %fieldset
-    %legend Metrics - Prometheus
-    %p
-      Enable a Prometheus metrics endpoint at
-      %code= metrics_path
-      to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
-      = link_to 'here', admin_health_check_path
-      \. This setting requires a
-      = link_to 'restart', help_page_path('administration/restart_gitlab')
-      to take effect.
-      = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/index')
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = 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
-              %code prometheus_multiproc_dir
-              does not exist or is not pointing to a valid directory.
-              = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/gitlab_metrics', anchor: 'metrics-shared-directory')
-
-  %fieldset
-    %legend Profiling - Performance Bar
-    %p
-      Enable the Performance Bar for a given group.
-      = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :performance_bar_enabled do
-            = f.check_box :performance_bar_enabled
-            Enable the Performance Bar
-    .form-group
-      = f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
-
-  %fieldset
-    %legend Background Jobs
-    %p
-      These settings require a
-      = link_to 'restart', help_page_path('administration/restart_gitlab')
-      to take effect.
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :sidekiq_throttling_enabled do
-            = f.check_box :sidekiq_throttling_enabled
-            Enable Sidekiq Job Throttling
-          .help-block
-            Limit the amount of resources slow running jobs are assigned.
-    .form-group
-      = f.label :sidekiq_throttling_queues, 'Sidekiq queues to throttle', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.select :sidekiq_throttling_queues, sidekiq_queue_options_for_select, { include_hidden: false }, multiple: true, class: 'select2 select-wide', data: { field: 'sidekiq_throttling_queues' }
-        .help-block
-          Choose which queues you wish to throttle.
-    .form-group
-      = f.label :sidekiq_throttling_factor, 'Throttling Factor', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :sidekiq_throttling_factor, class: 'form-control', min: '0.01', max: '0.99', step: '0.01'
-        .help-block
-          The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.
-
-  %fieldset
-    %legend Spam and Anti-bot Protection
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :recaptcha_enabled do
-            = f.check_box :recaptcha_enabled
-            Enable reCAPTCHA
-          %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
-
-    .form-group
-      = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :recaptcha_site_key, class: 'form-control'
-        .help-block
-          Generate site and private keys at
-          %a{ href: 'http://www.google.com/recaptcha', target: 'blank' } http://www.google.com/recaptcha
-
-    .form-group
-      = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :recaptcha_private_key, class: 'form-control'
-
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :akismet_enabled do
-            = f.check_box :akismet_enabled
-            Enable Akismet
-          %span.help-block#akismet_help_block Helps prevent bots from creating issues
-
-    .form-group
-      = f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :akismet_api_key, class: 'form-control'
-        .help-block
-          Generate API key at
-          %a{ href: 'http://www.akismet.com', target: 'blank' } http://www.akismet.com
-
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :unique_ips_limit_enabled do
-            = f.check_box :unique_ips_limit_enabled
-            Limit sign in from multiple ips
-          %span.help-block#unique_ip_help_block
-            Helps prevent malicious users hide their activity
-
-    .form-group
-      = f.label :unique_ips_limit_per_user, 'IPs per user', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :unique_ips_limit_per_user, class: 'form-control'
-        .help-block
-          Maximum number of unique IPs per user
-
-    .form-group
-      = f.label :unique_ips_limit_time_window, 'IP expiration time', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :unique_ips_limit_time_window, class: 'form-control'
-        .help-block
-          How many seconds an IP will be counted towards the limit
-
-  %fieldset
-    %legend Abuse reports
-    .form-group
-      = f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :admin_notification_email, class: 'form-control'
-        .help-block
-          Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
-
-  %fieldset
-    %legend Error Reporting and Logging
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :sentry_enabled do
-            = f.check_box :sentry_enabled
-            Enable Sentry
-          .help-block
-            %p This setting requires a restart to take effect.
-            Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here:
-            %a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com
-
-    .form-group
-      = f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :sentry_dsn, class: 'form-control'
-
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :clientside_sentry_enabled do
-            = f.check_box :clientside_sentry_enabled
-            Enable Clientside Sentry
-          .help-block
-            Sentry can also be used for reporting and logging clientside exceptions.
-            %a{ href: 'https://sentry.io/for/javascript/', target: '_blank', rel: 'noopener noreferrer' } https://sentry.io/for/javascript/
-
-    .form-group
-      = f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :clientside_sentry_dsn, class: 'form-control'
-
-  %fieldset
-    %legend Repository Storage
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :hashed_storage_enabled do
-            = f.check_box :hashed_storage_enabled
-            Create new projects using hashed storage paths
-          .help-block
-            Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents
-            repositories from having to be moved or renamed when the Project URL changes and may improve disk I/O performance.
-            %em (EXPERIMENTAL)
-    .form-group
-      = f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.select :repository_storages, repository_storages_options_for_select(@application_setting.repository_storages),
-          {include_hidden: false}, multiple: true, class: 'form-control'
-        .help-block
-          Manage repository storage paths. Learn more in the
-          = succeed "." do
-            = link_to "repository storages documentation", help_page_path("administration/repository_storages")
-
-  %fieldset
-    %legend Git Storage Circuitbreaker settings
-    .form-group
-      = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :circuitbreaker_check_interval, class: 'form-control'
-        .help-block
-          = circuitbreaker_check_interval_help_text
-    .form-group
-      = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :circuitbreaker_access_retries, class: 'form-control'
-        .help-block
-          = circuitbreaker_access_retries_help_text
-    .form-group
-      = f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
-        .help-block
-          = circuitbreaker_storage_timeout_help_text
-    .form-group
-      = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
-        .help-block
-          = circuitbreaker_failure_count_help_text
-    .form-group
-      = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
-        .help-block
-          = circuitbreaker_failure_reset_time_help_text
-
-  %fieldset
-    %legend Repository Checks
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :repository_checks_enabled do
-            = f.check_box :repository_checks_enabled
-            Enable Repository Checks
-          .help-block
-            GitLab will periodically run
-            %a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
-            in all project and wiki repositories to look for silent disk corruption issues.
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        = link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
-        .help-block
-          If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
-
-  - if koding_enabled?
-    %fieldset
-      %legend Koding
-      .form-group
-        .col-sm-offset-2.col-sm-10
-          .checkbox
-            = f.label :koding_enabled do
-              = f.check_box :koding_enabled
-              Enable Koding
-          .help-block
-            Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
-      .form-group
-        = f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
-        .col-sm-10
-          = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
-          .help-block
-            Koding has integration enabled out of the box for the
-            %strong gitlab
-            team, and you need to provide that team's URL here. Learn more in the
-            = succeed "." do
-              = link_to "Koding administration documentation", help_page_path("administration/integration/koding")
-
-  %fieldset
-    %legend PlantUML
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :plantuml_enabled do
-            = f.check_box :plantuml_enabled
-            Enable PlantUML
-    .form-group
-      = f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
-        .help-block
-          Allow rendering of
-          = link_to "PlantUML", "http://plantuml.com"
-          diagrams in Asciidoc documents using an external PlantUML service.
-
-  %fieldset
-    %legend#usage-statistics Usage statistics
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :version_check_enabled do
-            = f.check_box :version_check_enabled
-            Version check enabled
-          .help-block
-            Let GitLab inform you when an update is available.
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        - can_be_configured = @application_setting.usage_ping_can_be_configured?
-        .checkbox
-          = f.label :usage_ping_enabled do
-            = f.check_box :usage_ping_enabled, disabled: !can_be_configured
-            Enable usage ping
-          .help-block
-            - if can_be_configured
-              To help improve GitLab and its user experience, GitLab will
-              periodically collect usage information.
-              = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-ping")
-              about what information is shared with GitLab Inc. Visit
-              = link_to 'Cohorts', admin_cohorts_path(anchor: 'usage-ping')
-              to see the JSON payload sent.
-            - else
-              The usage ping is disabled, and cannot be configured through this
-              form. For more information, see the documentation on
-              = succeed '.' do
-                = link_to 'deactivating the usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'deactivate-the-usage-ping')
-
-  %fieldset
-    %legend Email
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :email_author_in_body do
-            = f.check_box :email_author_in_body
-            Include author name in notification email body
-          .help-block
-            Some email servers do not support overriding the email sender name.
-            Enable this option to include the name of the author of the issue,
-            merge request or comment in the email body instead.
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :html_emails_enabled do
-            = f.check_box :html_emails_enabled
-            Enable HTML emails
-          .help-block
-            By default GitLab sends emails in HTML and plain text formats so mail
-            clients can choose what format to use. Disable this option if you only
-            want to send emails in plain text format.
-  %fieldset
-    %legend Automatic Git repository housekeeping
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :housekeeping_enabled do
-            = f.check_box :housekeeping_enabled
-            Enable automatic repository housekeeping (git repack, git gc)
-          .help-block
-            If you keep automatic housekeeping disabled for a long time Git
-            repository access on your GitLab server will become slower and your
-            repositories will use more disk space. We recommend to always leave
-            this enabled.
-        .checkbox
-          = f.label :housekeeping_bitmaps_enabled do
-            = f.check_box :housekeeping_bitmaps_enabled
-            Enable Git pack file bitmap creation
-          .help-block
-            Creating pack file bitmaps makes housekeeping take a little longer but
-            bitmaps should accelerate 'git clone' performance.
-    .form-group
-      = f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :housekeeping_incremental_repack_period, class: 'form-control'
-        .help-block
-          Number of Git pushes after which an incremental 'git repack' is run.
-    .form-group
-      = f.label :housekeeping_full_repack_period, 'Full repack period', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :housekeeping_full_repack_period, class: 'form-control'
-        .help-block
-          Number of Git pushes after which a full 'git repack' is run.
-    .form-group
-      = f.label :housekeeping_gc_period, 'Git GC period', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :housekeeping_gc_period, class: 'form-control'
-        .help-block
-          Number of Git pushes after which 'git gc' is run.
-
-  %fieldset
-    %legend Gitaly Timeouts
-    .form-group
-      = f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :gitaly_timeout_default, class: 'form-control'
-        .help-block
-          Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced
-          for git fetch/push operations or Sidekiq jobs.
-    .form-group
-      = f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :gitaly_timeout_fast, class: 'form-control'
-        .help-block
-          Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast.
-          If they exceed this threshold, there may be a problem with a storage shard and 'failing fast'
-          can help maintain the stability of the GitLab instance.
-    .form-group
-      = f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :gitaly_timeout_medium, class: 'form-control'
-        .help-block
-          Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
-
-  %fieldset
-    %legend Web terminal
-    .form-group
-      = f.label :terminal_max_session_time, 'Max session time', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :terminal_max_session_time, class: 'form-control'
-        .help-block
-          Maximum time for web terminal websocket connection (in seconds).
-          0 for unlimited.
-
-  %fieldset
-    %legend Real-time features
-    .form-group
-      = f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :polling_interval_multiplier, class: 'form-control'
-        .help-block
-          Change this value to influence how frequently the GitLab UI polls for updates.
-          If you set the value to 2 all polling intervals are multiplied
-          by 2, which means that polling happens half as frequently.
-          The multiplier can also have a decimal value.
-          The default value (1) is a reasonable choice for the majority of GitLab
-          installations. Set to 0 to completely disable polling.
-          = link_to icon('question-circle'), help_page_path('administration/polling')
-
-  %fieldset
-    %legend Performance optimization
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :authorized_keys_enabled do
-            = f.check_box :authorized_keys_enabled
-            Write to "authorized_keys" file
-          .help-block
-            By default, we write to the "authorized_keys" file to support Git
-            over SSH without additional configuration. GitLab can be optimized
-            to authenticate SSH keys via the database file. Only uncheck this
-            if you have configured your OpenSSH server to use the
-            AuthorizedKeysCommand. Click on the help icon for more details.
-            = link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
-
-  %fieldset
-    %legend User and IP Rate Limits
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :throttle_unauthenticated_enabled do
-            = f.check_box :throttle_unauthenticated_enabled
-            Enable unauthenticated request rate limit
-          %span.help-block
-            Helps reduce request volume (e.g. from crawlers or abusive bots)
-    .form-group
-      = f.label :throttle_unauthenticated_requests_per_period, 'Max requests per period per IP', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :throttle_unauthenticated_requests_per_period, class: 'form-control'
-    .form-group
-      = f.label :throttle_unauthenticated_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control'
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :throttle_authenticated_api_enabled do
-            = f.check_box :throttle_authenticated_api_enabled
-            Enable authenticated API request rate limit
-          %span.help-block
-            Helps reduce request volume (e.g. from crawlers or abusive bots)
-    .form-group
-      = f.label :throttle_authenticated_api_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :throttle_authenticated_api_requests_per_period, class: 'form-control'
-    .form-group
-      = f.label :throttle_authenticated_api_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control'
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :throttle_authenticated_web_enabled do
-            = f.check_box :throttle_authenticated_web_enabled
-            Enable authenticated web request rate limit
-          %span.help-block
-            Helps reduce request volume (e.g. from crawlers or abusive bots)
-    .form-group
-      = f.label :throttle_authenticated_web_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :throttle_authenticated_web_requests_per_period, class: 'form-control'
-    .form-group
-      = f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
-
-  .form-actions
-    = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/application_settings/_gitaly.html.haml b/app/views/admin/application_settings/_gitaly.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4acc5b3a0c55d75f920bdbce2993d505224abd6c
--- /dev/null
+++ b/app/views/admin/application_settings/_gitaly.html.haml
@@ -0,0 +1,27 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :gitaly_timeout_default, class: 'form-control'
+        .help-block
+          Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced
+          for git fetch/push operations or Sidekiq jobs.
+    .form-group
+      = f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :gitaly_timeout_fast, class: 'form-control'
+        .help-block
+          Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast.
+          If they exceed this threshold, there may be a problem with a storage shard and 'failing fast'
+          can help maintain the stability of the GitLab instance.
+    .form-group
+      = f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :gitaly_timeout_medium, class: 'form-control'
+        .help-block
+          Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..3bc101ddf0475e05484905cc397f98d40aa5f87c
--- /dev/null
+++ b/app/views/admin/application_settings/_help_page.html.haml
@@ -0,0 +1,22 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :help_page_text, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :help_page_text, class: 'form-control', rows: 4
+        .help-block Markdown enabled
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :help_page_hide_commercial_content do
+            = f.check_box :help_page_hide_commercial_content
+            Hide marketing-related entries from help
+    .form-group
+      = f.label :help_page_support_url, 'Support page URL', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block'
+        %span.help-block#support_help_block Alternate support URL for help page
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_influx.html.haml b/app/views/admin/application_settings/_influx.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a173fd38a9c47f8457f475743f9ba70bb405856d
--- /dev/null
+++ b/app/views/admin/application_settings/_influx.html.haml
@@ -0,0 +1,68 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    %p
+      Setup InfluxDB to measure a wide variety of statistics like the time spent
+      in running SQL queries. These settings require a
+      = link_to 'restart', help_page_path('administration/restart_gitlab')
+      to take effect.
+      = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :metrics_enabled do
+            = f.check_box :metrics_enabled
+            Enable InfluxDB Metrics
+    .form-group
+      = f.label :metrics_host, 'InfluxDB host', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :metrics_host, class: 'form-control', placeholder: 'influxdb.example.com'
+    .form-group
+      = f.label :metrics_port, 'InfluxDB port', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :metrics_port, class: 'form-control', placeholder: '8089'
+        .help-block
+          The UDP port to use for connecting to InfluxDB. InfluxDB requires that
+          your server configuration specifies a database to store data in when
+          sending messages to this port, without it metrics data will not be
+          saved.
+    .form-group
+      = f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_pool_size, class: 'form-control'
+        .help-block
+          The amount of InfluxDB connections to open. Connections are opened
+          lazily. Users using multi-threaded application servers should ensure
+          enough connections are available (at minimum the amount of application
+          server threads).
+    .form-group
+      = f.label :metrics_timeout, 'Connection timeout', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_timeout, class: 'form-control'
+        .help-block
+          The amount of seconds after which an InfluxDB connection will time
+          out.
+    .form-group
+      = f.label :metrics_method_call_threshold, 'Method Call Threshold (ms)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_method_call_threshold, class: 'form-control'
+        .help-block
+          A method call is only tracked when it takes longer to complete than
+          the given amount of milliseconds.
+    .form-group
+      = f.label :metrics_sample_interval, 'Sampler Interval (sec)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_sample_interval, class: 'form-control'
+        .help-block
+          The sampling interval in seconds. Sampled data includes memory usage,
+          retained Ruby objects, file descriptors and so on.
+    .form-group
+      = f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_packet_size, class: 'form-control'
+        .help-block
+          The amount of points to store in a single UDP packet. More points
+          results in fewer but larger UDP packets being sent.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_ip_limits.html.haml b/app/views/admin/application_settings/_ip_limits.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b83ffc375d9cb6be3d66557ea953b6f80ae70cf6
--- /dev/null
+++ b/app/views/admin/application_settings/_ip_limits.html.haml
@@ -0,0 +1,54 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :throttle_unauthenticated_enabled do
+            = f.check_box :throttle_unauthenticated_enabled
+            Enable unauthenticated request rate limit
+          %span.help-block
+            Helps reduce request volume (e.g. from crawlers or abusive bots)
+    .form-group
+      = f.label :throttle_unauthenticated_requests_per_period, 'Max requests per period per IP', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :throttle_unauthenticated_requests_per_period, class: 'form-control'
+    .form-group
+      = f.label :throttle_unauthenticated_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control'
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :throttle_authenticated_api_enabled do
+            = f.check_box :throttle_authenticated_api_enabled
+            Enable authenticated API request rate limit
+          %span.help-block
+            Helps reduce request volume (e.g. from crawlers or abusive bots)
+    .form-group
+      = f.label :throttle_authenticated_api_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :throttle_authenticated_api_requests_per_period, class: 'form-control'
+    .form-group
+      = f.label :throttle_authenticated_api_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control'
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :throttle_authenticated_web_enabled do
+            = f.check_box :throttle_authenticated_web_enabled
+            Enable authenticated web request rate limit
+          %span.help-block
+            Helps reduce request volume (e.g. from crawlers or abusive bots)
+    .form-group
+      = f.label :throttle_authenticated_web_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :throttle_authenticated_web_requests_per_period, class: 'form-control'
+    .form-group
+      = f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_koding.html.haml b/app/views/admin/application_settings/_koding.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..17358cf775be58b5b6efd28f403f5ee734e1cced
--- /dev/null
+++ b/app/views/admin/application_settings/_koding.html.haml
@@ -0,0 +1,24 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :koding_enabled do
+            = f.check_box :koding_enabled
+            Enable Koding
+        .help-block
+          Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
+    .form-group
+      = f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
+        .help-block
+          Koding has integration enabled out of the box for the
+          %strong gitlab
+          team, and you need to provide that team's URL here. Learn more in the
+          = succeed "." do
+            = link_to "Koding administration documentation", help_page_path("administration/integration/koding")
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_logging.html.haml b/app/views/admin/application_settings/_logging.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..44a11ddc1200d88275a8a20ef7c59524abe8ff45
--- /dev/null
+++ b/app/views/admin/application_settings/_logging.html.haml
@@ -0,0 +1,36 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :sentry_enabled do
+            = f.check_box :sentry_enabled
+            Enable Sentry
+          .help-block
+            %p This setting requires a restart to take effect.
+            Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here:
+            %a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com
+
+    .form-group
+      = f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :sentry_dsn, class: 'form-control'
+
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :clientside_sentry_enabled do
+            = f.check_box :clientside_sentry_enabled
+            Enable Clientside Sentry
+          .help-block
+            Sentry can also be used for reporting and logging clientside exceptions.
+            %a{ href: 'https://sentry.io/for/javascript/', target: '_blank', rel: 'noopener noreferrer' } https://sentry.io/for/javascript/
+
+    .form-group
+      = f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :clientside_sentry_dsn, class: 'form-control'
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..d10f609006d0576239b3df133af6b5a1a98a4ba1
--- /dev/null
+++ b/app/views/admin/application_settings/_outbound.html.haml
@@ -0,0 +1,12 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :allow_local_requests_from_hooks_and_services do
+            = f.check_box :allow_local_requests_from_hooks_and_services
+            Allow requests to the local network from hooks and services
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_pages.html.haml b/app/views/admin/application_settings/_pages.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b28ecf9a039461ad6e5117edeed6b27963ee6b13
--- /dev/null
+++ b/app/views/admin/application_settings/_pages.html.haml
@@ -0,0 +1,22 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :max_pages_size, class: 'form-control'
+        .help-block 0 for unlimited
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :pages_domain_verification_enabled do
+            = f.check_box :pages_domain_verification_enabled
+            Require users to prove ownership of custom domains
+        .help-block
+          Domain verification is an essential security measure for public GitLab
+          sites. Users are required to demonstrate they control a domain before
+          it is enabled
+          = link_to icon('question-circle'), help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_performance.html.haml b/app/views/admin/application_settings/_performance.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..01d5a31aa9f01710ed5e8efe16acee073a862939
--- /dev/null
+++ b/app/views/admin/application_settings/_performance.html.haml
@@ -0,0 +1,19 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :authorized_keys_enabled do
+            = f.check_box :authorized_keys_enabled
+            Write to "authorized_keys" file
+          .help-block
+            By default, we write to the "authorized_keys" file to support Git
+            over SSH without additional configuration. GitLab can be optimized
+            to authenticate SSH keys via the database file. Only uncheck this
+            if you have configured your OpenSSH server to use the
+            AuthorizedKeysCommand. Click on the help icon for more details.
+            = link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5344f030c970b6241346bdf3b832076aff89d42b
--- /dev/null
+++ b/app/views/admin/application_settings/_performance_bar.html.haml
@@ -0,0 +1,16 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :performance_bar_enabled do
+            = f.check_box :performance_bar_enabled
+            Enable the Performance Bar
+    .form-group
+      = f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_plantuml.html.haml b/app/views/admin/application_settings/_plantuml.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..56764b3fb81bdbbc245c643c686461b08e915041
--- /dev/null
+++ b/app/views/admin/application_settings/_plantuml.html.haml
@@ -0,0 +1,20 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :plantuml_enabled do
+            = f.check_box :plantuml_enabled
+            Enable PlantUML
+    .form-group
+      = f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
+        .help-block
+          Allow rendering of
+          = link_to "PlantUML", "http://plantuml.com"
+          diagrams in Asciidoc documents using an external PlantUML service.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_prometheus.html.haml b/app/views/admin/application_settings/_prometheus.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..48745db2991733aff3ebbc77e557aa0624663916
--- /dev/null
+++ b/app/views/admin/application_settings/_prometheus.html.haml
@@ -0,0 +1,28 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    %p
+      Enable a Prometheus metrics endpoint at
+      %code= metrics_path
+      to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
+      = link_to 'here', admin_health_check_path
+      \. This setting requires a
+      = link_to 'restart', help_page_path('administration/restart_gitlab')
+      to take effect.
+      = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/index')
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = 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
+              %code prometheus_multiproc_dir
+              does not exist or is not pointing to a valid directory.
+              = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/gitlab_metrics', anchor: 'metrics-shared-directory')
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_realtime.html.haml b/app/views/admin/application_settings/_realtime.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..0a53a75119e8b601a12da77e0163f754d9961857
--- /dev/null
+++ b/app/views/admin/application_settings/_realtime.html.haml
@@ -0,0 +1,19 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :polling_interval_multiplier, class: 'form-control'
+        .help-block
+          Change this value to influence how frequently the GitLab UI polls for updates.
+          If you set the value to 2 all polling intervals are multiplied
+          by 2, which means that polling happens half as frequently.
+          The multiplier can also have a decimal value.
+          The default value (1) is a reasonable choice for the majority of GitLab
+          installations. Set to 0 to completely disable polling.
+          = link_to icon('question-circle'), help_page_path('administration/polling')
+
+  = f.submit 'Save changes', class: "btn btn-success"
+
diff --git a/app/views/admin/application_settings/_registry.html.haml b/app/views/admin/application_settings/_registry.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..3451ef62458f68c52c62bb4a83919ff00d567051
--- /dev/null
+++ b/app/views/admin/application_settings/_registry.html.haml
@@ -0,0 +1,10 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :container_registry_token_expire_delay, class: 'form-control'
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_repository_check.html.haml b/app/views/admin/application_settings/_repository_check.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f33769b23c29131bfb040ae66b13ad3fff35a688
--- /dev/null
+++ b/app/views/admin/application_settings/_repository_check.html.haml
@@ -0,0 +1,62 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .sub-section
+      %h4 Repository checks
+      .form-group
+        .col-sm-offset-2.col-sm-10
+          .checkbox
+            = f.label :repository_checks_enabled do
+              = f.check_box :repository_checks_enabled
+              Enable Repository Checks
+            .help-block
+              GitLab will periodically run
+              %a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
+              in all project and wiki repositories to look for silent disk corruption issues.
+      .form-group
+        .col-sm-offset-2.col-sm-10
+          = link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
+          .help-block
+            If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
+
+    .sub-section
+      %h4 Housekeeping
+      .form-group
+        .col-sm-offset-2.col-sm-10
+          .checkbox
+            = f.label :housekeeping_enabled do
+              = f.check_box :housekeeping_enabled
+              Enable automatic repository housekeeping (git repack, git gc)
+            .help-block
+              If you keep automatic housekeeping disabled for a long time Git
+              repository access on your GitLab server will become slower and your
+              repositories will use more disk space. We recommend to always leave
+              this enabled.
+          .checkbox
+            = f.label :housekeeping_bitmaps_enabled do
+              = f.check_box :housekeeping_bitmaps_enabled
+              Enable Git pack file bitmap creation
+            .help-block
+              Creating pack file bitmaps makes housekeeping take a little longer but
+              bitmaps should accelerate 'git clone' performance.
+      .form-group
+        = f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :housekeeping_incremental_repack_period, class: 'form-control'
+          .help-block
+            Number of Git pushes after which an incremental 'git repack' is run.
+      .form-group
+        = f.label :housekeeping_full_repack_period, 'Full repack period', class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :housekeeping_full_repack_period, class: 'form-control'
+          .help-block
+            Number of Git pushes after which a full 'git repack' is run.
+      .form-group
+        = f.label :housekeeping_gc_period, 'Git GC period', class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :housekeeping_gc_period, class: 'form-control'
+          .help-block
+            Number of Git pushes after which 'git gc' is run.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ac31977e1a98fb174f54cef7cea31abb24d40d44
--- /dev/null
+++ b/app/views/admin/application_settings/_repository_storage.html.haml
@@ -0,0 +1,58 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .sub-section
+      .form-group
+        .col-sm-offset-2.col-sm-10
+          .checkbox
+            = f.label :hashed_storage_enabled do
+              = f.check_box :hashed_storage_enabled
+              Create new projects using hashed storage paths
+            .help-block
+              Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents
+              repositories from having to be moved or renamed when the Project URL changes and may improve disk I/O performance.
+              %em (EXPERIMENTAL)
+      .form-group
+        = f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.select :repository_storages, repository_storages_options_for_select(@application_setting.repository_storages),
+            {include_hidden: false}, multiple: true, class: 'form-control'
+          .help-block
+            Manage repository storage paths. Learn more in the
+            = succeed "." do
+              = link_to "repository storages documentation", help_page_path("administration/repository_storages")
+    .sub-section
+      %h4 Circuit breaker
+      .form-group
+        = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :circuitbreaker_check_interval, class: 'form-control'
+          .help-block
+            = circuitbreaker_check_interval_help_text
+      .form-group
+        = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :circuitbreaker_access_retries, class: 'form-control'
+          .help-block
+            = circuitbreaker_access_retries_help_text
+      .form-group
+        = f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
+          .help-block
+            = circuitbreaker_storage_timeout_help_text
+      .form-group
+        = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
+          .help-block
+            = circuitbreaker_failure_count_help_text
+      .form-group
+        = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
+          .help-block
+            = circuitbreaker_failure_reset_time_help_text
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..48331c40bca49653d25ad074f7abce90b683d489
--- /dev/null
+++ b/app/views/admin/application_settings/_signin.html.haml
@@ -0,0 +1,60 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :password_authentication_enabled_for_web do
+            = f.check_box :password_authentication_enabled_for_web
+            Password authentication enabled for web interface
+            .help-block
+              When disabled, an external authentication provider must be used.
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :password_authentication_enabled_for_git do
+            = f.check_box :password_authentication_enabled_for_git
+            Password authentication enabled for Git over HTTP(S)
+            .help-block
+              When disabled, a Personal Access Token
+              - if Gitlab::Auth::LDAP::Config.enabled?
+                or LDAP password
+              must be used to authenticate.
+    - if omniauth_enabled? && button_based_providers.any?
+      .form-group
+        = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
+        = hidden_field_tag 'application_setting[enabled_oauth_sign_in_sources][]'
+        .col-sm-10
+          .btn-group{ data: { toggle: 'buttons' } }
+            - oauth_providers_checkboxes.each do |source|
+              = source
+    .form-group
+      = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
+      .col-sm-10
+        .checkbox
+          = f.label :require_two_factor_authentication do
+            = f.check_box :require_two_factor_authentication
+            Require all users to setup Two-factor authentication
+    .form-group
+      = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
+        .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
+    .form-group
+      = f.label :home_page_url, 'Home page URL', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block'
+        %span.help-block#home_help_block We will redirect non-logged in users to this page
+    .form-group
+      = f.label :after_sign_out_path, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :after_sign_out_path, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'after_sign_out_path_help_block'
+        %span.help-block#after_sign_out_path_help_block We will redirect users to this page after they sign out
+    .form-group
+      = f.label :sign_in_text, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :sign_in_text, class: 'form-control', rows: 4
+        .help-block Markdown enabled
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_signup.html.haml b/app/views/admin/application_settings/_signup.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..85f311dd894b1aa7974b11a48958781d817d4c5c
--- /dev/null
+++ b/app/views/admin/application_settings/_signup.html.haml
@@ -0,0 +1,58 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :signup_enabled do
+            = f.check_box :signup_enabled
+            Sign-up enabled
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :send_user_confirmation_email do
+            = f.check_box :send_user_confirmation_email
+            Send confirmation email on sign-up
+    .form-group
+      = f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :domain_whitelist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
+        .help-block ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
+    .form-group
+      = f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'control-label col-sm-2'
+      .col-sm-10
+        .checkbox
+          = f.label :domain_blacklist_enabled do
+            = f.check_box :domain_blacklist_enabled
+            Enable domain blacklist for sign ups
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .radio
+          = label_tag :blacklist_type_file do
+            = radio_button_tag :blacklist_type, :file
+            .option-title
+              Upload blacklist file
+        .radio
+          = label_tag :blacklist_type_raw do
+            = radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank?
+            .option-title
+              Enter blacklist manually
+    .form-group.blacklist-file
+      = f.label :domain_blacklist_file, 'Blacklist file', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.file_field :domain_blacklist_file, class: 'form-control', accept: '.txt,.conf'
+        .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.
+    .form-group.blacklist-raw
+      = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
+        .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
+
+    .form-group
+      = f.label :after_sign_up_text, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :after_sign_up_text, class: 'form-control', rows: 4
+        .help-block Markdown enabled
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..25e89097dfef9df0b5c2f81529654b4991f8dd25
--- /dev/null
+++ b/app/views/admin/application_settings/_spam.html.haml
@@ -0,0 +1,65 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :recaptcha_enabled do
+            = f.check_box :recaptcha_enabled
+            Enable reCAPTCHA
+          %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
+
+    .form-group
+      = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :recaptcha_site_key, class: 'form-control'
+        .help-block
+          Generate site and private keys at
+          %a{ href: 'http://www.google.com/recaptcha', target: 'blank' } http://www.google.com/recaptcha
+
+    .form-group
+      = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :recaptcha_private_key, class: 'form-control'
+
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :akismet_enabled do
+            = f.check_box :akismet_enabled
+            Enable Akismet
+          %span.help-block#akismet_help_block Helps prevent bots from creating issues
+
+    .form-group
+      = f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :akismet_api_key, class: 'form-control'
+        .help-block
+          Generate API key at
+          %a{ href: 'http://www.akismet.com', target: 'blank' } http://www.akismet.com
+
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :unique_ips_limit_enabled do
+            = f.check_box :unique_ips_limit_enabled
+            Limit sign in from multiple ips
+          %span.help-block#unique_ip_help_block
+            Helps prevent malicious users hide their activity
+
+    .form-group
+      = f.label :unique_ips_limit_per_user, 'IPs per user', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :unique_ips_limit_per_user, class: 'form-control'
+        .help-block
+          Maximum number of unique IPs per user
+
+    .form-group
+      = f.label :unique_ips_limit_time_window, 'IP expiration time', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :unique_ips_limit_time_window, class: 'form-control'
+        .help-block
+          How many seconds an IP will be counted towards the limit
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_terminal.html.haml b/app/views/admin/application_settings/_terminal.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..36d8838803f5ef1f57867758ddd7b36f4e89e11c
--- /dev/null
+++ b/app/views/admin/application_settings/_terminal.html.haml
@@ -0,0 +1,13 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :terminal_max_session_time, 'Max session time', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :terminal_max_session_time, class: 'form-control'
+        .help-block
+          Maximum time for web terminal websocket connection (in seconds).
+          0 for unlimited.
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..7684e2cfdd156cb3937e269840858971018e0380
--- /dev/null
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -0,0 +1,37 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :version_check_enabled do
+            = f.check_box :version_check_enabled
+            Enable version check
+          .help-block
+            GitLab will inform you if a new version is available.
+            = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "version-check")
+            about what information is shared with GitLab Inc.
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        - can_be_configured = @application_setting.usage_ping_can_be_configured?
+        .checkbox
+          = f.label :usage_ping_enabled do
+            = f.check_box :usage_ping_enabled, disabled: !can_be_configured
+            Enable usage ping
+          .help-block
+            - if can_be_configured
+              To help improve GitLab and its user experience, GitLab will
+              periodically collect usage information.
+              = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-ping")
+              about what information is shared with GitLab Inc. Visit
+              = link_to 'Cohorts', admin_cohorts_path(anchor: 'usage-ping')
+              to see the JSON payload sent.
+            - else
+              The usage ping is disabled, and cannot be configured through this
+              form. For more information, see the documentation on
+              = succeed '.' do
+                = link_to 'deactivating the usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'deactivate-the-usage-ping')
+
+  = f.submit 'Save changes', class: "btn btn-success"
+
diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a75dd90fe6b383ab9004abf98b61391bc34779c8
--- /dev/null
+++ b/app/views/admin/application_settings/_visibility_and_access.html.haml
@@ -0,0 +1,67 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+  = form_errors(@application_setting)
+
+  %fieldset
+    .form-group
+      = f.label :default_branch_protection, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
+    .form-group.visibility-level-setting
+      = f.label :default_project_visibility, class: 'control-label col-sm-2'
+      .col-sm-10
+        = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
+    .form-group.visibility-level-setting
+      = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
+      .col-sm-10
+        = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
+    .form-group.visibility-level-setting
+      = f.label :default_group_visibility, class: 'control-label col-sm-2'
+      .col-sm-10
+        = render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
+    .form-group
+      = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
+      .col-sm-10
+        - 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
+          Selected levels cannot be used by non-admin users for projects or snippets.
+          If the public level is restricted, user profiles are only visible to logged in users.
+    .form-group
+      = f.label :import_sources, class: 'control-label col-sm-2'
+      .col-sm-10
+        = hidden_field_tag 'application_setting[import_sources][]'
+        - import_sources_checkboxes('import-sources-help').each do |source|
+          .checkbox= source
+        %span.help-block#import-sources-help
+          Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
+          = link_to "(?)", help_page_path("integration/github")
+          , Bitbucket
+          = link_to "(?)", help_page_path("integration/bitbucket")
+          and GitLab.com
+          = link_to "(?)", help_page_path("integration/gitlab")
+
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :project_export_enabled do
+            = f.check_box :project_export_enabled
+            Project export enabled
+
+    .form-group
+      %label.control-label.col-sm-2 Enabled Git access protocols
+      .col-sm-10
+        = select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
+        %span.help-block#clone-protocol-help
+          Allow only the selected protocols to be used for Git access.
+
+    - ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
+      - field_name = :"#{type}_key_restriction"
+      .form-group
+        = f.label field_name, "#{type.upcase} SSH keys", class: 'control-label col-sm-2'
+        .col-sm-10
+          = f.select field_name, key_restriction_options_for_select(type), {}, class: 'form-control'
+
+  = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index ecc46d86afe798a6b103669d752bbce61db7efe7..caaa93aa1e271435467ce92f8980c13d7ddd541c 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -1,5 +1,304 @@
+- breadcrumb_title "Settings"
 - page_title "Settings"
+- @content_class = "limit-container-width" unless fluid_layout
+- expanded = Rails.env.test?
 
-%h3.page-title Settings
-%hr
-= render 'form'
+%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Visibility and access controls')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Set default and restrict visibility levels. Configure import sources and git access protocol.')
+  .settings-content
+    = render 'visibility_and_access'
+
+%section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Account and limit settings')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Session expiration, projects limit and attachment size.')
+  .settings-content
+    = render 'account_and_limit'
+
+%section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Sign-up restrictions')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Configure the way a user creates a new account.')
+  .settings-content
+    = render 'signup'
+
+%section.settings.as-signin.no-animate#js-signin-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Sign-in restrictions')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Set requirements for a user to sign-in. Enable mandatory two-factor authentication.')
+  .settings-content
+    = render 'signin'
+
+%section.settings.as-help-page.no-animate#js-help-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Help page')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Help page text and support page url.')
+  .settings-content
+    = render 'help_page'
+
+%section.settings.as-pages.no-animate#js-pages-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Pages')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Size and domain settings for static websites')
+  .settings-content
+    = render 'pages'
+
+%section.settings.as-ci-cd.no-animate#js-ci-cd-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Continuous Integration and Deployment')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Auto DevOps, runners and job artifacts')
+  .settings-content
+    = render 'ci_cd'
+
+%section.settings.as-influx.no-animate#js-influx-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Metrics - Influx')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Enable and configure InfluxDB metrics.')
+  .settings-content
+    = render 'influx'
+
+%section.settings.as-prometheus.no-animate#js-prometheus-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Metrics - Prometheus')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Enable and configure Prometheus metrics.')
+  .settings-content
+    = render 'prometheus'
+
+%section.settings.as-performance-bar.no-animate#js-performance-bar-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Profiling - Performance bar')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Enable the Performance Bar for a given group.')
+      = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
+  .settings-content
+    = render 'performance_bar'
+
+%section.settings.as-background.no-animate#js-background-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Background jobs')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Configure Sidekiq job throttling.')
+  .settings-content
+    = render 'background_jobs'
+
+%section.settings.as-spam.no-animate#js-spam-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Spam and Anti-bot Protection')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Enable reCAPTCHA or Akismet and set IP limits.')
+  .settings-content
+    = render 'spam'
+
+%section.settings.as-abuse.no-animate#js-abuse-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Abuse reports')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Set notification email for abuse reports.')
+  .settings-content
+    = render 'abuse'
+
+%section.settings.as-logging.no-animate#js-logging-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Error Reporting and Logging')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Enable Sentry for error reporting and logging.')
+  .settings-content
+    = render 'logging'
+
+%section.settings.as-repository-storage.no-animate#js-repository-storage-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Repository storage')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Configure storage path and circuit breaker settings.')
+  .settings-content
+    = render 'repository_storage'
+
+%section.settings.as-repository-check.no-animate#js-repository-check-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Repository maintenance')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Configure automatic git checks and housekeeping on repositories.')
+  .settings-content
+    = render 'repository_check'
+
+- if Gitlab.config.registry.enabled
+  %section.settings.as-registry.no-animate#js-registry-settings{ class: ('expanded' if expanded) }
+    .settings-header
+      %h4
+        = _('Container Registry')
+      %button.btn.js-settings-toggle{ type: 'button' }
+        = expanded ? 'Collapse' : 'Expand'
+      %p
+        = _('Various container registry settings.')
+    .settings-content
+      = render 'registry'
+
+- if koding_enabled?
+  %section.settings.as-koding.no-animate#js-koding-settings{ class: ('expanded' if expanded) }
+    .settings-header
+      %h4
+        = _('Koding')
+      %button.btn.js-settings-toggle{ type: 'button' }
+        = expanded ? 'Collapse' : 'Expand'
+      %p
+        = _('Online IDE integration settings.')
+    .settings-content
+      = render 'koding'
+
+%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('PlantUML')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
+  .settings-content
+    = render 'plantuml'
+
+%section.settings.as-usage.no-animate#js-usage-settings{ class: ('expanded' if expanded) }
+  .settings-header#usage-statistics
+    %h4
+      = _('Usage statistics')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Enable or disable version check and usage ping.')
+  .settings-content
+    = render 'usage'
+
+%section.settings.as-email.no-animate#js-email-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Email')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Various email settings.')
+  .settings-content
+    = render 'email'
+
+%section.settings.as-gitaly.no-animate#js-gitaly-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Gitaly')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Configure Gitaly timeouts.')
+  .settings-content
+    = render 'gitaly'
+
+%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Web terminal')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Set max session time for web terminal.')
+  .settings-content
+    = render 'terminal'
+
+%section.settings.as-realtime.no-animate#js-realtime-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Real-time features')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Change this value to influence how frequently the GitLab UI polls for updates.')
+  .settings-content
+    = render 'realtime'
+
+%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Performance optimization')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Various settings that affect GitLab performance.')
+  .settings-content
+    = render 'performance'
+
+%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('User and IP Rate Limits')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Configure limits for web and API requests.')
+  .settings-content
+    = render 'ip_limits'
+
+%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4
+      = _('Outbound requests')
+    %button.btn.js-settings-toggle{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = _('Allow requests to the local network from hooks and services.')
+  .settings-content
+    = render 'outbound'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index e3711421b611c76229756f9bb1b7090bd890138c..bbf0e0fb95c6068fc026f01d68f0fc64567ef9f8 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -126,6 +126,7 @@
               GitLab
               %span.pull-right
                 = Gitlab::VERSION
+                = "(#{Gitlab::REVISION})"
             %p
               GitLab Shell
               %span.pull-right
@@ -164,7 +165,7 @@
             %h4 Latest projects
             - @projects.each do |project|
               %p
-                = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
+                = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
                 %span.light.pull-right
                   #{time_ago_with_tooltip(project.created_at)}
       .col-md-4
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 2545cecc7218f74416e1100f59a0b91880f45419..324f3c0a22f2fbf2b495ee6e09e1d52d426856b7 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -68,7 +68,7 @@
         - @projects.each do |project|
           %li
             %strong
-              = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
+              = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project]
               %span.badge
                 = storage_counter(project.statistics.storage_size)
             %span.pull-right.light
@@ -86,7 +86,7 @@
           - @group.shared_projects.sort_by(&:name).each do |project|
             %li
               %strong
-                = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
+                = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project]
                 %span.badge
                   = storage_counter(project.statistics.storage_size)
               %span.pull-right.light
diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml
index d8f96ed5b0d5ecaf9762062bb4e9d11bb7c0062f..a6324a97fd5f1a4c3b5d603805e26590d1732c14 100644
--- a/app/views/admin/hooks/_form.html.haml
+++ b/app/views/admin/hooks/_form.html.haml
@@ -1,21 +1,20 @@
 = form_errors(hook)
 
 .form-group
-  = form.label :url, 'URL', class: 'control-label'
-  .col-sm-10
-    = form.text_field :url, class: 'form-control'
+  = form.label :url, 'URL', class: 'label-light'
+  = form.text_field :url, class: 'form-control'
 .form-group
-  = form.label :token, 'Secret Token', class: 'control-label'
-  .col-sm-10
-    = form.text_field :token, class: 'form-control'
-    %p.help-block
-      Use this token to validate received payloads
+  = form.label :token, 'Secret Token', class: 'label-light'
+  = form.text_field :token, class: 'form-control'
+  %p.help-block
+    Use this token to validate received payloads
 .form-group
-  = form.label :url, 'Trigger', class: 'control-label'
-  .col-sm-10.prepend-top-10
-    %div
-      System hook will be triggered on set of events like creating project
-      or adding ssh key. But you can also enable extra triggers like Push events.
+  = form.label :url, 'Trigger', class: 'label-light'
+  %ul.list-unstyled
+    %li
+      .help-block
+        System hook will be triggered on set of events like creating project
+        or adding ssh key. But you can also enable extra triggers like Push events.
 
     .prepend-top-default
       = form.check_box :repository_update_events, class: 'pull-left'
@@ -24,21 +23,21 @@
           %strong Repository update events
         %p.light
           This URL will be triggered when repository is updated
-    %div
+    %li
       = form.check_box :push_events, class: 'pull-left'
       .prepend-left-20
         = form.label :push_events, class: 'list-label' do
           %strong Push events
         %p.light
           This URL will be triggered for each branch updated to the repository
-    %div
+    %li
       = form.check_box :tag_push_events, class: 'pull-left'
       .prepend-left-20
         = form.label :tag_push_events, class: 'list-label' do
           %strong Tag push events
         %p.light
           This URL will be triggered when a new tag is pushed to the repository
-    %div
+    %li
       = form.check_box :merge_requests_events, class: 'pull-left'
       .prepend-left-20
         = form.label :merge_requests_events, class: 'list-label' do
@@ -46,9 +45,8 @@
         %p.light
           This URL will be triggered when a merge request is created/updated/merged
 .form-group
-  = form.label :enable_ssl_verification, 'SSL verification', class: 'control-label checkbox'
-  .col-sm-10
-    .checkbox
-      = form.label :enable_ssl_verification do
-        = form.check_box :enable_ssl_verification
-        %strong Enable SSL verification
+  = form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox'
+  .checkbox
+    = form.label :enable_ssl_verification do
+      = form.check_box :enable_ssl_verification
+      %strong Enable SSL verification
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index bc02d9969d6fea06986f03839e9eda36892c9c8a..d9e2ce5e74cf42804c4672caf7ca2a21cad93541 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -1,33 +1,35 @@
 - page_title 'System Hooks'
-%h3.page-title
-  System hooks
+.row.prepend-top-default
+  .col-lg-4
+    %h4.prepend-top-0
+      = page_title
+    %p
+      #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
+      used for binding events when GitLab creates a User or Project.
 
-%p.light
-  #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
-  used for binding events when GitLab creates a User or Project.
+  .col-lg-8.append-bottom-default
+    = form_for @hook, as: :hook, url: admin_hooks_path do |f|
+      = render partial: 'form', locals: { form: f, hook: @hook }
+      = f.submit 'Add system hook', class: 'btn btn-create'
 
-%hr
+    %hr
 
-= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
-  = render partial: 'form', locals: { form: f, hook: @hook }
-  .form-actions
-    = f.submit 'Add system hook', class: 'btn btn-create'
-%hr
+    - if @hooks.any?
+      .panel.panel-default
+        .panel-heading
+          System hooks (#{@hooks.count})
+        %ul.content-list
+          - @hooks.each do |hook|
+            %li
+              .controls
+                = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: hook, button_class: 'btn-sm'
+                = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
+                = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
+              .monospace= hook.url
+              %div
+                - SystemHook.triggers.each_value do |event|
+                  - if hook.public_send(event)
+                    %span.label.label-gray= event.to_s.titleize
+                %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
 
-- if @hooks.any?
-  .panel.panel-default
-    .panel-heading
-      System hooks (#{@hooks.count})
-    %ul.content-list
-      - @hooks.each do |hook|
-        %li
-          .controls
-            = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: hook, button_class: 'btn-sm'
-            = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
-            = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
-          .monospace= hook.url
-          %div
-            - SystemHook.triggers.each_value do |event|
-              - if hook.public_send(event)
-                %span.label.label-gray= event.to_s.titleize
-            %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
+= render 'shared/plugins/index'
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 42f92079d858465dcc8c57046ac9e1edbd50c0d8..aeba9788fda57eb4559da5b38d037a661afc13a5 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -1,8 +1,8 @@
 - add_to_breadcrumbs "Projects", admin_projects_path
-- breadcrumb_title @project.name_with_namespace
-- page_title @project.name_with_namespace, "Projects"
+- breadcrumb_title @project.full_name
+- page_title @project.full_name, "Projects"
 %h3.page-title
-  Project: #{@project.name_with_namespace}
+  Project: #{@project.full_name}
   = link_to edit_project_path(@project), class: "btn btn-nr pull-right" do
     %i.fa.fa-pencil-square-o
     Edit
@@ -62,12 +62,16 @@
             = link_to @project.ssh_url_to_repo, project_path(@project)
         - if @project.repository.exists?
           %li
-            %span.light fs:
+            %span.light Gitaly storage name:
             %strong
-              = @project.repository.path_to_repo
+              = @project.repository.storage
+          %li
+            %span.light Gitaly relative path:
+            %strong
+              = @project.repository.relative_path
 
           %li
-            %span.light Storage:
+            %span.light Storage used:
             %strong= storage_counter(@project.statistics.storage_size)
             (
             = storage_counter(@project.statistics.repository_size)
@@ -97,7 +101,7 @@
         - if @project.archived?
           %li
             %span.light archived:
-            %strong repository is read-only
+            %strong project is read-only
 
         %li
           %span.light access:
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 6d8fad0eb8d7a2a006df770ac9f09316494b8b15..37269862de60ecc5be3024bda96339161d134762 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -9,6 +9,10 @@
         %span.runner-state.runner-state-specific
           Specific
 
+- add_to_breadcrumbs _("Runners"), admin_runners_path
+- breadcrumb_title "##{@runner.id}"
+- @no_container = true
+
 - if @runner.shared?
   .bs-callout.bs-callout-success
     %h4 This Runner will process jobs from ALL UNASSIGNED projects
@@ -39,7 +43,7 @@
             %tr.alert-info
               %td
                 %strong
-                  = project.name_with_namespace
+                  = project.full_name
               %td
                 .pull-right
                   = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
@@ -61,7 +65,7 @@
       - @projects.each do |project|
         %tr
           %td
-            = project.name_with_namespace
+            = project.full_name
           %td
             .pull-right
               = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f|
@@ -95,7 +99,7 @@
 
           %td.status
             - if project
-              = project.name_with_namespace
+              = project.full_name
 
           %td.build-link
             - if project
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index bbfeceff5b90a4f4ba7559d44ef4b5a7a27e1ae9..2ff4221efbd51acc6d7bae262faac6daeaab997f 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -33,7 +33,7 @@
                 = link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put
             - if user.access_locked?
               %li
-                = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
+                = link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') }
           - if can?(current_user, :destroy_user, user)
             %li.divider
             - if user.can_be_removed?
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 4a440f3f6d4182d2d9428ff25988ae9504fc52ab..96835ee9af55e4553f2d5ad4f854ab1f1b2f1ace 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -29,12 +29,12 @@
     .panel.panel-default
       .panel-heading Joined projects (#{@joined_projects.count})
       %ul.well-list
-        - @joined_projects.sort_by(&:name_with_namespace).each do |project|
+        - @joined_projects.sort_by(&:full_name).each do |project|
           - member = project.team.find_member(@user.id)
           %li.project_member
             .list-item-name
               = link_to admin_project_path(project), class: dom_class(project) do
-                = project.name_with_namespace
+                = project.full_name
 
             - if member
               .pull-right
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 5f07d2720c2070c5cc70f8beb5e23fd257eb2cd5..4b3c52af16ac141e68ac0aec4bdde140f5fe21f1 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -3,13 +3,13 @@
 .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
   - awards_sort(grouped_emojis).each do |emoji, awards|
     %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
-      class: [(award_state_class(awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
+      class: [(award_state_class(awardable, awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
       data: { placement: "bottom", title: award_user_list(awards, current_user) } }
       = emoji_icon(emoji)
       %span.award-control-text.js-counter
         = awards.count
 
-  - if current_user
+  - if can?(current_user, :award_emoji, awardable)
     .award-menu-holder.js-award-holder
       %button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
         'aria-label': 'Add reaction',
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index 3c0881caa06e94ca9b7f7d2d9e33788419b41336..22f149d1caa35c431d53a3eb4146139879977f62 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,27 +1,9 @@
-- page_title "CI Lint"
-- page_description "Validate your GitLab CI configuration file"
-- content_for :library_javascripts do
-  = page_specific_javascript_tag('lib/ace.js')
-
-%h2 Check your .gitlab-ci.yml
-
-.ci-linter
-  .row
-    = form_tag ci_lint_path, method: :post do
-      .form-group
-        .col-sm-12
-          .file-holder
-            .js-file-title.file-title.clearfix
-              Content of .gitlab-ci.yml
-            #ci-editor.ci-editor= @content
-          = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
-      .col-sm-12
-        .pull-left.prepend-top-10
-          = submit_tag('Validate', class: 'btn btn-success submit-yml')
-        .pull-right.prepend-top-10
-          = button_tag('Clear', type: 'button', class: 'btn btn-default clear-yml')
-
-  .row.prepend-top-20
-    .col-sm-12
-      .results.ci-template
-        = render partial: 'create' if defined?(@status)
+.row.empty-state
+  .col-xs-12
+    .svg-content
+      = image_tag 'illustrations/feature_moved.svg'
+  .col-xs-12
+    .text-content.text-center
+      %h4= _("GitLab CI Linter has been moved")
+      %p
+        = _("To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button.")
diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml
index 35a3563dff1f24f3fcdebca4a2d4ac8d3a3cfb23..5114387984b44c08934276d5f39a11a994e37d05 100644
--- a/app/views/ci/status/_badge.html.haml
+++ b/app/views/ci/status/_badge.html.haml
@@ -4,10 +4,10 @@
 - css_classes = "ci-status ci-#{status.group} #{'has-tooltip' if title.present?}"
 
 - if link && status.has_details?
-  = link_to status.details_path, class: css_classes, title: title do
+  = link_to status.details_path, class: css_classes, title: title, data: { html: title.present? } do
     = sprite_icon(status.icon)
     = status.text
 - else
-  %span{ class: css_classes, title: title }
+  %span{ class: css_classes, title: title, data: { html: title.present? } }
     = sprite_icon(status.icon)
     = status.text
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
index c5b4439e27392941150c9b74347df0e8ba1313d4..db2040110fa0c3dd8688a877b10a3ce126554cb4 100644
--- a/app/views/ci/status/_dropdown_graph_badge.html.haml
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -3,14 +3,15 @@
 - subject = local_assigns.fetch(:subject)
 - status = subject.detailed_status(current_user)
 - klass = "ci-status-icon ci-status-icon-#{status.group}"
-- tooltip = "#{subject.name} - #{status.label}"
+- tooltip = "#{subject.name} - #{status.status_tooltip}"
 
 - if status.has_details?
-  = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' }  do
+  = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, html: true, container: 'body' }  do
     %span{ class: klass }= sprite_icon(status.icon)
     %span.ci-build-text= subject.name
+
 - else
-  .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
+  .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' } }
     %span{ class: klass }= sprite_icon(status.icon)
     %span.ci-build-text= subject.name
 
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
index 152017804512bc88a3746ddd067cef34d25f4468..440623b34f58fe2ad82048016ef3a4095c61e793 100644
--- a/app/views/ci/variables/_variable_row.html.haml
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -10,7 +10,7 @@
 - id_input_name = "#{form_field}[variables_attributes][][id]"
 - destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
 - key_input_name = "#{form_field}[variables_attributes][][key]"
-- value_input_name = "#{form_field}[variables_attributes][][value]"
+- value_input_name = "#{form_field}[variables_attributes][][secret_value]"
 - protected_input_name = "#{form_field}[variables_attributes][][protected]"
 
 %li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
@@ -43,7 +43,5 @@
           %span.toggle-icon
             = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
             = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
-      -# EE-specific start
-      -# EE-specific end
   %button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
     = icon('minus-circle')
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index 70ec6bc62576fc6fc39a4e7dc208fb302aacb613..d7b6fb9a4a144c7082eaaa3f4a3601191cde4746 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,5 +1,5 @@
 xml.title   "#{current_user.name} issues"
-xml.link    href: url_for(params), rel: "self", type: "application/atom+xml"
+xml.link    href: url_for(safe_params), rel: "self", type: "application/atom+xml"
 xml.link    href: issues_dashboard_url, rel: "alternate", type: "text/html"
 xml.id      issues_dashboard_url
 xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 3e85535dae041e61b13ad581b2b6515e11b3734c..4bf04dadf01a016512a5862342ce768b946166bb 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -1,15 +1,19 @@
 - @hide_top_links = true
-- page_title "Issues"
-- header_title  "Issues", issues_dashboard_path(assignee_id: current_user.id)
+- page_title _("Issues")
+- @breadcrumb_link = issues_dashboard_path(assignee_id: current_user.id)
 = content_for :meta_tags do
-  = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
+  = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
 
 .top-area
-  = render 'shared/issuable/nav', type: :issues
+  = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
   .nav-controls
-    = link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
+    = link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
       = icon('rss')
     = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
 
 = render 'shared/issuable/filter', type: :issues
-= render 'shared/issues'
+
+- if current_user && @no_filters_set
+  = render 'shared/dashboard/no_filter_selected'
+- else
+  = render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 53cd1130299b7faa700931817794afaf1f52ff20..61aae31be603891c479e9752856605b5d1f947a4 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,11 +1,15 @@
 - @hide_top_links = true
-- page_title "Merge Requests"
-- header_title  "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
+- page_title _("Merge Requests")
+- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
 
 .top-area
-  = render 'shared/issuable/nav', type: :merge_requests
+  = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
   .nav-controls
     = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
 
 = render 'shared/issuable/filter', type: :merge_requests
-= render 'shared/merge_requests'
+
+- if current_user && @no_filters_set
+  = render 'shared/dashboard/no_filter_selected'
+- else
+  = render 'shared/merge_requests'
diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml
index f943d25e41a78e7abca023fbaae3fb0ae107df5c..7bd414d64c39f118c2bb3266eb43fc468ab045d0 100644
--- a/app/views/devise/shared/_tab_single.html.haml
+++ b/app/views/devise/shared/_tab_single.html.haml
@@ -1,3 +1,3 @@
-%ul.nav-links.nav-tabs.new-session-tabs.single-tab
+%ul.nav-links.new-session-tabs.single-tab
   %li.active
     %a= tab_title
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index 270191f9452552a337e26d6b625fd851f4320837..f50e0724e09ba6f23f2bd7b978c6c43ae3072771 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -1,4 +1,4 @@
-%ul.new-session-tabs.nav-links.nav-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
+%ul.nav-links.new-session-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
   - if crowd_enabled?
     %li.active
       = link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
index 1ba6d3908754e7f723cefe28a4b1ddde7fa3eff6..fa3c3df7f603d9a5b2bc02d46ef90dde2d0fdd03 100644
--- a/app/views/devise/shared/_tabs_normal.html.haml
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -1,4 +1,4 @@
-%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' }
+%ul.nav-links.new-session-tabs{ role: 'tablist' }
   %li.active{ role: 'presentation' }
     %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
   - if allow_signup?
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index f9bfc01f21376e88bdce49d42d9abb8c27777e55..646e89e9bd13a8f6896bcbd04d74d104fc474fe6 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -2,8 +2,12 @@
 - blob = discussion.blob
 - discussions = { discussion.original_line_code => [discussion] }
 - diff_file_class = diff_file.text? ? 'text-file' : 'js-image-file'
+- diff_data = {}
+- expanded = discussion.expanded? || local_assigns.fetch(:expanded, nil)
+- unless expanded
+  - diff_data = { lines_path: project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion) }
 
-.diff-file.file-holder{ class: diff_file_class }
+.diff-file.file-holder.js-lazy-load-discussion{ class: diff_file_class, data: diff_data }
   .js-file-title.file-title.file-title-flex-parent
     .file-header-content
       = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
@@ -11,17 +15,27 @@
   - if diff_file.text?
     .diff-content.code.js-syntax-highlight
       %table
-        = render partial: "projects/diffs/line",
-          collection: discussion.truncated_diff_lines,
-          as: :line,
-          locals: { diff_file: diff_file,
-            discussions: discussions,
-            discussion_expanded: true,
-            plain: true }
+        - if expanded
+          - discussions = { discussion.original_line_code => [discussion] }
+          = render partial: "projects/diffs/line",
+            collection: discussion.truncated_diff_lines,
+            as: :line,
+            locals: { diff_file: diff_file,
+              discussions: discussions,
+              discussion_expanded: true,
+              plain: true }
+        - else
+          %tr.line_holder.line-holder-placeholder
+            %td.old_line.diff-line-num
+            %td.new_line.diff-line-num
+            %td.line_content.js-success-lazy-load
+              .js-code-placeholder
+            %td.js-error-lazy-load-diff.hidden.diff-loading-error-block
+              - button = button_tag(_("Try again"), class: "btn-link btn-link-retry btn-no-padding js-toggle-lazy-diff-retry-button")
+              = _("Unable to load the diff. %{button_try_again}").html_safe % { button_try_again: button}
+          = render "discussions/diff_discussion", discussions: [discussion], expanded: true
   - else
     - partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff'
-
     = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false }
-
     .note-container
       = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse_class: true }
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 8b9fa3d6b05de1f98f1594c20a878563ad51af46..e9589213f807c72e063c7eda909f39ca76328b54 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -8,7 +8,7 @@
       .discussion.js-toggle-container{ data: { discussion_id: discussion.id } }
         .discussion-header
           .discussion-actions
-            %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button" }
+            %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) }
               - if expanded
                 = icon("chevron-up")
               - else
diff --git a/app/views/email_rejection_mailer/rejection.text.haml b/app/views/email_rejection_mailer/rejection.text.haml
index 6693e6f90e8b9b869e0529b01bbaad1a6d9eb7a0..af518b5b5834bea475ece166bfe075e4ca459821 100644
--- a/app/views/email_rejection_mailer/rejection.text.haml
+++ b/app/views/email_rejection_mailer/rejection.text.haml
@@ -1,4 +1,3 @@
 Unfortunately, your email message to GitLab could not be processed.
-
-
+\
 = @reason
diff --git a/app/views/groups/boards/index.html.haml b/app/views/groups/boards/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..bb56769bd3ffaad7108eaaa69f8297a1418ae698
--- /dev/null
+++ b/app/views/groups/boards/index.html.haml
@@ -0,0 +1 @@
+= render "shared/boards/show", board: @boards.first
diff --git a/app/views/groups/boards/show.html.haml b/app/views/groups/boards/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..92838fa4b112a4ba74a1c89a7b5043401832a0be
--- /dev/null
+++ b/app/views/groups/boards/show.html.haml
@@ -0,0 +1 @@
+= render "shared/boards/show", board: @board, group: true
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index a239ea8caf009fe7dd82b032697636b07852ad76..2a385b661e50f910067b9ee161198a87f8286891 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -1,5 +1,5 @@
 xml.title   "#{@group.name} issues"
-xml.link    href: url_for(params), rel: "self", type: "application/atom+xml"
+xml.link    href: url_for(safe_params), rel: "self", type: "application/atom+xml"
 xml.link    href: issues_group_url, rel: "alternate", type: "text/html"
 xml.id      issues_group_url
 xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index ca3f018c5e6a8836d3d16981a01bc1fa2c0b14f4..bbfbea4ac7a2b9281ece3e7ab0c3c28ae8d18dfc 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,9 +1,6 @@
 - page_title "Issues"
 = content_for :meta_tags do
-  = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
-
-- content_for :page_specific_javascripts do
-  = webpack_bundle_tag 'common_vue'
+  = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
 
 - if group_issues_count(state: 'all').zero?
   = render 'shared/empty_states/issues', project_select_button: true
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index d10efdad53b8552c0ac775d0ab785dbf94aa83dc..ac7e12fcd0ba350cac19bbb8a88c291947683cb3 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,8 +1,10 @@
 - page_title 'Labels'
 
+- issuables = ['issues', 'merge requests']
+
 .top-area.adjust
   .nav-text
-    Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
+    = _("Labels can be applied to %{features}. Group labels are available for any project within the group.") % { features: issuables.to_sentence }
 
   .nav-controls
     - if can?(current_user, :admin_label, @group)
@@ -16,4 +18,4 @@
         = paginate @labels, theme: 'gitlab'
     - else
       .nothing-here-block
-        No labels created yet.
+        = _("No labels created yet.")
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 8d2bc810a7dfee66b540496967aaaa83a73f2fc5..ef181b425bc5d7680b903fc5991fbe0f1bf03ee2 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -14,7 +14,7 @@
         .list-item-name
           %span{ class: visibility_level_color(project.visibility_level) }
             = visibility_level_icon(project.visibility_level)
-          %strong= link_to project.name_with_namespace, project
+          %strong= link_to project.full_name, project
         .pull-right
           - if project.archived
             %span.label.label-warning archived
diff --git a/app/views/groups/settings/badges/index.html.haml b/app/views/groups/settings/badges/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..c7afb25d0f826492e5bdbad3231f046a41e53a1d
--- /dev/null
+++ b/app/views/groups/settings/badges/index.html.haml
@@ -0,0 +1,4 @@
+- breadcrumb_title _('Project Badges')
+- page_title _('Project Badges')
+
+= render 'shared/badges/badge_settings'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 7f9486d08d9fa2594b1fb81e059801640947cbd4..8e1dea4afc1cee3c45db871caa7162365248c081 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,5 +1,5 @@
 - @no_container = true
-- breadcrumb_title "Details"
+- breadcrumb_title _("Details")
 - can_create_subgroups = can?(current_user, :create_subgroup, @group)
 
 = content_for :meta_tags do
diff --git a/app/views/ide/index.html.haml b/app/views/ide/index.html.haml
index 3dbdfc9765404dff2e73155c3769116296d66c54..e0e8fe548d0bfe67884be6e87705709a13ece6fc 100644
--- a/app/views/ide/index.html.haml
+++ b/app/views/ide/index.html.haml
@@ -2,10 +2,11 @@
 - page_title 'IDE'
 
 - content_for :page_specific_javascripts do
-  = webpack_bundle_tag 'common_vue'
   = webpack_bundle_tag 'ide', force_same_domain: true
 
-#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg')} }
+#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg'),
+  "no-changes-state-svg-path" => image_path('illustrations/multi-editor_no_changes_empty.svg'),
+  "committed-state-svg-path" => image_path('illustrations/multi-editor_all_changes_committed_empty.svg') } }
   .text-center
     = icon('spinner spin 2x')
     %h2.clgray= _('Loading the GitLab IDE...')
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index e9a04e6c122586dc2e0a2d043a86849a8826479f..638c8b5a67258942b408e8eddc72e2bfcfb47204 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -2,11 +2,11 @@
 - provider_title = Gitlab::ImportSources.title(provider)
 
 %p.light
-  Select projects you want to import.
+  = import_githubish_choose_repository_message
 %hr
 %p
   = button_tag class: "btn btn-import btn-success js-import-all" do
-    Import all projects
+    = import_all_githubish_repositories_button_label
     = icon("spinner spin", class: "loading-icon")
 
 .table-responsive
@@ -16,9 +16,9 @@
     %colgroup.import-jobs-status-col
     %thead
       %tr
-        %th From #{provider_title}
-        %th To GitLab
-        %th Status
+        %th= _('From %{provider_title}') % { provider_title: provider_title }
+        %th= _('To GitLab')
+        %th= _('Status')
     %tbody
       - @already_added_projects.each do |project|
         %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
@@ -30,10 +30,12 @@
             - if project.import_status == 'finished'
               %span
                 %i.fa.fa-check
-                done
+                = _('Done')
             - elsif project.import_status == 'started'
               %i.fa.fa-spinner.fa-spin
-              started
+              = _('Started')
+            - elsif project.import_status == 'failed'
+              = _('Failed')
             - else
               = project.human_import_status_name
 
@@ -55,7 +57,9 @@
               = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
           %td.import-actions.job-status
             = button_tag class: "btn btn-import js-add-to-import" do
-              Import
+              = has_ci_cd_only_params? ? _('Connect') : _('Import')
               = icon("spinner spin", class: "loading-icon")
 
-.js-importer-status{ data: { jobs_import_path: "#{url_for([:jobs, :import, provider])}", import_path: "#{url_for([:import, provider])}" } }
+.js-importer-status{ data: { jobs_import_path: "#{url_for([:jobs, :import, provider])}",
+                             import_path: "#{url_for([:import, provider])}",
+                             ci_cd_only: "#{has_ci_cd_only_params?}" } }
diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml
index 9c2da3a3eec1884910885cdcacf5b32eb7243f4e..c63cf2b31cb454b3882a194d41714a05b3a99dcd 100644
--- a/app/views/import/github/new.html.haml
+++ b/app/views/import/github/new.html.haml
@@ -1,43 +1,28 @@
-- page_title "GitHub Import"
+- title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import')
+- page_title title
+- breadcrumb_title title
 - header_title "Projects", root_path
 
 %h3.page-title
-  = icon 'github', text: 'Import Projects from GitHub'
+  = icon 'github', text: import_github_title
 
 - if github_import_configured?
   %p
-    To import a GitHub project, you first need to authorize GitLab to access
-    the list of your GitHub repositories:
+    = import_github_authorize_message
 
-  = link_to 'List your GitHub repositories', status_import_github_path, class: 'btn btn-success'
+  = link_to _('List your GitHub repositories'), status_import_github_path(ci_cd_only: params[:ci_cd_only]), class: 'btn btn-success'
 
   %hr
 
 %p
-  - if github_import_configured?
-    Alternatively,
-  - else
-    To import a GitHub project,
-  you can use a
-  = succeed '.' do
-    = link_to 'Personal Access Token', 'https://github.com/settings/tokens'
-  When you create your Personal Access Token,
-  you will need to select the <code>repo</code> scope, so we can display a
-  list of your public and private repositories which are available for import.
+  = import_github_personal_access_token_message
 
 = form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do
   .form-group
-    = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40
-    = submit_tag 'List your GitHub repositories', class: 'btn btn-success'
+    = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: _('Personal Access Token'), size: 40
+    = submit_tag _('List your GitHub repositories'), class: 'btn btn-success'
 
 - unless github_import_configured?
   %hr
   %p
-    Note:
-    - if current_user.admin?
-      As an administrator you may like to configure
-    - else
-      Consider asking your GitLab administrator to configure
-    = link_to 'GitHub integration', help_page_path("integration/github")
-    which will allow login via GitHub and allow importing projects without
-    generating a Personal Access Token.
+    = import_configure_github_admin_message
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 0fe578a0036bf66092d95d977f07da94a15c530f..b00b972d9c92bdb6d6512e75d5a3eed0877ac9ef 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -1,6 +1,8 @@
-- page_title "GitHub Import"
+- title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import')
+- page_title title
+- breadcrumb_title title
 - header_title "Projects", root_path
 %h3.page-title
-  = icon 'github', text: 'Import Projects from GitHub'
+  = icon 'github', text: import_github_title
 
 = render 'import/githubish_status', provider: 'github'
diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index df5841d19118d22adbe622502d4d52beb0cd6796..dec85368d104a33224f533011acfb0f7957257db 100644
--- a/app/views/import/gitlab_projects/new.html.haml
+++ b/app/views/import/gitlab_projects/new.html.haml
@@ -13,13 +13,13 @@
       .form-group
         .input-group
           - if current_user.can_select_namespace?
-            .input-group-addon
+            .input-group-addon.has-tooltip{ title: root_url }
               = root_url
             = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1
 
           - else
-            .input-group-addon.static-namespace
-              #{root_url}#{current_user.username}/
+            .input-group-addon.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
+              #{user_url(current_user.username)}/
             = hidden_field_tag :namespace_id, value: current_user.namespace_id
     .form-group.col-xs-12.col-sm-6.project-path
       = label_tag :path, 'Project name', class: 'label-light'
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
index ad6213b4efd0b7d2dc22b491481e626392fac3d1..c2bb1216c5f2e08a4ec113f352c2632ae6c3d789 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, project_url(project)
+      = link_to project.full_name, project_url(project)
   - when Group
     - group = @member.source
     group
diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml
index b50537438a92236da6632f94b63c33e5317e4e5d..ddc1cdb24b5888e61416f8e79e53c7aa75d250e6 100644
--- a/app/views/layouts/_mailer.html.haml
+++ b/app/views/layouts/_mailer.html.haml
@@ -67,12 +67,8 @@
           %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
             %img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
             %div
-              %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications
-              &middot;
-              %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help
-            %div
-              You're receiving this email because of your account on
-              = succeed "." do
-                %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host
+              - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
+              - help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
+              = _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
 
         = yield :additional_footer
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index f0963cf9da8dc870c048ba511fb3c30826602c68..f67a8878c808590e891c9470ba140e476bda005c 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -6,6 +6,7 @@
     .mobile-overlay
     .alert-wrapper
       = render "layouts/broadcast"
+      = render 'layouts/header/read_only_banner'
       = yield :flash_message
       - unless @hide_breadcrumbs
         = render "layouts/nav/breadcrumbs"
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 257f7326409eaf1984ef069c27ecf5cf9d658241..6513b7191995b38c729666ca17134ad41fc071ca 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,5 +1,5 @@
 !!! 5
-%html.devise-layout-html
+%html.devise-layout-html{ class: system_message_class }
   = render "layouts/head"
   %body.ui_indigo.login-page.application.navless{ data: { page: body_data_page } }
     .page-wrap
@@ -16,7 +16,7 @@
               %h1
                 = brand_title
               = brand_image
-              - if brand_item&.description?
+              - if current_appearance&.description?
                 = brand_text
               - else
                 %h3 Open source software to collaborate on code
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
index 8718bb3db1a5e8955ccad9b9ac4d1ac37fc4852c..adf90cb7667c2b6139edfcfa7f73168352bfd917 100644
--- a/app/views/layouts/devise_empty.html.haml
+++ b/app/views/layouts/devise_empty.html.haml
@@ -1,5 +1,5 @@
 !!! 5
-%html{ lang: "en" }
+%html{ lang: "en", class: system_message_class }
   = render "layouts/head"
   %body.ui_indigo.login-page.application.navless
     = render "layouts/header/empty"
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index eb32f3933109caadcb42685a39f58e7111213983..6f53f5ac1ae20766bf799f9c385d64012de7312a 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -19,8 +19,8 @@
           %li.dropdown-bold-header GitLab
 
       - 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_issue = show_new_issue_link?(@project)
+        - merge_project = merge_request_source_project_for_project(@project)
         - create_project_snippet = can?(current_user, :create_project_snippet, @project)
         - if create_project_issue || merge_project || create_project_snippet
           %li.dropdown-bold-header This project
diff --git a/app/views/layouts/header/_read_only_banner.html.haml b/app/views/layouts/header/_read_only_banner.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f3d563c362fea00f9f8aa78263a39148f0c43338
--- /dev/null
+++ b/app/views/layouts/header/_read_only_banner.html.haml
@@ -0,0 +1,7 @@
+- message = read_only_message
+- if message
+  .flash-container.flash-container-page
+    .flash-notice
+      %div{ class: (container_class) }
+        %span
+          = message
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
index 198f30a1dc42eacd62936e3dfb2a5a7b4f55798e..8e20c4a4b2ac01aa9f20ded0fb09e1e321b1f7a1 100644
--- a/app/views/layouts/mailer.text.erb
+++ b/app/views/layouts/mailer.text.erb
@@ -1,4 +1,4 @@
 <%= yield -%>
 
----
+-- <%# signature marker %>
 You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
diff --git a/app/views/layouts/nav/projects_dropdown/_show.html.haml b/app/views/layouts/nav/projects_dropdown/_show.html.haml
index 59becb043d392765ee68d9b0b2425fb029a54aa9..5809d6f7feadb062a3b0a12e6365a789419d8458 100644
--- a/app/views/layouts/nav/projects_dropdown/_show.html.haml
+++ b/app/views/layouts/nav/projects_dropdown/_show.html.haml
@@ -1,4 +1,4 @@
-- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
+- project_meta = { id: @project.id, name: @project.name, namespace: @project.full_name, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
 .projects-dropdown-container
   .project-dropdown-sidebar.qa-projects-dropdown-sidebar
     %ul
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index b520f28123f0d273ffdd3eda4b7d524a52b1c33a..517d9aa3d99d2c65380311032e14e99d0b531ce4 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,6 +1,6 @@
 - issues_count = group_issues_count(state: 'opened')
 - merge_requests_count = group_merge_requests_count(state: 'opened')
-- issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index']
+- issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index', 'boards#index', 'boards#show']
 
 .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
   .nav-sidebar-inner-scroll
@@ -51,12 +51,19 @@
                 %strong.fly-out-top-item-name
                   #{ _('Issues') }
                 %span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count)
+
             %li.divider.fly-out-top-item
             = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
               = link_to issues_group_path(@group), title: 'List' do
                 %span
                   List
 
+            - if group_sidebar_link?(:boards)
+              = nav_link(path: ['boards#index', 'boards#show']) do
+                = link_to group_boards_path(@group), title: boards_link_text do
+                  %span
+                    = boards_link_text
+
             - if group_sidebar_link?(:labels)
               = nav_link(path: 'labels#index') do
                 = link_to group_labels_path(@group), title: 'Labels' do
@@ -105,7 +112,7 @@
             %span.nav-item-name
               Settings
           %ul.sidebar-sub-level-items
-            = nav_link(path: %w[groups#projects groups#edit ci_cd#show], html_options: { class: "fly-out-top-item" } ) do
+            = nav_link(path: %w[groups#projects groups#edit badges#index ci_cd#show], html_options: { class: "fly-out-top-item" } ) do
               = link_to edit_group_path(@group) do
                 %strong.fly-out-top-item-name
                   #{ _('Settings') }
@@ -115,6 +122,12 @@
                 %span
                   General
 
+            = nav_link(controller: :badges) do
+              = link_to group_settings_badges_path(@group), title: _('Project Badges') do
+                %span
+                  = _('Project Badges')
+
+
             = nav_link(path: 'groups#projects') do
               = link_to projects_group_path(@group), title: 'Projects' do
                 %span
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 059571f795f3f83fcbf07e37627e841f24241fe7..196db08cebdff52fc04ca12289288468eb3e6ad9 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -13,7 +13,7 @@
           .nav-icon-container
             = sprite_icon('project')
           %span.nav-item-name
-            Overview
+            Project
 
         %ul.sidebar-sub-level-items
           = nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
@@ -80,14 +80,6 @@
               = 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), class: 'shortcuts-container-registry' do
-            .nav-icon-container
-              = sprite_icon('disk')
-            %span.nav-item-name
-              Registry
-
       - if project_nav_tab? :issues
         = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
           = link_to project_issues_path(@project), class: 'shortcuts-issues' do
@@ -231,6 +223,14 @@
                   %span
                     Charts
 
+      - if project_nav_tab? :container_registry
+        = nav_link(controller: %w[projects/registry/repositories]) do
+          = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
+            .nav-icon-container
+              = sprite_icon('disk')
+            %span.nav-item-name
+              Registry
+
       - if project_nav_tab? :wiki
         = nav_link(controller: :wikis) do
           = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do
@@ -258,7 +258,7 @@
                   #{ _('Snippets') }
 
       - if project_nav_tab? :settings
-        = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
+        = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show]) do
           = link_to edit_project_path(@project), class: 'shortcuts-tree' do
             .nav-icon-container
               = sprite_icon('settings')
@@ -268,7 +268,7 @@
           %ul.sidebar-sub-level-items
             - can_edit = can?(current_user, :admin_project, @project)
             - if can_edit
-              = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show], html_options: { class: "fly-out-top-item" } ) do
+              = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show], html_options: { class: "fly-out-top-item" } ) do
                 = link_to edit_project_path(@project) do
                   %strong.fly-out-top-item-name
                     #{ _('Settings') }
@@ -281,6 +281,11 @@
               = link_to project_project_members_path(@project), title: 'Members' do
                 %span
                   Members
+            - if can_edit
+              = nav_link(controller: :badges) do
+                = link_to project_settings_badges_path(@project), title: _('Badges') do
+                  %span
+                    = _('Badges')
             - if can_edit
               = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
                 = link_to project_settings_integrations_path(@project), title: 'Integrations' do
diff --git a/app/views/layouts/notify.text.erb b/app/views/layouts/notify.text.erb
index de48f548a1b8f91af7c0cfb5a7bccff546d11704..9dc490efa9a3251e377f12c1a5a25db5333510ec 100644
--- a/app/views/layouts/notify.text.erb
+++ b/app/views/layouts/notify.text.erb
@@ -1,6 +1,6 @@
 <%= yield -%>
 
----
+-- <%# signature marker %>
 <%  if @target_url -%>
 <%    if @reply_by_email -%>
 <%=     "Reply to this email directly or view it on GitLab: #{@target_url}" -%>
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 6b847fb4b7cdb43401209be149e0ff067b89b1e9..6b51483810ee2d696a14081e67fc856eef4828fd 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -1,4 +1,4 @@
-- page_title       @project.name_with_namespace
+- page_title       @project.full_name
 - page_description @project.description    unless page_description
 - header_title     project_title(@project) unless header_title
 - nav              "project"
diff --git a/app/views/notify/issue_due_email.html.haml b/app/views/notify/issue_due_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e81144b8fcbe2ae1702f36328f7437929e9694d0
--- /dev/null
+++ b/app/views/notify/issue_due_email.html.haml
@@ -0,0 +1,12 @@
+%p.details
+  #{link_to @issue.author_name, user_url(@issue.author)}'s issue is due soon.
+
+- if @issue.assignees.any?
+  %p
+    Assignee: #{@issue.assignee_list}
+%p
+  This issue is due on: #{@issue.due_date.to_s(:medium)}
+
+- if @issue.description
+  %div
+  = markdown(@issue.description, pipeline: :email, author: @issue.author)
diff --git a/app/views/notify/issue_due_email.text.erb b/app/views/notify/issue_due_email.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..3c7a57a8a2e503aa4f936d49dee5a892e25c7dd3
--- /dev/null
+++ b/app/views/notify/issue_due_email.text.erb
@@ -0,0 +1,7 @@
+The following issue is due on <%= @issue.due_date %>:
+
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+Author:    <%= @issue.author_name %>
+Assignee:  <%= @issue.assignee_list %>
+
+<%= @issue.description %>
diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml
index f0ba7827cefb2cfcd6a52618db96ae9cfe85b69b..71c62f6be4e9ba61e6f99ce9b936393cd4690152 100644
--- a/app/views/notify/project_was_exported_email.html.haml
+++ b/app/views/notify/project_was_exported_email.html.haml
@@ -3,6 +3,6 @@
 %p
   The project export can be downloaded from:
   = link_to download_export_project_url(@project), rel: 'nofollow', download: '' do
-    = @project.name_with_namespace + " export"
+    = @project.full_name + " export"
 %p
   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 c476a39b6619de8c4e0f2128fa8a0eb26363e91e..1b6b1a81665d18e5c335d680bcc155483d5fb166 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -3,7 +3,7 @@
 %p
   The project is now located under
   = link_to project_url(@project) do
-    = @project.name_with_namespace
+    = @project.full_name
 %p
   To update the remote url in your local repository run (for ssh):
 %p{ style: "background: #f5f5f5; padding:10px; border:1px solid #ddd" }
diff --git a/app/views/notify/push_to_merge_request_email.html.haml b/app/views/notify/push_to_merge_request_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..67744ec1cee00c8619ec459a877224e2332401e0
--- /dev/null
+++ b/app/views/notify/push_to_merge_request_email.html.haml
@@ -0,0 +1,26 @@
+%h3
+  = @updated_by_user.name
+  pushed new commits to merge request
+  = link_to(@merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request))
+
+- if @existing_commits.any?
+  - count = @existing_commits.size
+  %ul
+    %li
+      - if count == 1
+        - commit_id = @existing_commits.first[:short_id]
+        = link_to(commit_id, project_commit_url(@merge_request.target_project, commit_id))
+      - else
+        = link_to(project_compare_url(@merge_request.target_project, from: @existing_commits.first[:short_id], to: @existing_commits.last[:short_id])) do
+          #{@existing_commits.first[:short_id]}...#{@existing_commits.last[:short_id]}
+      = precede '&nbsp;- ' do
+        - commits_text = "#{count} commit".pluralize(count)
+        #{commits_text} from branch `#{@merge_request.target_branch}`
+
+- if @new_commits.any?
+  %ul
+    - @new_commits.each do |commit|
+      %li
+        = link_to(commit[:short_id], project_commit_url(@merge_request.target_project, commit[:short_id]))
+        = precede ' - ' do
+          #{commit[:title]}
diff --git a/app/views/notify/push_to_merge_request_email.text.haml b/app/views/notify/push_to_merge_request_email.text.haml
new file mode 100644
index 0000000000000000000000000000000000000000..95759d127e2443c9806656df306ddd39ec71353b
--- /dev/null
+++ b/app/views/notify/push_to_merge_request_email.text.haml
@@ -0,0 +1,13 @@
+#{@updated_by_user.name} pushed new commits to merge request #{@merge_request.to_reference}
+\
+#{url_for(project_merge_request_url(@merge_request.target_project, @merge_request))}
+\
+- if @existing_commits.any?
+  - count = @existing_commits.size
+  - commits_id = count == 1 ? @existing_commits.first[:short_id] : "#{@existing_commits.first[:short_id]}...#{@existing_commits.last[:short_id]}"
+  - commits_text = "#{count} commit".pluralize(count)
+
+  * #{commits_id} - #{commits_text} from branch `#{@merge_request.target_branch}`
+\
+- @new_commits.each do |commit|
+  * #{commit[:short_id]} - #{raw commit[:title]}
diff --git a/app/views/peek/_bar.html.haml b/app/views/peek/_bar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b4d86e1601c527c6a28e15c998291fc395899e66
--- /dev/null
+++ b/app/views/peek/_bar.html.haml
@@ -0,0 +1,12 @@
+- return unless peek_enabled?
+
+#js-peek{ data: { env: Peek.env,
+         request_id: Peek.request_id,
+         peek_url: peek_routes.results_url,
+         profile_url: url_for(params.merge(lineprofiler: 'true')) },
+         class: Peek.env }
+
+#peek-view-performance-bar.hidden
+  = render_server_response_time
+  %span#serverstats
+  %ul.performance-bar
diff --git a/app/views/peek/views/_gc.html.haml b/app/views/peek/views/_gc.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..9fc83e56ee78726d3ed6087c496c176f85fdf8d8
--- /dev/null
+++ b/app/views/peek/views/_gc.html.haml
@@ -0,0 +1,7 @@
+- local_assigns.fetch(:view)
+
+%span.bold
+  %span{ title: 'Invoke Time', data: { defer_to: "#{view.defer_key}-gc_time" } }...
+  \/
+  %span{ title: 'Invoke Count', data: { defer_to: "#{view.defer_key}-invokes" } }...
+gc
diff --git a/app/views/peek/views/_gitaly.html.haml b/app/views/peek/views/_gitaly.html.haml
deleted file mode 100644
index a7d040d6821380a23dc371e3f19ebd47771a6fd0..0000000000000000000000000000000000000000
--- a/app/views/peek/views/_gitaly.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- local_assigns.fetch(:view)
-
-%strong
-  %span{ data: { defer_to: "#{view.defer_key}-duration" } } ...
-  \/
-  %span{ data: { defer_to: "#{view.defer_key}-calls" } } ...
-  Gitaly
diff --git a/app/views/peek/views/_host.html.haml b/app/views/peek/views/_host.html.haml
deleted file mode 100644
index 40769b5c6f669e2394b6c15680e06d388d2f19ea..0000000000000000000000000000000000000000
--- a/app/views/peek/views/_host.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%span.current-host
-  = truncate(view.hostname)
diff --git a/app/views/peek/views/_mysql2.html.haml b/app/views/peek/views/_mysql2.html.haml
deleted file mode 100644
index ac811a10ef5e93c671be117914944dbea316dd79..0000000000000000000000000000000000000000
--- a/app/views/peek/views/_mysql2.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- local_assigns.fetch(:view)
-
-= render 'peek/views/sql', view: view
-mysql
diff --git a/app/views/peek/views/_pg.html.haml b/app/views/peek/views/_pg.html.haml
deleted file mode 100644
index ee94c2f32749aa192a46a8657e6d9068b5284b6f..0000000000000000000000000000000000000000
--- a/app/views/peek/views/_pg.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- local_assigns.fetch(:view)
-
-= render 'peek/views/sql', view: view
-pg
diff --git a/app/views/peek/views/_rblineprof.html.haml b/app/views/peek/views/_rblineprof.html.haml
deleted file mode 100644
index 6c037930ca9905625d03d40485ef113bdc437fa3..0000000000000000000000000000000000000000
--- a/app/views/peek/views/_rblineprof.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-Profile:
-
-= link_to 'all', url_for(lineprofiler: 'true'), class: 'js-toggle-modal-peek-line-profile'
-\/
-= link_to 'app & lib', url_for(lineprofiler: 'app'), class: 'js-toggle-modal-peek-line-profile'
-\/
-= link_to 'views', url_for(lineprofiler: 'views'), class: 'js-toggle-modal-peek-line-profile'
diff --git a/app/views/peek/views/_redis.html.haml b/app/views/peek/views/_redis.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f7fba6c95fc42e94055f484d8ecb11c3ed4956c0
--- /dev/null
+++ b/app/views/peek/views/_redis.html.haml
@@ -0,0 +1,7 @@
+- local_assigns.fetch(:view)
+
+%span.bold
+  %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
+  \/
+  %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
+redis
diff --git a/app/views/peek/views/_sidekiq.html.haml b/app/views/peek/views/_sidekiq.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..7efbc05890d49b4746937f6f0b522cb76fb4012f
--- /dev/null
+++ b/app/views/peek/views/_sidekiq.html.haml
@@ -0,0 +1,7 @@
+- local_assigns.fetch(:view)
+
+%span.bold
+  %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
+  \/
+  %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
+sidekiq
diff --git a/app/views/peek/views/_sql.html.haml b/app/views/peek/views/_sql.html.haml
deleted file mode 100644
index dd8b524064f771d298d0326d3d7a993c43bca033..0000000000000000000000000000000000000000
--- a/app/views/peek/views/_sql.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-%strong
-  %a.js-toggle-modal-peek-sql
-    %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
-    \/
-    %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
-#modal-peek-pg-queries.modal{ tabindex: -1 }
-  .modal-dialog.modal-full
-    .modal-content
-      .modal-header
-        %button.close.btn.btn-link.btn-sm{ type: 'button', data: { dismiss: 'modal' } } X
-        %h4
-          SQL queries
-      .modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 022630955994d345ca0b2ef0266156a6cbf75425..9c95b6281baaa57c104d7f90b53ea49a477bab43 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -57,20 +57,8 @@
         = succeed '.' do
           = link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
     .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"
-          .input-group
-            .input-group-addon
-              = root_url
-            = f.text_field :username, required: true, class: 'form-control'
-        .help-block
-          Current path:
-          #{root_url}#{current_user.username}
-        .prepend-top-default
-          = f.button class: "btn btn-warning", type: "submit" do
-            = icon "spinner spin", class: "hidden loading-username"
-            Update username
+      - data = { initial_username: current_user.username, root_url: root_url, action_url: update_username_profile_path(format: :json) }
+      #update-username{ data: data }
   %hr
 
 .row.prepend-top-default
diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml
index fe1cf802971dde2e96234f2c1284c847bedc4c29..c7094800fb2fda36dc8126347541d30256c1e3ef 100644
--- a/app/views/profiles/chat_names/_chat_name.html.haml
+++ b/app/views/profiles/chat_names/_chat_name.html.haml
@@ -4,7 +4,7 @@
   %td
     %strong
       - if can?(current_user, :read_project, project)
-        = link_to project.name_with_namespace, project_path(project)
+        = link_to project.full_name, project_path(project)
       - else
         .light N/A
   %td
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 457583cfd3520b7bcead6a380d0211f469c5cd9c..1e206def7eef7959dd283b4b35adc451bd69cdfe 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -12,7 +12,9 @@
       Add an SSH key
     %p.profile-settings-content
       Before you can add an SSH key you need to
-      = link_to "generate it.", help_page_path("ssh/README")
+      = link_to "generate one", help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair')
+      or use an
+      = link_to "existing key.", help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair')
     = render 'form'
     %hr
     %h5
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 78848542810d2c5eee69cedb8f67e13f97a8989f..9b87a7aaca8474a56ee52666788771593bf52112 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -2,7 +2,6 @@
 - page_title "Personal Access Tokens"
 - @content_class = "limit-container-width" unless fluid_layout
 
-
 .row.prepend-top-default
   .col-lg-4.profile-settings-sidebar
     %h4.prepend-top-0
@@ -19,8 +18,10 @@
         %h5.prepend-top-0
           Your New Personal Access Token
         .form-group
-          = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
-          = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left")
+          .input-group
+            = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
+            %span.input-group-btn
+              = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left", class: "btn-default btn-clipboard")
           %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
 
       %hr
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 1bd10018b409b2004d28c31614c44ae3f5100bb3..d1eae05c46ceca20ef22b89e98911eaaa58d42e4 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -20,7 +20,7 @@
       - else
         %p
           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'))}.
+          More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}.
         .row.append-bottom-10
           .col-md-4
             = raw @qr_code
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index b55dc3dce5c6fbcaff22e1b30ee60660967c7cc3..b387e38c1a68fbc4ad4e93c6cc1f017a0d81d212 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -3,6 +3,4 @@
   = link_to 'Cancel', cancel_path,
             class: 'btn btn-cancel', data: {confirm: leave_edit_message}
 
-  - unless can?(current_user, :push_code, @project)
-    .inline.prepend-left-10
-      = commit_in_fork_help
+  = render 'shared/projects/edit_information'
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index 5dfe973f33cfe1754f49ae0a451ff6d545a50e41..1e7d9444986ce4a07573bc58a9353c2058da3268 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -7,7 +7,7 @@
   .settings-header
     %h4
       Export project
-    %button.btn.js-settings-toggle
+    %button.btn.js-settings-toggle{ type: 'button' }
       = expanded ? 'Collapse' : 'Expand'
     %p
       Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
@@ -21,11 +21,11 @@
           %li Project uploads
           %li Project configuration including web hooks and services
           %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
+          %li LFS objects
         %p
           The following items will NOT be exported:
         %ul
           %li Job traces and artifacts
-          %li LFS objects
           %li Container registry images
           %li CI variables
           %li Any encrypted tokens
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index b565f14747a92658c75fea35492757b2f4594855..043057e79ee78cf80815b2ee2b617e0169cfac3c 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -23,6 +23,15 @@
             - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
             = deleted_message % { project_name: fork_source_name(@project) }
 
+    .project-badges.prepend-top-default.append-bottom-default
+      - @project.badges.each do |badge|
+        %a.append-right-8{ href: badge.rendered_link_url(@project),
+          target: '_blank',
+          rel: 'noopener noreferrer' }>
+          %img.project-badge{ src: badge.rendered_image_url(@project),
+            'aria-hidden': true,
+            alt: '' }>
+
     .project-repo-buttons
       .count-buttons
         = render 'projects/buttons/star'
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 6f5eb828902a8eb8ec6f28cb3b2ed103bb5d7867..f6d396c81271025c653fcb50fd7b8714d41ac7b0 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -13,6 +13,7 @@
 
         #{time_ago_with_tooltip(event.created_at)}
 
-      .pull-right
-        = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
-          #{ _('Create merge request') }
+      - if can?(current_user, :create_merge_request_in, event.project.default_merge_request_target)
+        .flex-right
+          = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
+            #{ _('Create merge request') }
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index d367bd6be7b1431fa05fd3331b3cdae7523f00b1..241bc3dbca0a8f6e5e6f42535f2431ac44d87d06 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -1,18 +1,20 @@
 - visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
+- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
 
 .row{ id: project_name_id }
+  = f.hidden_field :ci_cd_only, value: ci_cd_only
   .form-group.project-path.col-sm-6
     = f.label :namespace_id, class: 'label-light' do
       %span
         Project path
     .input-group
       - if current_user.can_select_namespace?
-        .input-group-addon
+        .input-group-addon.has-tooltip{ title: root_url }
           = root_url
         = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1}
 
       - else
-        .input-group-addon.static-namespace
+        .input-group-addon.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
           #{user_url(current_user.username)}/
         = f.hidden_field :namespace_id, value: current_user.namespace_id
   .form-group.project-path.col-sm-6
diff --git a/app/views/projects/_visibility_select.html.haml b/app/views/projects/_visibility_select.html.haml
deleted file mode 100644
index 4026b9e3c46d6f1e0b6b8a9b5305ed80959013e2..0000000000000000000000000000000000000000
--- a/app/views/projects/_visibility_select.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- if can_change_visibility_level?(@project, current_user)
-  .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)
-    %strong
-      = visibility_level_label(@project.visibility_level)
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 5d48a35dc4cf4684f88a9f09f515f069fc3c8c66..48ff66900be9ba6cbb59fe26d830248b38cab959 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -17,6 +17,4 @@
             = submit_tag _("Create directory"), class: 'btn btn-create'
             = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
 
-            - unless can?(current_user, :push_code, @project)
-              .inline.prepend-left-10
-                = commit_in_fork_help
+            = render 'shared/projects/edit_information'
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index f1324c61500579f0a4e5710d040bcb4199149dec..182d02376bf7cf3f693fd542b5cc67efdadfb3ce 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -24,6 +24,4 @@
               = button_title
             = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
 
-            - unless can?(current_user, :push_code, @project)
-              .inline.prepend-left-10
-                = commit_in_fork_help
+            = render 'shared/projects/edit_information'
diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml
index 3124443b4e465d54b8cd22ca9983358c82212d64..b9663bbba1525ff366e12f466f64ae0563c79956 100644
--- a/app/views/projects/blob/_viewer.html.haml
+++ b/app/views/projects/blob/_viewer.html.haml
@@ -2,13 +2,16 @@
 - render_error = viewer.render_error
 - rich_type = viewer.type == :rich ? viewer.partial_name : nil
 - load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
+- external_embed = local_assigns.fetch(:external_embed, false)
 
-- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
+- viewer_url = local_assigns.fetch(:viewer_url) { url_for(safe_params.merge(viewer: viewer.type, format: :json)) } if load_async
 .blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
   - if render_error
     = render 'projects/blob/render_error', viewer: viewer
   - elsif load_async
     = render viewer.loading_partial_path, viewer: viewer
+  - elsif external_embed
+    = render 'projects/blob/viewers/highlight_embed', blob: viewer.blob
   - else
     - viewer.prepare!
 
diff --git a/app/views/projects/blob/viewers/_highlight_embed.html.haml b/app/views/projects/blob/viewers/_highlight_embed.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..9bd4ef6ad0bf389ba58766157d801fe61318b998
--- /dev/null
+++ b/app/views/projects/blob/viewers/_highlight_embed.html.haml
@@ -0,0 +1,7 @@
+.file-content.code.js-syntax-highlight
+  .line-numbers
+    - if blob.data.present?
+      - blob.data.each_line.each_with_index do |_, index|
+        %span.diff-line-num= index + 1
+  .blob-content{ data: { blob_id: blob.id } }
+    = highlight(blob.path, blob.data, repository: nil, plain: blob.no_highlighting?)
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 1da0e865a412ec792b597a7e2144c5dd694c6661..d0c01f95cb70253bf6f417981e01bba27ea1a51e 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -4,82 +4,83 @@
 - diverging_commit_counts = @repository.diverging_commit_counts(branch)
 - number_commits_behind = diverging_commit_counts[:behind]
 - number_commits_ahead = diverging_commit_counts[:ahead]
-- 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 project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
-      = sprite_icon('fork', size: 12)
-      = branch.name
-    &nbsp;
-    - if branch.name == @repository.root_ref
-      %span.label.label-primary default
-    - elsif merged
-      %span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
-        = s_('Branches|merged')
+- merge_project = merge_request_source_project_for_project(@project)
+%li{ class: "branch-item js-branch-#{branch.name}" }
+  .branch-info
+    .branch-title
+      = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name' do
+        = sprite_icon('fork', size: 12)
+        = branch.name
+      &nbsp;
+      - if branch.name == @repository.root_ref
+        %span.label.label-primary default
+      - elsif merged
+        %span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
+          = s_('Branches|merged')
 
-    - if protected_branch?(@project, branch)
-      %span.label.label-success
-        = s_('Branches|protected')
-    .controls.hidden-xs<
-      - if merge_project && create_mr_button?(@repository.root_ref, branch.name)
-        = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
-          = _('Merge request')
+      - if protected_branch?(@project, branch)
+        %span.label.label-success
+          = s_('Branches|protected')
 
-      - if branch.name != @repository.root_ref
-        = 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: s_('Branches|Compare') do
-          = s_('Branches|Compare')
+    .block-truncated
+      - if commit
+        = render 'projects/branches/commit', commit: commit, project: @project
+      - else
+        = s_('Branches|Cant find HEAD commit for this branch')
 
-      = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
+  - if branch.name != @repository.root_ref
+    .divergence-graph.hidden-xs{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
+      default_branch: @repository.root_ref,
+      number_commits_ahead: diverging_count_label(number_commits_ahead) } }
+      .graph-side
+        .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
+        %span.count.count-behind= diverging_count_label(number_commits_behind)
+      .graph-separator
+      .graph-side
+        .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
+        %span.count.count-ahead= diverging_count_label(number_commits_ahead)
 
-      - if can?(current_user, :push_code, @project)
-        - if branch.name == @project.repository.root_ref
-          %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
-            disabled: true,
-            title: s_('Branches|The default branch cannot be deleted') }
-            = icon("trash-o")
-        - elsif protected_branch?(@project, branch)
-          - if can?(current_user, :delete_protected_branch, @project)
-            %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
-              title: s_('Branches|Delete protected branch'),
-              data: { toggle: "modal",
-                target: "#modal-delete-branch",
-                delete_path: project_branch_path(@project, branch.name),
-                branch_name: branch.name,
-                is_merged: ("true" if merged) } }
-              = icon("trash-o")
-          - else
-            %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
-              disabled: true,
-              title: s_('Branches|Only a project master or owner can delete a protected branch') }
-              = icon("trash-o")
-        - else
-          = link_to project_branch_path(@project, branch.name),
-            class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
-            title: s_('Branches|Delete branch'),
-            method: :delete,
-            data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
-            remote: true,
-            'aria-label' => s_('Branches|Delete branch') do
-            = icon("trash-o")
+  .controls.hidden-xs<
+    - if merge_project && create_mr_button?(@repository.root_ref, branch.name)
+      = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
+        = _('Merge request')
 
     - if branch.name != @repository.root_ref
-      .divergence-graph{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
-        default_branch: @repository.root_ref,
-        number_commits_ahead: diverging_count_label(number_commits_ahead) } }
-        .graph-side
-          .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
-          %span.count.count-behind= diverging_count_label(number_commits_behind)
-        .graph-separator
-        .graph-side
-          .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
-          %span.count.count-ahead= diverging_count_label(number_commits_ahead)
+      = 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: s_('Branches|Compare') do
+        = s_('Branches|Compare')
 
+    = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
 
-  - if commit
-    = render 'projects/branches/commit', commit: commit, project: @project
-  - else
-    %p
-      = s_('Branches|Cant find HEAD commit for this branch')
+    - if can?(current_user, :push_code, @project)
+      - if branch.name == @project.repository.root_ref
+        %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
+          disabled: true,
+          title: s_('Branches|The default branch cannot be deleted') }
+          = icon("trash-o")
+      - elsif protected_branch?(@project, branch)
+        - if can?(current_user, :push_to_delete_protected_branch, @project)
+          %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
+            title: s_('Branches|Delete protected branch'),
+            data: { toggle: "modal",
+              target: "#modal-delete-branch",
+              delete_path: project_branch_path(@project, branch.name),
+              branch_name: branch.name,
+              is_merged: ("true" if merged) } }
+            = icon("trash-o")
+        - else
+          %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
+            disabled: true,
+            title: s_('Branches|Only a project master or owner can delete a protected branch') }
+            = icon("trash-o")
+      - else
+        = link_to project_branch_path(@project, branch.name),
+          class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
+          title: s_('Branches|Delete branch'),
+          method: :delete,
+          data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
+          remote: true,
+          'aria-label' => s_('Branches|Delete branch') do
+          = icon("trash-o")
diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..12e5a8e8d69db05b16a6a71dc2271e3afdd81398
--- /dev/null
+++ b/app/views/projects/branches/_panel.html.haml
@@ -0,0 +1,19 @@
+- branches = local_assigns.fetch(:branches)
+- state = local_assigns.fetch(:state)
+- panel_title = local_assigns.fetch(:panel_title)
+- show_more_text = local_assigns.fetch(:show_more_text)
+- project = local_assigns.fetch(:project)
+- overview_max_branches = local_assigns.fetch(:overview_max_branches)
+
+- return unless branches.any?
+
+.panel.panel-default.prepend-top-10
+  .panel-heading
+    %h4.panel-title
+      = panel_title
+  %ul.content-list.all-branches
+    - branches.first(overview_max_branches).each do |branch|
+      = render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
+  - if branches.size > overview_max_branches
+    .panel-footer.text-center
+      = link_to show_more_text, project_branches_filtered_path(project, state: state), id: "state-#{state}", data: { state: state }
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index fb7707643641d8c7d58762d963e7fd792de3bf74..5dcc72d826306332005dcad7a8f383f15bd14030 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -3,26 +3,35 @@
 
 %div{ class: container_class }
   .top-area.adjust
-    - if can?(current_user, :admin_project, @project)
-      .nav-text
-        - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
-        = s_('Branches|Protected branches can be managed in %{project_settings_link}').html_safe % { project_settings_link: project_settings_link }
+    %ul.nav-links.issues-state-filters
+      %li{ class: active_when(@mode == 'overview') }>
+        = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches')
+
+      %li{ class: active_when(@mode == 'active') }>
+        = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches')
+
+      %li{ class: active_when(@mode == 'stale') }>
+        = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches')
+
+      %li{ class: active_when(!%w[overview active stale].include?(@mode)) }>
+        = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches')
 
     .nav-controls
-      = form_tag(filter_branches_path, method: :get) do
+      = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do
         = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
 
-      .dropdown.inline>
-        %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
-          %span.light
-            = branches_sort_options_hash[@sort]
-          = icon('chevron-down')
-        %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
-          %li.dropdown-header
-            = s_('Branches|Sort by')
-          - branches_sort_options_hash.each do |value, title|
-            %li
-              = link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value)
+      - unless @mode == 'overview'
+        .dropdown.inline>
+          %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
+            %span.light
+              = branches_sort_options_hash[@sort]
+            = icon('chevron-down')
+          %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
+            %li.dropdown-header
+              = s_('Branches|Sort by')
+            - branches_sort_options_hash.each do |value, title|
+              %li
+                = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value)
 
       - if can? current_user, :push_code, @project
         = link_to project_merged_branches_path(@project),
@@ -35,7 +44,17 @@
         = link_to new_project_branch_path(@project), class: 'btn btn-create' do
           = s_('Branches|New branch')
 
-  - if @branches.any?
+  - if can?(current_user, :admin_project, @project)
+    - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
+    .row-content-block
+      %h5
+        = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link }
+
+  - if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?)
+    = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches
+    = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches
+
+  - elsif @branches.any?
     %ul.content-list.all-branches
       - @branches.each do |branch|
         = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name)
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index fa9a9bfc8f707dc543e39e04a335522ddcd8724f..f49f6e630d2ac3dac6fa4f472fbc418f342c1dc5 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,6 +1,7 @@
 - pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) }
 
 - if !project.empty_repo? && can?(current_user, :download_code, project)
+  - archive_prefix = "#{project.path}-#{ref.tr('/', '-')}"
   .project-action-button.dropdown.inline>
     %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
       = sprite_icon('download')
@@ -10,16 +11,16 @@
       %li.dropdown-header
         #{ _('Source code') }
       %li
-        = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
+        = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'zip'), rel: 'nofollow', download: '' do
           %span=  _('Download zip')
       %li
-        = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
+        = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.gz'), rel: 'nofollow', download: '' do
           %span= _('Download tar.gz')
       %li
-        = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
+        = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.bz2'), rel: 'nofollow', download: '' do
           %span= _('Download tar.bz2')
       %li
-        = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
+        = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar'), rel: 'nofollow', download: '' do
           %span= _('Download tar')
 
       - if pipeline && pipeline.latest_builds_with_artifacts.any?
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 18e948ce35accc84d67df04c03add0fd65c9d3d9..2e86a7d36d74893c761eb8b470d8bf18a573a748 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -1,13 +1,17 @@
-- if current_user
+- can_create_issue = show_new_issue_link?(@project)
+- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
+- can_push_code = can?(current_user, :push_code, @project)
+- create_mr_from_new_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
+- merge_project = merge_request_source_project_for_project(@project)
+
+- show_menu = can_create_issue || can_create_project_snippet || can_push_code || create_mr_from_new_fork || merge_project
+
+- if show_menu
   .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...') }
       = icon('plus')
       = icon("caret-down")
     %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
-      - can_create_issue = can?(current_user, :create_issue, @project)
-      - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
-      - can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
-
       - if can_create_issue || merge_project || can_create_project_snippet
         %li.dropdown-header= _('This project')
 
@@ -20,17 +24,17 @@
       - if can_create_project_snippet
         %li= link_to _('New snippet'), new_project_snippet_path(@project)
 
-      - if can?(current_user, :push_code, @project)
+      - if can_push_code
         %li.dropdown-header= _('This repository')
 
-      - if can?(current_user, :push_code, @project)
+      - if can_push_code
         %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
         - unless @project.empty_repo?
           %li= link_to _('New branch'), new_project_branch_path(@project)
           %li= link_to _('New tag'), new_project_tag_path(@project)
-      - elsif current_user && current_user.already_forked?(@project)
+      - elsif can_collaborate_with_project?(@project)
         %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
-      - elsif can?(current_user, :fork_project, @project)
+      - elsif create_mr_from_new_fork
         - 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 }
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 0cd2d45c74bc34b8ee1c68b67e2f8f6abd55e996..9126476e79eb7a511f464230a978b91921464fc5 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -63,7 +63,7 @@
   - if admin
     %td
       - if job.project
-        = link_to job.project.name_with_namespace, admin_project_path(job.project)
+        = link_to job.project.full_name, admin_project_path(job.project)
     %td
       - if job.try(:runner)
         = runner_link(job.runner)
diff --git a/app/views/ci/lints/_create.html.haml b/app/views/projects/ci/lints/_create.html.haml
similarity index 100%
rename from app/views/ci/lints/_create.html.haml
rename to app/views/projects/ci/lints/_create.html.haml
diff --git a/app/views/projects/ci/lints/show.html.haml b/app/views/projects/ci/lints/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6ca8152183dc53e43d6e5a4b8b083aa9a6ec26a6
--- /dev/null
+++ b/app/views/projects/ci/lints/show.html.haml
@@ -0,0 +1,27 @@
+- page_title "CI Lint"
+- page_description "Validate your GitLab CI configuration file"
+- content_for :library_javascripts do
+  = page_specific_javascript_tag('lib/ace.js')
+
+%h2 Check your .gitlab-ci.yml
+
+.project-ci-linter
+  .row
+    = form_tag project_ci_lint_path(@project), method: :post do
+      .form-group
+        .col-sm-12
+          .file-holder
+            .js-file-title.file-title.clearfix
+              Content of .gitlab-ci.yml
+            #ci-editor.ci-editor= @content
+          = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
+      .col-sm-12
+        .pull-left.prepend-top-10
+          = submit_tag('Validate', class: 'btn btn-success submit-yml')
+        .pull-right.prepend-top-10
+          = button_tag('Clear', type: 'button', class: 'btn btn-default clear-yml')
+
+  .row.prepend-top-20
+    .col-sm-12
+      .results.project-ci-template
+        = render partial: 'create' if defined?(@status)
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml
index 112dde66ff7620edd811bb64ac07b81e92ee7dec..5f49d03b1bb2e0df0e8015c9adcc29d1c069626f 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/projects/clusters/_empty_state.html.haml
@@ -7,5 +7,6 @@
       - link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
       %p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
 
-      .text-center
-        = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
+      - if can?(current_user, :create_cluster, @project)
+        .text-center
+          = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/projects/clusters/_integration_form.html.haml
index d4c0cd82ce3e6a7e5e0c679f2d14f6f44293847c..db97203a2aa202cb2981773cb24616e14bd5598a 100644
--- a/app/views/projects/clusters/_integration_form.html.haml
+++ b/app/views/projects/clusters/_integration_form.html.haml
@@ -20,6 +20,12 @@
           = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
           = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
 
+  .form-group
+    %h5= s_('ClusterIntegration|Security')
+    %p
+      = s_("ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application.")
+      = link_to s_("ClusterIntegration|Learn more about security configuration"),  help_page_path('user/project/clusters/index.md', anchor: 'security-implications')
+
   .form-group
     %h5= s_('ClusterIntegration|Environment scope')
     %p
diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml
index ebb7d247125813efbeafef2b784a92bfb00f162d..e004966bdcce7cee169d7779c576f7b0e5624645 100644
--- a/app/views/projects/clusters/new.html.haml
+++ b/app/views/projects/clusters/new.html.haml
@@ -8,6 +8,6 @@
     %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up Kubernetes cluster integration')
 
     %p= s_('ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab')
-    = link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
+    = link_to s_('ClusterIntegration|Create on Google Kubernetes Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
     %p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster')
     = link_to s_('ClusterIntegration|Add an existing Kubernetes cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index 2ee0eafcf1af341df963ac2ce9360881d331a798..4c510293204aa66b81cea241663c28bfdd5f166b 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -31,7 +31,7 @@
   %section.settings#js-cluster-details{ class: ('expanded' if expanded) }
     .settings-header
       %h4= s_('ClusterIntegration|Kubernetes cluster details')
-      %button.btn.js-settings-toggle
+      %button.btn.js-settings-toggle{ type: 'button' }
         = expanded ? 'Collapse' : 'Expand'
       %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
     .settings-content
@@ -43,7 +43,7 @@
   %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
     .settings-header
       %h4= _('Advanced settings')
-      %button.btn.js-settings-toggle
+      %button.btn.js-settings-toggle{ type: 'button' }
         = expanded ? 'Collapse' : 'Expand'
       %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
     .settings-content
diff --git a/app/views/projects/clusters/user/_header.html.haml b/app/views/projects/clusters/user/_header.html.haml
index 04c7ce96a4b2aaf4422dcc5863f6c96aec19ca15..37f6a788518cfa1c5dc31dc8d8a68814b7b83442 100644
--- a/app/views/projects/clusters/user/_header.html.haml
+++ b/app/views/projects/clusters/user/_header.html.haml
@@ -1,5 +1,5 @@
 %h4.prepend-top-20
   = s_('ClusterIntegration|Enter the details for your Kubernetes cluster')
 %p
-  - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+  - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index', anchor: 'adding-an-existing-kubernetes-cluster'), target: '_blank', rel: 'noopener noreferrer')
   = s_('ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes').html_safe % { link_to_help_page: link_to_help_page }
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index 93407956f56261b51a44097e890b6620437f314b..21e4664d4e4e8f0dc0eac0841e8ca177a5fb9d89 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -35,6 +35,4 @@
             = submit_tag label, class: 'btn btn-create'
             = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
 
-            - unless can?(current_user, :push_code, @project)
-              .inline.prepend-left-10
-                = commit_in_fork_help
+            = render 'shared/projects/edit_information'
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 461129a3e0e9de4e28c39ff2c9eccabbc2b5ff9a..213c4c90a0eb18dd352e0126058dc6ff8cd181d5 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,3 +1,5 @@
+- can_collaborate = can_collaborate_with_project?(@project)
+
 .page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
   .header-main-content
     = render partial: 'signature', object: @commit.signature
@@ -32,12 +34,13 @@
         %li.visible-xs-block.visible-sm-block
           = link_to project_tree_path(@project, @commit) do
             #{ _('Browse Files') }
-        - unless @commit.has_been_reverted?(current_user)
+        - if can_collaborate && !@commit.has_been_reverted?(current_user)
           %li.clearfix
             = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
-        %li.clearfix
-          = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
-        - if can_collaborate_with_project?
+        - if can_collaborate
+          %li.clearfix
+            = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
+        - if can?(current_user, :push_code, @project)
           %li.clearfix
             = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
         %li.divider
@@ -49,10 +52,10 @@
 
 .commit-box{ data: { project_path: project_path(@project) } }
   %h3.commit-title
-    = markdown(@commit.title, pipeline: :single_line, author: @commit.author)
+    = markdown_field(@commit, :title)
   - if @commit.description.present?
     %pre.commit-description
-      = preserve(markdown(@commit.description, pipeline: :single_line, author: @commit.author))
+      = preserve(markdown_field(@commit, :description))
 
 .info-well
   .well-segment.branch-info
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index abb292f8f279711371de65da04782edca410540d..541ae905246929f9d8b0c762fbf9e694e52a6269 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -17,6 +17,6 @@
 
   .limited-width-notes
     = render "shared/notes/notes_with_form", :autocomplete => true
-    - if can_collaborate_with_project?
+    - if can_collaborate_with_project?(@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 50f7e7a3a336384a0f3317f140605460da77ac31..640b5ecf99e3dd86247268020ec12b2a89039d77 100644
--- a/app/views/projects/commits/_commit.atom.builder
+++ b/app/views/projects/commits/_commit.atom.builder
@@ -10,5 +10,5 @@ xml.entry do
     xml.email commit.author_email
   end
 
-  xml.summary markdown(commit.description, pipeline: :single_line), type: 'html'
+  xml.summary markdown_field(commit, :description), type: 'html'
 end
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 078bd0eee63aaa5bd3dc7e1a8fe95251f08b5ae8..3fd0fa348b3d3852000e37c361024617e3097906 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -5,6 +5,7 @@
 
 - link = commit_path(project, commit, merge_request: merge_request)
 - cache_key = [project.full_path,
+               ref,
                commit.id,
                Gitlab::CurrentSettings.current_application_settings,
                @path.presence,
@@ -21,8 +22,11 @@
       = author_avatar(commit, size: 36)
 
     .commit-detail.flex-list
-      .commit-content
-        = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
+      .commit-content.qa-commit-content
+        - if view_details && merge_request
+          = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
+        - else
+          = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
         %span.commit-row-message.visible-xs-inline
           &middot;
           = commit.short_id
@@ -51,10 +55,10 @@
         - if commit.status(ref)
           = render_commit_status(commit, ref: ref)
 
-        .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
-        = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
-        = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
-        = link_to_browse_code(project, commit)
+        .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
 
-        - if view_details && merge_request
-          = link_to "View details", project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "btn btn-default"
+        .commit-sha-group
+          .label.label-monospace
+            = commit.short_id
+          = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
+          = link_to_browse_code(project, commit)
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 02395b6eb9b9156824ce274d056d1c01223e9b54..5041f32261263d66232495cf0e3451b33790cc5f 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -1,7 +1,5 @@
 - @no_container = true
 - page_title "Cycle Analytics"
-- content_for :page_specific_javascripts do
-  = webpack_bundle_tag('common_vue')
 
 #cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
   - if @cycle_analytics_no_data
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index 75dd4c9ae15fb1a096a46389f9c669a7bb700f51..7dd8dc28e5b58c5ac8e9a80530f882341b704b8f 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -3,7 +3,7 @@
   .settings-header
     %h4
       Deploy Keys
-    %button.btn.js-settings-toggle.qa-expand-deploy-keys
+    %button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
       = 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.
diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f8db30df7b4a861dd08dd63d19fe84e967e4c869
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_form.html.haml
@@ -0,0 +1,29 @@
+%p.profile-settings-content
+  = s_("DeployTokens|Pick a name for the application, and we'll give you a unique deploy token.")
+
+= form_for token, url: create_deploy_token_namespace_project_settings_repository_path(project.namespace, project), method: :post do |f|
+  = form_errors(token)
+
+  .form-group
+    = f.label :name, class: 'label-light'
+    = f.text_field :name, class: 'form-control', required: true
+
+  .form-group
+    = f.label :expires_at, class: 'label-light'
+    = f.text_field :expires_at, class: 'datepicker form-control', value: f.object.expires_at
+
+  .form-group
+    = f.label :scopes, class: 'label-light'
+    %fieldset
+      = f.check_box :read_repository
+      = label_tag ("deploy_token_read_repository"), 'read_repository'
+      %span= s_('DeployTokens|Allows read-only access to the repository')
+
+    - if container_registry_enabled?(project)
+      %fieldset
+        = f.check_box :read_registry
+        = label_tag ("deploy_token_read_registry"), 'read_registry'
+        %span= s_('DeployTokens|Allows read-only access to the registry images')
+
+  .prepend-top-default
+    = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success'
diff --git a/app/views/projects/deploy_tokens/_index.html.haml b/app/views/projects/deploy_tokens/_index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..50e5950ced473c9f405dbcf9255c13c8bc82c231
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_index.html.haml
@@ -0,0 +1,18 @@
+- expanded = expand_deploy_tokens_section?(@new_deploy_token)
+
+%section.settings.no-animate{ class: ('expanded' if expanded) }
+  .settings-header
+    %h4= s_('DeployTokens|Deploy Tokens')
+    %button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
+      = expanded ? 'Collapse' : 'Expand'
+    %p
+      = s_('DeployTokens|Deploy tokens allow read-only access to your repository and registry images.')
+  .settings-content
+    - if @new_deploy_token.persisted?
+      = render 'projects/deploy_tokens/new_deploy_token', deploy_token: @new_deploy_token
+    - else
+      %h5.prepend-top-0
+        = s_('DeployTokens|Add a deploy token')
+      = render 'projects/deploy_tokens/form', project: @project, token: @new_deploy_token, presenter: @deploy_tokens
+      %hr
+    = render 'projects/deploy_tokens/table', project: @project, active_tokens: @deploy_tokens
diff --git a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..1e715681e59d0cefeb0cd03af0afd46cc32e3c30
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
@@ -0,0 +1,14 @@
+.created-deploy-token-container
+  %h5.prepend-top-0
+    = s_('DeployTokens|Your New Deploy Token')
+
+  .form-group
+    = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+    = clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username to clipboard'), placement: 'left')
+    %span.deploy-token-help-block.prepend-top-5.text-success= s_("DeployTokens|Use this username as a login.")
+
+  .form-group
+    = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+    = clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token to clipboard'), placement: 'left')
+    %span.deploy-token-help-block.prepend-top-5.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.")
+%hr
diff --git a/app/views/projects/deploy_tokens/_revoke_modal.html.haml b/app/views/projects/deploy_tokens/_revoke_modal.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..085964fe22e0f06ba78f9682ff63305a0bd88f18
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_revoke_modal.html.haml
@@ -0,0 +1,17 @@
+.modal{ id: "revoke-modal-#{token.id}" }
+  .modal-dialog
+    .modal-content
+      .modal-header
+        %h4.modal-title.pull-left
+          = s_('DeployTokens|Revoke')
+          %b #{token.name}?
+        %button.close{ 'aria-label' => _('Close'), 'data-dismiss' => 'modal', type: 'button' }
+          %span{ 'aria-hidden' => 'true' } &times;
+      .modal-body
+        %p
+          = s_('DeployTokens|You are about to revoke')
+          %b #{token.name}.
+          = s_('DeployTokens|This action cannot be undone.')
+      .modal-footer
+        %a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' }= _('Cancel')
+        = link_to s_('DeployTokens|Revoke %{name}') % { name: token.name }, revoke_project_deploy_token_path(project, token), method: :put, class: 'btn btn-danger'
diff --git a/app/views/projects/deploy_tokens/_table.html.haml b/app/views/projects/deploy_tokens/_table.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5013a9b250dca97d082cf28cf29b3ce7029afb36
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_table.html.haml
@@ -0,0 +1,31 @@
+%h5= s_("DeployTokens|Active Deploy Tokens (%{active_tokens})") % { active_tokens: active_tokens.length }
+
+- if active_tokens.present?
+  .table-responsive.deploy-tokens
+    %table.table
+      %thead
+        %tr
+          %th= s_('DeployTokens|Name')
+          %th= s_('DeployTokens|Username')
+          %th= s_('DeployTokens|Created')
+          %th= s_('DeployTokens|Expires')
+          %th= s_('DeployTokens|Scopes')
+          %th
+      %tbody
+        - active_tokens.each do |token|
+          %tr
+            %td= token.name
+            %td= token.username
+            %td= token.created_at.to_date.to_s(:medium)
+            %td
+              - if token.expires?
+                %span{ class: ('text-warning' if token.expires_soon?) }
+                  In #{distance_of_time_in_words_to_now(token.expires_at)}
+              - else
+                %span.token-never-expires-label Never
+            %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
+            %td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger pull-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
+            = render 'projects/deploy_tokens/revoke_modal', token: token, project: project
+- else
+  .settings-message.text-center
+    = s_('DeployTokens|This project has no active Deploy Tokens.')
diff --git a/app/views/projects/diffs/_collapsed.html.haml b/app/views/projects/diffs/_collapsed.html.haml
index 8772bd4705f2815a18f096ea16e8034a62e8ab7e..5762f4d86d7ee532c8242b560dfb7f3ca8cd0766 100644
--- a/app/views/projects/diffs/_collapsed.html.haml
+++ b/app/views/projects/diffs/_collapsed.html.haml
@@ -1,5 +1,5 @@
 - diff_file = viewer.diff_file
-- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
+- url = url_for(safe_params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
 .nothing-here-block.diff-collapsed{ data: { diff_for_path: url } }
   This diff is collapsed.
   %a.click-to-expand Click to expand it.
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index b082ad0ef0e79ed201ab2c5777eb692b4b2707bc..6fd6018dea38467a934d95db78e4d437d6d9a40e 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -7,9 +7,9 @@
     = icon("caret-down", class: "prepend-left-5")
   %span.diff-stats-additions-deletions-expanded#diff-stats
     with
-    %strong.cgreen #{sum_added_lines} additions
+    %strong.cgreen= pluralize(sum_added_lines, 'addition')
     and
-    %strong.cred #{sum_removed_lines} deletions
+    %strong.cred= pluralize(sum_removed_lines, 'deletion')
   .diff-stats-additions-deletions-collapsed.pull-right.hidden-xs.hidden-sm{ "aria-hidden": "true", "aria-describedby": "diff-stats" }
     %strong.cgreen<
       +#{sum_added_lines}
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index a96485ab1553051044db6543a903fe69e6263388..0994498c6bea2fe3bd54341188c372626e5b83e6 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -8,7 +8,7 @@
     .settings-header
       %h4
         General project settings
-      %button.btn.js-settings-toggle
+      %button.btn.js-settings-toggle{ type: 'button' }
         = expanded ? 'Collapse' : 'Expand'
       %p
         Update your project name, description, avatar, and other general settings.
@@ -64,7 +64,7 @@
     .settings-header
       %h4
         Permissions
-      %button.btn.js-settings-toggle
+      %button.btn.js-settings-toggle{ type: 'button' }
         = expanded ? 'Collapse' : 'Expand'
       %p
         Enable or disable certain project features and choose access levels.
@@ -79,7 +79,7 @@
     .settings-header
       %h4
         Merge request settings
-      %button.btn.js-settings-toggle
+      %button.btn.js-settings-toggle{ type: 'button' }
         = expanded ? 'Collapse' : 'Expand'
       %p
         Customize your merge request restrictions.
@@ -94,7 +94,7 @@
     .settings-header
       %h4
         Advanced settings
-      %button.btn.js-settings-toggle
+      %button.btn.js-settings-toggle{ type: 'button' }
         = expanded ? 'Collapse' : 'Expand'
       %p
         Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project.
@@ -114,17 +114,18 @@
               Archive project
           - if @project.archived?
             %p
-              Unarchiving the project will mark its repository as active. The project can be committed to.
+              Unarchiving the project will restore people's ability to make changes to it.
+              The repository can be committed to, and issues, comments and other entities can be created.
               %strong Once active this project shows up in the search and on the dashboard.
             = 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." },
+                data: { confirm: "Are you sure that you want to unarchive this project?" },
                 method: :post, class: "btn btn-success"
           - else
             %p
-              Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
-              %strong Archived projects cannot be committed to!
+              Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
+              %strong The repository cannot be committed to, and no issues, comments or other entities can be created.
             = 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." },
+                data: { confirm: "Are you sure that you want to archive this project?" },
                 method: :post, class: "btn btn-warning"
       .sub-section.rename-respository
         %h4.warning-title
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 8a36fada3892a843b0dd68b097d13a45d0d6096d..b15fe514a0812e7bf006667440c8e63580dd4818 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,5 +1,5 @@
 - @no_container = true
-- breadcrumb_title "Details"
+- breadcrumb_title _("Details")
 
 = render partial: 'flash_messages', locals: { project: @project }
 
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 0d656b25bc89c55fa66c941b3cfae4ea5ffdf38d..7ebe617766f67d3ec205b2da1fa77367e1a87166 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -2,9 +2,6 @@
 - page_title "Environments"
 - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
 
-- content_for :page_specific_javascripts do
-  = webpack_bundle_tag("common_vue")
-
 #environments-list-view{ data: { environments_data: environments_list_data,
   "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
   "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index 9d9759ebc5fb140d27cccba33c431aedeb9cf03e..d6f0b230b5890af7405c54ace91975952ada65eb 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -14,8 +14,10 @@
     "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
     "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
     "empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
+    "empty-no-data-svg-path": image_path('illustrations/monitoring/no_data.svg'),
     "empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'),
-    "additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
+    "metrics-endpoint": additional_metrics_project_environment_path(@project, @environment, format: :json),
+    "deployment-endpoint": project_environment_deployments_path(@project, @environment, format: :json),
     "project-path": project_path(@project),
     "tags-path": project_tags_path(@project),
-    "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
+    "has-metrics": "#{@environment.has_metrics?}" } }
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 2599ce5c4b84a35d94a210e95afb6afc50a9c980..620fd1906ba76e52019161e084664f05521d3a92 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
@@ -53,7 +53,7 @@
   - if admin
     %td
       - if generic_commit_status.project
-        = link_to generic_commit_status.project.name_with_namespace, admin_project_path(generic_commit_status.project)
+        = link_to generic_commit_status.project.full_name, 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/imports/show.html.haml b/app/views/projects/imports/show.html.haml
index 8c490773a569b35dddac802a5adee38a08363c57..3b0c828ccd11e710f72eb3c1198d007494693006 100644
--- a/app/views/projects/imports/show.html.haml
+++ b/app/views/projects/imports/show.html.haml
@@ -1,12 +1,11 @@
-- page_title @project.forked? ? "Forking in progress" : "Import in progress"
+- page_title import_in_progress_title
+
 .save-project-loader
   .center
     %h2
       %i.fa.fa-spinner.fa-spin
-      - if @project.forked?
-        Forking in progress.
-      - else
-        Import in progress.
-    - if @project.external_import?
+      = import_in_progress_title
+    - if !has_ci_cd_only_params? && @project.external_import?
       %p.monospace git clone --bare #{@project.safe_import_url}
-    %p Please wait while we import the repository for you. Refresh at will.
+    %p
+      = import_wait_and_refresh_message
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index cdfc3e232c5e6fb9786d1a30ed34ceb9d9ffc82b..816f2fa816de2b597cfe16160fbc3762698aba75 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -8,4 +8,5 @@
 %section.js-vue-notes-event
   #js-vue-notes{ data: { notes_data: notes_data(@issue),
     noteable_data: serialize_issuable(@issue),
+    noteable_type: 'issue',
     current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 64c648f201b3d1a9f58a7eac1aa2377911ee0eed..e27f5658e87b14252d58dea5dfa8f1bafcfa5656 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -7,7 +7,9 @@
       .issue-main-info
         .issue-title.title
           %span.issue-title-text
-            = confidential_icon(issue)
+            - if issue.confidential?
+              %span.has-tooltip{ title: _('Confidential') }
+                = confidential_icon(issue)
             = link_to issue.title, issue_path(issue)
           - if issue.tasks?
             %span.task-status.hidden-xs
@@ -24,11 +26,11 @@
           - if issue.milestone
             %span.issuable-milestone.hidden-xs
               &nbsp;
-              = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(issue.milestone) } do
+              = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do
                 = icon('clock-o')
                 = issue.milestone.title
           - if issue.due_date
-            %span.issuable-due-date.hidden-xs{ class: "#{'cred' if issue.overdue?}" }
+            %span.issuable-due-date.hidden-xs.has-tooltip{ class: "#{'cred' if issue.overdue?}", title: _('Due date') }
               &nbsp;
               = icon('calendar')
               = issue.due_date.to_s(:medium)
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index 5f97d31f610142e6b7c5ab2b0622a8830e14858f..5c36d2202a6680936cbd917e11eb952256c3783f 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -18,7 +18,7 @@
           - unless @issue.project.id == merge_request.target_project.id
             in
             - project = merge_request.target_project
-            = link_to project.name_with_namespace, project_path(project)
+            = link_to project.full_name, 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
index 0d39edb7bfde9f81ee33f79d7ade5b113b985f06..297b928f020ae2e3289c205b705d4b422545912f 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,10 +1,11 @@
-= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
+= link_to safe_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: finder.assignee.try(:id),
-                                                       milestone_id: finder.milestones.first.try(:id) }),
-                                              class: "btn btn-new",
-                                              title: "New issue",
-                                              id: "new_issue_link"
+- if show_new_issue_link?(@project)
+  = link_to "New issue", new_project_issue_path(@project,
+                                                issue: { assignee_id: finder.assignee.try(:id),
+                                                         milestone_id: 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 36e240372141e9b9b0e3752520f3bb155238ebfb..4b8bf578b2831a34c317ad735a98c89d7f81d6a0 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,8 +1,8 @@
-- can_create_merge_request = can?(current_user, :create_merge_request, @project)
-- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
-- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
-
 - if can?(current_user, :push_code, @project)
+  - can_create_merge_request = can?(current_user, :create_merge_request_in, @project)
+  - data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
+  - value = can_create_merge_request ? 'Create merge request' : 'Create branch'
+
   - can_create_path = can_create_branch_project_issue_path(@project, @issue)
   - create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch)
   - create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 4029926f373dcb286cc207dfcc1f85e7c3f1be90..6330245954e743e5d2a8b330610c53005ec7e410 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,5 +1,5 @@
 xml.title   "#{@project.name} issues"
-xml.link    href: url_for(params), rel: "self", type: "application/atom+xml"
+xml.link    href: url_for(safe_params), rel: "self", type: "application/atom+xml"
 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?
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index fb06ba58c2728c3063dd68e2de71c6d637d896b2..1e7737aeb976609c796be8fa73963c1064517f48 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -4,11 +4,8 @@
 - page_title "Issues"
 - new_issue_email = @project.new_issuable_address(current_user, 'issue')
 
-- content_for :page_specific_javascripts do
-  = webpack_bundle_tag 'common_vue'
-
 = content_for :meta_tags do
-  = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
+  = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
 
 - if project_issues(@project).exists?
   %div{ class: (container_class) }
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index ec7e87219f5a78cc0a7b96af1afa281c8d686b44..f1fc1c2316df5d6b91057c2ab2a659c8f126e986 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -7,6 +7,7 @@
 
 - can_update_issue = can?(current_user, :update_issue, @issue)
 - can_report_spam = @issue.submittable_as_spam_by?(current_user)
+- can_create_issue = show_new_issue_link?(@project)
 
 .detail-page-header
   .detail-page-header-body
@@ -42,16 +43,18 @@
             %li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
           - if can_report_spam
             %li= 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_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
+          - if can_create_issue
+            - if can_update_issue || can_report_spam
+              %li.divider
+            %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
 
     = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
 
     - if can_report_spam
       = 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
+    - if can_create_issue
+      = 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
   .detail-page-description.content-block
diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml
index c66313bdbf3075260fafcf32b82eee1a303b32cf..311934d9c334484132cbba328608e2db1c23ded1 100644
--- a/app/views/projects/jobs/_empty_state.html.haml
+++ b/app/views/projects/jobs/_empty_state.html.haml
@@ -1,7 +1,7 @@
 - illustration = local_assigns.fetch(:illustration)
 - illustration_size = local_assigns.fetch(:illustration_size)
 - title = local_assigns.fetch(:title)
-- content = local_assigns.fetch(:content)
+- content = local_assigns.fetch(:content, nil)
 - action = local_assigns.fetch(:action, nil)
 
 .row.empty-state
@@ -11,7 +11,8 @@
   .col-xs-12
     .text-content
       %h4.text-center= title
-      %p= content
+      - if content
+        %p= content
       - if action
         .text-center
           = action
diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e5198d047dfc8db4731a9c84c1ddfeb5a73648e2
--- /dev/null
+++ b/app/views/projects/jobs/_empty_states.html.haml
@@ -0,0 +1,9 @@
+- detailed_status = @build.detailed_status(current_user)
+- illustration = detailed_status.illustration
+
+= render 'empty_state',
+    illustration: illustration[:image],
+    illustration_size: illustration[:size],
+    title: illustration[:title],
+    content: illustration[:content],
+    action: detailed_status.has_action? ? link_to(detailed_status.action_button_title, detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: detailed_status.action_button_title) : nil
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index e779473c23953815bd5ce152b2e07013dcda9889..7f0bef5ede0e5cdfbfb2dd197cf0ead9db7d2d89 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -1,17 +1,8 @@
-- builds = @build.pipeline.builds.to_a
-
 %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
   .sidebar-container
     .blocks-container
-      .block
-        %strong.inline.prepend-top-8
-          = @build.name
-        - if can?(current_user, :update_build, @build) && @build.retryable?
-          = link_to "Retry", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'js-retry-button pull-right btn btn-inverted-secondary btn-retry visible-md-block visible-lg-block', method: :post
-        %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' }
-          = icon('angle-double-right')
 
-      #js-details-block-vue
+      #js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } }
 
       - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
         .block
@@ -35,7 +26,7 @@
               = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
                 Download
 
-              - if @build.artifacts_metadata?
+              - if @build.browsable_artifacts?
                 = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
                   Browse
 
@@ -91,7 +82,8 @@
       - 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 project_job_path(@project, build) do
+            - tooltip = build.tooltip_message
+            = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' }) do
               = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right')
               %span{ class: "ci-status-icon-#{build.status}" }
                 = ci_icon_for_status(build.status)
@@ -101,5 +93,4 @@
                 - else
                   = build.id
               - if build.retried?
-                %span.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
-                  = sprite_icon('retry', size:16, css_class: 'icon-retry')
+                = sprite_icon('retry', size:16, css_class: 'icon-retry')
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 849c273db8c14668fb4ab685316aae795093843a..cbbcc8f1db5ef1211eac3ab9aa1a5cf04ef905f6 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -54,7 +54,8 @@
             Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
           - else
             Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
-    - if @build.started?
+
+    - if @build.running? || @build.has_trace?
       .build-trace-container.prepend-top-default
         .top-bar.js-top-bar
           .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden<
@@ -88,27 +89,11 @@
         %pre.build-trace#build-trace
           %code.bash.js-build-output
           .build-loader-animation.js-build-refresh
-    - elsif @build.playable?
-      = render 'empty_state',
-        illustration: 'illustrations/manual_action.svg',
-        illustration_size: 'svg-394',
-        title: _('This job requires a manual action'),
-        content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'),
-        action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), method: :post, class: 'btn btn-primary', title: _('Trigger this manual action') )
-    - elsif @build.created?
-      = render 'empty_state',
-        illustration: 'illustrations/job_not_triggered.svg',
-        illustration_size: 'svg-306',
-        title: _('This job has not been triggered yet'),
-        content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
     - else
-      = render 'empty_state',
-        illustration: 'illustrations/pending_job_empty.svg',
-        illustration_size: 'svg-430',
-        title: _('This job has not started yet'),
-        content: _('This job is in pending state and is waiting to be picked by a runner')
-  = render "sidebar"
+      = render "empty_states"
+
+  = render "sidebar", builds: @builds
 
 .js-build-options{ data: javascript_build_options }
 
-#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json) } }
+#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner') } }
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 80e4dce1a8028d15140bbc8478203f85dcfade6d..9c78bade254e4cd69f1af7de8f2276036fc1fcf4 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -4,6 +4,7 @@
 - can_admin_label = can?(current_user, :admin_label, @project)
 
 - if @labels.exists? || @prioritized_labels.exists?
+  #promote-label-modal
   %div{ class: container_class }
     .top-area.adjust
       .nav-text
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index f45a000833b881a5592d8917105fc418636087a9..027a9ff1416be1b593f37af7e4694ce7775aa178 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -23,11 +23,11 @@
         - 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), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(merge_request.milestone) } do
+            = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_due_date(merge_request.milestone) } do
               = icon('clock-o')
               = merge_request.milestone.title
         - if merge_request.target_project.default_branch != merge_request.target_branch
-          %span.project-ref-path
+          %span.project-ref-path.has-tooltip{ title: _('Target branch') }
             &nbsp;
             = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
               = sprite_icon('fork', size: 12, css_class: 'fork-sprite')
@@ -51,11 +51,11 @@
             = render_pipeline_status(merge_request.head_pipeline)
         - if merge_request.open? && merge_request.broken?
           %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
+            = link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') 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")
+            = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: _('Assigned to :name'))
 
         = render 'shared/issuable_meta_data', issuable: merge_request
 
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index 9d5cebdda538c1863ae9ae2951394c92c9c1f56c..f81db9b4e28043da0b330f36f68703f584fae069 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -3,7 +3,7 @@
 
 = 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.js-merge-request-new-compare.row{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
+  .js-merge-request-new-compare.row{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
     .col-md-6
       .panel.panel-default.panel-new-merge-request
         .panel-heading
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 376ac377562cb392dd5f45e98b7342f91cfdd003..68780cedeb1963ca9b0607f4678bec9328344d32 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -26,16 +26,16 @@
   - else
     %ul.merge-request-tabs.nav-links.no-top.no-bottom
       %li.commits-tab.active
-        = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
+        = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
           Commits
           %span.badge= @commits.size
       - if @pipelines.any?
         %li.builds-tab
-          = link_to url_for(params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
+          = link_to url_for(safe_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: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
+        = link_to url_for(safe_params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
           Changes
           %span.badge= @merge_request.diff_size
 
@@ -46,7 +46,7 @@
         -# This tab is always loaded via AJAX
       - if @pipelines.any?
         #pipelines.pipelines.tab-pane
-          = render 'projects/merge_requests/pipelines', endpoint: url_for(params.merge(action: 'pipelines', format: :json)), disable_initialization: true
+          = render 'projects/merge_requests/pipelines', endpoint: url_for(safe_params.merge(action: 'pipelines', format: :json)), disable_initialization: true
 
   .mr-loading-status
     = spinner
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 720ba236434b017783de0b8218d807e8d146e18d..623380c9c61277c37e8235a6685bb5a4e0bf4251 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,14 +1,11 @@
 - @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))
+- merge_project = merge_request_source_project_for_project(@project)
 - new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
 
 - page_title "Merge Requests"
 - new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
 
-- content_for :page_specific_javascripts do
-  = webpack_bundle_tag 'common_vue'
-
 %div{ class: container_class }
   = render 'projects/last_push'
 
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 9866cc716eea92991b47dba175cd5ee495d9a693..15a0e4d7ef5a47137d331f6b8b275dc36e2ecd16 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -80,6 +80,7 @@
               - if has_vue_discussions_cookie?
                 #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request),
                   noteable_data: serialize_issuable(@merge_request),
+                  noteable_type: 'merge_request',
                   current_user_data: UserSerializer.new.represent(current_user).to_json} }
 
       #commits.commits.tab-pane
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 6a7bc4b18882a239f0560ea85f187f4486d64ab2..5b0197ed58cdc0abaf0de3a7256eb87820c44a4f 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -13,6 +13,7 @@
 
   .milestones
     #delete-milestone-modal
+    #promote-milestone-modal
 
     %ul.content-list
       = render @milestones
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index de381d489c6e59d6ce19a7c7f88e9443bfcbd942..5ec219fdf009f9a389175d9f53f919d248180afa 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -27,8 +27,16 @@
           Edit
 
         - if @project.group
-          = link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "Promoting #{@milestone.title} will make it available for all projects inside #{@project.group.name}. Existing project milestones with the same name will be merged. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
-            Promote
+          %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
+            target: '#promote-milestone-modal',
+            milestone_title: @milestone.title,
+            group_name: @project.group.name,
+            url: promote_project_milestone_path(@milestone.project, @milestone),
+            container: 'body' },
+            disabled: true,
+            type: 'button' }
+            = _('Promote')
+          #promote-milestone-modal
 
         - if @milestone.active?
           = link_to 'Close milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 679ba23a4dbc39471de8cc9636eaa7ef3b1b682d..b66e05596038d0ad600fe7f9bc8c11461caee8cd 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -12,11 +12,12 @@
   .row.prepend-top-default
     .col-lg-3.profile-settings-sidebar
       %h4.prepend-top-0
-        New project
+        = _('New project')
       %p
-        A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}.
+        - among_other_things_link = link_to _('among other things'), help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'
+        = _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link }
       %p
-        All features are enabled when you create a project, but you can disable the ones you don鈥檛 need in the project settings.
+        = _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.')
       .md
         = brand_new_project_guidelines
       %p
@@ -28,36 +29,36 @@
 
     .col-lg-9.js-toggle-container
       %ul.nav-links.gitlab-tabs{ role: 'tablist' }
-        %li{ class: ('active' if active_tab == 'blank'), role: 'presentation' }
+        %li{ class: active_when(active_tab == 'blank'), role: 'presentation' }
           %a{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' }
             %span.hidden-xs Blank project
             %span.visible-xs Blank
-        %li{ class: ('active' if active_tab == 'template'), role: 'presentation' }
+        %li{ class: active_when(active_tab == 'template'), role: 'presentation' }
           %a{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' }
             %span.hidden-xs Create from template
             %span.visible-xs Template
-        %li{ class: ('active' if active_tab == 'import'), role: 'presentation' }
+        %li{ class: active_when(active_tab == 'import'), role: 'presentation' }
           %a{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' }
             %span.hidden-xs Import project
             %span.visible-xs Import
 
       .tab-content.gitlab-tab-content
-        .tab-pane{ id: 'blank-project-pane', class: ('active' if active_tab == 'blank'), role: 'tabpanel' }
+        .tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' }
           = form_for @project, html: { class: 'new_project' } do |f|
             = render 'new_project_fields', f: f, project_name_id: "blank-project-name"
 
-        .tab-pane.no-padding{ id: 'create-from-template-pane', class: ('active' if active_tab == 'template'), role: 'tabpanel' }
+        .tab-pane.no-padding{ id: 'create-from-template-pane', class: active_when(active_tab == 'template'), role: 'tabpanel' }
           = form_for @project, html: { class: 'new_project' } do |f|
             .project-template
               .form-group
                 %div
                   = render 'project_templates', f: f
 
-        .tab-pane.import-project-pane{ id: 'import-project-pane', class: ('active' if active_tab == 'import'), role: 'tabpanel' }
+        .tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' }
           = form_for @project, html: { class: 'new_project' } do |f|
             - if import_sources_enabled?
               .project-import.row
-                .col-sm-12
+                .col-lg-12
                   .form-group.import-btn-container.clearfix
                     = f.label :visibility_level, class: 'label-light' do #the label here seems wrong
                       Import project from
@@ -68,7 +69,7 @@
                             = icon('gitlab', text: 'GitLab export')
                       %div
                         - if github_import_enabled?
-                          = link_to new_import_github_path, class: 'btn import_github' do
+                          = link_to new_import_github_path, class: 'btn js-import-github' do
                             = icon('github', text: 'GitHub')
                       %div
                         - if bitbucket_import_enabled?
@@ -97,7 +98,7 @@
                             Gitea
                       %div
                         - if git_import_enabled?
-                          %button.btn.js-toggle-button.import_git{ type: "button" }
+                          %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } }
                             = icon('git', text: 'Repo by URL')
                 .col-lg-12
                   .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index ba5845877e54fc25ee6b38bcda47dfeef1fcf055..14d880028c7ba762771858d6f2fc260c56f5d3f7 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -1,3 +1,5 @@
+- breadcrumb_title _("Details")
+
 %h2
   %i.fa.fa-warning
   #{ _('No repository') }
@@ -10,7 +12,7 @@
 
 .no-repo-actions
   = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
-    #{ _('Create empty bare repository') }
+    #{ _('Create empty repository') }
 
   %strong.prepend-left-10.append-right-10 or
 
@@ -19,4 +21,4 @@
 
 - if can? current_user, :remove_project, @project
   .prepend-top-20
-    = link_to _('Remove project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+    = link_to _('Remove project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove pull-right"
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index 5ea653ccad5995955f42bd5d09a637c88b9726b5..b4fe1cabdfd0417733c35e9b72005de8d9e5c45d 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -36,7 +36,7 @@
           %template{ 'v-else' => '' }
             = render 'shared/icons/icon_resolve_discussion.svg'
 
-- if current_user
+- if can?(current_user, :award_emoji, note)
   - if note.emoji_awardable?
     - user_authored = note.user_authored?(current_user)
     .note-actions-item
diff --git a/app/views/projects/pages/_https_only.html.haml b/app/views/projects/pages/_https_only.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6a3ffce949f568740805da77f75ae61f694693d9
--- /dev/null
+++ b/app/views/projects/pages/_https_only.html.haml
@@ -0,0 +1,10 @@
+= form_for @project, url: namespace_project_pages_path(@project.namespace.becomes(Namespace), @project), html: { class: 'inline', title: pages_https_only_title } do |f|
+  = f.check_box :pages_https_only, class: 'pull-left', disabled: pages_https_only_disabled?
+
+  .prepend-left-20
+    = f.label :pages_https_only, class: pages_https_only_label_class do
+      %strong Force domains with SSL certificates to use HTTPS
+
+  - unless pages_https_only_disabled?
+    .prepend-top-10
+    = f.submit 'Save', class: 'btn btn-success'
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 75df92b05a742c37dd0b32c7fe357f1844ed159c..27bbe52a7142b8ce6d150ba570ff303d70e59fb7 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -1,28 +1,29 @@
+- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+
 - if can?(current_user, :update_pages, @project) && @domains.any?
   .panel.panel-default
     .panel-heading
       Domains (#{@domains.count})
-    %ul.well-list
-      - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+    %ul.well-list.pages-domain-list{ class: ("has-verification-status" if verification_enabled) }
       - @domains.each do |domain|
-        %li
-          .pull-right
+        %li.pages-domain-list-item.unstyled
+          - if verification_enabled
+            - tooltip, status = domain.unverified? ? [_('Unverified'), 'failed'] : [_('Verified'), 'success']
+            .domain-status.ci-status-icon.has-tooltip{ class: "ci-status-icon-#{status}", title: tooltip }
+              = sprite_icon("status_#{status}", size: 16 )
+          .domain-name
+            = link_to domain.url do
+              = domain.url
+              = icon('external-link')
+            - if domain.subject
+              %p
+                %span.label.label-gray Certificate: #{domain.subject}
+                - if domain.expired?
+                  %span.label.label-danger Expired
+          %div
             = 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
-            - if verification_enabled
-              - tooltip, status = domain.unverified? ? ['Unverified', 'failed'] : ['Verified', 'success']
-              = link_to domain.url, title: tooltip, class: 'has-tooltip' do
-                = sprite_icon("status_#{status}", size: 16, css_class: "has-tooltip ci-status-icon ci-status-icon-#{status}")
-                = domain.domain
-            - else
-              = link_to domain.domain, domain.url
-          %p
-            - if domain.subject
-              %span.label.label-gray Certificate: #{domain.subject}
-            - if domain.expired?
-              %span.label.label-danger Expired
         - if verification_enabled && domain.unverified?
           %li.warning-row
             #{domain.domain} is not verified. To learn how to verify ownership, visit your
-            = link_to 'domain details', project_pages_domain_path(@project, domain)
+            #{link_to 'domain details', project_pages_domain_path(@project, domain)}.
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index 04e647c0dc613396bba29a2ec5dcd2427fff7cef..6adaea799b21597ccfc34dba08c8c3d2fb6707ad 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,11 +1,10 @@
 - page_title 'Pages'
 
-%h3.page_title
+%h3.page-title.with-button
   Pages
 
   - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
     = link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do
-      %i.fa.fa-plus
       New Domain
 
 %p.light
@@ -13,6 +12,9 @@
   Combined with the power of GitLab CI and the help of GitLab Runner
   you can deploy static pages for your individual projects, your user or your group.
 
+- if Gitlab.config.pages.external_https
+  = render 'https_only'
+
 %hr.clearfix
 
 = render 'access'
diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml
index 5645a4604bf7bb530eaa2cf30e79a518cbda1c3c..6c4049904924e1d3e880498b76ce1aa06eb0f4cf 100644
--- a/app/views/projects/pages_domains/edit.html.haml
+++ b/app/views/projects/pages_domains/edit.html.haml
@@ -1,7 +1,7 @@
 - add_to_breadcrumbs "Pages", project_pages_path(@project)
 - breadcrumb_title @domain.domain
 - page_title @domain.domain
-%h3.page_title
+%h3.page-title
   = @domain.domain
 %hr.clearfix
 %div
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index 5a397c9d3c7db1f34fe964677e8ac0cfaf19634d..269df803a2bd071c1999acafb5e4e4f0ca3ad026 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -1,6 +1,6 @@
 - add_to_breadcrumbs "Pages", project_pages_path(@project)
 - page_title 'New Pages Domain'
-%h3.page_title
+%h3.page-title
   New Pages Domain
 %hr.clearfix
 %div
@@ -8,3 +8,5 @@
     = render 'form', { f: f }
     .form-actions
       = f.submit 'Create New Domain', class: "btn btn-save"
+      .pull-right
+        = link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index ba0713daee91920868cd492fc2dee4e6f2d6525f..44d66f3b2d03a9a50f8625bb1fc76b9dc83862e5 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -1,17 +1,19 @@
 - add_to_breadcrumbs "Pages", project_pages_path(@project)
 - breadcrumb_title @domain.domain
 - page_title "#{@domain.domain}", 'Pages Domains'
+- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}."
 
 - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+
 - if verification_enabled && @domain.unverified?
-  %p.alert.alert-warning
-    %strong
-      This domain is not verified. You will need to verify ownership before
-      access is enabled.
+  = content_for :flash_message do
+    .alert.alert-warning
+      .container-fluid.container-limited
+        This domain is not verified. You will need to verify ownership before access is enabled.
 
-%h3.page-title
-  Pages Domain
+%h3.page-title.with-button
   = link_to 'Edit', edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success pull-right'
+  Pages Domain
 
 .table-holder
   %table.table
@@ -19,31 +21,41 @@
       %td
         Domain
       %td
-        = link_to @domain.domain, @domain.url
+        = link_to @domain.url do
+          = @domain.url
+          = icon('external-link')
     %tr
       %td
         DNS
       %td
-        %p
-          To access this domain create a new DNS record:
-        %pre
-          #{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}.
+        .input-group
+          = text_field_tag :domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true
+          .input-group-btn
+            = clipboard_button(target: '#domain_dns', class: 'btn-default hidden-xs')
+        %p.help-block
+          To access this domain create a new DNS record
+
     - if verification_enabled
+      - verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}"
       %tr
         %td
           Verification status
         %td
-          %p
+          = form_tag verify_project_pages_domain_path(@project, @domain) do
+            .status-badge
+              - text, status = @domain.unverified? ? [_('Unverified'), 'label-danger'] : [_('Verified'), 'label-success']
+              .label{ class: status }
+                = text
+              %button.btn.has-tooltip{ type: "submit", data: { container: 'body' }, title: _("Retry verification") }
+                = sprite_icon('redo')
+          .input-group
+            = text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
+            .input-group-btn
+              = clipboard_button(target: '#domain_verification', class: 'btn-default hidden-xs')
+          %p.help-block
             - help_link = help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
-            To #{link_to 'verify ownership', help_link} of your domain, create
-            this DNS record:
-          %pre
-            #{@domain.verification_domain} TXT #{@domain.keyed_verification_code}
-          %p
-            - if @domain.verified?
-              #{@domain.domain} has been successfully verified.
-            - else
-              = button_to 'Verify ownership', verify_project_pages_domain_path(@project, @domain), class: 'btn btn-save btn-sm'
+            To #{link_to 'verify ownership', help_link} of your domain,
+            add the above key to a TXT record within to your DNS configuration.
 
     %tr
       %td
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index cf95cdbfec21eb86e4d57a2f6ccfacd21f212064..c0ee81fe28d4003a9701c16aad4f3a82954f2ada 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -7,8 +7,9 @@
     "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
     "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
     "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
-    "new-pipeline-path" => new_project_pipeline_path(@project),
+    "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
     "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
-    "has-ci" => @repository.gitlab_ci_yml,
-    "ci-lint-path" => ci_lint_path,
-    "reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } }
+    "new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project),
+    "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project),
+    "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) ,
+    "has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } }
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 877101b05ca5b2e35161c92faa58d74f02e7f0d6..8f2142af2cef8cd575b35ca388fc1644408904fb 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -1,24 +1,25 @@
 - breadcrumb_title "Pipelines"
-- page_title "New Pipeline"
+- page_title = s_("Pipeline|Run Pipeline")
 
 %h3.page-title
-  New Pipeline
+  = s_("Pipeline|Run Pipeline")
 %hr
 
 = 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'
+    = f.label :ref, s_('Pipeline|Run on'), class: 'control-label'
     .col-sm-10
       = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
       = dropdown_tag(params[:ref] || @project.default_branch,
                      options: { toggle_class: 'js-branch-select wide git-revision-dropdown-toggle',
-                                filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: "Search branches",
+                                filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: s_("Pipeline|Search branches"),
                                 data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
-      .help-block Existing branch name, tag
+      .help-block
+        = s_("Pipeline|Existing branch name, tag")
   .form-actions
-    = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
-    = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel'
+    = f.submit s_('Pipeline|Run pipeline'), class: 'btn btn-success', tabindex: 3
+    = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-default pull-right'
 
 -# haml-lint:disable InlineJavaScript
 %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index ffb0ae95f9b4919d7a5a82e543e982cc5c4e26fd..a7d7c923957a0d20a980322e7b9bf4e5c1fd4400 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -10,6 +10,3 @@
   = render "projects/pipelines/with_tabs", pipeline: @pipeline
 
 .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/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
deleted file mode 100644
index 646c01c0989377ba64d720f1d101589e047dba1a..0000000000000000000000000000000000000000
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ /dev/null
@@ -1,160 +0,0 @@
-.row.prepend-top-default
-  .col-lg-12
-    = form_for @project, url: project_pipelines_settings_path(@project) do |f|
-      %fieldset.builds-feature
-        .form-group
-          %h5 Auto DevOps (Beta)
-          %p
-            Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.
-            = link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md')
-            - message = auto_devops_warning_message(@project)
-            - if message
-              %p.settings-message.text-center
-                = message.html_safe
-          = f.fields_for :auto_devops_attributes, @auto_devops do |form|
-            .radio
-              = form.label :enabled_true do
-                = form.radio_button :enabled, 'true'
-                %strong Enable Auto DevOps
-                %br
-                %span.descr
-                  The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project.
-
-            .radio
-              = form.label :enabled_false do
-                = form.radio_button :enabled, 'false'
-                %strong Disable Auto DevOps
-                %br
-                %span.descr
-                  An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery.
-
-            .radio
-              = form.label :enabled_ do
-                = form.radio_button :enabled, ''
-                %strong Instance default (#{Gitlab::CurrentSettings.auto_devops_enabled? ? 'enabled' : 'disabled'})
-                %br
-                %span.descr
-                  Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>.
-            %p
-              You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.
-            = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
-
-        %hr
-        .form-group.append-bottom-default.js-secret-runner-token
-          = f.label :runners_token, "Runner token", class: 'label-light'
-          .form-control.js-secret-value-placeholder
-            = '*' * 20
-          = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89'
-          %p.help-block The secure token used by the Runner to checkout the project
-          %button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } }
-            = _('Reveal value')
-
-        %hr
-        .form-group
-          %h5.prepend-top-0
-            Git strategy for pipelines
-          %p
-            Choose between <code>clone</code> or <code>fetch</code> to get the recent application code
-            = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank'
-          .radio
-            = f.label :build_allow_git_fetch_false do
-              = f.radio_button :build_allow_git_fetch, 'false'
-              %strong git clone
-              %br
-              %span.descr
-                Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job
-          .radio
-            = f.label :build_allow_git_fetch_true do
-              = f.radio_button :build_allow_git_fetch, 'true'
-              %strong git fetch
-              %br
-              %span.descr
-                Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)
-
-        %hr
-        .form-group
-          = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
-          = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
-          %p.help-block
-            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
-            = f.label :public_builds do
-              = f.check_box :public_builds
-              %strong Public pipelines
-            .help-block
-              Allow public access to pipelines and job details, including output logs and artifacts
-              = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines'), target: '_blank'
-            .bs-callout.bs-callout-info
-              %p If enabled:
-              %ul
-                %li
-                  For public projects, anyone can view pipelines and access job details (output logs and artifacts)
-                %li
-                  For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)
-                %li
-                  For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)
-              %p
-                If disabled, the access level will depend on the user's
-                permissions in the project.
-
-        %hr
-        .form-group
-          .checkbox
-            = f.label :auto_cancel_pending_pipelines do
-              = f.check_box :auto_cancel_pending_pipelines, {}, 'enabled', 'disabled'
-              %strong Auto-cancel redundant, pending pipelines
-            .help-block
-              New pipelines will cancel older, pending pipelines on the same branch
-              = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'auto-cancel-pending-pipelines'), target: '_blank'
-
-        %hr
-        .form-group
-          = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
-          .input-group
-            %span.input-group-addon /
-            = f.text_field :build_coverage_regex, class: 'form-control', placeholder: 'Regular expression'
-            %span.input-group-addon /
-          %p.help-block
-            A regular expression that will be used to find the test coverage
-            output in the job trace. Leave blank to disable
-            = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank'
-          .bs-callout.bs-callout-info
-            %p Below are examples of regex for existing tools:
-            %ul
-              %li
-                Simplecov (Ruby) -
-                %code \(\d+.\d+\%\) covered
-              %li
-                pytest-cov (Python) -
-                %code \d+\%\s*$
-              %li
-                phpunit --coverage-text --colors=never (PHP) -
-                %code ^\s*Lines:\s*\d+.\d+\%
-              %li
-                gcovr (C/C++) -
-                %code ^TOTAL.*\s+(\d+\%)$
-              %li
-                tap --coverage-report=text-summary (NodeJS) -
-                %code ^Statements\s*:\s*([^%]+)
-              %li
-                excoveralls (Elixir) -
-                %code \[TOTAL\]\s+(\d+\.\d+)%
-
-        = f.submit 'Save changes', class: "btn btn-save"
-
-%hr
-
-.row.prepend-top-default
-  = render partial: 'projects/pipelines_settings/badge', collection: @badges
diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml
index 98d56a3e5c5d8b584fd5b1998645b23ee2a83028..12ccae10260fbf71d253f88bbc4f159d331936ba 100644
--- a/app/views/projects/protected_branches/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml
@@ -7,8 +7,8 @@
 - content_for :push_access_levels do
   .push_access_levels-container
     = dropdown_tag('Select',
-                    options: { toggle_class: 'js-allowed-to-push wide',
-                    dropdown_class: 'dropdown-menu-selectable capitalize-header',
+                    options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push-select wide',
+                    dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown capitalize-header',
                     data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
 
 = render 'projects/protected_branches/shared/create_protected_branch'
diff --git a/app/views/projects/protected_branches/_update_protected_branch.html.haml b/app/views/projects/protected_branches/_update_protected_branch.html.haml
index c61b2951e1e8971dfdcc287c5296c8c7ee10e4e2..98363f2018ae16f054422c9ef9d265747fa4bfb7 100644
--- a/app/views/projects/protected_branches/_update_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_update_protected_branch.html.haml
@@ -6,5 +6,5 @@
 %td
   = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level
   = dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') ,
-                 options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
+                 options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
                  data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }})
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index 2a0704bc7affc5010f382fd445386e4748db3c2c..d1ed438eb2110a6555cf5a73170572ab1f09167e 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,8 +1,8 @@
-.panel.panel-default.protected-branches-list.js-protected-branches-list
+.protected-branches-list.js-protected-branches-list.qa-protected-branches-list
   - if @protected_branches.empty?
     .panel-heading
       %h3.panel-title
-        Protected branch (#{@protected_branches.size})
+        Protected branch (#{@protected_branches_count})
     %p.settings-message.text-center
       There are currently no protected branches, protect a branch with the form above.
   - else
@@ -16,7 +16,7 @@
           %col
       %thead
         %tr
-          %th Protected branch (#{@protected_branches.size})
+          %th Protected branch (#{@protected_branches_count})
           %th Last commit
           %th Allowed to merge
           %th Allowed to push
diff --git a/app/views/projects/protected_branches/shared/_dropdown.html.haml b/app/views/projects/protected_branches/shared/_dropdown.html.haml
index 74435236808ca29cd832a6d66be9a93140cf5d0e..b3d6068039a348ec81df5de54d6c6b893449a158 100644
--- a/app/views/projects/protected_branches/shared/_dropdown.html.haml
+++ b/app/views/projects/protected_branches/shared/_dropdown.html.haml
@@ -1,8 +1,8 @@
 = f.hidden_field(:name)
 
 = dropdown_tag('Select branch or create wildcard',
-               options: { toggle_class: 'js-protected-branch-select js-filter-submit wide git-revision-dropdown-toggle',
-                          filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: "Search protected branches",
+               options: { toggle_class: 'js-protected-branch-select js-filter-submit wide git-revision-dropdown-toggle qa-protected-branch-select',
+                          filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown qa-protected-branch-dropdown", placeholder: "Search protected branches",
                           footer_content: true,
                           data: { show_no: true, show_any: true, show_upcoming: true,
                                   selected: params[:protected_branch_name],
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
index e662b877fbb4b5f85055fb0ddffc0249ea9faf38..fd5c1aa342a1c38e4c2867919fb3bfd1ab9d9fcc 100644
--- a/app/views/projects/protected_branches/shared/_index.html.haml
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -4,7 +4,7 @@
   .settings-header
     %h4
       Protected Branches
-    %button.btn.js-settings-toggle
+    %button.btn.js-settings-toggle.qa-expand-protected-branches{ type: 'button' }
       = expanded ? 'Collapse' : 'Expand'
     %p
       Keep stable branches secure and force developers to use merge requests.
diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
index 10b81e425725b70b24e3d6e70b482c8372f97b58..f5b21f0e887a309cc6c963c8d890d76c84cd9e3c 100644
--- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
@@ -2,7 +2,7 @@
 
 %tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
   %td
-    %span.ref-name= protected_branch.name
+    %span.ref-name.qa-protected-branch-name= protected_branch.name
 
     - if @project.root_ref?(protected_branch.name)
       %span.label.label-info.prepend-left-5 default
diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml
index 24baf1cfc892e4e0e629505b5353b5d9e5a7b658..c33723d8072232b103e0b2532bfe6dd3404d406b 100644
--- a/app/views/projects/protected_tags/shared/_index.html.haml
+++ b/app/views/projects/protected_tags/shared/_index.html.haml
@@ -4,7 +4,7 @@
   .settings-header
     %h4
       Protected Tags
-    %button.btn.js-settings-toggle
+    %button.btn.js-settings-toggle{ type: 'button' }
       = expanded ? 'Collapse' : 'Expand'
     %p
       Limit access to creating and updating tags.
diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml
index 3f42ae58438957d7fbf780ed8f7aa8e67e33ef87..3ed82e51dbe97618b113bb5d443c99b673115c98 100644
--- a/app/views/projects/protected_tags/shared/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -1,8 +1,8 @@
-.panel.panel-default.protected-tags-list.js-protected-tags-list
+.protected-tags-list.js-protected-tags-list
   - if @protected_tags.empty?
     .panel-heading
       %h3.panel-title
-        Protected tag (#{@protected_tags.size})
+        Protected tag (#{@protected_tags_count})
     %p.settings-message.text-center
       There are currently no protected tags, protect a tag with the form above.
   - else
@@ -17,7 +17,7 @@
           %col
       %thead
         %tr
-          %th Protected tag (#{@protected_tags.size})
+          %th Protected tag (#{@protected_tags_count})
           %th Last commit
           %th Allowed to create
           - if can_admin_project
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 27e1f9fba3e34c172e477039ec5fa29e278444b8..2c80f7c3fa301f3dc3b7e3cf6d6dc8446a62671e 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -14,8 +14,6 @@
     .col-lg-12
       #js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json) } }
 
-      = webpack_bundle_tag('common_vue')
-
   .row.prepend-top-10
     .col-lg-12
       .panel.panel-default
@@ -30,6 +28,10 @@
           %pre
             docker login #{Gitlab.config.registry.host_port}
           %br
+          %p
+            - deploy_token = link_to(_('deploy token'), help_page_path('user/projects/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank')
+            = s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token }
+          %br
           %p
             = s_('ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands').html_safe % { build: "<code>build</code>".html_safe, push: "<code>push</code>".html_safe }
           %pre
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index 49c9086914605c7b9177a4cafc3a436b91e2bf1c..6a681736b6f662a74189c8f2527337b202867c69 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -39,6 +39,12 @@
       Description
     .col-sm-10
       = f.text_field :description, class: 'form-control'
+  .form-group
+    = label_tag :maximum_timeout_human_readable, class: 'control-label' do
+      Maximum job timeout
+    .col-sm-10
+      = f.text_field :maximum_timeout_human_readable, class: 'form-control'
+      .help-block This timeout will take precedence when lower than Project-defined timeout
   .form-group
     = label_tag :tag_list, class: 'control-label' do
       Tags
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index 4e57f5f844d3602602a212c1792a6836afbebc6a..f33e7e25b682d3216e327f298c052101eedb7981 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -55,6 +55,9 @@
     %tr
       %td Description
       %td= @runner.description
+    %tr
+      %td Maximum job timeout
+      %td= @runner.maximum_timeout_human_readable
     %tr
       %td Last contact
       %td
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 17e804d682bd89f45972bc3d82ea2f117fddabf3..684b082efbb618d4f2b40bfdfbf93f96c3be1496 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -5,6 +5,9 @@
       = boolean_to_icon @service.activated?
 
     %p= @service.description
+
+    - if @service.respond_to?(:detailed_description)
+      %p= @service.detailed_description
   .col-lg-9
     = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal integration-settings-form 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
@@ -12,11 +15,6 @@
         .footer-block.row-content-block
           = service_save_button(@service)
           &nbsp;
-          - if @service.valid? && @service.activated?
-            - unless @service.can_test?
-              - disabled_class = 'disabled'
-              - disabled_title = @service.disabled_title
-
           = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
 
 - if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
index 5dbcbf7eba66ea78e25d8a038c4084164847b440..2ab0227126af2b3cf0d1ab31bfd23a841ee21de9 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
@@ -1,4 +1,4 @@
-- run_actions_text = "Perform common operations on GitLab project: #{@project.name_with_namespace}"
+- run_actions_text = "Perform common operations on GitLab project: #{@project.full_name}"
 
 %p To setup this service:
 %ul.list-unstyled.indent-list
@@ -20,7 +20,7 @@
   .form-group
     = label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label'
     .col-sm-10.col-xs-12.input-group
-      = text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly'
+      = text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control input-sm', readonly: 'readonly'
       .input-group-btn
         = clipboard_button(target: '#display_name')
 
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index 6dc2b85fd3220b5005190faa90b6d5218197bd47..43e6a17310817edb218d81ad77d144d06e1b65d4 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -7,21 +7,19 @@
       = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus')
 
   .col-lg-9
-    .panel.panel-default.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(@project, :json) } }
+    .panel.panel-default.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(@project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } }
       .panel-heading
         %h3.panel-title
-          = s_('PrometheusService|Monitored')
+          = s_('PrometheusService|Common metrics')
           %span.badge.js-monitored-count 0
       .panel-body
-        .loading-metrics.text-center.js-loading-metrics
-          = icon('spinner spin 3x', class: 'metrics-load-spinner')
-          %p
+        .loading-metrics.js-loading-metrics
+          %p.prepend-top-10.prepend-left-10
+            = icon('spinner spin', class: 'metrics-load-spinner')
             = s_('PrometheusService|Finding and configuring metrics...')
-        .empty-metrics.text-center.hidden.js-empty-metrics
-          = custom_icon('icon_empty_metrics')
-          %p
-            = s_('PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment.')
-          = link_to s_('PrometheusService|View environments'), project_environments_path(@project), class: 'btn btn-success'
+        .empty-metrics.hidden.js-empty-metrics
+          %p.text-tertiary.prepend-top-10.prepend-left-10
+            = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics')
         %ul.list-unstyled.metrics-list.hidden.js-metrics-list
 
     .panel.panel-default.hidden.js-panel-missing-env-vars
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index c31c95608c617eff9ccfd65ac381bdf45e266756..d592a5e4663e3398828ced921f61e3d63d4944d8 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -1,4 +1,4 @@
-- pretty_name = defined?(@project) ? @project.name_with_namespace : 'namespace / path'
+- pretty_name = defined?(@project) ? @project.full_name : 'namespace / path'
 - run_actions_text = "Perform common operations on GitLab project: #{pretty_name}"
 
 .well
diff --git a/app/views/projects/settings/badges/index.html.haml b/app/views/projects/settings/badges/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b68ed70de892e72cb7e8d808553b5450a1c3e215
--- /dev/null
+++ b/app/views/projects/settings/badges/index.html.haml
@@ -0,0 +1,4 @@
+- breadcrumb_title _('Badges')
+- page_title _('Badges')
+
+= render 'shared/badges/badge_settings'
diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/settings/ci_cd/_badge.html.haml
similarity index 100%
rename from app/views/projects/pipelines_settings/_badge.html.haml
rename to app/views/projects/settings/ci_cd/_badge.html.haml
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..20868f9ba5d6d5df05bba0c7af028e7d8e2cbb38
--- /dev/null
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -0,0 +1,164 @@
+.row.prepend-top-default
+  .col-lg-12
+    = form_for @project, url: project_settings_ci_cd_path(@project) do |f|
+      = form_errors(@project)
+      %fieldset.builds-feature
+        .form-group
+          %h5 Auto DevOps (Beta)
+          %p
+            Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.
+            = link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md')
+            - message = auto_devops_warning_message(@project)
+            - if message
+              %p.settings-message.text-center
+                = message.html_safe
+          = f.fields_for :auto_devops_attributes, @auto_devops do |form|
+            .radio
+              = form.label :enabled_true do
+                = form.radio_button :enabled, 'true'
+                %strong Enable Auto DevOps
+                %br
+                %span.descr
+                  The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project.
+
+            .radio
+              = form.label :enabled_false do
+                = form.radio_button :enabled, 'false'
+                %strong Disable Auto DevOps
+                %br
+                %span.descr
+                  An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery.
+
+            .radio
+              = form.label :enabled_ do
+                = form.radio_button :enabled, ''
+                %strong Instance default (#{Gitlab::CurrentSettings.auto_devops_enabled? ? 'enabled' : 'disabled'})
+                %br
+                %span.descr
+                  Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>.
+            %p
+              You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.
+            = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
+
+        %hr
+        .form-group.append-bottom-default.js-secret-runner-token
+          = f.label :runners_token, "Runner token", class: 'label-light'
+          .form-control.js-secret-value-placeholder
+            = '*' * 20
+          = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89'
+          %p.help-block The secure token used by the Runner to checkout the project
+          %button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } }
+            = _('Reveal value')
+
+        %hr
+        .form-group
+          %h5.prepend-top-0
+            Git strategy for pipelines
+          %p
+            Choose between <code>clone</code> or <code>fetch</code> to get the recent application code
+            = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank'
+          .radio
+            = f.label :build_allow_git_fetch_false do
+              = f.radio_button :build_allow_git_fetch, 'false'
+              %strong git clone
+              %br
+              %span.descr
+                Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job
+          .radio
+            = f.label :build_allow_git_fetch_true do
+              = f.radio_button :build_allow_git_fetch, 'true'
+              %strong git fetch
+              %br
+              %span.descr
+                Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)
+
+        %hr
+        .form-group
+          = f.label :build_timeout_human_readable, 'Timeout', class: 'label-light'
+          = f.text_field :build_timeout_human_readable, class: 'form-control'
+          %p.help-block
+            Per job. 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
+            = f.label :public_builds do
+              = f.check_box :public_builds
+              %strong Public pipelines
+            .help-block
+              Allow public access to pipelines and job details, including output logs and artifacts
+              = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines'), target: '_blank'
+            .bs-callout.bs-callout-info
+              %p If enabled:
+              %ul
+                %li
+                  For public projects, anyone can view pipelines and access job details (output logs and artifacts)
+                %li
+                  For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)
+                %li
+                  For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)
+              %p
+                If disabled, the access level will depend on the user's
+                permissions in the project.
+
+        %hr
+        .form-group
+          .checkbox
+            = f.label :auto_cancel_pending_pipelines do
+              = f.check_box :auto_cancel_pending_pipelines, {}, 'enabled', 'disabled'
+              %strong Auto-cancel redundant, pending pipelines
+            .help-block
+              New pipelines will cancel older, pending pipelines on the same branch
+              = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'auto-cancel-pending-pipelines'), target: '_blank'
+
+        %hr
+        .form-group
+          = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
+          .input-group
+            %span.input-group-addon /
+            = f.text_field :build_coverage_regex, class: 'form-control', placeholder: 'Regular expression'
+            %span.input-group-addon /
+          %p.help-block
+            A regular expression that will be used to find the test coverage
+            output in the job trace. Leave blank to disable
+            = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank'
+          .bs-callout.bs-callout-info
+            %p Below are examples of regex for existing tools:
+            %ul
+              %li
+                Simplecov (Ruby) -
+                %code \(\d+.\d+\%\) covered
+              %li
+                pytest-cov (Python) -
+                %code \d+\%\s*$
+              %li
+                phpunit --coverage-text --colors=never (PHP) -
+                %code ^\s*Lines:\s*\d+.\d+\%
+              %li
+                gcovr (C/C++) -
+                %code ^TOTAL.*\s+(\d+\%)$
+              %li
+                tap --coverage-report=text-summary (NodeJS) -
+                %code ^Statements\s*:\s*([^%]+)
+              %li
+                excoveralls (Elixir) -
+                %code \[TOTAL\]\s+(\d+\.\d+)%
+              %li
+                JaCoCo (Java/Kotlin)
+                %code Total.*?([0-9]{1,3})%
+
+        = f.submit 'Save changes', class: "btn btn-save"
+
+%hr
+
+.row.prepend-top-default
+  = render partial: 'badge', collection: @badges
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 756f31f91d942a461e154a46284149f36f815ffc..09268c9943b578aff87208a5a6031c4e09b3475e 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -3,23 +3,24 @@
 - page_title "CI / CD"
 
 - expanded = Rails.env.test?
+- general_expanded = @project.errors.empty? ? expanded : true
 
-%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if expanded) }
+%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
   .settings-header
     %h4
       General pipelines settings
-    %button.btn.js-settings-toggle
+    %button.btn.js-settings-toggle{ type: 'button' }
       = expanded ? 'Collapse' : 'Expand'
     %p
       Update your CI/CD configuration, like job timeout or Auto DevOps.
   .settings-content
-    = render 'projects/pipelines_settings/show'
+    = render 'form'
 
 %section.settings.no-animate{ class: ('expanded' if expanded) }
   .settings-header
     %h4
       Runners settings
-    %button.btn.js-settings-toggle
+    %button.btn.js-settings-toggle{ type: 'button' }
       = expanded ? 'Collapse' : 'Expand'
     %p
       Register and see your runners for this project.
@@ -31,7 +32,7 @@
     %h4
       = _('Secret variables')
       = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer'
-    %button.btn.js-settings-toggle
+    %button.btn.js-settings-toggle{ type: 'button' }
       = expanded ? 'Collapse' : 'Expand'
     %p.append-bottom-0
       = render "ci/variables/content"
@@ -42,7 +43,7 @@
   .settings-header
     %h4
       Pipeline triggers
-    %button.btn.js-settings-toggle
+    %button.btn.js-settings-toggle{ type: 'button' }
       = expanded ? 'Collapse' : 'Expand'
     %p
       Triggers can force a specific branch or tag to get rebuilt with an API call.  These tokens will
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index 235d532bf988db9702320ea9d2f18465d90121af..f57590a908ff66dd5cbafa726f2d09fa0c50a349 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -2,9 +2,6 @@
 - page_title "Repository"
 - @content_class = "limit-container-width" unless fluid_layout
 
-- content_for :page_specific_javascripts do
-  = webpack_bundle_tag('common_vue')
-
 -# Protected branches & tags use a lot of nested partials.
 -# The shared parts of the views can be found in the `shared` directory.
 -# Those are used throughout the actual views. These `shared` views are then
@@ -12,3 +9,4 @@
 = render "projects/protected_branches/index"
 = render "projects/protected_tags/index"
 = render @deploy_keys
+= render "projects/deploy_tokens/index"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index fa281327eb70332dd4fbeb0ee5bebe4c1032fe03..e28accd5b434c9caa7fb617718293a79d8043546 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,5 +1,5 @@
 - @no_container = true
-- breadcrumb_title "Details"
+- breadcrumb_title _("Details")
 - @content_class = "limit-container-width" unless fluid_layout
 - show_auto_devops_callout = show_auto_devops_callout?(@project)
 
@@ -24,7 +24,7 @@
     .text-warning.center.prepend-top-20
       %p
         = icon("exclamation-triangle fw")
-        #{ _('Archived project! Repository is read-only') }
+        #{ _('Archived project! Repository and other project resources are read-only') }
 
   - view_path = @project.default_view
 
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 3d5f92f9aaab2e0a70410b63f4feb5ea74bd7bb1..98b4d6339da754745aa61313fdc36c7d3b63666e 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -31,6 +31,6 @@
       = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
         = icon("pencil")
 
-    - if can?(current_user, :admin_project, @project)
-      = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
-        = icon("trash-o")
+      - if can?(current_user, :admin_project, @project)
+        = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, 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 da364b58e3609fb6e1f93b7e79b60168a170f673..10415d011d69048af66ce4dd4149e682361624f0 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -1,7 +1,6 @@
 - @no_container = true
 - @sort ||= sort_value_recently_updated
 - page_title s_('TagsPage|Tags')
-- add_to_breadcrumbs("Repository", project_tree_path(@project))
 
 .flex-list{ class: container_class }
   .top-area.adjust
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index dfe2c37ed8e57158883a25e5017ff51c80fd52e7..7a3469cdd2663e2c1c2dec95d0eb346fda320fd3 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -28,7 +28,7 @@
         = icon('history')
       .btn-container.controls-item
         = render 'projects/buttons/download', project: @project, ref: @tag.name
-      - if can?(current_user, :admin_project, @project)
+      - if can?(current_user, :push_code, @project) && can?(current_user, :admin_project, @project)
         .btn-container.controls-item-full
           = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do
             %i.fa.fa-trash-o
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 395114355086661398da8ed111f2684fe885fbc8..8587d3b0c0d59d1dd430a4cc92bd4ec409f440db 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,3 +1,6 @@
+- can_collaborate = can_collaborate_with_project?(@project)
+- can_create_mr_from_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
+
 .tree-ref-container
   .tree-ref-holder
     = render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
@@ -15,7 +18,7 @@
       %li
         = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
 
-    - if current_user
+    - if can_collaborate || can_create_mr_from_fork
       %li
         %a.btn.add-to-tree{ addtotree_toggle_attributes }
           = sprite_icon('plus', size: 16, css_class: 'pull-left')
@@ -35,7 +38,7 @@
                 %li
                   = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
                     #{ _('New directory') }
-              - elsif can?(current_user, :fork_project, @project)
+              - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
                 %li
                   - continue_params = { to:         project_new_blob_path(@project, @id),
                                         notice:     edit_in_new_fork_notice,
@@ -61,24 +64,25 @@
                   = link_to fork_path, method: :post do
                     #{ _('New directory') }
 
-              %li.divider
-              %li.dropdown-header
-                #{ _('This repository') }
-              %li
-                = link_to new_project_branch_path(@project) do
-                  #{ _('New branch') }
-              %li
-                = link_to new_project_tag_path(@project) do
-                  #{ _('New tag') }
+              - if can?(current_user, :push_code, @project)
+                %li.divider
+                %li.dropdown-header
+                  #{ _('This repository') }
+                %li
+                  = link_to new_project_branch_path(@project) do
+                    #{ _('New branch') }
+                %li
+                  = link_to new_project_tag_path(@project) do
+                    #{ _('New tag') }
 
 .tree-controls
-  - if show_new_ide?
-    = succeed " " do
-      = link_to ide_edit_path(@project, @id), class: 'btn btn-default' do
-        = _('Web IDE')
-
   = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
 
   = render 'projects/find_file_link'
 
+  - if can_collaborate
+    = succeed " " do
+      = link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do
+        = _('Web IDE')
+
   = render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index 915e648a5d3b9185f39071d003661f44607cb775..7d43fd61081c6c08779181cc751f67100e84a07a 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -14,25 +14,25 @@
           = link_to search_filter_path(scope: 'issues') do
             Issues
             %span.badge
-              = @search_results.issues_count
+              = limited_count(@search_results.limited_issues_count)
       - if project_search_tabs?(:merge_requests)
         %li{ class: active_when(@scope == 'merge_requests') }
           = link_to search_filter_path(scope: 'merge_requests') do
             Merge requests
             %span.badge
-              = @search_results.merge_requests_count
+              = limited_count(@search_results.limited_merge_requests_count)
       - if project_search_tabs?(:milestones)
         %li{ class: active_when(@scope == 'milestones') }
           = link_to search_filter_path(scope: 'milestones') do
             Milestones
             %span.badge
-              = @search_results.milestones_count
+              = limited_count(@search_results.limited_milestones_count)
       - if project_search_tabs?(:notes)
         %li{ class: active_when(@scope == 'notes') }
           = link_to search_filter_path(scope: 'notes') do
             Comments
             %span.badge
-              = @search_results.notes_count
+              = limited_count(@search_results.limited_notes_count)
       - if project_search_tabs?(:wiki)
         %li{ class: active_when(@scope == 'wiki_blobs') }
           = link_to search_filter_path(scope: 'wiki_blobs') do
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index e43796e9654a6776a41a1fbd014d253882fc8027..e4902d368e7ca8864a355a4c45684eafcaf70450 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -22,7 +22,7 @@
     %span.dropdown-toggle-text
       Project:
       - if @project.present?
-        = @project.name_with_namespace
+        = @project.full_name
       - else
         Any
     = icon("chevron-down")
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 60ef44482f07b12f28d707721c03a9ec1e504d6f..ab56f48ba4d57b8a96a3cf9835b5fa6b9273608b 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -6,7 +6,7 @@
       = search_entries_info(@search_objects, @scope, @search_term)
     - unless @show_snippets
       - if @project
-        in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
+        in project #{link_to @project.full_name, [@project.namespace.becomes(Namespace), @project]}
       - elsif @group
         in group #{link_to @group.name, @group}
 
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index b4bc8982c05cceb4cbb38cd4ead8f755211ececc..b7a27ef6be2235c0a881a0eeff6c922d4d7e71db 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -10,4 +10,4 @@
     .description.term
       = search_md_sanitize(issue, :description)
   %span.light
-    #{issue.project.name_with_namespace}
+    #{issue.project.full_name}
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 1a5499e4d5864e6c068f78bf75dfd95feffa02ab..8b0fd74f68000a03f797d558e04e3708dc33c4d4 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -11,4 +11,4 @@
     .description.term
       = search_md_sanitize(merge_request, :description)
   %span.light
-    #{merge_request.project.name_with_namespace}
+    #{merge_request.project.full_name}
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index a7e178dfa713157d84698a9dcfc982695e553847..e4ab7b0541f88c067500c386e940c885cdca85b0 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -7,7 +7,7 @@
     %i.fa.fa-comment
     = link_to_member(project, note.author, avatar: false)
     commented on
-    = link_to project.name_with_namespace, project
+    = link_to project.full_name, project
     &middot;
 
     - if note.for_commit?
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 65710c09a892c81d061ce9145fe3ac3271449483..d46c4d11e516c0237a245d01637990b7634de882 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, project_path(snippet_title.project)
+      = link_to snippet_title.project.full_name, project_path(snippet_title.project)
 
   .snippet-info
     = snippet_title.to_reference
diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml
index de52fd00157002e9ff55ca455b2d9355e70102b8..7d3e243495f3480bde2c1fd61634372dfdf81adf 100644
--- a/app/views/sent_notifications/unsubscribe.html.haml
+++ b/app/views/sent_notifications/unsubscribe.html.haml
@@ -1,7 +1,7 @@
 - noteable = @sent_notification.noteable
 - noteable_type = @sent_notification.noteable_type.titleize.downcase
 - noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
-- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.name_with_namespace
+- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
 
 %h3.page-title
   Unsubscribe from #{noteable_type}
diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml
index 934d65e8b42f52382c517cfb221bcff9c2d2200a..e9ac192f5f73e30ebf8e4bd2aa8a749cf231e740 100644
--- a/app/views/shared/_auto_devops_callout.html.haml
+++ b/app/views/shared/_auto_devops_callout.html.haml
@@ -1,14 +1,14 @@
-.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
+.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20.prepend-top-10{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
   .banner-graphic
     = custom_icon('icon_autodevops')
 
-  .prepend-top-10.prepend-left-10.append-bottom-10
-    %h5= s_('AutoDevOps|Auto DevOps (Beta)')
+  .banner-body.prepend-left-10.append-bottom-10
+    %h5.banner-title= s_('AutoDevOps|Auto DevOps (Beta)')
     %p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
     %p
       - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
       = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
-    .prepend-top-10
+    .banner-buttons
       = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout'
 
   %button.btn-transparent.banner-close.close.js-close-callout{ type: 'button',
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 736afa085e838b296dc4b3a9d8e88b132bc133de..3806ead6c873e8ee2255f22225afc80ed31752f7 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -1,17 +1,19 @@
+- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
+
 .form-group.import-url-data
   = f.label :import_url, class: 'label-light' do
-    %span Git repository URL
+    %span
+      = _('Git repository URL')
 
-  = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
+  = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true
 
   .well.prepend-top-20
     %ul
       %li
-        The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.
+        = _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe
       %li
-        If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
+        = _('If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.').html_safe
       %li
-        The import will time out after #{time_interval_in_words(Gitlab.config.gitlab_shell.git_timeout)}.
-        For repositories that take longer, use a clone/push combination.
+        = import_will_timeout_message(ci_cd_only)
       %li
-        To migrate an SVN repository, check out #{link_to "this document", help_page_path('user/project/import/svn')}.
+        = import_svn_message(ci_cd_only)
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 435acbc634cabd37d9e695d47d0ad036fead6bed..430d9a9dd766a4717d66eee1124891ac2fc4c7ad 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.issuable-mr.hidden-xs
+  %li.issuable-mr.hidden-xs.has-tooltip{ title: _('Related merge requests') }
     = image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
     = issuable_mr
 
 - if upvotes > 0
-  %li.issuable-upvotes.hidden-xs
+  %li.issuable-upvotes.hidden-xs.has-tooltip{ title: _('Upvotes') }
     = icon('thumbs-up')
     = upvotes
 
 - if downvotes > 0
-  %li.issuable-downvotes.hidden-xs
+  %li.issuable-downvotes.hidden-xs.has-tooltip{ title: _('Downvotes') }
     = icon('thumbs-down')
     = downvotes
 
 %li.issuable-comments.hidden-xs
-  = link_to issuable_url, class: ('no-comments' if note_count.zero?) do
+  = link_to issuable_url, class: ['has-tooltip', ('no-comments' if note_count.zero?)], title: _('Comments') do
     = icon('comments')
     = note_count
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 8847d11f623ab276aa2ed7b5e51976f40a3d2da0..836df57a3a2bea635c7ece751fbea212eb014c4a 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -47,11 +47,20 @@
               class: 'text-danger'
 
   .pull-right.hidden-xs.hidden-sm
-    - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
-      = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting #{label.title} will make it available for all projects inside #{label.project.group.name}. Existing project labels with the same name will be merged. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
-        %span.sr-only Promote to Group
-        = sprite_icon('level-up')
     - if can?(current_user, :admin_label, label)
+      - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
+        %button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'),
+          disabled: true,
+          type: 'button',
+          data: { url: promote_project_label_path(label.project, label),
+                  label_title: label.title,
+                  label_color: label.color,
+                  label_text_color: label.text_color,
+                  group_name: label.project.group.name,
+                  target: '#promote-label-modal',
+                  container: 'body',
+                  toggle: 'modal' } }
+          = sprite_icon('level-up')
       = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
         %span.sr-only Edit
         = sprite_icon('pencil')
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 0a4a24ae8075a772679784d9312acbe7cd17893d..9221fd1e025b9b3c07f796b2f27839e9f179d0ed 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -1,3 +1,6 @@
+- project = @project.present(current_user: current_user)
+- branch_name = selected_branch
+
 = render 'shared/commit_message_container', placeholder: placeholder
 
 - if @project.empty_repo?
@@ -7,12 +10,14 @@
     .form-group.branch
       = 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"
+        = text_field_tag 'branch_name', branch_name, required: true, class: "form-control js-branch-name ref-name"
 
         .js-create-merge-request-container
           = render 'shared/new_merge_request_checkbox'
+  - elsif project.can_current_user_push_to_branch?(branch_name)
+    = hidden_field_tag 'branch_name', branch_name
   - else
-    = hidden_field_tag 'branch_name', @branch_name || tree_edit_branch
+    = hidden_field_tag 'branch_name', branch_name
     = hidden_field_tag 'create_merge_request', 1
 
   = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
diff --git a/app/views/shared/_recaptcha_form.html.haml b/app/views/shared/_recaptcha_form.html.haml
index 93a4301f366046d84289ef19f63d63e401bc8bbf..a0ba1afc28425c83097fafafc00df09219fdfad5 100644
--- a/app/views/shared/_recaptcha_form.html.haml
+++ b/app/views/shared/_recaptcha_form.html.haml
@@ -10,7 +10,7 @@
       = hidden_field(resource_name, field, value: value)
     = hidden_field_tag(:spam_log_id, spammable.spam_log.id)
     = hidden_field_tag(:recaptcha_verification, true)
-    = recaptcha_tags script: script, callback: 'recaptchaDialogCallback'
+    = recaptcha_tags script: script, callback: 'recaptchaDialogCallback' unless Rails.env.test?
 
     -# Yields a block with given extra params.
     = yield
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 479bd2cdb38c225c96b6c35457c0866295dfc6cb..f1c39b9e923fc300caa10994e5281a55d235fd73 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,6 +1,5 @@
 - show_create = local_assigns.fetch(:show_create, false)
 
-- show_new_branch_form = show_new_ide? && show_create && can?(current_user, :push_code, @project)
 - dropdown_toggle_text = @ref || @project.default_branch
 = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
   = hidden_field_tag :destination, destination
@@ -9,21 +8,10 @@
   - @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_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown" }
-    .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
+    = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
+    .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
       .dropdown-page-one
         = dropdown_title _("Switch branch/tag")
         = dropdown_filter _("Search branches and tags")
         = dropdown_content
         = dropdown_loading
-        - if show_new_branch_form
-          = dropdown_footer do
-            %ul.dropdown-footer-list
-              %li
-                %a.dropdown-toggle-page{ href: "#" }
-                  Create new branch
-      - if show_new_branch_form
-        .dropdown-page-two
-          = dropdown_title("Create new branch", options: { back: true })
-          = dropdown_content do
-            .js-new-branch-dropdown
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 61b39afb5d43e56fcbf4285cb8729da183e548d1..a41aaed66a3e3194127a4c719ddeb43e2e46e6c1 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -13,12 +13,12 @@
       .col-sm-10
         = form.check_box :active, disabled: disable_fields_service?(@service)
 
-  - if @service.supported_events.present?
+  - if @service.configurable_events.present?
     .form-group
       = form.label :url, "Trigger", class: 'control-label'
 
       .col-sm-10
-        - @service.supported_events.each do |event|
+        - @service.configurable_events.each do |event|
           %div
             = form.check_box service_event_field_name(event), class: 'pull-left'
             .prepend-left-20
@@ -33,7 +33,7 @@
               = form.text_field field[:name], class: "form-control", placeholder: field[:placeholder]
 
           %p.light
-            = service_event_description(event)
+            = @service.class.event_description(event)
 
   - @service.global_fields.each do |field|
     - type = field[:type]
diff --git a/app/views/shared/badges/_badge_settings.html.haml b/app/views/shared/badges/_badge_settings.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b7c250d3b1ced88cc0baaa313fb55aab436552fb
--- /dev/null
+++ b/app/views/shared/badges/_badge_settings.html.haml
@@ -0,0 +1,4 @@
+#badge-settings{ data: { api_endpoint_url: @badge_api_endpoint,
+  docs_url: help_page_path('user/project/badges')} }
+  .text-center.prepend-top-default
+    = icon('spinner spin 2x')
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 014b8de1dc9766ea3eccc45514436458ab89a5c9..dac600946861c0b32dc58d7a29b9ae79c97c0962 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -1,3 +1,5 @@
+- board = local_assigns.fetch(:board, nil)
+- group = local_assigns.fetch(:group, false)
 - @no_breadcrumb_container = true
 - @no_container = true
 - @content_class = "issue-boards-content"
@@ -5,7 +7,6 @@
 - page_title "Boards"
 
 - content_for :page_specific_javascripts do
-  = webpack_bundle_tag 'common_vue'
 
   -# haml-lint:disable InlineJavaScript
   %script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
@@ -26,8 +27,8 @@
       ":issue-link-base" => "issueLinkBase",
       ":root-path" => "rootPath",
       ":board-id" => "boardId",
-      ":key" => "_uid" }
-  = render "shared/boards/components/sidebar"
+      ":key" => "list.id" }
+  = render "shared/boards/components/sidebar", group: group
   - if @project
     %board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project),
       "milestone-path" => milestones_filter_dropdown_path,
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index c687e66fd43c0c55bbbb4b60b6e5b3f1bf300384..149bf8da4b97462b6093660996d34114ac29dc21 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -4,7 +4,7 @@
     %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" }
       %h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' }
         %i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
-          ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }",
+          ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded }",
           "aria-hidden": "true" }
 
         %span.board-title-text.has-tooltip{ "v-if": "list.type !== \"label\"",
@@ -42,6 +42,7 @@
       ":disabled" => "disabled",
       ":issue-link-base" => "issueLinkBase",
       ":root-path" => "rootPath",
+      ":groupId" => ((current_board_parent.id if @group) || 'null'),
       "ref" => "board-list" }
     - if can?(current_user, :admin_list, current_board_parent)
       %board-blank-state{ "v-if" => 'list.id == "blank"' }
diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index 8e5e32e9f1691b37f43c52b378ef2e4003cd7fdf..b385cc3f962f435a6218457db032645d88fd66ac 100644
--- a/app/views/shared/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -22,6 +22,6 @@
           = render "shared/boards/components/sidebar/labels"
           = render "shared/boards/components/sidebar/notifications"
           %remove-btn{ ":issue" => "issue",
-            ":issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'",
+            ":issue-update" => "issue.sidebarInfoEndpoint",
             ":list" => "list",
             "v-if" => "canRemove" }
diff --git a/app/views/shared/boards/components/sidebar/_assignee.html.haml b/app/views/shared/boards/components/sidebar/_assignee.html.haml
index 3d2e8471a60349a82d1743737a47ab5dcd782bc7..1374da9d82cf8ce632ba2ea745ddb49027fec546 100644
--- a/app/views/shared/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/shared/boards/components/sidebar/_assignee.html.haml
@@ -21,8 +21,7 @@
       .dropdown
         - 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: board_sidebar_user_data,
-          ":data-issuable-id" => "issue.iid",
-          ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+          ":data-issuable-id" => "issue.iid" }
           = dropdown_options[:title]
           = icon("chevron-down")
         .dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml
index db794d6f855a3002421c4b9ce9d814600d52190b..d13b998e6f425ba80059e95a47eb8da8975766ea 100644
--- a/app/views/shared/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml
@@ -22,8 +22,7 @@
         ":value" => "issue.dueDate" }
       .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" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+          data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" } }
           %span.dropdown-toggle-text Due date
           = icon('chevron-down')
         .dropdown-menu.dropdown-menu-due-date
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index dfc0f9be32100e1d741e8537696569d095ddd0e6..1c73534c64207bf009b2ea8fa4c835403ea90f7d 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -4,7 +4,7 @@
     - if can_admin_issue?
       = icon("spinner spin", class: "block-loading")
       = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
-  .value.issuable-show-labels
+  .value.issuable-show-labels.dont-hide
     %span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" }
       None
     %a{ href: "#",
@@ -26,8 +26,7 @@
             project_id: @project&.try(:id),
             labels: labels_filter_path(false),
             namespace_path: @namespace_path,
-            project_path: @project.try(:path) },
-          ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+            project_path: @project.try(:path) } }
           %span.dropdown-toggle-text
             Label
           = icon('chevron-down')
diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index d09c7c218e0062d4ab68f3a01298565ed3f67754..f51c4a97f2bc78ab1ada754963b2c4d4bd2660a0 100644
--- a/app/views/shared/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -18,8 +18,7 @@
       .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]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
           ":data-selected" => "milestoneTitle",
-          ":data-issuable-id" => "issue.iid",
-          ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+          ":data-issuable-id" => "issue.iid" }
           Milestone
           = icon("chevron-down")
         .dropdown-menu.dropdown-select.dropdown-menu-selectable
diff --git a/app/views/shared/dashboard/_no_filter_selected.html.haml b/app/views/shared/dashboard/_no_filter_selected.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b2e6967f6aa8ab6a53b41b7e630d83bcb300a5ef
--- /dev/null
+++ b/app/views/shared/dashboard/_no_filter_selected.html.haml
@@ -0,0 +1,8 @@
+.row.empty-state.text-center
+  .col-xs-12
+    .svg-130.prepend-top-default
+      = image_tag 'illustrations/issue-dashboard_results-without-filter.svg'
+  .col-xs-12
+    .text-content
+      %h4
+        = _("Please select at least one filter to see results")
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 7704c88905b4703e9de04d93f8500a36aa501beb..1bd5b4164b15b7de42e510f7897b8e0e29cfbfc5 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -24,12 +24,9 @@
         .filter-item.inline.labels-filter
           = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
 
-        - if issuable_filter_present?
-          .filter-item.inline.reset-filters
-            %a{ href: page_filter_path(without: issuable_filter_params) } Reset filters
-
-        .pull-right
-          = render 'shared/sort_dropdown'
+        - unless @no_filters_set
+          .pull-right
+            = render 'shared/sort_dropdown'
 
   - has_labels = @labels && @labels.any?
   .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 6dfabd7ba4cd8363fbeb35c64d097dfea0d2ffa3..4c8f03f149835aef1094e42cf966605eb99438b2 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -33,6 +33,8 @@
 
 = render 'shared/issuable/form/merge_params', issuable: issuable
 
+= render 'shared/issuable/form/contribution', issuable: issuable, form: form
+
 - if @merge_request_to_resolve_discussions_of
   .form-group
     .col-sm-10.col-sm-offset-2
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
index d5e7d3b87b703845e21362e782e9408354cce64a..91aa329eb9352018805ed8c569b0b20d442d566f 100644
--- a/app/views/shared/issuable/_label_page_create.html.haml
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -1,5 +1,6 @@
+- subject = @project || @group
 .dropdown-page-two.dropdown-new-label
-  = dropdown_title("Create new label", options: { back: true })
+  = dropdown_title(create_label_title(subject), options: { back: true })
   = dropdown_content do
     .dropdown-labels-error.js-label-error
     %input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') }
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 6a83321abcb2753d40a25d45e4b05999fa701dd6..2bd922bca2b53f56db0597effa356ac9e3c073ef 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -3,6 +3,7 @@
 - show_footer = local_assigns.fetch(:show_footer, true)
 - filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search')
 - show_boards_content = local_assigns.fetch(:show_boards_content, false)
+- subject = @project || @group
 .dropdown-page-one
   = dropdown_title(title)
   - if show_boards_content
@@ -17,11 +18,11 @@
         - if can?(current_user, :admin_label, current_board_parent)
           %li
             %a.dropdown-toggle-page{ href: "#" }
-              = _('Create new label')
+              = create_label_title(subject)
         %li
           = link_to labels_path, :"data-is-link" => true do
             - if show_create && can?(current_user, :admin_label, current_board_parent)
-              = _('Manage labels')
+              = manage_labels_title(subject)
             - else
-              = _('View labels')
+              = view_labels_title(subject)
   = dropdown_loading
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 4d8109eb90ca8a531ab146f5662d137e2663d383..a5f40ea934b68e2c0b02d403d20ba4eb839cff1d 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -1,22 +1,23 @@
 - type = local_assigns.fetch(:type, :issues)
 - page_context_word = type.to_s.humanize(capitalize: false)
+- display_count = local_assigns.fetch(:display_count, :true)
 
 %ul.nav-links.issues-state-filters.mobile-separator
   %li{ class: active_when(params[: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)}
+      #{issuables_state_counter_text(type, :opened, display_count)}
 
   - if type == :merge_requests
     %li{ class: active_when(params[: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)}
+        #{issuables_state_counter_text(type, :merged, display_count)}
 
     %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)}
+        #{issuables_state_counter_text(type, :closed, display_count)}
   - 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)}
+        #{issuables_state_counter_text(type, :closed, display_count)}
 
-  = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all)
+  = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count)
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index fabb17c7340acdf97fd17bc358c2c8ef545383e8..fc6f71ef60f59bf63b0bb03a72fcb882b9d594f4 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -112,6 +112,7 @@
                   - if can?(current_user, :admin_label, board.parent)
                     = render partial: "shared/issuable/label_page_create"
                   = dropdown_loading
-              #js-add-issues-btn.prepend-left-10
+              - if @project
+                #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
           - elsif type != :boards_modal
             = render 'shared/sort_dropdown'
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index adaddda13ebf93f84ff39ed7d759a429cbba6332..093033775a917503a2e913ee326a9dc390335270 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -7,7 +7,7 @@
       - if current_user
         %span.issuable-header-text.hide-collapsed.pull-left
           = _('Todo')
-      %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
+      %a.gutter-toggle.pull-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left' } }
         = sidebar_gutter_toggle_icon
       - if current_user
         = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable
@@ -19,12 +19,11 @@
       .block.assignee
         = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present?
       .block.milestone
-        .sidebar-collapsed-icon
+        .sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } }
           = icon('clock-o', 'aria-hidden': 'true')
           %span.milestone-title
             - if issuable.milestone
-              %span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_tooltip_title(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } }
-                = issuable.milestone.title
+              = issuable.milestone.title
             - else
               = _('None')
         .title.hide-collapsed
@@ -34,7 +33,7 @@
             = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
         .value.hide-collapsed
           - if issuable.milestone
-            = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_title(issuable.milestone), data: { container: "body", html: 1 }
+            = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 1 }
           - else
             %span.no-value
               = _('None')
@@ -50,7 +49,7 @@
             = icon('spinner spin', 'aria-hidden': 'true')
       - if issuable.has_attribute?(:due_date)
         .block.due_date
-          .sidebar-collapsed-icon
+          .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 1 }, title: sidebar_due_date_tooltip_label(issuable) }
             = icon('calendar', 'aria-hidden': 'true')
             %span.js-due-date-sidebar-value
               = issuable.due_date.try(:to_s, :medium) || 'None'
@@ -84,7 +83,7 @@
                   = dropdown_content do
                     .js-due-date-calendar
 
-      - if @labels && @labels.any?
+      - if @labels
         - selected_labels = issuable.labels
         .block.labels
           .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
@@ -96,7 +95,7 @@
             = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
             - if can_edit_issuable
               = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
-          .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
+          .value.issuable-show-labels.dont-hide.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
             - if selected_labels.any?
               - selected_labels.each do |label|
                 = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name)
@@ -107,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: (project_labels_path(@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: (labels_filter_path(false) 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 304df38a09613e329e58ee435d29c3cda26d763b..21006a76b28eac55184e545ecc490545c4068441 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -4,7 +4,7 @@
       = _('Assignee')
       = icon('spinner spin')
 - else
-  .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) }
+  .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: sidebar_assignee_tooltip_label(issuable) }
     - if issuable.assignee
       = link_to_member(@project, issuable.assignee, size: 24)
     - else
diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml
index b77e104c07252156648e12ba41eb512417366f56..74327fb1ba8966ef4730f3431f6e694d50651d88 100644
--- a/app/views/shared/issuable/_sidebar_todo.html.haml
+++ b/app/views/shared/issuable/_sidebar_todo.html.haml
@@ -1,11 +1,11 @@
 - is_collapsed = local_assigns.fetch(:is_collapsed, false)
-- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark done')
+- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark todo as done')
 - todo_content = is_collapsed ? icon('plus-square') : _('Add todo')
 
 %button.issuable-todo-btn.js-issuable-todo{ type: 'button',
   class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn pull-right'),
-  title: (todo.nil? ? _('Add todo') : _('Mark done')),
-  'aria-label' => (todo.nil? ? _('Add todo') : _('Mark done')),
+  title: (todo.nil? ? _('Add todo') : _('Mark todo as done')),
+  'aria-label' => (todo.nil? ? _('Add todo') : _('Mark todo as done')),
   data: issuable_todo_button_data(issuable, todo, is_collapsed) }
   %span.issuable-todo-inner.js-issuable-todo-inner<
     - if todo
diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..de508278d7c8093ec5e27f10d338b44ed2038a88
--- /dev/null
+++ b/app/views/shared/issuable/form/_contribution.html.haml
@@ -0,0 +1,20 @@
+- issuable = local_assigns.fetch(:issuable)
+- form = local_assigns.fetch(:form)
+
+- return unless issuable.is_a?(MergeRequest)
+- return unless issuable.for_fork?
+- return unless can?(current_user, :push_code, issuable.source_project)
+
+%hr
+
+.form-group
+  .control-label
+    = _('Contribution')
+  .col-sm-10
+    .checkbox
+      = form.label :allow_maintainer_to_push do
+        = form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user)
+        = _('Allow edits from maintainers.')
+        = link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access')
+        .help-block
+          = allow_maintainer_push_unavailable_reason(issuable)
diff --git a/app/views/shared/issuable/form/_merge_request_assignee.html.haml b/app/views/shared/issuable/form/_merge_request_assignee.html.haml
index bf8613b0f0d21f0ae343c88898b54729954dbd06..d7740eddcca645fba1ecaccea32db3e9609dc020 100644
--- a/app/views/shared/issuable/form/_merge_request_assignee.html.haml
+++ b/app/views/shared/issuable/form/_merge_request_assignee.html.haml
@@ -1,6 +1,6 @@
 - merge_request = issuable
 .block.assignee
-  .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (merge_request.assignee.name if merge_request.assignee) }
+  .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: sidebar_assignee_tooltip_label(issuable) }
     - if merge_request.assignee
       = link_to_member(@project, merge_request.assignee, size: 24)
     - else
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 5868c52566d09f2c8ab302ca2f68a168fbe4f9c7..fc634856061a231b0c6d1e8dd3f48c583763ef93 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -8,7 +8,7 @@
     %strong
       = link_to group.full_name, group_path(group)
     .cgray
-      Joined #{time_ago_with_tooltip(group.created_at)}
+      Given access #{time_ago_with_tooltip(group_link.created_at)}
       - if group_link.expires?
         路
         %span{ class: ('text-warning' if group_link.expires_soon?) }
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index ba57d922c6df7f1d15ea0762a3acccc1d9aca64b..1c139827acfbab46bf9dea978090d09f800b821b 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -29,7 +29,7 @@
             Requested
             = time_ago_with_tooltip(member.requested_at)
           - else
-            Joined #{time_ago_with_tooltip(member.created_at)}
+            Given access #{time_ago_with_tooltip(member.created_at)}
           - if member.expires?
             路
             %span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) }
diff --git a/app/views/shared/milestones/_deprecation_message.html.haml b/app/views/shared/milestones/_deprecation_message.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4a8f90937eaa59b3c3a452488507eb186e76a81d
--- /dev/null
+++ b/app/views/shared/milestones/_deprecation_message.html.haml
@@ -0,0 +1,14 @@
+.banner-callout.compact.milestone-deprecation-message.js-milestone-deprecation-message.prepend-top-20
+  .banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
+  .banner-body.prepend-left-10.append-right-10
+    %h5.banner-title.prepend-top-0= _('This page will be removed in a future release.')
+    %p.milestone-banner-text= _('Use group milestones to manage issues from multiple projects in the same milestone.')
+    = button_tag _('Promote these project milestones into a group milestone.'), class: 'btn btn-link js-popover-link text-align-left milestone-banner-link'
+    .milestone-banner-buttons.prepend-top-20= link_to _('Learn more'), help_page_url('user/project/milestones/index', anchor: 'promoting-project-milestones-to-group-milestones'), class: 'btn btn-default', target: '_blank'
+
+  %template.js-milestone-deprecation-message-template
+    .milestone-popover-body
+      %ol.milestone-popover-instructions-list.append-bottom-0
+        %li= _('Click any <strong>project name</strong> in the project list below to navigate to the project milestone.').html_safe
+        %li= _('Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone.').html_safe
+    .milestone-popover-footer= link_to _('Learn more'), help_page_url('user/project/milestones/index', anchor: 'promoting-project-milestones-to-group-milestones'), class: 'btn btn-link prepend-left-0', target: '_blank'
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index 129f6ab604e173346bb854d415f60f68b1afa694..eba64daaadc1ede03178f5f52dab579228d89043 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -12,7 +12,7 @@
     - if show_project_name
       %strong #{project.name} &middot;
     - elsif show_full_project_name
-      %strong #{project.name_with_namespace} &middot;
+      %strong #{project.full_name} &middot;
     - if issuable.is_a?(Issue)
       = confidential_icon(issuable)
     = link_to issuable.title, issuable_url_args, title: issuable.title
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index e3b2b53833e9cc3417353aa6d323b991a5427a89..ac494814f55d47dad9c37f03acb8a27972f66ba7 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -27,7 +27,7 @@
             - milestone.milestones.each do |milestone|
               = link_to milestone_path(milestone) do
                 %span.label.label-gray
-                  = dashboard ? milestone.project.name_with_namespace : milestone.project.name
+                  = dashboard ? milestone.project.full_name : milestone.project.name
       - if @group
         .col-sm-6.milestone-actions
           - if can?(current_user, :admin_milestones, @group)
@@ -51,18 +51,26 @@
           \
 
           - if @project.group
-            = link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "Promoting #{milestone.title} will make it available for all projects inside #{@project.group.name}. Existing project milestones with the same name will be merged. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
-              Promote
+            %button.js-promote-project-milestone-button.btn.btn-xs.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'),
+              disabled: true,
+              type: 'button',
+              data: { url: promote_project_milestone_path(milestone.project, milestone),
+                      milestone_title: milestone.title,
+                      group_name: @project.group.name,
+                      target: '#promote-milestone-modal',
+                      container: 'body',
+                      toggle: 'modal' } }
+              = _('Promote')
 
           = 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"
 
-        %button.js-delete-milestone-button.btn.btn-xs.btn-grouped.btn-danger{ data: { toggle: 'modal',
-          target: '#delete-milestone-modal',
-          milestone_id: milestone.id,
-          milestone_title: markdown_field(milestone, :title),
-          milestone_url: project_milestone_path(milestone.project, milestone),
-          milestone_issue_count: milestone.issues.count,
-          milestone_merge_request_count: milestone.merge_requests.count },
-          disabled: true }
-          = _('Delete')
-          = icon('spin spinner', class: 'js-loading-icon hidden' )
+          %button.js-delete-milestone-button.btn.btn-xs.btn-grouped.btn-danger{ data: { toggle: 'modal',
+            target: '#delete-milestone-modal',
+            milestone_id: milestone.id,
+            milestone_title: markdown_field(milestone, :title),
+            milestone_url: project_milestone_path(milestone.project, milestone),
+            milestone_issue_count: milestone.issues.count,
+            milestone_merge_request_count: milestone.merge_requests.count },
+            disabled: true }
+            = _('Delete')
+            = icon('spin spinner', class: 'js-loading-icon hidden' )
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index a942ebc328b7e4b217a2a58a03bd35a88845b9a5..8e9a1b56bb88683869bea37292793349c7ed9486 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -4,12 +4,8 @@
 %aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix", "always-show-toggle" => true }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
   .issuable-sidebar.milestone-sidebar
     .block.milestone-progress.issuable-sidebar-header
-      %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
+      %a.gutter-toggle.pull-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left' } }
         = sidebar_gutter_toggle_icon
-
-      .sidebar-collapsed-icon
-        %span== #{milestone.percent_complete(current_user)}%
-        = milestone_progress_bar(milestone)
       .title.hide-collapsed
         %strong.bold== #{milestone.percent_complete(current_user)}%
         %span.hide-collapsed
@@ -17,6 +13,11 @@
       .value.hide-collapsed
         = milestone_progress_bar(milestone)
 
+    .block.milestone-progress.hide-expanded
+      .sidebar-collapsed-icon.has-tooltip{ title: milestone_progress_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } }
+        %span== #{milestone.percent_complete(current_user)}%
+        = milestone_progress_bar(milestone)
+
     .block.start_date.hide-collapsed
       .title
         Start date
@@ -35,19 +36,25 @@
         %span.collapsed-milestone-date
           - if milestone.start_date && milestone.due_date
             - if milestone.start_date.year == milestone.due_date.year
-              .milestone-date= milestone.start_date.strftime('%b %-d')
+              .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
+                = milestone.start_date.strftime('%b %-d')
             - else
-              .milestone-date= milestone.start_date.strftime('%b %-d %Y')
+              .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
+                = milestone.start_date.strftime('%b %-d %Y')
             .date-separator -
-            .due_date= milestone.due_date.strftime('%b %-d %Y')
+            .due_date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 1, placement: 'left' } }
+              = milestone.due_date.strftime('%b %-d %Y')
           - elsif milestone.start_date
             From
-            .milestone-date= milestone.start_date.strftime('%b %-d %Y')
+            .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
+              = milestone.start_date.strftime('%b %-d %Y')
           - elsif milestone.due_date
             Until
-            .milestone-date= milestone.due_date.strftime('%b %-d %Y')
+            .milestone-date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 1, placement: 'left' } }
+              = milestone.due_date.strftime('%b %-d %Y')
           - else
-            None
+            .has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
+              None
       .title.hide-collapsed
         Due date
         - if @project && can?(current_user, :admin_milestone, @project)
@@ -58,21 +65,21 @@
             %span.bold= milestone.due_date.to_s(:medium)
           - else
             %span.no-value No due date
-        - remaining_days = milestone_remaining_days(milestone)
+        - remaining_days = remaining_days_in_words(milestone)
         - if remaining_days.present?
           = surround '(', ')' do
             %span.remaining-days= remaining_days
 
     - if !project || can?(current_user, :read_issue, project)
       .block.issues
-        .sidebar-collapsed-icon
+        .sidebar-collapsed-icon.has-tooltip{ title: milestone_issues_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } }
           %strong
             = custom_icon('issues')
           %span= milestone.issues_visible_to_user(current_user).count
         .title.hide-collapsed
           Issues
           %span.badge= milestone.issues_visible_to_user(current_user).count
-          - if project && can?(current_user, :create_issue, project)
+          - if show_new_issue_link?(project)
             = 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
@@ -93,7 +100,7 @@
           = icon('spinner spin')
 
     .block.merge-requests
-      .sidebar-collapsed-icon
+      .sidebar-collapsed-icon.has-tooltip{ title: milestone_merge_requests_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } }
         %strong
           = custom_icon('mr_bold')
         %span= milestone.merge_requests.count
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index fd0760d83a540729a668b1c63a3b22f88d289526..797ff034bb2d256ce6bf76b6b9bc6d2f5c5e499c 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -1,6 +1,8 @@
-- page_title milestone.title, "Milestones"
+- page_title milestone.title
+- @breadcrumb_link = dashboard_milestone_path(milestone.safe_title, title: milestone.title)
 
 - group = local_assigns[:group]
+- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
 
 .detail-page-header
   %a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" }
@@ -17,7 +19,7 @@
     Milestone #{milestone.title}
   - if milestone.due_date || milestone.start_date
     %span.creator
-      &middot;
+      &nbsp;&middot;
       = milestone_date_range(milestone)
   - if group
     .pull-right
@@ -30,21 +32,23 @@
         - else
           = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
 
+= render 'shared/milestones/deprecation_message' if is_dynamic_milestone
+
 .detail-page-description.milestone-detail
   %h2.title
     = markdown_field(milestone, :title)
-  - if @milestone.group_milestone? && @milestone.description.present?
+  - if milestone.group_milestone? && milestone.description.present?
     %div
       .description
         .wiki
-          = markdown_field(@milestone, :description)
+          = markdown_field(milestone, :description)
 
 - if milestone.complete?(current_user) && milestone.active?
   .alert.alert-success.prepend-top-default
     - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
     %span All issues for this milestone are closed. #{close_msg}
 
-- if @milestone.legacy_group_milestone? || @milestone.dashboard_milestone?
+- if is_dynamic_milestone
   .table-holder
     %table.table
       %thead
@@ -56,7 +60,7 @@
       - milestone.milestones.each do |ms|
         %tr
           %td
-            - project_name = group ? ms.project.name : ms.project.name_with_namespace
+            - project_name = group ? ms.project.name : ms.project.full_name
             = link_to project_name, project_milestone_path(ms.project, ms)
           %td
             = ms.issues_visible_to_user(current_user).opened.count
@@ -67,7 +71,7 @@
               Open
           %td
             = ms.expires_at
-- elsif @milestone.group_milestone?
+- elsif milestone.group_milestone?
   %br
   View
   = link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title)
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index bf359774eadf9d7e848a06db095cafbe43b63a16..893a7f26ebdd8ccf562264c6ce45a2956b902163 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -2,7 +2,7 @@
 - return if note.cross_reference_not_visible_for?(current_user)
 
 - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false)
-- note_editable = note_editable?(note)
+- note_editable = can?(current_user, :admin_note, note)
 - note_counter = local_assigns.fetch(:note_counter, 0)
 
 %li.timeline-entry{ id: dom_id(note),
diff --git a/app/views/shared/plugins/_index.html.haml b/app/views/shared/plugins/_index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..fc643c3ecc2b703362fa1614a5182957dbdfc7c8
--- /dev/null
+++ b/app/views/shared/plugins/_index.html.haml
@@ -0,0 +1,23 @@
+- plugins = Gitlab::Plugin.files
+
+.row.prepend-top-default
+  .col-lg-4
+    %h4.prepend-top-0
+      Plugins
+    %p
+      #{link_to 'Plugins', help_page_path('administration/plugins')} are similar to
+      system hooks but are executed as files instead of sending data to a URL.
+
+  .col-lg-8.append-bottom-default
+    - if plugins.any?
+      .panel.panel-default
+        .panel-heading
+          Plugins (#{plugins.count})
+        %ul.content-list
+          - plugins.each do |file|
+            %li
+              .monospace
+                = File.basename(file)
+    - else
+      %p.light-well.text-center
+        No plugins found.
diff --git a/app/views/shared/projects/_edit_information.html.haml b/app/views/shared/projects/_edit_information.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ec9dc8f62c2a40b99e4de6e609e78dbd10476daa
--- /dev/null
+++ b/app/views/shared/projects/_edit_information.html.haml
@@ -0,0 +1,6 @@
+- unless can?(current_user, :push_code, @project)
+  .inline.prepend-left-10
+    - if @project.branch_allows_maintainer_push?(current_user, selected_branch)
+      = commit_in_single_accessible_branch
+    - else
+      = commit_in_fork_help
diff --git a/app/views/shared/snippets/_embed.html.haml b/app/views/shared/snippets/_embed.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..2d93e51a2d9a882ac133c952b9fe272f6eb7ee29
--- /dev/null
+++ b/app/views/shared/snippets/_embed.html.haml
@@ -0,0 +1,24 @@
+- blob = @snippet.blob
+.gitlab-embed-snippets
+  .js-file-title.file-title-flex-parent
+    .file-header-content
+      = external_snippet_icon('doc_text')
+
+      %strong.file-title-name
+        %a.gitlab-embedded-snippets-title{ href: url_for(only_path: false, overwrite_params: nil) }
+          = blob.name
+
+      %small
+        = number_to_human_size(blob.raw_size)
+      %a.gitlab-logo{ href: url_for(only_path: false, overwrite_params: nil), title: 'view on gitlab' }
+        on &nbsp;
+        %span.logo-text
+          GitLab
+
+    .file-actions.hidden-xs
+      .btn-group{ role: "group" }<
+        = embedded_snippet_raw_button
+
+        = embedded_snippet_download_button
+  %article.file-holder.snippet-file-content
+    = render 'projects/blob/viewer', viewer: @snippet.blob.simple_viewer, load_async: false, external_embed: true
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 12df79a28c7370dff9de38154836107710e96874..836230ae8eeccbc5b8acdc8072eb68ac77fed5ad 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -19,11 +19,32 @@
   %h2.snippet-title.prepend-top-0.append-bottom-0
     = markdown_field(@snippet, :title)
 
-  - if @snippet.updated_at != @snippet.created_at
-    = edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
   - if @snippet.description.present?
     .description
       .wiki
         = markdown_field(@snippet, :description)
       %textarea.hidden.js-task-list-field
         = @snippet.description
+
+  - if @snippet.updated_at != @snippet.created_at
+    = edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
+
+  - if public_snippet?
+    .embed-snippet
+      .input-group
+        .input-group-btn
+          %button.btn.embed-toggle{ 'data-toggle': 'dropdown', type: 'button' }
+            %span.js-embed-action= _("Embed")
+            = sprite_icon('angle-down', size: 12)
+          %ul.dropdown-menu.dropdown-menu-selectable.embed-toggle-list
+            %li
+              %button.js-embed-btn.btn.btn-transparent.is-active{ type: 'button' }
+                %strong.embed-toggle-list-item= _("Embed")
+            %li
+              %button.js-share-btn.btn.btn-transparent{ type: 'button' }
+                %strong.embed-toggle-list-item= _("Share")
+        %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
+        .input-group-btn
+          %button.js-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '#snippet-url-area' }
+            = sprite_icon('duplicate', size: 16)
+    .clearfix
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 491a8a4109042d131481049c5ae4a458ad2c2186..3acec88c2e31b18d91356f6a713531e6c830db0a 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -31,7 +31,7 @@
       %span.hidden-xs
         in
         = link_to project_path(snippet.project) do
-          = snippet.project.name_with_namespace
+          = snippet.project.full_name
 
     .pull-right.snippet-updated-at
       %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')}
diff --git a/app/views/shared/snippets/show.js.haml b/app/views/shared/snippets/show.js.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a9af732bbb5e587a679ed919f01e2c5b279ab4bc
--- /dev/null
+++ b/app/views/shared/snippets/show.js.haml
@@ -0,0 +1,2 @@
+document.write('#{escape_javascript(stylesheet_link_tag "#{stylesheet_url 'snippets'}")}');
+document.write('#{escape_javascript(render 'shared/snippets/embed')}');
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index ad4d39b4aa147c9893cd71fef25bf261a4a7fcb5..d36ca0325583e80230123c5893a96c90350cf45a 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -32,6 +32,13 @@
           %strong Comments
         %p.light
           This URL will be triggered when someone adds a comment
+    %li
+      = form.check_box :confidential_note_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :confidential_note_events, class: 'list-label' do
+          %strong Confidential Comments
+        %p.light
+          This URL will be triggered when someone adds a comment on a confidential issue
     %li
       = form.check_box :issues_events, class: 'pull-left'
       .prepend-left-20
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 328db19be296e9235904ba5490fc596d39396ac0..9aea3bad27b20937e142b2995561f392163c9e9e 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -18,6 +18,7 @@
 - cronjob:stuck_import_jobs
 - cronjob:stuck_merge_jobs
 - cronjob:trending_projects
+- cronjob:issue_due_scheduler
 
 - gcp_cluster:cluster_install_app
 - gcp_cluster:cluster_provision
@@ -39,16 +40,21 @@
 - github_importer:github_import_stage_import_pull_requests
 - github_importer:github_import_stage_import_repository
 
+- mail_scheduler:mail_scheduler_issue_due
+
+- object_storage_upload
+- object_storage:object_storage_background_move
+- object_storage:object_storage_migrate_uploads
+
 - pipeline_cache:expire_job_cache
 - pipeline_cache:expire_pipeline_cache
 - pipeline_creation:create_pipeline
 - pipeline_creation:run_pipeline_schedule
+- pipeline_background:archive_trace
 - pipeline_default:build_coverage
 - pipeline_default:build_trace_sections
-- pipeline_default:create_trace_artifact
 - pipeline_default:pipeline_metrics
 - pipeline_default:pipeline_notification
-- pipeline_default:update_head_pipeline_for_merge_request
 - pipeline_hooks:build_hooks
 - pipeline_hooks:pipeline_hooks
 - pipeline_processing:build_finished
@@ -58,6 +64,7 @@
 - pipeline_processing:pipeline_success
 - pipeline_processing:pipeline_update
 - pipeline_processing:stage_update
+- pipeline_processing:update_head_pipeline_for_merge_request
 
 - repository_check:repository_check_clear
 - repository_check:repository_check_single_repository
diff --git a/app/workers/archive_trace_worker.rb b/app/workers/archive_trace_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dea7425ad88d4d331d57f02e17e233d13f04fd03
--- /dev/null
+++ b/app/workers/archive_trace_worker.rb
@@ -0,0 +1,10 @@
+class ArchiveTraceWorker
+  include ApplicationWorker
+  include PipelineBackgroundQueue
+
+  def perform(job_id)
+    Ci::Build.find_by(id: job_id).try do |job|
+      job.trace.archive!
+    end
+  end
+end
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index d7e24491516597481239bca4185aefc618b87c02..8fe3619f6ee2de70df8c9183fd3a201bb044c40e 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -2,6 +2,14 @@ class AuthorizedProjectsWorker
   include ApplicationWorker
   prepend WaitableWorker
 
+  # This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the
+  # visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231
+  # for more details.
+  if Rails.env.test?
+    def self.bulk_perform_and_wait(args_list, timeout: 10)
+    end
+  end
+
   def perform(user_id)
     user = User.find_by(id: user_id)
 
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index b5ed8d607b350ce4bd2ba324565739fbbd4839e3..46f1ac099156468446c05c8a89a471dfd53065c6 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -12,7 +12,7 @@ class BuildFinishedWorker
 
       # We execute that async as this are two indepentent operations that can be executed after TraceSections and Coverage
       BuildHooksWorker.perform_async(build.id)
-      CreateTraceArtifactWorker.perform_async(build.id)
+      ArchiveTraceWorker.perform_async(build.id)
     end
   end
 end
diff --git a/app/workers/concerns/gitlab/github_import/object_importer.rb b/app/workers/concerns/gitlab/github_import/object_importer.rb
index 9a9fbaad653c78a030ed8de29cef2ec3575636cd..100d86e38c829cce46c09e1afc078d926a74e0d2 100644
--- a/app/workers/concerns/gitlab/github_import/object_importer.rb
+++ b/app/workers/concerns/gitlab/github_import/object_importer.rb
@@ -22,7 +22,7 @@ module Gitlab
 
         importer_class.new(object, project, client).execute
 
-        counter.increment(project: project.path_with_namespace)
+        counter.increment(project: project.full_path)
       end
 
       def counter
diff --git a/app/workers/concerns/mail_scheduler_queue.rb b/app/workers/concerns/mail_scheduler_queue.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9df55ad952245feec490fa66dc04d16d24dfb521
--- /dev/null
+++ b/app/workers/concerns/mail_scheduler_queue.rb
@@ -0,0 +1,7 @@
+module MailSchedulerQueue
+  extend ActiveSupport::Concern
+
+  included do
+    queue_namespace :mail_scheduler
+  end
+end
diff --git a/app/workers/concerns/object_storage_queue.rb b/app/workers/concerns/object_storage_queue.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a80f473a6d44cbec23bb40d21cce88cfd7d7ceff
--- /dev/null
+++ b/app/workers/concerns/object_storage_queue.rb
@@ -0,0 +1,8 @@
+# Concern for setting Sidekiq settings for the various GitLab ObjectStorage workers.
+module ObjectStorageQueue
+  extend ActiveSupport::Concern
+
+  included do
+    queue_namespace :object_storage
+  end
+end
diff --git a/app/workers/concerns/pipeline_background_queue.rb b/app/workers/concerns/pipeline_background_queue.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8bf43de6b26e892250321c705b26344a6f28a7b0
--- /dev/null
+++ b/app/workers/concerns/pipeline_background_queue.rb
@@ -0,0 +1,10 @@
+##
+# Concern for setting Sidekiq settings for the low priority CI pipeline workers.
+#
+module PipelineBackgroundQueue
+  extend ActiveSupport::Concern
+
+  included do
+    queue_namespace :pipeline_background
+  end
+end
diff --git a/app/workers/create_trace_artifact_worker.rb b/app/workers/create_trace_artifact_worker.rb
deleted file mode 100644
index 11cda58021e9a2aec0643bd71e10c6836979a644..0000000000000000000000000000000000000000
--- a/app/workers/create_trace_artifact_worker.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class CreateTraceArtifactWorker
-  include ApplicationWorker
-  include PipelineQueue
-
-  def perform(job_id)
-    Ci::Build.preload(:project, :user).find_by(id: job_id).try do |job|
-      Ci::CreateTraceArtifactService.new(job.project, job.user).execute(job)
-    end
-  end
-end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 21da27973fece784113d1021747cd5ac689f9e16..2a4d65b5cb3b18f15a6ab495db2c91c27920314f 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -66,7 +66,7 @@ class EmailsOnPushWorker
 
       # These are input errors and won't be corrected even if Sidekiq retries
       rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
-        logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
+        logger.info("Failed to send e-mail for project '#{project.full_name}' to #{recipient}: #{e}")
       end
     end
   ensure
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index 7ba224d74c84d9002a4e33d9f379bf41353a6bbe..be4203bc7ad2f78de6793cd2457c1872197f55cc 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -28,22 +28,27 @@ class GitGarbageCollectWorker
 
     task = task.to_sym
     cmd = command(task)
-    repo_path = project.repository.path_to_repo
-    description = "'#{cmd.join(' ')}' in #{repo_path}"
-
-    Gitlab::GitLogger.info(description)
 
     gitaly_migrate(GITALY_MIGRATED_TASKS[task]) do |is_enabled|
       if is_enabled
         gitaly_call(task, project.repository.raw_repository)
       else
+        repo_path = project.repository.path_to_repo
+        description = "'#{cmd.join(' ')}' in #{repo_path}"
+        Gitlab::GitLogger.info(description)
+
         output, status = Gitlab::Popen.popen(cmd, repo_path)
+
         Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero?
       end
     end
 
     # Refresh the branch cache in case garbage collection caused a ref lookup to fail
     flush_ref_caches(project) if task == :gc
+
+    # In case pack files are deleted, release libgit2 cache and open file
+    # descriptors ASAP instead of waiting for Ruby garbage collection
+    project.cleanup
   ensure
     cancel_lease(lease_key, lease_uuid) if lease_key.present? && lease_uuid.present?
   end
diff --git a/app/workers/gitlab/github_import/stage/finish_import_worker.rb b/app/workers/gitlab/github_import/stage/finish_import_worker.rb
index 073d6608082140659a9adf91be0ddffa6ab6b6ca..a779e631516be80835a9b89fa5e3a501a048fcd6 100644
--- a/app/workers/gitlab/github_import/stage/finish_import_worker.rb
+++ b/app/workers/gitlab/github_import/stage/finish_import_worker.rb
@@ -16,7 +16,7 @@ module Gitlab
 
         def report_import_time(project)
           duration = Time.zone.now - project.created_at
-          path = project.path_with_namespace
+          path = project.full_path
 
           histogram.observe({ project: path }, duration)
           counter.increment
diff --git a/app/workers/issue_due_scheduler_worker.rb b/app/workers/issue_due_scheduler_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..16ab5d069e020485a7822153e29f57924691b4d0
--- /dev/null
+++ b/app/workers/issue_due_scheduler_worker.rb
@@ -0,0 +1,10 @@
+class IssueDueSchedulerWorker
+  include ApplicationWorker
+  include CronjobQueue
+
+  def perform
+    project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
+
+    MailScheduler::IssueDueWorker.bulk_perform_async(project_ids)
+  end
+end
diff --git a/app/workers/mail_scheduler/issue_due_worker.rb b/app/workers/mail_scheduler/issue_due_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b06079d68cafe7df66f1a4f92f6d64353ee67374
--- /dev/null
+++ b/app/workers/mail_scheduler/issue_due_worker.rb
@@ -0,0 +1,14 @@
+module MailScheduler
+  class IssueDueWorker
+    include ApplicationWorker
+    include MailSchedulerQueue
+
+    def perform(project_id)
+      notification_service = NotificationService.new
+
+      Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
+        notification_service.issue_due(issue)
+      end
+    end
+  end
+end
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
index 67c54fbf10e05a127386c8129bd7cdc02fbd19ab..b925741934a894c59a0182d5c54fe5b2fc3f681f 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -5,7 +5,7 @@ class NewNoteWorker
   # old `NewNoteWorker` jobs (can remove later)
   def perform(note_id, _params = {})
     if note = Note.find_by(id: note_id)
-      NotificationService.new.new_note(note)
+      NotificationService.new.new_note(note) if note.can_create_notification?
       Notes::PostProcessService.new(note).execute
     else
       Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
diff --git a/app/workers/object_storage/background_move_worker.rb b/app/workers/object_storage/background_move_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9c4d72e0ecf4a99de7933120a51c0cd602e3ffc6
--- /dev/null
+++ b/app/workers/object_storage/background_move_worker.rb
@@ -0,0 +1,29 @@
+module ObjectStorage
+  class BackgroundMoveWorker
+    include ApplicationWorker
+    include ObjectStorageQueue
+
+    sidekiq_options retry: 5
+
+    def perform(uploader_class_name, subject_class_name, file_field, subject_id)
+      uploader_class = uploader_class_name.constantize
+      subject_class = subject_class_name.constantize
+
+      return unless uploader_class < ObjectStorage::Concern
+      return unless uploader_class.object_store_enabled?
+      return unless uploader_class.background_upload_enabled?
+
+      subject = subject_class.find(subject_id)
+      uploader = build_uploader(subject, file_field&.to_sym)
+      uploader.migrate!(ObjectStorage::Store::REMOTE)
+    end
+
+    def build_uploader(subject, mount_point)
+      case subject
+      when Upload then subject.build_uploader(mount_point)
+      else
+        subject.send(mount_point) # rubocop:disable GitlabSecurity/PublicSend
+      end
+    end
+  end
+end
diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a6b2c25125416f892ea564a723d04b7268c8a62f
--- /dev/null
+++ b/app/workers/object_storage/migrate_uploads_worker.rb
@@ -0,0 +1,214 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Style/Documentation
+
+module ObjectStorage
+  class MigrateUploadsWorker
+    include ApplicationWorker
+    include ObjectStorageQueue
+
+    SanityCheckError = Class.new(StandardError)
+
+    class Upload < ActiveRecord::Base
+      # Upper limit for foreground checksum processing
+      CHECKSUM_THRESHOLD = 100.megabytes
+
+      belongs_to :model, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
+
+      validates :size, presence: true
+      validates :path, presence: true
+      validates :model, presence: true
+      validates :uploader, presence: true
+
+      before_save  :calculate_checksum!, if: :foreground_checksummable?
+      after_commit :schedule_checksum,   if: :checksummable?
+
+      scope :stored_locally, -> { where(store: [nil, ObjectStorage::Store::LOCAL]) }
+      scope :stored_remotely, -> { where(store: ObjectStorage::Store::REMOTE) }
+
+      def self.hexdigest(path)
+        Digest::SHA256.file(path).hexdigest
+      end
+
+      def absolute_path
+        raise ObjectStorage::RemoteStoreError, "Remote object has no absolute path." unless local?
+        return path unless relative_path?
+
+        uploader_class.absolute_path(self)
+      end
+
+      def calculate_checksum!
+        self.checksum = nil
+        return unless checksummable?
+
+        self.checksum = self.class.hexdigest(absolute_path)
+      end
+
+      def build_uploader(mounted_as = nil)
+        uploader_class.new(model, mounted_as).tap do |uploader|
+          uploader.upload = self
+          uploader.retrieve_from_store!(identifier)
+        end
+      end
+
+      def exist?
+        File.exist?(absolute_path)
+      end
+
+      def local?
+        return true if store.nil?
+
+        store == ObjectStorage::Store::LOCAL
+      end
+
+      private
+
+      def checksummable?
+        checksum.nil? && local? && exist?
+      end
+
+      def foreground_checksummable?
+        checksummable? && size <= CHECKSUM_THRESHOLD
+      end
+
+      def schedule_checksum
+        UploadChecksumWorker.perform_async(id)
+      end
+
+      def relative_path?
+        !path.start_with?('/')
+      end
+
+      def identifier
+        File.basename(path)
+      end
+
+      def uploader_class
+        Object.const_get(uploader)
+      end
+    end
+
+    class MigrationResult
+      attr_reader :upload
+      attr_accessor :error
+
+      def initialize(upload, error = nil)
+        @upload, @error = upload, error
+      end
+
+      def success?
+        error.nil?
+      end
+
+      def to_s
+        success? ? "Migration successful." : "Error while migrating #{upload.id}: #{error.message}"
+      end
+    end
+
+    module Report
+      class MigrationFailures < StandardError
+        attr_reader :errors
+
+        def initialize(errors)
+          @errors = errors
+        end
+
+        def message
+          errors.map(&:message).join("\n")
+        end
+      end
+
+      def report!(results)
+        success, failures = results.partition(&:success?)
+
+        Rails.logger.info header(success, failures)
+        Rails.logger.warn failures(failures)
+
+        raise MigrationFailures.new(failures.map(&:error)) if failures.any?
+      end
+
+      def header(success, failures)
+        "Migrated #{success.count}/#{success.count + failures.count} files."
+      end
+
+      def failures(failures)
+        failures.map { |f| "\t#{f}" }.join('\n')
+      end
+    end
+
+    include Report
+
+    def self.enqueue!(uploads, model_class, mounted_as, to_store)
+      sanity_check!(uploads, model_class, mounted_as)
+
+      perform_async(uploads.ids, model_class.to_s, mounted_as, to_store)
+    end
+
+    # We need to be sure all the uploads are for the same uploader and model type
+    # and that the mount point exists if provided.
+    #
+    def self.sanity_check!(uploads, model_class, mounted_as)
+      upload = uploads.first
+      uploader_class = upload.uploader.constantize
+      uploader_types = uploads.map(&:uploader).uniq
+      model_types = uploads.map(&:model_type).uniq
+      model_has_mount = mounted_as.nil? || model_class.uploaders[mounted_as] == uploader_class
+
+      raise(SanityCheckError, "Multiple uploaders found: #{uploader_types}") unless uploader_types.count == 1
+      raise(SanityCheckError, "Multiple model types found: #{model_types}") unless model_types.count == 1
+      raise(SanityCheckError, "Mount point #{mounted_as} not found in #{model_class}.") unless model_has_mount
+    end
+
+    def perform(*args)
+      args_check!(args)
+
+      (ids, model_type, mounted_as, to_store) = args
+
+      @model_class = model_type.constantize
+      @mounted_as = mounted_as&.to_sym
+      @to_store = to_store
+
+      uploads = Upload.preload(:model).where(id: ids)
+
+      sanity_check!(uploads)
+      results = migrate(uploads)
+
+      report!(results)
+    rescue SanityCheckError => e
+      # do not retry: the job is insane
+      Rails.logger.warn "#{self.class}: Sanity check error (#{e.message})"
+    end
+
+    def sanity_check!(uploads)
+      self.class.sanity_check!(uploads, @model_class, @mounted_as)
+    end
+
+    def args_check!(args)
+      return if args.count == 4
+
+      case args.count
+      when 3 then raise SanityCheckError, "Job is missing the `model_type` argument."
+      else
+        raise SanityCheckError, "Job has wrong arguments format."
+      end
+    end
+
+    def build_uploaders(uploads)
+      uploads.map { |upload| upload.build_uploader(@mounted_as) }
+    end
+
+    def migrate(uploads)
+      build_uploaders(uploads).map(&method(:process_uploader))
+    end
+
+    def process_uploader(uploader)
+      MigrationResult.new(uploader.upload).tap do |result|
+        begin
+          uploader.migrate!(@to_store)
+        rescue => e
+          result.error = e
+        end
+      end
+    end
+  end
+end
diff --git a/app/workers/object_storage_upload_worker.rb b/app/workers/object_storage_upload_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5c80f34069c2412f961f8941f33b2127f053a10d
--- /dev/null
+++ b/app/workers/object_storage_upload_worker.rb
@@ -0,0 +1,21 @@
+# @Deprecated - remove once the `object_storage_upload` queue is empty
+# The queue has been renamed `object_storage:object_storage_background_upload`
+#
+class ObjectStorageUploadWorker
+  include ApplicationWorker
+
+  sidekiq_options retry: 5
+
+  def perform(uploader_class_name, subject_class_name, file_field, subject_id)
+    uploader_class = uploader_class_name.constantize
+    subject_class = subject_class_name.constantize
+
+    return unless uploader_class < ObjectStorage::Concern
+    return unless uploader_class.object_store_enabled?
+    return unless uploader_class.background_upload_enabled?
+
+    subject = subject_class.find(subject_id)
+    uploader = subject.public_send(file_field) # rubocop:disable GitlabSecurity/PublicSend
+    uploader.migrate!(ObjectStorage::Store::REMOTE)
+  end
+end
diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb
index d3b95009364f4e06c2a043baf34f32de0b7534b4..66a0ff83bef28d9d64e74db814d4d1fa889b8203 100644
--- a/app/workers/pages_worker.rb
+++ b/app/workers/pages_worker.rb
@@ -1,7 +1,7 @@
 class PagesWorker
   include ApplicationWorker
 
-  sidekiq_options retry: false
+  sidekiq_options retry: 3
 
   def perform(action, *arg)
     send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index f2b2c4428d3cef5f855db64df60f28843e8b52a2..f88b3fdbfb145d8b36305a73fb14ba761df55ab2 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -33,7 +33,7 @@ class PostReceive
 
       unless @user
         log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
-        return false
+        return false # rubocop:disable Cop/AvoidReturnFromBlocks
       end
 
       if Gitlab::Git.tag_ref?(ref)
@@ -55,7 +55,7 @@ class PostReceive
   end
 
   def process_wiki_changes(post_received)
-    # Nothing defined here yet.
+    post_received.project.touch(:last_activity_at, :last_repository_updated_at)
   end
 
   def log(message)
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index 5b25d980bdbbc2da417d1e645f95564700be30c1..201e7f332b4c39c90788c1e4808c3b816a19c6db 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -30,10 +30,9 @@ class ProcessCommitWorker
   end
 
   def process_commit_message(project, commit, user, author, default = false)
-    # this is a GitLab generated commit message, ignore it.
-    return if commit.merged_merge_request?(user)
-
-    closed_issues = default ? commit.closes_issues(user) : []
+    # Ignore closing references from GitLab-generated commit messages.
+    find_closing_issues = default && !commit.merged_merge_request?(user)
+    closed_issues = find_closing_issues ? commit.closes_issues(user) : []
 
     close_issues(project, user, author, commit, closed_issues) if closed_issues.any?
     commit.create_cross_references!(author, closed_issues)
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index c100852374a1e05be0f4611e6a48aabbebc5353e..c3d84bb0b93bf18bc6865d4024163ca6aa6189c2 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -4,10 +4,19 @@ class ProjectExportWorker
 
   sidekiq_options retry: 3
 
-  def perform(current_user_id, project_id)
+  def perform(current_user_id, project_id, after_export_strategy = {}, params = {})
     current_user = User.find(current_user_id)
     project = Project.find(project_id)
+    after_export = build!(after_export_strategy)
 
-    ::Projects::ImportExport::ExportService.new(project, current_user).execute
+    ::Projects::ImportExport::ExportService.new(project, current_user, params).execute(after_export)
+  end
+
+  private
+
+  def build!(after_export_strategy)
+    strategy_klass = after_export_strategy&.delete('klass')
+
+    Gitlab::ImportExport::AfterExportStrategyBuilder.build!(strategy_klass, after_export_strategy)
   end
 end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 07584fab7c8ea177c2720cabc77f52e760b20870..51fad4faf3618fc2f9b0e148b760447b56c6e7e8 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -4,24 +4,47 @@ class RepositoryForkWorker
   include ProjectStartImport
   include ProjectImportOptions
 
-  def perform(project_id, forked_from_repository_storage_path, source_disk_path)
-    project = Project.find(project_id)
+  def perform(*args)
+    target_project_id = args.shift
+    target_project = Project.find(target_project_id)
 
-    return unless start_fork(project)
+    # By v10.8, we should've drained the queue of all jobs using the old arguments.
+    # We can remove the else clause if we're no longer logging the message in that clause.
+    # See https://gitlab.com/gitlab-org/gitaly/issues/1110
+    if args.empty?
+      source_project = target_project.forked_from_project
+      return target_project.mark_import_as_failed('Source project cannot be found.') unless source_project
 
-    Gitlab::Metrics.add_event(:fork_repository,
-                              source_path: source_disk_path,
-                              target_path: project.disk_path)
+      fork_repository(target_project, source_project.repository_storage, source_project.disk_path)
+    else
+      Rails.logger.info("Project #{target_project.id} is being forked using old-style arguments.")
+
+      source_repository_storage_path, source_disk_path = *args
 
-    result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_disk_path,
-                                          project.repository_storage_path, project.disk_path)
-    raise "Unable to fork project #{project_id} for repository #{source_disk_path} -> #{project.disk_path}" unless result
+      source_repository_storage_name = Gitlab.config.repositories.storages.find do |_, info|
+        info.legacy_disk_path == source_repository_storage_path
+      end&.first || raise("no shard found for path '#{source_repository_storage_path}'")
 
-    project.after_import
+      fork_repository(target_project, source_repository_storage_name, source_disk_path)
+    end
   end
 
   private
 
+  def fork_repository(target_project, source_repository_storage_name, source_disk_path)
+    return unless start_fork(target_project)
+
+    Gitlab::Metrics.add_event(:fork_repository,
+                              source_path: source_disk_path,
+                              target_path: target_project.disk_path)
+
+    result = gitlab_shell.fork_repository(source_repository_storage_name, source_disk_path,
+                                          target_project.repository_storage, target_project.disk_path)
+    raise "Unable to fork project #{target_project.id} for repository #{source_disk_path} -> #{target_project.disk_path}" unless result
+
+    target_project.after_import
+  end
+
   def start_fork(project)
     return true if start(project)
 
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index fb26fa4c515ce34c44b650f9f76e3a3a2bd7bb17..7ebf69bdc397d7f5b68f84fa87b7a0a8faded6d0 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -38,7 +38,7 @@ class StuckCiJobsWorker
 
   def drop_stuck(status, timeout)
     search(status, timeout) do |build|
-      return unless build.stuck?
+      break unless build.stuck?
 
       drop_build :stuck, build, status, timeout
     end
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index f09d89aa170c9ec496c490c04fbb3c15eb5e91db..76f84ff920f302480bed2b0eeac6c42fb07dc326 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -2,6 +2,8 @@ class UpdateHeadPipelineForMergeRequestWorker
   include ApplicationWorker
   include PipelineQueue
 
+  queue_namespace :pipeline_processing
+
   def perform(merge_request_id)
     merge_request = MergeRequest.find(merge_request_id)
     pipeline = Ci::Pipeline.where(project: merge_request.source_project, ref: merge_request.source_branch).last
diff --git a/bin/rails b/bin/rails
index 0138d79b751b9668a1036329a9ef29a213836162..228f812ccaf658a031037900241e68b3c55f6435 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,9 +1,14 @@
 #!/usr/bin/env ruby
-begin
-  load File.expand_path('../spring', __FILE__)
-rescue LoadError => e
-  raise unless e.message.include?('spring')
+
+# Remove this block when upgraded to rails 5.0.
+unless %w[1 true].include?(ENV["RAILS5"])
+  begin
+    load File.expand_path('../spring', __FILE__)
+  rescue LoadError => e
+    raise unless e.message.include?('spring')
+  end
 end
-APP_PATH = File.expand_path('../../config/application', __FILE__)
+
+APP_PATH = File.expand_path('../config/application', __dir__)
 require_relative '../config/boot'
 require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index d87d5f578104597c1d1b951b55942e37f8af1277..b52a8321f1a3ed7a299151f6847852da30cac6ef 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,9 +1,14 @@
 #!/usr/bin/env ruby
-begin
-  load File.expand_path('../spring', __FILE__)
-rescue LoadError => e
-  raise unless e.message.include?('spring')
+
+# Remove this block when upgraded to rails 5.0.
+unless %w[1 true].include?(ENV["RAILS5"])
+  begin
+    load File.expand_path('../spring', __FILE__)
+  rescue LoadError => e
+    raise unless e.message.include?('spring')
+  end
 end
+
 require_relative '../config/boot'
 require 'rake'
 Rake.application.run
diff --git a/bin/rspec b/bin/rspec
index 6e6709219af4b84b143079e7d7b6493080c743f9..26583242051c4b0628072a88dcf65e7f6c170af1 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -1,4 +1,10 @@
 #!/usr/bin/env ruby
+
+# Remove these two lines below when upgraded to rails 5.0.
+# Allow run `rspec` command as `RAILS5=1 rspec ...` instead of `BUNDLE_GEMFILE=Gemfile.rails5 rspec ...`
+gemfile = %w[1 true].include?(ENV["RAILS5"]) ? "Gemfile.rails5" : "Gemfile"
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__)
+
 begin
   load File.expand_path('../spring', __FILE__)
 rescue LoadError => e
diff --git a/bin/secpick b/bin/secpick
new file mode 100755
index 0000000000000000000000000000000000000000..76ae231e9137b22694baa55ea2dc7aa532023f72
--- /dev/null
+++ b/bin/secpick
@@ -0,0 +1,47 @@
+#!/usr/bin/env ruby
+require 'optparse'
+require 'open3'
+require 'rainbow/refinement'
+using Rainbow
+
+BRANCH_PREFIX = 'security'.freeze
+STABLE_BRANCH_SUFFIX = 'stable'.freeze
+REMOTE = 'dev'.freeze
+
+options = { version: nil, branch: nil, sha: nil }
+
+parser = OptionParser.new do |opts|
+  opts.banner = "Usage: #{$0} [options]"
+  opts.on('-v', '--version 10.0', 'Version') do |version|
+    options[:version] = version&.tr('.', '-')
+  end
+
+  opts.on('-b', '--branch security-fix-branch', 'Original branch name') do |branch|
+    options[:branch] = branch
+  end
+
+  opts.on('-s', '--sha abcd', 'SHA to cherry pick') do |sha|
+    options[:sha] = sha
+  end
+
+  opts.on('-h', '--help', 'Displays Help') do
+    puts opts
+
+    exit
+  end
+end
+
+parser.parse!
+
+abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
+abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
+
+branch = [BRANCH_PREFIX, options[:branch], options[:version]].join('-').freeze
+stable_branch = "#{options[:version]}-#{STABLE_BRANCH_SUFFIX}".freeze
+
+command = "git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch}"
+
+_stdin, stdout, stderr = Open3.popen3(command)
+
+puts stdout.read&.green
+puts stderr.read&.red
diff --git a/bin/setup b/bin/setup
index 6cb2d7f1e3a52f1621dd62b6b2790878b4984aa0..c60c1267e069c4d1ddb667151a3bc30e120d4903 100755
--- a/bin/setup
+++ b/bin/setup
@@ -1,29 +1,61 @@
 #!/usr/bin/env ruby
-require 'pathname'
+
+def rails5?
+  %w[1 true].include?(ENV["RAILS5"])
+end
+
+require "pathname"
 
 # path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../',  __FILE__)
+APP_ROOT = Pathname.new File.expand_path("../../", __FILE__)
+
+if rails5?
+  def system!(*args)
+    system(*args) || abort("\n== Command #{args} failed ==")
+  end
+end
 
 Dir.chdir APP_ROOT do
   # This script is a starting point to setup your application.
   # Add necessary setup steps to this file:
 
   puts "== Installing dependencies =="
-  system "gem install bundler --conservative"
-  system "bundle check || bundle install"
+
+  if rails5?
+    system! "gem install bundler --conservative"
+    system("bundle check") || system!("bundle install")
+  else
+    system "gem install bundler --conservative"
+    system "bundle check || bundle install"
+  end
 
   # puts "\n== Copying sample files =="
   # unless File.exist?("config/database.yml")
-  #   system "cp config/database.yml.sample config/database.yml"
+  #   cp "config/database.yml.sample", "config/database.yml"
   # end
 
   puts "\n== Preparing database =="
-  system "bin/rake db:reset"
+
+  if rails5?
+    system! "bin/rails db:setup"
+  else
+    system "bin/rake db:reset"
+  end
 
   puts "\n== Removing old logs and tempfiles =="
-  system "rm -f log/*"
-  system "rm -rf tmp/cache"
+
+  if rails5?
+    system! "bin/rails log:clear tmp:clear"
+  else
+    system "rm -f log/*"
+    system "rm -rf tmp/cache"
+  end
 
   puts "\n== Restarting application server =="
-  system "touch tmp/restart.txt"
+
+  if rails5?
+    system! "bin/rails restart"
+  else
+    system "touch tmp/restart.txt"
+  end
 end
diff --git a/bin/spinach b/bin/spinach
index 474050e29d11879520382b50833e37e1b68be46f..eda81c9ed8a5944408f3ce7d79ca8271993032a6 100755
--- a/bin/spinach
+++ b/bin/spinach
@@ -1,4 +1,9 @@
 #!/usr/bin/env ruby
+
+# Remove this block when removing rails5? code.
+gemfile = %w[1 true].include?(ENV["RAILS5"]) ? "Gemfile.rails5" : "Gemfile"
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__)
+
 begin
   load File.expand_path('../spring', __FILE__)
 rescue LoadError => e
diff --git a/bin/update b/bin/update
new file mode 100755
index 0000000000000000000000000000000000000000..a8e4462f20340b73db6df04da3a3fa0dd842713f
--- /dev/null
+++ b/bin/update
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+require 'pathname'
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+def system!(*args)
+  system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+  # This script is a way to update your development environment automatically.
+  # Add necessary update steps to this file.
+
+  puts '== Installing dependencies =='
+  system! 'gem install bundler --conservative'
+  system('bundle check') || system!('bundle install')
+
+  puts "\n== Updating database =="
+  system! 'bin/rails db:migrate'
+
+  puts "\n== Removing old logs and tempfiles =="
+  system! 'bin/rails log:clear tmp:clear'
+
+  puts "\n== Restarting application server =="
+  system! 'bin/rails restart'
+end
diff --git a/changelogs/no-rm-rf-gitlab-basics.yml b/changelogs/no-rm-rf-gitlab-basics.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d5aa1091b4504a44d245885a55f3dacff6441ea7
--- /dev/null
+++ b/changelogs/no-rm-rf-gitlab-basics.yml
@@ -0,0 +1,5 @@
+---
+  title: Do not use '-f' with 'rm' in gitlab-basics docs
+  merge_request: 18027
+  author: Elias Werberich
+  type: changed
diff --git a/changelogs/unreleased-ee/39118-dynamic-pipeline-variables-fe.yml b/changelogs/unreleased-ee/39118-dynamic-pipeline-variables-fe.yml
deleted file mode 100644
index a38b447e345dbe13ccb1f3d6f2276bd8f0fa4801..0000000000000000000000000000000000000000
--- a/changelogs/unreleased-ee/39118-dynamic-pipeline-variables-fe.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Update CI/CD secret variables list to be dynamic and save without reloading
-  the page
-merge_request: 4110
-author:
-type: added
diff --git a/changelogs/unreleased-ee/4378-fix-cluster-js-not-running-on-update-page.yml b/changelogs/unreleased-ee/4378-fix-cluster-js-not-running-on-update-page.yml
deleted file mode 100644
index bbb6cbd05beb7aaaba6816ad6d93368b03d4046e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased-ee/4378-fix-cluster-js-not-running-on-update-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix JavaScript bundle running on Cluster update/destroy pages
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased-ee/bvl-external-policy-classification.yml b/changelogs/unreleased-ee/bvl-external-policy-classification.yml
deleted file mode 100644
index 074629c8c12fbfd8bd7cb6d4fd5d7d924eaacaf8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased-ee/bvl-external-policy-classification.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Authorize project access with an external service
-merge_request: 4675
-author:
-type: added
diff --git a/changelogs/unreleased/16957-issue-due-email.yml b/changelogs/unreleased/16957-issue-due-email.yml
new file mode 100644
index 0000000000000000000000000000000000000000..83944ca4f7364c9161a1c89922f39d05d3b1e60a
--- /dev/null
+++ b/changelogs/unreleased/16957-issue-due-email.yml
@@ -0,0 +1,5 @@
+---
+title: Add cron job to email users on issue due date
+merge_request: 17985
+author: Stuart Nelson
+type: added
diff --git a/changelogs/unreleased/17203-add-missing-pagination-commit-diff-endpoint.yml b/changelogs/unreleased/17203-add-missing-pagination-commit-diff-endpoint.yml
deleted file mode 100644
index efd936ca1049acf112104fb92ddfa1c2670a0c71..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/17203-add-missing-pagination-commit-diff-endpoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-  title: Add missing pagination on the commit diff endpoint
-  merge_request: 17203
-  author: Maxime Roussin-B茅langer
-  type: fixed
diff --git a/changelogs/unreleased/17359-move-oauth-modules-to-auth-dir-structure.yml b/changelogs/unreleased/17359-move-oauth-modules-to-auth-dir-structure.yml
deleted file mode 100644
index ca049f9edaa5e8aba34a4e1f300c501ca349eb1f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/17359-move-oauth-modules-to-auth-dir-structure.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Moved o_auth/saml/ldap modules under gitlab/auth
-merge_request: 17359
-author: Horatiu Eugen Vlad
diff --git a/changelogs/unreleased/17500-mr-multiple-issues-oxford-comma.yml b/changelogs/unreleased/17500-mr-multiple-issues-oxford-comma.yml
deleted file mode 100644
index a94e6153a0530f9e76f5e2384c9fea788ba8e9e1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/17500-mr-multiple-issues-oxford-comma.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update issue closing pattern to allow variations in punctuation
-merge_request: 17198
-author: Vicky Chijwani
-type: changed
diff --git a/changelogs/unreleased/17516-nested-restore-changelog.yml b/changelogs/unreleased/17516-nested-restore-changelog.yml
new file mode 100644
index 0000000000000000000000000000000000000000..89753f45457ec1bca21d849a54db04f3a6387fb7
--- /dev/null
+++ b/changelogs/unreleased/17516-nested-restore-changelog.yml
@@ -0,0 +1,5 @@
+---
+title: Enable restore rake task to handle nested storage directories
+merge_request: 17516
+author: Balasankar C
+type: fixed
diff --git a/changelogs/unreleased/17939-osw-patch-support-gfm.yml b/changelogs/unreleased/17939-osw-patch-support-gfm.yml
new file mode 100644
index 0000000000000000000000000000000000000000..576581e25d6a69715d0a37ad0c268ebcb0ca5343
--- /dev/null
+++ b/changelogs/unreleased/17939-osw-patch-support-gfm.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for patch link extension for commit links on GitLab Flavored Markdown
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/20394-protected-branches-wildcard.yml b/changelogs/unreleased/20394-protected-branches-wildcard.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3fa8ee4f69feeb7d9217c1e8ae34e08af1465c0c
--- /dev/null
+++ b/changelogs/unreleased/20394-protected-branches-wildcard.yml
@@ -0,0 +1,5 @@
+---
+title: Include matching branches and tags in protected branches / tags count
+merge_request:
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/21677-run-pipeline-word.yml b/changelogs/unreleased/21677-run-pipeline-word.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9cc280244e4e5bf6dcb903e253268850492055ba
--- /dev/null
+++ b/changelogs/unreleased/21677-run-pipeline-word.yml
@@ -0,0 +1,5 @@
+---
+title: Improves wording in new pipeline page
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/23460-send-email-when-pushing-more-commits-to-the-merge-request.yml b/changelogs/unreleased/23460-send-email-when-pushing-more-commits-to-the-merge-request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a62137ea2c9f9849ab5be4cbe5be487217d19c7a
--- /dev/null
+++ b/changelogs/unreleased/23460-send-email-when-pushing-more-commits-to-the-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Send notification emails when push to a merge request
+merge_request: 7610
+author: YarNayar
+type: feature
diff --git a/changelogs/unreleased/24774-clear-the-Labels-dropdown-search-filter.yml b/changelogs/unreleased/24774-clear-the-Labels-dropdown-search-filter.yml
deleted file mode 100644
index b909bb2d021aa617365fe54b415b72c03cf4024f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24774-clear-the-Labels-dropdown-search-filter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clear the Labels dropdown search filter after a selection is made
-merge_request: 17393
-author: Andrew Torres
-type: changed
diff --git a/changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml b/changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1226fb4eefd933eec92a822ae23dbc6373cb52c2
--- /dev/null
+++ b/changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml
@@ -0,0 +1,5 @@
+---
+title: Improve tooltips in collapsed right sidebar
+merge_request: 17714
+author:
+type: changed
diff --git a/changelogs/unreleased/26039-Update-to-github-linguist5-3-x.yml b/changelogs/unreleased/26039-Update-to-github-linguist5-3-x.yml
deleted file mode 100644
index 0f1cb2fef9de9f2c8a4053219a3dfd50cb1ca24b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26039-Update-to-github-linguist5-3-x.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update to github-linguist 5.3.x
-merge_request: 17241
-author: Ken Ding
-type: other
diff --git a/changelogs/unreleased/26466-natural-sort-mrs.yml b/changelogs/unreleased/26466-natural-sort-mrs.yml
deleted file mode 100644
index e3bf9834f24d1c5df60dd9b61471d047e3746f52..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26466-natural-sort-mrs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Group MRs on issue page by project and namespace.
-merge_request: 8494
-author: Jeff Stubler
diff --git a/changelogs/unreleased/27210-add-cancel-btn-to-new-page-domain.yml b/changelogs/unreleased/27210-add-cancel-btn-to-new-page-domain.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d96f7e54c8dd178bc9513a9246a588d2919f25d5
--- /dev/null
+++ b/changelogs/unreleased/27210-add-cancel-btn-to-new-page-domain.yml
@@ -0,0 +1,5 @@
+---
+title: Adds cancel btn to new pages domain page
+merge_request: 18026
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/29497-pages-custom-domain-dns-verification.yml b/changelogs/unreleased/29497-pages-custom-domain-dns-verification.yml
deleted file mode 100644
index f958f3f12721cf6c51bc9ab138ad178b374c46b0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/29497-pages-custom-domain-dns-verification.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add verification for GitLab Pages custom domains
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/30665-add-email-button-to-new-issue-by-email.yml b/changelogs/unreleased/30665-add-email-button-to-new-issue-by-email.yml
deleted file mode 100644
index 175b3103d907c0628f4f3bd9c2d57846017bdbb2..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/30665-add-email-button-to-new-issue-by-email.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add email button to new issue by email
-merge_request: 10942
-author: Islam Wazery
diff --git a/changelogs/unreleased/30739-fix-joined-information-on-project-members-page.yml b/changelogs/unreleased/30739-fix-joined-information-on-project-members-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f2d5b5036619b0652b1ca7984c7b2ad201a14e0f
--- /dev/null
+++ b/changelogs/unreleased/30739-fix-joined-information-on-project-members-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix `joined` information on project members page
+merge_request: 18290
+author: Fabian Schneider
+type: fixed
diff --git a/changelogs/unreleased/31114-internal-ids-are-not-atomic.yml b/changelogs/unreleased/31114-internal-ids-are-not-atomic.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc1955bc66fc0fe3c51c22fdb39037f12366f624
--- /dev/null
+++ b/changelogs/unreleased/31114-internal-ids-are-not-atomic.yml
@@ -0,0 +1,5 @@
+---
+title: Atomic generation of internal ids for issues.
+merge_request: 17580
+author:
+type: other
diff --git a/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml b/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5546d26d0fb544e119b42d7940f5ddf908415b59
--- /dev/null
+++ b/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml
@@ -0,0 +1,5 @@
+---
+title: Create Deploy Tokens to allow permanent access to repository and registry
+merge_request: 17894
+author:
+type: added
diff --git a/changelogs/unreleased/32564-fix-double-system-closing-notes.yml b/changelogs/unreleased/32564-fix-double-system-closing-notes.yml
deleted file mode 100644
index e6e1ef8c76dad2309a0e157b3913333768365e07..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32564-fix-double-system-closing-notes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix duplicate system notes when merging a merge request.
-merge_request: 17035
-author:
-type: fixed
diff --git a/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml b/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c73be5a901ea04571be793d8fd128ac50b03aba5
--- /dev/null
+++ b/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml
@@ -0,0 +1,6 @@
+---
+title: Fix template selector menu visibility when toggling preview mode in file edit
+  view
+merge_request: 18118
+author: Fabian Schneider
+type: fixed
diff --git a/changelogs/unreleased/32831-single-deploy-of-runner-in-k8s-cluster.yml b/changelogs/unreleased/32831-single-deploy-of-runner-in-k8s-cluster.yml
deleted file mode 100644
index 74675992105e75ba6bbba9aebc785b1f889f2be6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/32831-single-deploy-of-runner-in-k8s-cluster.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow installation of GitLab Runner with a single click
-merge_request: 17134
-author:
-type: added
diff --git a/changelogs/unreleased/33570-slack-notify-default-branch.yml b/changelogs/unreleased/33570-slack-notify-default-branch.yml
deleted file mode 100644
index 5c90ce477298cf936fc16b74a61f3dc16394ee60..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/33570-slack-notify-default-branch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Slack/Mattermost notifications not respecting `notify_only_default_branch` setting for pushes
-merge_request: 17345
-author:
-type: fixed
diff --git a/changelogs/unreleased/33803-drop-json-support-in-project-milestone.yml b/changelogs/unreleased/33803-drop-json-support-in-project-milestone.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0382ede4565b2625f0ad083f77f48f256263229f
--- /dev/null
+++ b/changelogs/unreleased/33803-drop-json-support-in-project-milestone.yml
@@ -0,0 +1,5 @@
+---
+title: Drop JSON response in Project Milestone along with avoiding error
+merge_request: 17977
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/34262-show-current-labels-when-editing.yml b/changelogs/unreleased/34262-show-current-labels-when-editing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3b15b9ddd1769aba21b3372a34ffef00826822a
--- /dev/null
+++ b/changelogs/unreleased/34262-show-current-labels-when-editing.yml
@@ -0,0 +1,5 @@
+---
+title: Keep current labels visible when editing them in the sidebar
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml b/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c4b5f59b724354f107d7241aed89c0f1d02ff8b5
--- /dev/null
+++ b/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml
@@ -0,0 +1,5 @@
+---
+title: Fix generated URL when listing repoitories for import
+merge_request: 17692
+author:
+type: fixed
diff --git a/changelogs/unreleased/35418-remove-underline-for-avatar.yml b/changelogs/unreleased/35418-remove-underline-for-avatar.yml
deleted file mode 100644
index 034365e1137196f381552c3c26f75dfc926e6896..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/35418-remove-underline-for-avatar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: remove avater underline
-merge_request: 17219
-author: Ken Ding
-type: fixed
diff --git a/changelogs/unreleased/35475-lazy-diff.yml b/changelogs/unreleased/35475-lazy-diff.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bafa66ebe3907f6ccab65df348764330b5d9b194
--- /dev/null
+++ b/changelogs/unreleased/35475-lazy-diff.yml
@@ -0,0 +1,5 @@
+---
+title: lazy load diffs on merge request discussions
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/35530-teleporting-emoji.yml b/changelogs/unreleased/35530-teleporting-emoji.yml
deleted file mode 100644
index a60a42b9e4881c1ffcace403c0dc1f1cf0798dcb..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/35530-teleporting-emoji.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Teleporting Emoji
-merge_request: 16963
-author: Jared Deckard <jared.deckard@gmail.com>
-type: fixed
diff --git a/changelogs/unreleased/36847-update-update-toml-rb-to-1-0-0.yml b/changelogs/unreleased/36847-update-update-toml-rb-to-1-0-0.yml
deleted file mode 100644
index 74eaf57c056fb18f7c28f90a41e41143e0593423..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/36847-update-update-toml-rb-to-1-0-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: update toml-rb to 1.0.0
-merge_request: 17259
-author: Ken Ding
-type: other
diff --git a/changelogs/unreleased/37050-ext-issue-tracker.yml b/changelogs/unreleased/37050-ext-issue-tracker.yml
deleted file mode 100644
index 29bccdded0236e92ad1a93d76c78fdc5fd8e9fbb..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/37050-ext-issue-tracker.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display a link to external issue tracker when enabled
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/38167-ui-bug-when-creating-new-branch.yml b/changelogs/unreleased/38167-ui-bug-when-creating-new-branch.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cec06bf2dfe1689656d471171235f927bcd1c3f7
--- /dev/null
+++ b/changelogs/unreleased/38167-ui-bug-when-creating-new-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed bug in dropdown selector when selecting the same selection again
+merge_request: 14631
+author: bitsapien
+type: fixed
diff --git a/changelogs/unreleased/39584-nesting-depth-5-framework-dropdowns.yml b/changelogs/unreleased/39584-nesting-depth-5-framework-dropdowns.yml
new file mode 100644
index 0000000000000000000000000000000000000000..30a8dc639835affe908b1c6a62e56b7e65866b03
--- /dev/null
+++ b/changelogs/unreleased/39584-nesting-depth-5-framework-dropdowns.yml
@@ -0,0 +1,5 @@
+---
+title: Apply NestingDepth (level 5) (framework/dropdowns.scss)
+merge_request: 17820
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/39607-fix-avatar--vertical-align.yml b/changelogs/unreleased/39607-fix-avatar--vertical-align.yml
deleted file mode 100644
index 4d9fee12f049a307b706276555d5dcd125321ca5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/39607-fix-avatar--vertical-align.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Fix user avatar's vertical align on the issues and merge requests pages"
-merge_request: 17072
-author: Laszlo Karpati
-type: fixed
diff --git a/changelogs/unreleased/39880-merge-method-api.yml b/changelogs/unreleased/39880-merge-method-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dd44a752c4fbc4b27ec7734e832c7c8116d32953
--- /dev/null
+++ b/changelogs/unreleased/39880-merge-method-api.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Add parameter merge_method to projects'
+merge_request: 18031
+author: Jan Beckmann
+type: added
diff --git a/changelogs/unreleased/40402-time-estimate-system-notes-can-be-confusing.yml b/changelogs/unreleased/40402-time-estimate-system-notes-can-be-confusing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e47577f90582500e12c972e8a682a976fd6955b2
--- /dev/null
+++ b/changelogs/unreleased/40402-time-estimate-system-notes-can-be-confusing.yml
@@ -0,0 +1,5 @@
+---
+title: Add a comma to the time estimate system notes
+merge_request: 18326
+author:
+type: changed
diff --git a/changelogs/unreleased/40487-axios-pipelines.yml b/changelogs/unreleased/40487-axios-pipelines.yml
new file mode 100644
index 0000000000000000000000000000000000000000..437d5e87e1a6e25005efe0785adc9f2739f89cd4
--- /dev/null
+++ b/changelogs/unreleased/40487-axios-pipelines.yml
@@ -0,0 +1,4 @@
+title: Replace vue resource with axios in pipelines table
+merge_request:
+author:
+type: other
\ No newline at end of file
diff --git a/changelogs/unreleased/40502-osw-keep-link-when-redacting-unauthorized-objects.yml b/changelogs/unreleased/40502-osw-keep-link-when-redacting-unauthorized-objects.yml
deleted file mode 100644
index dddd8473df5dd5a3d4c6980ecfd1cb6edecff93b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/40502-osw-keep-link-when-redacting-unauthorized-objects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Keep link when redacting unauthorized object links
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml b/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml
deleted file mode 100644
index 9e4811ca3086ef626541248bd3cb43708f6c440f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Sanitize extra blank spaces used when uploading a SSH key
-merge_request: 40552
-author:
-type: fixed
diff --git a/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml b/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml
deleted file mode 100644
index 543fd7c5e8d35eeb340ee795a077633573132482..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-title: Fix 404 when listing archived projects in a group where all projects have been archived
-merge_request: 17077
-author: Ashley Dumaine
-type: fixed
diff --git a/changelogs/unreleased/40668-pages-domain-api-returns-404-when-using-a-specific-domain.yml b/changelogs/unreleased/40668-pages-domain-api-returns-404-when-using-a-specific-domain.yml
deleted file mode 100644
index d77572d6175d6508bcdeb1019c204e84a21349f8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/40668-pages-domain-api-returns-404-when-using-a-specific-domain.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix get a single pages domain when project path contains a period
-merge_request: 17206
-author: Travis Miller
-type: fixed
diff --git a/changelogs/unreleased/40781-os-to-ce.yml b/changelogs/unreleased/40781-os-to-ce.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4a364292c60572c76eec222cc36f2883f717e522
--- /dev/null
+++ b/changelogs/unreleased/40781-os-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Add object storage support for LFS objects, CI artifacts, and uploads.
+merge_request: 17358
+author:
+type: added
diff --git a/changelogs/unreleased/40994-expose-features-as-ci-cd-variable.yml b/changelogs/unreleased/40994-expose-features-as-ci-cd-variable.yml
deleted file mode 100644
index 1e37709479102c1eaac83e77264b911aa852a091..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/40994-expose-features-as-ci-cd-variable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Expose GITLAB_FEATURES as CI/CD variable (fixes #40994)'
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/41059-calculate-artifact-size-more-efficiently.yml b/changelogs/unreleased/41059-calculate-artifact-size-more-efficiently.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e3f94bbf0812c21a630fe4d27c43698fec7ed44a
--- /dev/null
+++ b/changelogs/unreleased/41059-calculate-artifact-size-more-efficiently.yml
@@ -0,0 +1,5 @@
+---
+title: Improve DB performance of calculating total artifacts size
+merge_request: 17839
+author:
+type: performance
diff --git a/changelogs/unreleased/41224-pipeline-icons.yml b/changelogs/unreleased/41224-pipeline-icons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3fe05448d1cdbcf81f04ea8a280a8002490f12bc
--- /dev/null
+++ b/changelogs/unreleased/41224-pipeline-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Increase dropdown width in pipeline graph & center action icon
+merge_request: 18089
+author:
+type: fixed
diff --git a/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml b/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ea007670332a85465887b9bca7e5a8f344aa5b27
--- /dev/null
+++ b/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml
@@ -0,0 +1,5 @@
+---
+title: 'Introduce simpler env vars for auto devops REPLICAS and CANARY_REPLICAS #41436'
+merge_request: 18036
+author:
+type: added
diff --git a/changelogs/unreleased/41748-vertical-misalignment-login-box.yml b/changelogs/unreleased/41748-vertical-misalignment-login-box.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77a97400323050a4448fad45c636015bc42982d4
--- /dev/null
+++ b/changelogs/unreleased/41748-vertical-misalignment-login-box.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor CSS to eliminate vertical misalignment of login nav
+merge_request: 16275
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml b/changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml
new file mode 100644
index 0000000000000000000000000000000000000000..36e79ea1ed420f3b25a12bbd7bbfceb4d36fce39
--- /dev/null
+++ b/changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml
@@ -0,0 +1,5 @@
+---
+title: Added confirmation modal for changing username
+merge_request: 17405
+author:
+type: added
diff --git a/changelogs/unreleased/41777-include-cycle-time-in-usage-ping.yml b/changelogs/unreleased/41777-include-cycle-time-in-usage-ping.yml
deleted file mode 100644
index 8d8a5dfefa3319e6ae7ac377efad645c91bb22ee..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/41777-include-cycle-time-in-usage-ping.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include cycle time in usage ping data
-merge_request: 16973
-author:
-type: added
diff --git a/changelogs/unreleased/41851-enable-eslint-codeclimate.yml b/changelogs/unreleased/41851-enable-eslint-codeclimate.yml
deleted file mode 100644
index 98924f3eae822f10ddbb1d36d1f2fa4f42e4cb09..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/41851-enable-eslint-codeclimate.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enables eslint in codeclimate job
-merge_request: 17392
-author:
-type: other
diff --git a/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml b/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml
deleted file mode 100644
index 29ab7cc7cab2c87c2ccc9c3c5c0247f7c8b09e24..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: API endpoint for importing a project export
-merge_request: 17025
-author:
-type: added
diff --git a/changelogs/unreleased/41902-add-api-option-to-overwrite-project-description-on-project-export.yml b/changelogs/unreleased/41902-add-api-option-to-overwrite-project-description-on-project-export.yml
new file mode 100644
index 0000000000000000000000000000000000000000..60a649f22c99057776fbd2852fda48f791b0eddf
--- /dev/null
+++ b/changelogs/unreleased/41902-add-api-option-to-overwrite-project-description-on-project-export.yml
@@ -0,0 +1,5 @@
+---
+title: Adds the option to the project export API to override the project description and display GitLab export description once imported
+merge_request: 17744
+author:
+type: added
diff --git a/changelogs/unreleased/41905_merge_request_and_issue_metrics.yml b/changelogs/unreleased/41905_merge_request_and_issue_metrics.yml
deleted file mode 100644
index c9e23360e3b534d656a89d878a714c1c6f2a0aa8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/41905_merge_request_and_issue_metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: expose more metrics in merge requests api
-merge_request: 16589
-author: haseebeqx
-type: added
diff --git a/changelogs/unreleased/41949-move.yml b/changelogs/unreleased/41949-move.yml
deleted file mode 100644
index 40ccac63a2896230501576051bf587306a1da40b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/41949-move.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remember assignee when moving an issue
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/41967_issue_api_closed_by_info.yml b/changelogs/unreleased/41967_issue_api_closed_by_info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..436574c3638edc2fb9be92ed985d45d5a15226dd
--- /dev/null
+++ b/changelogs/unreleased/41967_issue_api_closed_by_info.yml
@@ -0,0 +1,5 @@
+---
+title: adds closed by informations in issue api
+merge_request: 17042
+author: haseebeqx
+type: added
diff --git a/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml b/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml
new file mode 100644
index 0000000000000000000000000000000000000000..30481e7af84ac6daf3c997662b3d7f95cd226c32
--- /dev/null
+++ b/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml
@@ -0,0 +1,5 @@
+---
+title: 'Allow group owner to enable runners from subgroups (#41981)'
+merge_request: 18009
+author:
+type: fixed
diff --git a/changelogs/unreleased/42028-xss-diffs.yml b/changelogs/unreleased/42028-xss-diffs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a05f9d3c78da2059669f955486b0882cd61468e9
--- /dev/null
+++ b/changelogs/unreleased/42028-xss-diffs.yml
@@ -0,0 +1,5 @@
+---
+title: Fix XSS on diff view stored on filenames
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/42037-long-instance-names-group-names-covers-namespace-dropdown.yml b/changelogs/unreleased/42037-long-instance-names-group-names-covers-namespace-dropdown.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f7758734a6f19853947cb774611b3e7fcc551f03
--- /dev/null
+++ b/changelogs/unreleased/42037-long-instance-names-group-names-covers-namespace-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Long instance urls do not overflow anymore during project creation
+merge_request: 17717
+author:
+type: fixed
diff --git a/changelogs/unreleased/42044-osw-add-button-to-deploy-runner-to-kubernetes.yml b/changelogs/unreleased/42044-osw-add-button-to-deploy-runner-to-kubernetes.yml
deleted file mode 100644
index 6cf0de5b3fa3893de832762de55bd8c705764856..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42044-osw-add-button-to-deploy-runner-to-kubernetes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a button to deploy a runner to a Kubernetes cluster in the settings page
-merge_request: 17278
-author:
-type: changed
diff --git a/changelogs/unreleased/42274-group-request-membership-long-too.yml b/changelogs/unreleased/42274-group-request-membership-long-too.yml
deleted file mode 100644
index 03efedba638398ff455143a17cbce94f7a8e76db..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42274-group-request-membership-long-too.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix long list of recipients on group request membership email
-merge_request: 17121
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/42314-diff-file.yml b/changelogs/unreleased/42314-diff-file.yml
deleted file mode 100644
index 1eed5ef1a340db87cc34fa65b32e5a676de4aa42..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42314-diff-file.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Render modified icon for moved file in changes dropdown
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/42332-actionview-template-error-366-524-out-of-range.yml b/changelogs/unreleased/42332-actionview-template-error-366-524-out-of-range.yml
deleted file mode 100644
index 626c761bfbda3341d9a698492cf1d04e25f5741a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42332-actionview-template-error-366-524-out-of-range.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 500 error being shown when diff has context marker with invalid encoding
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/42431-add-auto-devops-and-clusters-button-to-projects.yml b/changelogs/unreleased/42431-add-auto-devops-and-clusters-button-to-projects.yml
deleted file mode 100644
index 5613b2af7639d8c291effb8d86f7d9b23ef54dc3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42431-add-auto-devops-and-clusters-button-to-projects.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Add a button on the project page to set up a Kubernetes cluster and enable
-  Auto DevOps
-merge_request: 16900
-author:
-type: added
diff --git a/changelogs/unreleased/42434-allow-commits-endpoint-to-work-over-all-commits.yml b/changelogs/unreleased/42434-allow-commits-endpoint-to-work-over-all-commits.yml
deleted file mode 100644
index c596a88ba0b7b8dfd63e7cb3350e1cc0cf33d649..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42434-allow-commits-endpoint-to-work-over-all-commits.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow commits endpoint to work over all commits of a repository
-merge_request: 17182
-author:
-type: added
diff --git a/changelogs/unreleased/42448-change-commit-row-actions-and-sha-design-for-project-commit-list.yml b/changelogs/unreleased/42448-change-commit-row-actions-and-sha-design-for-project-commit-list.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77d1ebf69df5a15a1c5ff6cb86fcb1d618cb6beb
--- /dev/null
+++ b/changelogs/unreleased/42448-change-commit-row-actions-and-sha-design-for-project-commit-list.yml
@@ -0,0 +1,6 @@
+---
+title: Improved visual styles and consistency for commit hash and possible actions
+  across commit lists
+merge_request: 17406
+author:
+type: changed
diff --git a/changelogs/unreleased/42481-remove-notification-settings-left-projects.yml b/changelogs/unreleased/42481-remove-notification-settings-left-projects.yml
deleted file mode 100644
index ea99649131b06fccafc9b9cc8ed1b913f963b7e5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42481-remove-notification-settings-left-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove user notification settings for groups and projects when user leaves
-merge_request: 16906
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/42509-fix-API-PUT-projects-fails-when-only-ci_config_path-is-specified.yml b/changelogs/unreleased/42509-fix-API-PUT-projects-fails-when-only-ci_config_path-is-specified.yml
deleted file mode 100644
index a3dc1917001509f75d01064f43c4985ef9297560..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42509-fix-API-PUT-projects-fails-when-only-ci_config_path-is-specified.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow to call PUT /projects/:id API with only ci_config_path specified
-merge_request: 17105
-author: Laszlo Karpati
-type: fixed
diff --git a/changelogs/unreleased/42543-hide-divergence-graph-on-branches-for-mobile.yml b/changelogs/unreleased/42543-hide-divergence-graph-on-branches-for-mobile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7452a264bfd391bfebd7f55fcf2b04ee7824a851
--- /dev/null
+++ b/changelogs/unreleased/42543-hide-divergence-graph-on-branches-for-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Remove ahead/behind graphs on project branches on mobile
+merge_request: 18415
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/42545-milestion-quick-actions-for-groups.yml b/changelogs/unreleased/42545-milestion-quick-actions-for-groups.yml
deleted file mode 100644
index d29f79aaaf83cf8b10a2b0fa25dfc4040c0f2a9b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42545-milestion-quick-actions-for-groups.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allows the usage of /milestone quick action for group milestones
-merge_request: 17239
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/42568-pipeline-empty-state.yml b/changelogs/unreleased/42568-pipeline-empty-state.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d36edcf1b373ef65ac3e00af9a800832a6c1a05a
--- /dev/null
+++ b/changelogs/unreleased/42568-pipeline-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Improve empty state for canceled job
+merge_request: 17646
+author:
+type: fixed
diff --git a/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml b/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c0a247dc895af0fb34da8cf911b61ca8ff571ba0
--- /dev/null
+++ b/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hover style of dropdown items in the right sidebar
+merge_request: 17519
+author:
+type: fixed
diff --git a/changelogs/unreleased/42643-persist-external-ip-of-ingress-controller-gke.yml b/changelogs/unreleased/42643-persist-external-ip-of-ingress-controller-gke.yml
deleted file mode 100644
index 35457db82f420621fde052fb5b15bedc2918a6b3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42643-persist-external-ip-of-ingress-controller-gke.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display ingress IP address in the Kubernetes page
-merge_request: 17052
-author:
-type: added
diff --git a/changelogs/unreleased/42800-change-usage-of-avatar_icon.yml b/changelogs/unreleased/42800-change-usage-of-avatar_icon.yml
deleted file mode 100644
index 00f4b7436a72eb458c107314e0a2c536e24e672d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42800-change-usage-of-avatar_icon.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Use a user object in ApplicationHelper#avatar_icon where possible to avoid
-  N+1 queries.
-merge_request: 42800
-author:
-type: performance
diff --git a/changelogs/unreleased/42880-loss-of-input-text-on-comments-after-preview.yml b/changelogs/unreleased/42880-loss-of-input-text-on-comments-after-preview.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0e892a51bc5d4c6be1cbeae563270cf0c3096344
--- /dev/null
+++ b/changelogs/unreleased/42880-loss-of-input-text-on-comments-after-preview.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Firefox stealing formatting characters on issue notes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/42889-avoid-return-inside-block.yml b/changelogs/unreleased/42889-avoid-return-inside-block.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e3e1341ddcc467a9b04ef04a30ae1df4e58491e5
--- /dev/null
+++ b/changelogs/unreleased/42889-avoid-return-inside-block.yml
@@ -0,0 +1,5 @@
+---
+title: Rubocop rule to avoid returning from a block
+merge_request: 18000
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/42922-environment-name.yml b/changelogs/unreleased/42922-environment-name.yml
deleted file mode 100644
index 0e9544245f66bb0cf3b2272efacf0f5048644632..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42922-environment-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds tooltip in environment names to increase readability
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/42923-close-issue.yml b/changelogs/unreleased/42923-close-issue.yml
deleted file mode 100644
index e332bbf5decfadbc7a0858f48c2f9d97a9591942..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42923-close-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix close button on issues not working on mobile
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/42929-hide-new-variable-values.yml b/changelogs/unreleased/42929-hide-new-variable-values.yml
deleted file mode 100644
index 68decd25b5ac1f98463c8a9d613e21629f66ab9c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42929-hide-new-variable-values.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide CI secret variable values after saving
-merge_request: 17044
-author:
-type: changed
diff --git a/changelogs/unreleased/42946-update-pipeline-cancel-tooltip-to-stop.yml b/changelogs/unreleased/42946-update-pipeline-cancel-tooltip-to-stop.yml
deleted file mode 100644
index 0e566dd0abf5fc9bc9fae51ec554ecee4c7ea228..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/42946-update-pipeline-cancel-tooltip-to-stop.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update tooltip on pipeline cancel to Stop (#42946)
-merge_request: 17444
-author:
-type: fixed
diff --git a/changelogs/unreleased/43098-controller-projects-issuescontroller-show-executes-more-than-100-sql-queries.yml b/changelogs/unreleased/43098-controller-projects-issuescontroller-show-executes-more-than-100-sql-queries.yml
new file mode 100644
index 0000000000000000000000000000000000000000..686258460e0852f3a2479ae37db0ba6d502f795e
--- /dev/null
+++ b/changelogs/unreleased/43098-controller-projects-issuescontroller-show-executes-more-than-100-sql-queries.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of loading issues with lots of references to merge requests
+merge_request: 17986
+author:
+type: performance
diff --git a/changelogs/unreleased/43134-reduce-queries-pipelines-controller-show.yml b/changelogs/unreleased/43134-reduce-queries-pipelines-controller-show.yml
deleted file mode 100644
index c1e9614b67613659bb652d31b6df11f5ec455977..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43134-reduce-queries-pipelines-controller-show.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance of pipeline page by reducing DB queries
-merge_request: 17168
-author:
-type: performance
diff --git a/changelogs/unreleased/43198-fix-settings-panel-expanding-when-fragment-hash-linked.yml b/changelogs/unreleased/43198-fix-settings-panel-expanding-when-fragment-hash-linked.yml
deleted file mode 100644
index 49ba48a0feff98367d4897ac5301a72961638600..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43198-fix-settings-panel-expanding-when-fragment-hash-linked.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix settings panels not expanding when fragment hash linked
-merge_request: 17074
-author:
-type: fixed
diff --git a/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml b/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml
deleted file mode 100644
index b527000332e2d21c665b27f200765c6d9154c79e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allows project rename after validation error
-merge_request: 17150
-author:
-type: fixed
diff --git a/changelogs/unreleased/43215-update-design-for-verifying-domains.yml b/changelogs/unreleased/43215-update-design-for-verifying-domains.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8326540f7b2bee3297964113ebcd70908a268844
--- /dev/null
+++ b/changelogs/unreleased/43215-update-design-for-verifying-domains.yml
@@ -0,0 +1,5 @@
+---
+title: Polish design for verifying domains
+merge_request: 17767
+author:
+type: changed
diff --git a/changelogs/unreleased/43246-checkfilter.yml b/changelogs/unreleased/43246-checkfilter.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e6c0e7162135b72998e58ce85f4100376e8cc05f
--- /dev/null
+++ b/changelogs/unreleased/43246-checkfilter.yml
@@ -0,0 +1,6 @@
+---
+title: Require at least one filter when listing issues or merge requests on dashboard
+  page
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/43261-fix-import-from-url-name-collision-active-tab.yml b/changelogs/unreleased/43261-fix-import-from-url-name-collision-active-tab.yml
deleted file mode 100644
index 71073b2e21445cdb83722d3bb4456a008f2f9595..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43261-fix-import-from-url-name-collision-active-tab.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Keep "Import project" tab/form active when validation fails trying to import
-  "Repo by URL"
-merge_request: 17136
-author:
-type: fixed
diff --git a/changelogs/unreleased/43261-fix-prometheus-installation.yml b/changelogs/unreleased/43261-fix-prometheus-installation.yml
deleted file mode 100644
index b5fc7980390488e168a2b2a869ef7c90ab5cccac..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43261-fix-prometheus-installation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow Prometheus application to be installed from Cluster applications
-merge_request: 17372
-author:
-type: fixed
diff --git a/changelogs/unreleased/43275-improve-variables-validation-message.yml b/changelogs/unreleased/43275-improve-variables-validation-message.yml
deleted file mode 100644
index 88ef93123a074688d7aea248e2d5869725ff3cc5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43275-improve-variables-validation-message.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicated error message on duplicate variable validation
-merge_request: 17135
-author:
-type: fixed
diff --git a/changelogs/unreleased/43315-gpg-popover.yml b/changelogs/unreleased/43315-gpg-popover.yml
deleted file mode 100644
index 69238aa8075d54990702ea46cb190973632751ca..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43315-gpg-popover.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes gpg popover layout
-merge_request: 17323
-author:
-type: fixed
diff --git a/changelogs/unreleased/43316-controller-parameters-handling-sensitive-information-should-use-a-more-specific-name.yml b/changelogs/unreleased/43316-controller-parameters-handling-sensitive-information-should-use-a-more-specific-name.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de1cee6e436510eb89af36f188ea645418e60bfb
--- /dev/null
+++ b/changelogs/unreleased/43316-controller-parameters-handling-sensitive-information-should-use-a-more-specific-name.yml
@@ -0,0 +1,5 @@
+---
+title: Use specific names for filtered CI variable controller parameters
+merge_request: 17796
+author:
+type: other
diff --git a/changelogs/unreleased/43404-pipelines-commit.yml b/changelogs/unreleased/43404-pipelines-commit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0b9a4a6451f2220fb577641df5c9272cdd1c4253
--- /dev/null
+++ b/changelogs/unreleased/43404-pipelines-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Breaks commit not found message in pipelines table
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/43482-enabling-auto-devops-on-an-empty-project-gives-you-wrong-information.yml b/changelogs/unreleased/43482-enabling-auto-devops-on-an-empty-project-gives-you-wrong-information.yml
new file mode 100644
index 0000000000000000000000000000000000000000..889fd008bad2d7b87e004ba4a9cd074a513aea42
--- /dev/null
+++ b/changelogs/unreleased/43482-enabling-auto-devops-on-an-empty-project-gives-you-wrong-information.yml
@@ -0,0 +1,5 @@
+---
+title: Add empty repo check before running AutoDevOps pipeline
+merge_request: 17605
+author:
+type: changed
diff --git a/changelogs/unreleased/43489-display-runner-ip.yml b/changelogs/unreleased/43489-display-runner-ip.yml
deleted file mode 100644
index 621c2ec709a4c8c8f50f6a693bf17402421c2145..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43489-display-runner-ip.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display Runner IP Address
-merge_request: 17286
-author:
-type: added
diff --git a/changelogs/unreleased/43496-error-message-for-gke-clusters-persists-in-the-next-page.yml b/changelogs/unreleased/43496-error-message-for-gke-clusters-persists-in-the-next-page.yml
deleted file mode 100644
index c10b0e7a3cf2b6a86aebdab705ec8aa2aa6440d1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43496-error-message-for-gke-clusters-persists-in-the-next-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do not persist Google Project verification flash errors after a page reload
-merge_request: 17299
-author:
-type: fixed
diff --git a/changelogs/unreleased/43510-merge-requests-and-issues-don-t-show-for-all-subgroups.yml b/changelogs/unreleased/43510-merge-requests-and-issues-don-t-show-for-all-subgroups.yml
deleted file mode 100644
index e163c04f4300ef14c7ad819d2ee681265279dc1f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43510-merge-requests-and-issues-don-t-show-for-all-subgroups.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Ensure group issues and merge requests pages show results from subgroups when
-  there are no results from the current group
-merge_request: 17312
-author:
-type: fixed
diff --git a/changelogs/unreleased/43512-add-support-for-omniauth-jwt-provider.yml b/changelogs/unreleased/43512-add-support-for-omniauth-jwt-provider.yml
new file mode 100644
index 0000000000000000000000000000000000000000..039d3de716852be2965b99007034c06896d51a1d
--- /dev/null
+++ b/changelogs/unreleased/43512-add-support-for-omniauth-jwt-provider.yml
@@ -0,0 +1,5 @@
+---
+title: Adds support for OmniAuth JWT provider
+merge_request: 17774
+author:
+type: added
diff --git a/changelogs/unreleased/43525-limit-number-of-failed-logins-using-ldap.yml b/changelogs/unreleased/43525-limit-number-of-failed-logins-using-ldap.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f30fea3c4a738dd8f7c269774ceec232a70f6527
--- /dev/null
+++ b/changelogs/unreleased/43525-limit-number-of-failed-logins-using-ldap.yml
@@ -0,0 +1,5 @@
+---
+title: Limit the number of failed logins when using LDAP for authentication
+merge_request: 43525
+author:
+type: added
diff --git a/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml b/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml
deleted file mode 100644
index 173710412a51b204fcbdf39a026afa1366a994e6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix code and wiki search results pages when non-ASCII text is displayed
-merge_request: 17413
-author:
-type: fixed
diff --git a/changelogs/unreleased/43532-error-on-admin-applications-prometheus-template.yml b/changelogs/unreleased/43532-error-on-admin-applications-prometheus-template.yml
deleted file mode 100644
index 25bcbf2fbabac3d8901b8fc906f18cd4635d1225..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43532-error-on-admin-applications-prometheus-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes Prometheus admin configuration page
-merge_request: 17377
-author:
-type: fixed
diff --git a/changelogs/unreleased/43552-user-owned-projects-query-performance-improvement.yml b/changelogs/unreleased/43552-user-owned-projects-query-performance-improvement.yml
new file mode 100644
index 0000000000000000000000000000000000000000..39f92c281ad8f197ea485173083eef6a8d990c1b
--- /dev/null
+++ b/changelogs/unreleased/43552-user-owned-projects-query-performance-improvement.yml
@@ -0,0 +1,5 @@
+---
+title: Improves the performance of projects list page
+merge_request: 17934
+author:
+type: performance
diff --git a/changelogs/unreleased/43567-replace-gke.yml b/changelogs/unreleased/43567-replace-gke.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8ec79fc3d4ded67c9673bb7c6245b331f9c1d8e7
--- /dev/null
+++ b/changelogs/unreleased/43567-replace-gke.yml
@@ -0,0 +1,5 @@
+---
+title: Replace GKE acronym with Google Kubernetes Engine
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/43598-fix-duplicate-label-load-failure.yml b/changelogs/unreleased/43598-fix-duplicate-label-load-failure.yml
deleted file mode 100644
index bda4ec84e5c8fbcfebbb2e92e3b5e5d80dde1d9c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43598-fix-duplicate-label-load-failure.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Group labels load failure when there are duplicate labels present
-merge_request: 17353
-author:
-type: fixed
diff --git a/changelogs/unreleased/43603-ci-lint-support.yml b/changelogs/unreleased/43603-ci-lint-support.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e4a92c0287167c656521dedba3629b6142f107b
--- /dev/null
+++ b/changelogs/unreleased/43603-ci-lint-support.yml
@@ -0,0 +1,5 @@
+---
+title: Move ci/lint under project's namespace
+merge_request: 17729
+author:
+type: added
diff --git a/changelogs/unreleased/43617-mailsig.yml b/changelogs/unreleased/43617-mailsig.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2c7568e32caa23ef651faa1317660a8b6889ba4a
--- /dev/null
+++ b/changelogs/unreleased/43617-mailsig.yml
@@ -0,0 +1,5 @@
+---
+title: Use RFC 3676 mail signature delimiters
+merge_request: 17979
+author: Enrico Scholz
+type: changed
diff --git a/changelogs/unreleased/43643-fix-mr-label-filtering.yml b/changelogs/unreleased/43643-fix-mr-label-filtering.yml
deleted file mode 100644
index 32a44aef243f10cf5a1acfb438f3d97f0ca55275..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/43643-fix-mr-label-filtering.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable filtering MR list based on clicked label in MR sidebar
-merge_request: 17390
-author:
-type: fixed
diff --git a/changelogs/unreleased/43702-update-label-dropdown-wording.yml b/changelogs/unreleased/43702-update-label-dropdown-wording.yml
new file mode 100644
index 0000000000000000000000000000000000000000..97100ec89de89d202c060f4929c5b6da1607c56a
--- /dev/null
+++ b/changelogs/unreleased/43702-update-label-dropdown-wording.yml
@@ -0,0 +1,5 @@
+---
+title: Update wording to specify create/manage project vs group labels in labels dropdown
+merge_request: 17640
+author:
+type: changed
diff --git a/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3aec71d5ac43e69216979d44399af89394c57b8b
--- /dev/null
+++ b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml
@@ -0,0 +1,5 @@
+---
+title: Set breadcrumb for admin/runners/show
+merge_request: 17431
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/43720-update-fe-webpack-docs.yml b/changelogs/unreleased/43720-update-fe-webpack-docs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9e461eaaec8b618130ac7d31e16a75b670616c0a
--- /dev/null
+++ b/changelogs/unreleased/43720-update-fe-webpack-docs.yml
@@ -0,0 +1,6 @@
+---
+title: Update documentation to reflect current minimum required versions of node and
+  yarn
+merge_request: 17706
+author:
+type: other
diff --git a/changelogs/unreleased/43745-store-metadata-checksum-for-artifacts.yml b/changelogs/unreleased/43745-store-metadata-checksum-for-artifacts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6283e7979308c1970c52ef149c548d1de7cb2d4e
--- /dev/null
+++ b/changelogs/unreleased/43745-store-metadata-checksum-for-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Store sha256 checksum of artifact metadata
+merge_request: 18149
+author:
+type: added
diff --git a/changelogs/unreleased/43771-improve-avatar-error-message.yml b/changelogs/unreleased/43771-improve-avatar-error-message.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1fae10f4d1f28565a6bd056e5a4fdf2593affc4a
--- /dev/null
+++ b/changelogs/unreleased/43771-improve-avatar-error-message.yml
@@ -0,0 +1,5 @@
+---
+title: Change avatar error message to include allowed file formats
+merge_request: 17747
+author: Fabian Schneider
+type: changed
diff --git a/changelogs/unreleased/43786-on-the-issuable-list-add-tooltips-to-icons.yml b/changelogs/unreleased/43786-on-the-issuable-list-add-tooltips-to-icons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..19b633daace7ff374681d20e69c3d02b7261783c
--- /dev/null
+++ b/changelogs/unreleased/43786-on-the-issuable-list-add-tooltips-to-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Add tooltips to icons in lists of issues and merge requests
+merge_request: 17700
+author:
+type: changed
diff --git a/changelogs/unreleased/43805-list-gitaly-calls-and-arguments-in-the-performance-bar.yml b/changelogs/unreleased/43805-list-gitaly-calls-and-arguments-in-the-performance-bar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4c63e69f0bb32bde07d6915549bc45014924c3c2
--- /dev/null
+++ b/changelogs/unreleased/43805-list-gitaly-calls-and-arguments-in-the-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Add Gitaly call details to performance bar
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml b/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7335d31351001c9eb971e5299cf13d7ac038246c
--- /dev/null
+++ b/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update ruby-saml to 1.7.2 and omniauth-saml to 1.10.0
+merge_request: 17734
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/43933-always-notify-mentions.yml b/changelogs/unreleased/43933-always-notify-mentions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7b494d385417beaa7be60ff9b34ca67bd0a236d6
--- /dev/null
+++ b/changelogs/unreleased/43933-always-notify-mentions.yml
@@ -0,0 +1,6 @@
+---
+title: Send @mention notifications even if a user has explicitly unsubscribed from
+  item
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/43949-verify-job-artifacts.yml b/changelogs/unreleased/43949-verify-job-artifacts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..45e1916ae175a2fa0d4a11b3df60077e023a2ff7
--- /dev/null
+++ b/changelogs/unreleased/43949-verify-job-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Implement foreground verification of CI artifacts
+merge_request: 17578
+author:
+type: added
diff --git a/changelogs/unreleased/43976-fix-access-token-clipboard-button-style.yml b/changelogs/unreleased/43976-fix-access-token-clipboard-button-style.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b341d5dfa7fcae138234ab9ba1d69a6f436bbbec
--- /dev/null
+++ b/changelogs/unreleased/43976-fix-access-token-clipboard-button-style.yml
@@ -0,0 +1,5 @@
+---
+title: Fix personal access token clipboard button style
+merge_request: 17978
+author: Fabian Schneider
+type: fixed
diff --git a/changelogs/unreleased/44022-singular-1-diff.yml b/changelogs/unreleased/44022-singular-1-diff.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f4942925a73843ab033103c54b64f964de83cdef
--- /dev/null
+++ b/changelogs/unreleased/44022-singular-1-diff.yml
@@ -0,0 +1,5 @@
+---
+title: Use singular in the diff stats if only one line has been changed
+merge_request: 17697
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml b/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dd5f2f06d6c0dc708a7ce2014ff53efde3647e9f
--- /dev/null
+++ b/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml
@@ -0,0 +1,6 @@
+---
+title: Use object ID to prevent duplicate keys Vue warning on Issue Boards page during
+  development
+merge_request: 17682
+author:
+type: other
diff --git a/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml
new file mode 100644
index 0000000000000000000000000000000000000000..990d188eb78cfceb99622292b437fcce0f4ef593
--- /dev/null
+++ b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update foreman from 0.78.0 to 0.84.0
+merge_request: 17690
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/44191-reduce-redis-usage-from-merge-request-diffs-caching.yml b/changelogs/unreleased/44191-reduce-redis-usage-from-merge-request-diffs-caching.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8fdca6eec83fd82b3cd9c95faa95100a89891f47
--- /dev/null
+++ b/changelogs/unreleased/44191-reduce-redis-usage-from-merge-request-diffs-caching.yml
@@ -0,0 +1,5 @@
+---
+title: Stop caching highlighted diffs in Redis unnecessarily
+merge_request: 17746
+author:
+type: performance
diff --git a/changelogs/unreleased/44218-add-internationalization-support-for-the-prometheus-merge-request-widget.yml b/changelogs/unreleased/44218-add-internationalization-support-for-the-prometheus-merge-request-widget.yml
new file mode 100644
index 0000000000000000000000000000000000000000..12c7328199824f0113af423536e23d8f3fca3d98
--- /dev/null
+++ b/changelogs/unreleased/44218-add-internationalization-support-for-the-prometheus-merge-request-widget.yml
@@ -0,0 +1,5 @@
+---
+title: Added i18n support for the prometheus memory widget
+merge_request: 17753
+author:
+type: other
diff --git a/changelogs/unreleased/44224-remove-gl.yml b/changelogs/unreleased/44224-remove-gl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1c792883f09a8cd8a7e9771ab9418bd7bc1585ae
--- /dev/null
+++ b/changelogs/unreleased/44224-remove-gl.yml
@@ -0,0 +1,5 @@
+---
+title: Removes modal boards store and mixins from global scope
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/44235-update-knapsack-to-1-16-0.yml b/changelogs/unreleased/44235-update-knapsack-to-1-16-0.yml
new file mode 100644
index 0000000000000000000000000000000000000000..265d36b763fd39ce1e71f9882ada21e932d59d98
--- /dev/null
+++ b/changelogs/unreleased/44235-update-knapsack-to-1-16-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update knapsack to 1.16.0
+merge_request: 17735
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/44257-viewing-a-particular-commit-gives-500-error-error-undefined-method-binary.yml b/changelogs/unreleased/44257-viewing-a-particular-commit-gives-500-error-error-undefined-method-binary.yml
new file mode 100644
index 0000000000000000000000000000000000000000..934860b95fe981bfbb715027d02ddb1400a4b95e
--- /dev/null
+++ b/changelogs/unreleased/44257-viewing-a-particular-commit-gives-500-error-error-undefined-method-binary.yml
@@ -0,0 +1,5 @@
+---
+title: Fix viewing diffs on old merge requests
+merge_request: 17805
+author:
+type: fixed
diff --git a/changelogs/unreleased/44269-show-failure-reason-on-upgrade-tooltip-of-jobs.yml b/changelogs/unreleased/44269-show-failure-reason-on-upgrade-tooltip-of-jobs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b3ae8ca7340ea605e9f3724f9c44ed6c9058d99e
--- /dev/null
+++ b/changelogs/unreleased/44269-show-failure-reason-on-upgrade-tooltip-of-jobs.yml
@@ -0,0 +1,5 @@
+---
+title: Display error message on job's tooltip if this one fails
+merge_request: 17782
+author:
+type: added
diff --git a/changelogs/unreleased/44280-fix-code-search.yml b/changelogs/unreleased/44280-fix-code-search.yml
new file mode 100644
index 0000000000000000000000000000000000000000..07f3abb224ca058402d7aacf84db48df7bc7a40d
--- /dev/null
+++ b/changelogs/unreleased/44280-fix-code-search.yml
@@ -0,0 +1,5 @@
+---
+title: Fix search results stripping last endline when parsing the results
+merge_request: 17777
+author: Jasper Maes
+type: fixed
diff --git a/changelogs/unreleased/44291-usage-ping-for-kubernetes-integration.yml b/changelogs/unreleased/44291-usage-ping-for-kubernetes-integration.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b5c12d8f40eb547e610dea5e7d9e7fe8b694557b
--- /dev/null
+++ b/changelogs/unreleased/44291-usage-ping-for-kubernetes-integration.yml
@@ -0,0 +1,5 @@
+---
+title: Add additional cluster usage metrics to usage ping.
+merge_request: 17922
+author:
+type: changed
diff --git a/changelogs/unreleased/44296-commit-path.yml b/changelogs/unreleased/44296-commit-path.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b658178f8dc9f3b34bd00f9d4d4b35dc236311af
--- /dev/null
+++ b/changelogs/unreleased/44296-commit-path.yml
@@ -0,0 +1,6 @@
+---
+title: Verifiy if pipeline has commit idetails and render information in MR widget
+  when branch is deleted
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/44382-ui-breakdown-for-create-merge-request.yml b/changelogs/unreleased/44382-ui-breakdown-for-create-merge-request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dd8c0b19d5f8228131597fbe5a884d9c413a31f9
--- /dev/null
+++ b/changelogs/unreleased/44382-ui-breakdown-for-create-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Fix UI breakdown for Create merge request button
+merge_request: 17821
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/44383-cleanup-framework-header.yml b/changelogs/unreleased/44383-cleanup-framework-header.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ef9be9f48de79def0f9019f33d5d231bfb80bc6c
--- /dev/null
+++ b/changelogs/unreleased/44383-cleanup-framework-header.yml
@@ -0,0 +1,5 @@
+---
+title: Clean up selectors in framework/header.scss
+merge_request: 17822
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/44384-cleanup-css-for-nested-lists.yml b/changelogs/unreleased/44384-cleanup-css-for-nested-lists.yml
new file mode 100644
index 0000000000000000000000000000000000000000..79c470ea4e1930f155058be0b5b02cc198a0ad05
--- /dev/null
+++ b/changelogs/unreleased/44384-cleanup-css-for-nested-lists.yml
@@ -0,0 +1,5 @@
+---
+title: Unify format for nested non-task lists
+merge_request: 17823
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/44386-better-ux-for-long-name-branches.yml b/changelogs/unreleased/44386-better-ux-for-long-name-branches.yml
new file mode 100644
index 0000000000000000000000000000000000000000..16712486f0f9dabf77128179663927a451f0fb8c
--- /dev/null
+++ b/changelogs/unreleased/44386-better-ux-for-long-name-branches.yml
@@ -0,0 +1,5 @@
+---
+title: UX re-design branch items with flexbox
+merge_request: 17832
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/44388-update-rack-protection-to-2-0-1.yml b/changelogs/unreleased/44388-update-rack-protection-to-2-0-1.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c21d02d4d87e5c9652f82907b7df30c413136e7b
--- /dev/null
+++ b/changelogs/unreleased/44388-update-rack-protection-to-2-0-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update rack-protection to 2.0.1
+merge_request: 17835
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/44389-always-allow-http-for-ci-git-operations.yml b/changelogs/unreleased/44389-always-allow-http-for-ci-git-operations.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2e5a0302ee6b1acb5a33c11cbb238cdd502ac5f6
--- /dev/null
+++ b/changelogs/unreleased/44389-always-allow-http-for-ci-git-operations.yml
@@ -0,0 +1,5 @@
+---
+title: Allow HTTP(s) when git request is made by GitLab CI
+merge_request: 18021
+author:
+type: changed
diff --git a/changelogs/unreleased/44392-resolve-projects-creation-silently-failing-on-after-create-error.yml b/changelogs/unreleased/44392-resolve-projects-creation-silently-failing-on-after-create-error.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd5a05b98b4a5c4ab244e66f26c94b976c73bb
--- /dev/null
+++ b/changelogs/unreleased/44392-resolve-projects-creation-silently-failing-on-after-create-error.yml
@@ -0,0 +1,5 @@
+---
+title: Project creation will now raise an error if a service template is invalid
+merge_request: 18013
+author:
+type: fixed
diff --git a/changelogs/unreleased/44425-use-gitlab_environment.yml b/changelogs/unreleased/44425-use-gitlab_environment.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a774143d5f596f114dc4bcd0970a555416f64e0d
--- /dev/null
+++ b/changelogs/unreleased/44425-use-gitlab_environment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix `gitlab-rake gitlab:two_factor:disable_for_all_users`
+merge_request: 18154
+author:
+type: fixed
diff --git a/changelogs/unreleased/44508-fix-fork-namespace-images.yml b/changelogs/unreleased/44508-fix-fork-namespace-images.yml
new file mode 100644
index 0000000000000000000000000000000000000000..63b4b9a5e560816dc322e95f2fec494c020f6f1f
--- /dev/null
+++ b/changelogs/unreleased/44508-fix-fork-namespace-images.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug rendering group icons when forking
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml b/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ff734fe0c05a1fa08efdffafeebc32525085750a
--- /dev/null
+++ b/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipeline status in branch/tag tree page
+merge_request: 17995
+author:
+type: fixed
diff --git a/changelogs/unreleased/44582-clear-pipeline-status-cache.yml b/changelogs/unreleased/44582-clear-pipeline-status-cache.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1777f2ffaabcb9efd70e3e2cb624b92279912120
--- /dev/null
+++ b/changelogs/unreleased/44582-clear-pipeline-status-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Now `rake cache:clear` will also clear pipeline status cache
+merge_request: 18257
+author:
+type: fixed
diff --git a/changelogs/unreleased/44657-reuse-root_ref_hash-on-branches.yml b/changelogs/unreleased/44657-reuse-root_ref_hash-on-branches.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4f21aadd86b1cbb8a7cf4b1a04fcb5f88113931d
--- /dev/null
+++ b/changelogs/unreleased/44657-reuse-root_ref_hash-on-branches.yml
@@ -0,0 +1,5 @@
+---
+title: Reuse root_ref_hash for performance on Branches
+merge_request: 17998
+author: Takuya Noguchi
+type: performance
diff --git a/changelogs/unreleased/44665-fix-db-trace-stream-by-raw-access.yml b/changelogs/unreleased/44665-fix-db-trace-stream-by-raw-access.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4166d4fe320f71b34000cb0263e1bcbe4eef3b98
--- /dev/null
+++ b/changelogs/unreleased/44665-fix-db-trace-stream-by-raw-access.yml
@@ -0,0 +1,5 @@
+---
+title: Fix `JobsController#raw` endpoint can not read traces in database
+merge_request: 18101
+author:
+type: fixed
diff --git a/changelogs/unreleased/44697-prevue.yml b/changelogs/unreleased/44697-prevue.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9fdce5869aea1671821fb88d7463940a693522a7
--- /dev/null
+++ b/changelogs/unreleased/44697-prevue.yml
@@ -0,0 +1,5 @@
+---
+title: Make toggle markdown preview shortcut only toggle selected field
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/44712-update-asciidoctor-from-1-5-3-to-1-5-6-2.yml b/changelogs/unreleased/44712-update-asciidoctor-from-1-5-3-to-1-5-6-2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bdfed89d2ea4114f2a3727d17c16a9dc437b601e
--- /dev/null
+++ b/changelogs/unreleased/44712-update-asciidoctor-from-1-5-3-to-1-5-6-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update asciidoctor-plantuml to 0.0.8
+merge_request: 18022
+author: Takuya Noguchi
+type: performance
diff --git a/changelogs/unreleased/44774-migrate-upload-task-fails-for-upload-with-store-nil.yml b/changelogs/unreleased/44774-migrate-upload-task-fails-for-upload-with-store-nil.yml
new file mode 100644
index 0000000000000000000000000000000000000000..372f42939642613789cb6ff3182e4c97f17a996e
--- /dev/null
+++ b/changelogs/unreleased/44774-migrate-upload-task-fails-for-upload-with-store-nil.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed gitlab:uploads:migrate task ignoring some uploads.
+merge_request: 18082
+author:
+type: fixed
diff --git a/changelogs/unreleased/44776-fix-upload-migrate-fails-for-group.yml b/changelogs/unreleased/44776-fix-upload-migrate-fails-for-group.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6094fcd0b3e276fc070a77055c6e56f2b18d4ca0
--- /dev/null
+++ b/changelogs/unreleased/44776-fix-upload-migrate-fails-for-group.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed gitlab:uploads:migrate task failing for Groups' avatar.
+merge_request: 18088
+author:
+type: fixed
diff --git a/changelogs/unreleased/44834-ide-remove-branch-from-bottom-bar.yml b/changelogs/unreleased/44834-ide-remove-branch-from-bottom-bar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3f838ad3621722888c599730ccd03c77660bfbc
--- /dev/null
+++ b/changelogs/unreleased/44834-ide-remove-branch-from-bottom-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Remove branch name from the status bar of WebIDE
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/44878-update-brakeman-3-6-1-to-4-2-1.yml b/changelogs/unreleased/44878-update-brakeman-3-6-1-to-4-2-1.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5710cf4f7f0a34b8a329601f2d2b59dc7f0ab6d
--- /dev/null
+++ b/changelogs/unreleased/44878-update-brakeman-3-6-1-to-4-2-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update brakeman 3.6.1 to 4.2.1
+merge_request: 18122
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/44902-remove-rake-test-ci.yml b/changelogs/unreleased/44902-remove-rake-test-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..459de1c2ca372f4963c8d2543b92610b6efcac3f
--- /dev/null
+++ b/changelogs/unreleased/44902-remove-rake-test-ci.yml
@@ -0,0 +1,5 @@
+---
+title: Remove test_ci rake task
+merge_request: 18139
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/44981-http-io-trace-with-multi-byte-char.yml b/changelogs/unreleased/44981-http-io-trace-with-multi-byte-char.yml
new file mode 100644
index 0000000000000000000000000000000000000000..64a17990ebc3e8ed4075966696c54cbe448acbe4
--- /dev/null
+++ b/changelogs/unreleased/44981-http-io-trace-with-multi-byte-char.yml
@@ -0,0 +1,5 @@
+---
+title: Fix `Trace::HttpIO` can not render multi-byte chars
+merge_request: 18417
+author:
+type: fixed
diff --git a/changelogs/unreleased/44985-fix-protected-branch-delete-modal.yml b/changelogs/unreleased/44985-fix-protected-branch-delete-modal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4af2af2a5613dfc22a3ea98022bf76dc4fd576dd
--- /dev/null
+++ b/changelogs/unreleased/44985-fix-protected-branch-delete-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Fix confirmation modal for deleting a protected branch
+merge_request: 18176
+author: Paul Bonaud @PaulRbR
+type: fixed
diff --git a/changelogs/unreleased/45159-fix-illustration.yml b/changelogs/unreleased/45159-fix-illustration.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3b9cb45b916681bc197eb0cff2c71b9f461ef67d
--- /dev/null
+++ b/changelogs/unreleased/45159-fix-illustration.yml
@@ -0,0 +1,5 @@
+---
+title: Adds illustration for when job log was erased
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/45271-collpased-diff-loading.yml b/changelogs/unreleased/45271-collpased-diff-loading.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fdd13a82a4c6813a8fd4c967052d808c236bc09c
--- /dev/null
+++ b/changelogs/unreleased/45271-collpased-diff-loading.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes unresolved discussions rendering the error state instead of the diff
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/45287-align-icons.yml b/changelogs/unreleased/45287-align-icons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0a1cccf9ca6faabe62a9feb597f7d9f6886dd410
--- /dev/null
+++ b/changelogs/unreleased/45287-align-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Align action icons in pipeline graph
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/45363-optional-params-on-api-endpoint-produce-invalid-pagination-header-links.yml b/changelogs/unreleased/45363-optional-params-on-api-endpoint-produce-invalid-pagination-header-links.yml
new file mode 100644
index 0000000000000000000000000000000000000000..963ec8939639cfd498e6e2b8a550aaaef97ec454
--- /dev/null
+++ b/changelogs/unreleased/45363-optional-params-on-api-endpoint-produce-invalid-pagination-header-links.yml
@@ -0,0 +1,6 @@
+---
+title: '[API] Fix URLs in the `Link` header for `GET /projects/:id/repository/contributors`
+  when no value is passed for `order_by` or `sort`'
+merge_request: 18393
+author:
+type: fixed
diff --git a/changelogs/unreleased/45397-update-faraday_middleware-to-0-12-2.yml b/changelogs/unreleased/45397-update-faraday_middleware-to-0-12-2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3370ec3febaa829c736271e701f0fd2e55c838a0
--- /dev/null
+++ b/changelogs/unreleased/45397-update-faraday_middleware-to-0-12-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update faraday_middlewar to 0.12.2
+merge_request: 18397
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/45436-markdown-is-not-rendering-error-loading-viewer-undefined-method-html_escape.yml b/changelogs/unreleased/45436-markdown-is-not-rendering-error-loading-viewer-undefined-method-html_escape.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0f1d111ca58d820a172b979a16cdff38f0032839
--- /dev/null
+++ b/changelogs/unreleased/45436-markdown-is-not-rendering-error-loading-viewer-undefined-method-html_escape.yml
@@ -0,0 +1,5 @@
+---
+title: Fix undefined `html_escape` method during markdown rendering
+merge_request: 18418
+author:
+type: fixed
diff --git a/changelogs/unreleased/45476-geo-statement-timeout-counting-local-job-artifacts.yml b/changelogs/unreleased/45476-geo-statement-timeout-counting-local-job-artifacts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..763d3a2820088b2648e12e8bdf56f41f5ef42444
--- /dev/null
+++ b/changelogs/unreleased/45476-geo-statement-timeout-counting-local-job-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Add index to file_store on ci_job_artifacts
+merge_request: 18444
+author:
+type: performance
diff --git a/changelogs/unreleased/45507-fix-repository-archive-url.yml b/changelogs/unreleased/45507-fix-repository-archive-url.yml
new file mode 100644
index 0000000000000000000000000000000000000000..548c9c386892ce34bb097328d6c38bb8136feade
--- /dev/null
+++ b/changelogs/unreleased/45507-fix-repository-archive-url.yml
@@ -0,0 +1,6 @@
+---
+title: Fix specifying a non-default ref when requesting an archive using the legacy
+  URL
+merge_request: 18468
+author:
+type: fixed
diff --git a/changelogs/unreleased/4826-create-empty-wiki-when-it-s-enabled.yml b/changelogs/unreleased/4826-create-empty-wiki-when-it-s-enabled.yml
deleted file mode 100644
index c0fa8e2e3778c3af238a61cf44224dc4dbbcad84..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/4826-create-empty-wiki-when-it-s-enabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make sure wiki exists when it's enabled
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/4826-geo-wikisyncservice-attempts-to-sync-projects.yml b/changelogs/unreleased/4826-geo-wikisyncservice-attempts-to-sync-projects.yml
deleted file mode 100644
index 7f1ccbfcc7e70e2ec13674a75ab68521fa98caaa..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/4826-geo-wikisyncservice-attempts-to-sync-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create empty wiki when import from GitLab and wiki is not there
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/4826-github-import-wiki-fix-1.yml b/changelogs/unreleased/4826-github-import-wiki-fix-1.yml
deleted file mode 100644
index 69145cb6daf32c134df2feac31d81bd37c069cc7..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/4826-github-import-wiki-fix-1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "[GitHub Import] Create an empty wiki if wiki import failed"
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/8088_embedded_snippets_support.yml b/changelogs/unreleased/8088_embedded_snippets_support.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7bd77a69dbd42aea6615aefe7bae57045c248002
--- /dev/null
+++ b/changelogs/unreleased/8088_embedded_snippets_support.yml
@@ -0,0 +1,5 @@
+---
+title: Adds Embedded Snippets Support
+merge_request: 15695
+author: haseebeqx
+type: added
diff --git a/changelogs/unreleased/Link_to_project_labels_page.yml b/changelogs/unreleased/Link_to_project_labels_page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7bdeec423fcd380a3c0b7b923e2c4fc2a8b36289
--- /dev/null
+++ b/changelogs/unreleased/Link_to_project_labels_page.yml
@@ -0,0 +1,5 @@
+---
+title: Always display Labels section in issuable sidebar, even when the project has no labels
+merge_request: 18081
+author: Branka Martinovic
+type: fixed
diff --git a/changelogs/unreleased/ab-37125-assigned-issues-query.yml b/changelogs/unreleased/ab-37125-assigned-issues-query.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5d4aad08764361a2625e2f3f944ccd98ecd9445a
--- /dev/null
+++ b/changelogs/unreleased/ab-37125-assigned-issues-query.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce complexity of issuable finder query.
+merge_request: 18219
+author:
+type: performance
diff --git a/changelogs/unreleased/ab-37462-cache-personal-projects-count.yml b/changelogs/unreleased/ab-37462-cache-personal-projects-count.yml
new file mode 100644
index 0000000000000000000000000000000000000000..55069b1f2d2c34a119f198be25339b2eb2fe16ce
--- /dev/null
+++ b/changelogs/unreleased/ab-37462-cache-personal-projects-count.yml
@@ -0,0 +1,5 @@
+---
+title: Cache personal projects count.
+merge_request: 18197
+author:
+type: performance
diff --git a/changelogs/unreleased/ab-43150-users-controller-show-query-limit.yml b/changelogs/unreleased/ab-43150-users-controller-show-query-limit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..502c1176d2d3996de86d70ae5fed32151f6b471a
--- /dev/null
+++ b/changelogs/unreleased/ab-43150-users-controller-show-query-limit.yml
@@ -0,0 +1,5 @@
+---
+title: Remove N+1 query for Noteable association.
+merge_request: 17956
+author:
+type: performance
diff --git a/changelogs/unreleased/ab-44467-remove-index.yml b/changelogs/unreleased/ab-44467-remove-index.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fb772ce85d55a7852d895c826eab307564a56f12
--- /dev/null
+++ b/changelogs/unreleased/ab-44467-remove-index.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused index from events table.
+merge_request: 18014
+author:
+type: other
diff --git a/changelogs/unreleased/ab-45247-project-lookups-validation.yml b/changelogs/unreleased/ab-45247-project-lookups-validation.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cd5ebdebc5855fd75647ceae1e30481430d598f3
--- /dev/null
+++ b/changelogs/unreleased/ab-45247-project-lookups-validation.yml
@@ -0,0 +1,5 @@
+---
+title: Validate project path prior to hitting the database.
+merge_request: 18322
+author:
+type: performance
diff --git a/changelogs/unreleased/ac-fix-use_file-race.yml b/changelogs/unreleased/ac-fix-use_file-race.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f1315d5d50e9ba77393c23e1e1a64902f05026ba
--- /dev/null
+++ b/changelogs/unreleased/ac-fix-use_file-race.yml
@@ -0,0 +1,5 @@
+---
+title: Fix data race between ObjectStorage background_upload and Pages publishing
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ac-lfs-direct-upload-ee-to-ce.yml b/changelogs/unreleased/ac-lfs-direct-upload-ee-to-ce.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4db7f76e0aff7c341d617ba7423e2ebe2765e33a
--- /dev/null
+++ b/changelogs/unreleased/ac-lfs-direct-upload-ee-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Port direct upload of LFS artifacts from EE
+merge_request: 17752
+author:
+type: added
diff --git a/changelogs/unreleased/ac-pages-port.yml b/changelogs/unreleased/ac-pages-port.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4f7257b47989f80c5d29593f4e1e0e64a1e64f28
--- /dev/null
+++ b/changelogs/unreleased/ac-pages-port.yml
@@ -0,0 +1,5 @@
+---
+title: Add missing port to artifact links
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/adamco-gitlab-ce-move-issue-command.yml b/changelogs/unreleased/adamco-gitlab-ce-move-issue-command.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3b057373e7d596c724c44d3312245ec6e9baa2c5
--- /dev/null
+++ b/changelogs/unreleased/adamco-gitlab-ce-move-issue-command.yml
@@ -0,0 +1,5 @@
+---
+title: Add slash command for moving issues
+merge_request:
+author: Adam Pahlevi
+type: added
diff --git a/changelogs/unreleased/add-canary-favicon.yml b/changelogs/unreleased/add-canary-favicon.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1af6572588d409fe689ac5b023eae3f69bc790fb
--- /dev/null
+++ b/changelogs/unreleased/add-canary-favicon.yml
@@ -0,0 +1,5 @@
+---
+title: Add yellow favicon when `CANARY=true` to differientate canary environment
+merge_request: 12477
+author:
+type: changed
diff --git a/changelogs/unreleased/add-cpu-mem-totals.yml b/changelogs/unreleased/add-cpu-mem-totals.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc8babab7318267100e632da934dc96a942948dd
--- /dev/null
+++ b/changelogs/unreleased/add-cpu-mem-totals.yml
@@ -0,0 +1,5 @@
+---
+title: Add Total CPU/Memory consumption metrics for Kubernetes
+merge_request: 17731
+author:
+type: added
diff --git a/changelogs/unreleased/add-milestone-path-to-dashboard-milestones-breadcrumb-link.yml b/changelogs/unreleased/add-milestone-path-to-dashboard-milestones-breadcrumb-link.yml
new file mode 100644
index 0000000000000000000000000000000000000000..015bee99170b7a3f465c1ac33913133721c402cc
--- /dev/null
+++ b/changelogs/unreleased/add-milestone-path-to-dashboard-milestones-breadcrumb-link.yml
@@ -0,0 +1,5 @@
+---
+title: Update dashboard milestones breadcrumb link
+merge_request: 17933
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/add-per-runner-job-timeout.yml b/changelogs/unreleased/add-per-runner-job-timeout.yml
new file mode 100644
index 0000000000000000000000000000000000000000..336b4d15ddfa2a9b867521b7f29ce76f375ab978
--- /dev/null
+++ b/changelogs/unreleased/add-per-runner-job-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Add per-runner configured job timeout
+merge_request: 17221
+author:
+type: added
diff --git a/changelogs/unreleased/add-query-counts-to-profiler-output.yml b/changelogs/unreleased/add-query-counts-to-profiler-output.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8a90b1cbeb02a36c38622a49ae4c6fc654f86d93
--- /dev/null
+++ b/changelogs/unreleased/add-query-counts-to-profiler-output.yml
@@ -0,0 +1,5 @@
+---
+title: Add query counts to profiler output
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/ajax-requests-in-performance-bar.yml b/changelogs/unreleased/ajax-requests-in-performance-bar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..88cc3678c2b6fcaaa664950e60399b2a5be3cdc8
--- /dev/null
+++ b/changelogs/unreleased/ajax-requests-in-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Allow viewing timings for AJAX requests in the performance bar
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/api-refs-for-commit.yml b/changelogs/unreleased/api-refs-for-commit.yml
deleted file mode 100644
index df8a2b0eccc9ec15c7dd695c1e7a00348a733c9f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/api-refs-for-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Get references a commit is pushed to'
-merge_request: 15026
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/asciidoc_inter_document_cross_references.yml b/changelogs/unreleased/asciidoc_inter_document_cross_references.yml
deleted file mode 100644
index 34b26753312429436a8a18ed265a238f59bce64c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/asciidoc_inter_document_cross_references.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Asciidoc now support inter-document cross references between files in repository
-merge_request: 17125
-author: Turo Soisenniemi
-type: changed
diff --git a/changelogs/unreleased/ash-mckenzie-include-sha-with-version.yml b/changelogs/unreleased/ash-mckenzie-include-sha-with-version.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b49c48e0fe1421f311f9926e3c88892d5055c665
--- /dev/null
+++ b/changelogs/unreleased/ash-mckenzie-include-sha-with-version.yml
@@ -0,0 +1,5 @@
+---
+title: git SHA is now displayed alongside the GitLab version on the Admin Dashboard
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/assignees-vue-component-missing-data-container.yml b/changelogs/unreleased/assignees-vue-component-missing-data-container.yml
deleted file mode 100644
index 233d983b41582bb1017def88056c4abe85a8ac16..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/assignees-vue-component-missing-data-container.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Assignees vue component missing data container
-merge_request: 17426
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/blackst0ne-bump-html-pipeline-gem.yml b/changelogs/unreleased/blackst0ne-bump-html-pipeline-gem.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9885c8853cc06dfac29d4c3229db8a45316993bd
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-bump-html-pipeline-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Bump html-pipeline to 2.7.1
+merge_request: 18132
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/blackst0ne-rails5-update-state_machines-activerecord-gem.yml b/changelogs/unreleased/blackst0ne-rails5-update-state_machines-activerecord-gem.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a9c6fcbf428624a86c566d407953412a2c16b09b
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-rails5-update-state_machines-activerecord-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Bump `state_machines-activerecord` to 0.5.1
+merge_request: 17924
+author: blackst0ne
+type: other
diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bcfba4ae70df8ed94003c0fec9af9a7f31c29419
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml
@@ -0,0 +1,5 @@
+---
+title: "Replace the `project/commits/branches.feature` spinach test with an rspec analog"
+merge_request: 18302
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-comments-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-comments-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e7077f275550d224f5f59b37f4cb37c8d62499bf
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-comments-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Replace the `project/commits/comments.feature` spinach test with an rspec analog
+merge_request: 18356
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-issues-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-issues-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7defdc0a28ffd8b556d706062e938b50aeb70d4e
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-issues-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Replace the spinach test with an rspec analog
+merge_request: 17950
+author: blackst0ne
+type: other
diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-labels-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-labels-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4e1bb15f150d75090af4feff010da8a6af86f5d8
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-labels-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Replace the `project/issues/labels.feature` spinach test with an rspec analog
+merge_request: 18126
+author: blackst0ne
+type: other
diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0dcac0a80eb2b315b03d7893d396822eff472441
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Replace the `project/issues/milestones.feature` spinach test with an rspec analog
+merge_request: 18300
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/bvl-export-import-lfs.yml b/changelogs/unreleased/bvl-export-import-lfs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dd1f499c3a304f5a3e7cd480d5c26854ac755bdd
--- /dev/null
+++ b/changelogs/unreleased/bvl-export-import-lfs.yml
@@ -0,0 +1,5 @@
+---
+title: Support LFS objects when importing/exporting GitLab project archives
+merge_request: 18115
+author:
+type: added
diff --git a/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml b/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml
new file mode 100644
index 0000000000000000000000000000000000000000..86bd5faf8edc1c25fb49ac652a43d1fa1e2c2086
--- /dev/null
+++ b/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml
@@ -0,0 +1,5 @@
+---
+title: Fix importing multiple assignees from GitLab export
+merge_request: 17718
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-no-permanent-redirect.yml b/changelogs/unreleased/bvl-no-permanent-redirect.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c34a3789b584f19a235b05e5c5d657983b614864
--- /dev/null
+++ b/changelogs/unreleased/bvl-no-permanent-redirect.yml
@@ -0,0 +1,5 @@
+---
+title: Don't create permanent redirect routes
+merge_request: 17521
+author:
+type: changed
diff --git a/changelogs/unreleased/bvl-override-import-params.yml b/changelogs/unreleased/bvl-override-import-params.yml
new file mode 100644
index 0000000000000000000000000000000000000000..18cfef873df8e5063b1856f97ba7bc8f1eb02b6f
--- /dev/null
+++ b/changelogs/unreleased/bvl-override-import-params.yml
@@ -0,0 +1,5 @@
+---
+title: Allow overriding params on project import through API
+merge_request: 18086
+author:
+type: added
diff --git a/changelogs/unreleased/bvl-shared-groups-on-group-page.yml b/changelogs/unreleased/bvl-shared-groups-on-group-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6c0703fd1387269bbbbff7409f46c2b24b2f46e4
--- /dev/null
+++ b/changelogs/unreleased/bvl-shared-groups-on-group-page.yml
@@ -0,0 +1,5 @@
+---
+title: Show shared projects on group page
+merge_request: 18390
+author:
+type: fixed
diff --git a/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml b/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml
deleted file mode 100644
index a51781396ee056b2772bd89c26580061187ce0ce..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove whitespace from the username/email sign in form field
-merge_request: 17020
-author: Peter lauck
-type: changed
diff --git a/changelogs/unreleased/ci-pipeline-commit-lookup.yml b/changelogs/unreleased/ci-pipeline-commit-lookup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b2a1e4c21634bca8e02c63a7df58ca6fef8e076e
--- /dev/null
+++ b/changelogs/unreleased/ci-pipeline-commit-lookup.yml
@@ -0,0 +1,5 @@
+---
+title: Use porcelain commit lookup method on CI::CreatePipelineService
+merge_request: 17911
+author:
+type: fixed
diff --git a/changelogs/unreleased/da-gitaly-calculate-repository-checksum.yml b/changelogs/unreleased/da-gitaly-calculate-repository-checksum.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de09f87a7c9e362194fe0849ea6f46aa71ca2a1b
--- /dev/null
+++ b/changelogs/unreleased/da-gitaly-calculate-repository-checksum.yml
@@ -0,0 +1,5 @@
+---
+title: Repository checksum calculation is handled by Gitaly when feature is enabled
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/dashboard-view-user-choices-issues-merge-requests.yml b/changelogs/unreleased/dashboard-view-user-choices-issues-merge-requests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..92a03070d786560d332ded4c7d481fb03ea28d30
--- /dev/null
+++ b/changelogs/unreleased/dashboard-view-user-choices-issues-merge-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Add 'Assigned Issues' and 'Assigned Merge Requests' as dashboard view choices for users
+merge_request: 17860
+author: Elias Werberich
+type: added
diff --git a/changelogs/unreleased/deploy-tokens-container-registry-specs.yml b/changelogs/unreleased/deploy-tokens-container-registry-specs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d86f955c966fdb9e540f3812f9cd277cff09f42f
--- /dev/null
+++ b/changelogs/unreleased/deploy-tokens-container-registry-specs.yml
@@ -0,0 +1,5 @@
+---
+title: Verify that deploy token has valid access when pulling container registry image
+merge_request: 18260
+author:
+type: fixed
diff --git a/changelogs/unreleased/deprecation-warning-for-dynamic-milestones.yml b/changelogs/unreleased/deprecation-warning-for-dynamic-milestones.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3e1ac7b795dd6de675e7aff40ab0d789f45d4258
--- /dev/null
+++ b/changelogs/unreleased/deprecation-warning-for-dynamic-milestones.yml
@@ -0,0 +1,5 @@
+---
+title: Add deprecation message to dynamic milestone pages
+merge_request: 17505
+author:
+type: added
diff --git a/changelogs/unreleased/direct-upload-of-uploads.yml b/changelogs/unreleased/direct-upload-of-uploads.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7900fa5f58d4d12140719885a91d89540ec80560
--- /dev/null
+++ b/changelogs/unreleased/direct-upload-of-uploads.yml
@@ -0,0 +1,5 @@
+---
+title: Allow to store uploads by default on Object Storage
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/dm-deploy-keys-default-user.yml b/changelogs/unreleased/dm-deploy-keys-default-user.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b82d67d028ce49333d69dfa4bfd4e7593e8c14e6
--- /dev/null
+++ b/changelogs/unreleased/dm-deploy-keys-default-user.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure hooks run when a deploy key without a user pushes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-dont-cache-nil-root-ref.yml b/changelogs/unreleased/dm-dont-cache-nil-root-ref.yml
deleted file mode 100644
index 4dab7d0ffcaf745f97b53f5b3846655db2e2db12..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-dont-cache-nil-root-ref.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't cache a nil repository root ref to prevent caching issues
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-escape-commit-message.yml b/changelogs/unreleased/dm-escape-commit-message.yml
deleted file mode 100644
index 89af2da3484dc31b8cb4db26b4126b1454747132..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-escape-commit-message.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Escape HTML entities in commit messages
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-flatten-tree-plus-chars.yml b/changelogs/unreleased/dm-flatten-tree-plus-chars.yml
new file mode 100644
index 0000000000000000000000000000000000000000..23f1b30d8fa1cddd4270e7b93a5a109625dc71ca
--- /dev/null
+++ b/changelogs/unreleased/dm-flatten-tree-plus-chars.yml
@@ -0,0 +1,5 @@
+---
+title: Fix links to subdirectories of a directory with a plus character in its path
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-go-get-api-token.yml b/changelogs/unreleased/dm-go-get-api-token.yml
deleted file mode 100644
index ad9cfe05849894652f73387b77c8c92f46255d32..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-go-get-api-token.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow token authentication on go-get request
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/dm-internal-user-namespace.yml b/changelogs/unreleased/dm-internal-user-namespace.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8517c116795e26476a918e308efeb7eca3d35d24
--- /dev/null
+++ b/changelogs/unreleased/dm-internal-user-namespace.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure internal users (ghost, support bot) get assigned a namespace
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-stuck-import-jobs-verify.yml b/changelogs/unreleased/dm-stuck-import-jobs-verify.yml
deleted file mode 100644
index ed2c2d30f0d6d2c3f759a49201342af91631da3e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-stuck-import-jobs-verify.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Verify project import status again before marking as failed
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/docs-for-failure-reason-tooltip.yml b/changelogs/unreleased/docs-for-failure-reason-tooltip.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ef37654b189ea63dec7a5cd8552a5f72ef1908e9
--- /dev/null
+++ b/changelogs/unreleased/docs-for-failure-reason-tooltip.yml
@@ -0,0 +1,5 @@
+---
+title: Add documentation for Pipelines failure reasons
+merge_request: 18352
+author:
+type: other
diff --git a/changelogs/unreleased/docs-update-vue-naming-guidelines.yml b/changelogs/unreleased/docs-update-vue-naming-guidelines.yml
deleted file mode 100644
index 95bfd21237039460334d8a90cebf30e5d1fe0692..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/docs-update-vue-naming-guidelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update vue component naming guidelines
-merge_request: 17018
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/dz-improve-app-settings-2.yml b/changelogs/unreleased/dz-improve-app-settings-2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ebe571decb83650c07e5a8743ee2add9e74af821
--- /dev/null
+++ b/changelogs/unreleased/dz-improve-app-settings-2.yml
@@ -0,0 +1,5 @@
+---
+title: Redesign application settings to match project settings
+merge_request: 18019
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-namespace-id-not-null.yml b/changelogs/unreleased/dz-namespace-id-not-null.yml
deleted file mode 100644
index 07b32aeeb86cd0be4a0b7d795d0b8bb1f187de5b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dz-namespace-id-not-null.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add NOT NULL constraint to projects.namespace_id
-merge_request: 17448
-author:
-type: other
diff --git a/changelogs/unreleased/dz-system-hooks-plugins.yml b/changelogs/unreleased/dz-system-hooks-plugins.yml
deleted file mode 100644
index e6eb1dfb03bc0a638b45b04ce1020f331dbd87ed..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dz-system-hooks-plugins.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add ability to use external plugins as an alternative to system hooks
-merge_request: 17003
-author:
-type: added
diff --git a/changelogs/unreleased/escape-autocomplete-values-for-markdown.yml b/changelogs/unreleased/escape-autocomplete-values-for-markdown.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eea9da4c579046b0e9ce00f9d4f93ef8f2c22fc2
--- /dev/null
+++ b/changelogs/unreleased/escape-autocomplete-values-for-markdown.yml
@@ -0,0 +1,5 @@
+---
+title: Escape Markdown characters properly when using autocomplete
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/expose-commits-mr-api.yml b/changelogs/unreleased/expose-commits-mr-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77ea2f274312d0218d383686636fca01e35dbc2a
--- /dev/null
+++ b/changelogs/unreleased/expose-commits-mr-api.yml
@@ -0,0 +1,5 @@
+---
+title: Allow merge requests related to a commit to be found via API
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/feature--2848-display-time-tracking-totals-milestone-page.yml b/changelogs/unreleased/feature--2848-display-time-tracking-totals-milestone-page.yml
deleted file mode 100644
index ca877d32b059b877a443c9fa7f3258e9aea78582..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature--2848-display-time-tracking-totals-milestone-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "#28481: Display time tracking totals on milestone page"
-merge_request: 16753
-author: Riccardo Padovani
-type: added
diff --git a/changelogs/unreleased/feature-26598-clear-button-ci-lint.yml b/changelogs/unreleased/feature-26598-clear-button-ci-lint.yml
deleted file mode 100644
index fcf237f20f0f75be84f9a3d5c5a9fb5efaaee353..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-26598-clear-button-ci-lint.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added clear button to ci lint editor
-merge_request:
-author: Michael Robinson
diff --git a/changelogs/unreleased/feature-add-language-in-repository-to-api.yml b/changelogs/unreleased/feature-add-language-in-repository-to-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bd9bd377212ee2a65df7fe7f43d3d02fdffed1bc
--- /dev/null
+++ b/changelogs/unreleased/feature-add-language-in-repository-to-api.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: add languages of project GET /projects/:id/languages'
+merge_request: 17770
+author: Roger R眉ttimann
+type: added
diff --git a/changelogs/unreleased/feature-add_target_to_tags.yml b/changelogs/unreleased/feature-add_target_to_tags.yml
new file mode 100644
index 0000000000000000000000000000000000000000..75816005e1f25ace36cb020ee390ae352417d0a3
--- /dev/null
+++ b/changelogs/unreleased/feature-add_target_to_tags.yml
@@ -0,0 +1,5 @@
+---
+title: Expose the target commit ID through the tag API
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/feature-edit_pages_domain.yml b/changelogs/unreleased/feature-edit_pages_domain.yml
deleted file mode 100644
index bd0af53296c69887cf5821219c5ff582e1803296..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-edit_pages_domain.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Pages custom domain: allow update of key/certificate'
-merge_request: 17376
-author: rfwatson
-type: changed
diff --git a/changelogs/unreleased/feature-gb-pipeline-variable-expressions.yml b/changelogs/unreleased/feature-gb-pipeline-variable-expressions.yml
deleted file mode 100644
index 28820649af3abff7f54b3273c287b248b42570d1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-gb-pipeline-variable-expressions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add catch-up background migration to migrate pipeline stages
-merge_request: 15741
-author:
-type: performance
diff --git a/changelogs/unreleased/feature-gb-variables-expressions-in-only-except.yml b/changelogs/unreleased/feature-gb-variables-expressions-in-only-except.yml
new file mode 100644
index 0000000000000000000000000000000000000000..84977ce11c8c00f5e018e3b43a63936fe04d17cc
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-variables-expressions-in-only-except.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for pipeline variables expressions in only/except
+merge_request: 17316
+author:
+type: added
diff --git a/changelogs/unreleased/feature-include-custom-attributes-in-api.yml b/changelogs/unreleased/feature-include-custom-attributes-in-api.yml
deleted file mode 100644
index f1087d7f7cc9326edf97b33f0a85cc9f7b7dd0c8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-include-custom-attributes-in-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow including custom attributes in API responses
-merge_request: 16526
-author: Markus Koller
-type: changed
diff --git a/changelogs/unreleased/feature-oidc-groups-claim.yml b/changelogs/unreleased/feature-oidc-groups-claim.yml
deleted file mode 100644
index bde191301142c27c63bc49177ea5f7a8c4f6cb21..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-oidc-groups-claim.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add groups to OpenID Connect claims
-merge_request: 16929
-author: Hassan Zamani
diff --git a/changelogs/unreleased/feature_detect_co_authored_commits.yml b/changelogs/unreleased/feature_detect_co_authored_commits.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7b1269ed98229a8b6399ca92b44c8697a12d2282
--- /dev/null
+++ b/changelogs/unreleased/feature_detect_co_authored_commits.yml
@@ -0,0 +1,6 @@
+---
+title: Detect commit message trailers and link users properly to their accounts
+  on Gitlab
+merge_request: 17919
+author: cousine
+type: added
diff --git a/changelogs/unreleased/fix-40798-namespace-forking.yml b/changelogs/unreleased/fix-40798-namespace-forking.yml
new file mode 100644
index 0000000000000000000000000000000000000000..095235725f8aafffd2ae652a731c5cd3a8df94fd
--- /dev/null
+++ b/changelogs/unreleased/fix-40798-namespace-forking.yml
@@ -0,0 +1,5 @@
+---
+title: Fix forking to subgroup via API when namespace is given by name
+merge_request: 17815
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/fix-42459---in-branch.yml b/changelogs/unreleased/fix-42459---in-branch.yml
new file mode 100644
index 0000000000000000000000000000000000000000..26cc2046206473fbd8ab73dc7e7f3362d79cdf5f
--- /dev/null
+++ b/changelogs/unreleased/fix-42459---in-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Fix relative uri when "#" is in branch name
+merge_request:
+author: Jan
+type: fixed
diff --git a/changelogs/unreleased/fix-500-error-when-mr-ref-is-not-yet-fetched.yml b/changelogs/unreleased/fix-500-error-when-mr-ref-is-not-yet-fetched.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e21554f091a6a8a46bd5cf77f41b2d84063ecfe0
--- /dev/null
+++ b/changelogs/unreleased/fix-500-error-when-mr-ref-is-not-yet-fetched.yml
@@ -0,0 +1,6 @@
+---
+title: Fix 500 error when a merge request from a fork has conflicts and has not yet
+  been updated
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-auth0-unsafe-login.yml b/changelogs/unreleased/fix-auth0-unsafe-login.yml
new file mode 100644
index 0000000000000000000000000000000000000000..01c6ea69dcc693f9b3172429ca728b3a0708dea7
--- /dev/null
+++ b/changelogs/unreleased/fix-auth0-unsafe-login.yml
@@ -0,0 +1,5 @@
+---
+title: Fix GitLab Auth0 integration signing in the wrong user
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/fix-change-event-body-label-font-size.yml b/changelogs/unreleased/fix-change-event-body-label-font-size.yml
deleted file mode 100644
index 3192a7bff92756181a4f6515fb0ec31ea744cdd9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-change-event-body-label-font-size.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Apply new default and inline label design
-merge_request: 16956
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/fix-dashboard-sorting.yml b/changelogs/unreleased/fix-dashboard-sorting.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2ba13a93fa968bbd39b7a2659e426e8e75a6d86f
--- /dev/null
+++ b/changelogs/unreleased/fix-dashboard-sorting.yml
@@ -0,0 +1,5 @@
+---
+title: Prioritize weight over title when sorting charts
+merge_request: 18233
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-direct-upload-for-old-records.yml b/changelogs/unreleased/fix-direct-upload-for-old-records.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a062b9e73e94d7b39e96505fa60138c34a73e26e
--- /dev/null
+++ b/changelogs/unreleased/fix-direct-upload-for-old-records.yml
@@ -0,0 +1,5 @@
+---
+title: Fix direct_upload when records with null file_store are used
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-emoji-popup.yml b/changelogs/unreleased/fix-emoji-popup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c81d061a5d774b71d9a309bddf51269dc7be586b
--- /dev/null
+++ b/changelogs/unreleased/fix-emoji-popup.yml
@@ -0,0 +1,5 @@
+---
+title: Hide emoji popup after multiple spaces
+merge_request:
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-fix-empty-secret-variables.yml b/changelogs/unreleased/fix-gb-fix-empty-secret-variables.yml
new file mode 100644
index 0000000000000000000000000000000000000000..94010c06a07bce6a1bf53d2314867c663c29b026
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-empty-secret-variables.yml
@@ -0,0 +1,5 @@
+---
+title: Fix a case with secret variables being empty sometimes
+merge_request: 18400
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-mattermost-delete-team.yml b/changelogs/unreleased/fix-mattermost-delete-team.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d14ae0231146d3efd24691c98139a24c6daba9f2
--- /dev/null
+++ b/changelogs/unreleased/fix-mattermost-delete-team.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed group deletion linked to Mattermost
+merge_request: 16209
+author: Julien Millau
+type: fixed
diff --git a/changelogs/unreleased/fix-new-project-path-input-overlapping.yml b/changelogs/unreleased/fix-new-project-path-input-overlapping.yml
deleted file mode 100644
index fb33ce9437af3a32d0c2defaa62384c6468bf133..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-new-project-path-input-overlapping.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix new project path input overlapping
-merge_request: 16755
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/fix-projects-no-repository-placeholder.yml b/changelogs/unreleased/fix-projects-no-repository-placeholder.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3d11c8970209ddbff639e8221f762ad01c82aebc
--- /dev/null
+++ b/changelogs/unreleased/fix-projects-no-repository-placeholder.yml
@@ -0,0 +1,5 @@
+---
+title: Update no repository placeholder
+merge_request: 17964
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/fix-references-in-group-context.yml b/changelogs/unreleased/fix-references-in-group-context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b436c2089ed3a28419ec23446f10a543b3f12c12
--- /dev/null
+++ b/changelogs/unreleased/fix-references-in-group-context.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore project internal references in group context
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-squash-with-renamed-files.yml b/changelogs/unreleased/fix-squash-with-renamed-files.yml
deleted file mode 100644
index f7cd3a84367f1c8b168e4fdae9b8dbe2cf689bef..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-squash-with-renamed-files.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix squashing when a file is renamed
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-template-project-visibility.yml b/changelogs/unreleased/fix-template-project-visibility.yml
deleted file mode 100644
index 6576097822bd1a15610391fcaf342e161557d165..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-template-project-visibility.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Respect description and visibility when creating project from template
-merge_request: 16820
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/fix-wiki-find-file-gitaly.yml b/changelogs/unreleased/fix-wiki-find-file-gitaly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5c536be7ae51eb5e564f4b01c48404dd05735945
--- /dev/null
+++ b/changelogs/unreleased/fix-wiki-find-file-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Fix finding wiki file when Gitaly is enabled
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-15329-services-callbacks-ssrf.yml b/changelogs/unreleased/fj-15329-services-callbacks-ssrf.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7fa6f6a5874aa1d6f666274acc5fe0f4f5dea55a
--- /dev/null
+++ b/changelogs/unreleased/fj-15329-services-callbacks-ssrf.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed some SSRF vulnerabilities in services, hooks and integrations
+merge_request: 2337
+author:
+type: security
diff --git a/changelogs/unreleased/fj-174-better-ldap-connection-handling.yml b/changelogs/unreleased/fj-174-better-ldap-connection-handling.yml
new file mode 100644
index 0000000000000000000000000000000000000000..be0b83505fbc175cd842a7008ac42342c2277d83
--- /dev/null
+++ b/changelogs/unreleased/fj-174-better-ldap-connection-handling.yml
@@ -0,0 +1,5 @@
+---
+title: Add better LDAP connection handling
+merge_request: 18039
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-28141-redirection-loop.yml b/changelogs/unreleased/fj-28141-redirection-loop.yml
deleted file mode 100644
index db7e109a06e1534dff480f12352cadb574453d6e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fj-28141-redirection-loop.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removing the two factor check when the user sets a new password
-merge_request: 17457
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-41900-import-endpoint-with-overwrite-support.yml b/changelogs/unreleased/fj-41900-import-endpoint-with-overwrite-support.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0553cc684ce92c16737e3d723474df6549c50738
--- /dev/null
+++ b/changelogs/unreleased/fj-41900-import-endpoint-with-overwrite-support.yml
@@ -0,0 +1,5 @@
+---
+title: Extend API for importing a project export with overwrite support
+merge_request: 17883
+author:
+type: added
diff --git a/changelogs/unreleased/fj-42354-custom-hooks-not-triggered-by-UI-wiki-edit.yml b/changelogs/unreleased/fj-42354-custom-hooks-not-triggered-by-UI-wiki-edit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9fe458aba4a4937bab91efce7d1027cc36f2d67a
--- /dev/null
+++ b/changelogs/unreleased/fj-42354-custom-hooks-not-triggered-by-UI-wiki-edit.yml
@@ -0,0 +1,5 @@
+---
+title: Triggering custom hooks by Wiki UI edit
+merge_request: 18251
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-42601-respect-visibility-options.yml b/changelogs/unreleased/fj-42601-respect-visibility-options.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eabb337234c6d82c95eee3a5a629ff488edfa686
--- /dev/null
+++ b/changelogs/unreleased/fj-42601-respect-visibility-options.yml
@@ -0,0 +1,5 @@
+---
+title: Respect visibility options and description when importing project from template
+merge_request: 18473
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-42685-extend-project-export-endpoint.yml b/changelogs/unreleased/fj-42685-extend-project-export-endpoint.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a06499d821a0f8baea024e0d3a56cd34981cd677
--- /dev/null
+++ b/changelogs/unreleased/fj-42685-extend-project-export-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Extend API for exporting a project with direct upload URL
+merge_request: 17686
+author:
+type: added
diff --git a/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml b/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml
deleted file mode 100644
index cef339ef78745929b952d34325683300be3eadbf..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed bug with unauthenticated requests through git ssh
-merge_request: 17149
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-change-gollum-gems-to-custom-ones.yml b/changelogs/unreleased/fj-change-gollum-gems-to-custom-ones.yml
new file mode 100644
index 0000000000000000000000000000000000000000..53883e8d907cf0708676e7a22a948a38f9963d7c
--- /dev/null
+++ b/changelogs/unreleased/fj-change-gollum-gems-to-custom-ones.yml
@@ -0,0 +1,5 @@
+---
+title: Replacing gollum libraries for gitlab custom libs
+merge_request: 18343
+author:
+type: other
diff --git a/changelogs/unreleased/fl-fix-annoying-actions.yml b/changelogs/unreleased/fl-fix-annoying-actions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fe17f9a8978299e8f2f62b091ea22cc3d08c3c15
--- /dev/null
+++ b/changelogs/unreleased/fl-fix-annoying-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Stop redirecting the page in pipeline main actions
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fl-pipelines-details-axios.yml b/changelogs/unreleased/fl-pipelines-details-axios.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0b72e54cba34060ab8942ad6f26e2889de1030fb
--- /dev/null
+++ b/changelogs/unreleased/fl-pipelines-details-axios.yml
@@ -0,0 +1,5 @@
+---
+title: Replace vue resource with axios for pipelines details page
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/fl-refresh-btn.yml b/changelogs/unreleased/fl-refresh-btn.yml
deleted file mode 100644
index 640fdda9ce745f60e08613a2efc721485c486da2..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fl-refresh-btn.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show loading button inline in refresh button in MR widget
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/group-label-page-breadcrumb.yml b/changelogs/unreleased/group-label-page-breadcrumb.yml
deleted file mode 100644
index c6cc4618c520054f84790ef4122f55bcef04cab9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/group-label-page-breadcrumb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix breadcrumb on labels page for groups
-merge_request: 17045
-author: Onuwa Nnachi Isaac
-type: fixed
diff --git a/changelogs/unreleased/grpc-unavailable-restart.yml b/changelogs/unreleased/grpc-unavailable-restart.yml
deleted file mode 100644
index 5ce08d66004e3fccf4dcc402b65784647b20f431..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/grpc-unavailable-restart.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Restart Unicorn and Sidekiq when GRPC throws 14:Endpoint read failed
-merge_request: 17293
-author:
-type: fixed
diff --git a/changelogs/unreleased/ide-file-row-hover-style.yml b/changelogs/unreleased/ide-file-row-hover-style.yml
new file mode 100644
index 0000000000000000000000000000000000000000..158379a5aefec76fd862a78ac19118072d2e61f6
--- /dev/null
+++ b/changelogs/unreleased/ide-file-row-hover-style.yml
@@ -0,0 +1,5 @@
+---
+title: Added hover background color to IDE file list rows
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/ide-folder-button-path.yml b/changelogs/unreleased/ide-folder-button-path.yml
new file mode 100644
index 0000000000000000000000000000000000000000..84a122fab752384634fea0015478a1a78d36211c
--- /dev/null
+++ b/changelogs/unreleased/ide-folder-button-path.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed IDE button opening the wrong URL in tree list
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-mr-changes-alert-box.yml b/changelogs/unreleased/ide-mr-changes-alert-box.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fec2719c2b1e94ec258a26de7a360ece4e6f3c66
--- /dev/null
+++ b/changelogs/unreleased/ide-mr-changes-alert-box.yml
@@ -0,0 +1,5 @@
+---
+title: Removed alert box in IDE when redirecting to new merge request
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-project-avatar-identicon.yml b/changelogs/unreleased/ide-project-avatar-identicon.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2b8b00018a8db1c3c5ced351476f812689f90699
--- /dev/null
+++ b/changelogs/unreleased/ide-project-avatar-identicon.yml
@@ -0,0 +1,5 @@
+---
+title: Make project avatar in IDE consistent with the rest of GitLab
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/ide-subgroup-fix.yml b/changelogs/unreleased/ide-subgroup-fix.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2234c42b4bd73fdd2463dce43f2b94699df0b2ac
--- /dev/null
+++ b/changelogs/unreleased/ide-subgroup-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed IDE not loading for sub groups
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-tree-loading-fix.yml b/changelogs/unreleased/ide-tree-loading-fix.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2fb43380a48ba18804552ad42bd931f36ca18c93
--- /dev/null
+++ b/changelogs/unreleased/ide-tree-loading-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed IDE not showing loading state when tree is loading
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/improve-jobs-queuing-time-metric.yml b/changelogs/unreleased/improve-jobs-queuing-time-metric.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cee8b8523fd83b99a9680d26b7a06651d7241b9c
--- /dev/null
+++ b/changelogs/unreleased/improve-jobs-queuing-time-metric.yml
@@ -0,0 +1,5 @@
+---
+title: Partition job_queue_duration_seconds with jobs_running_for_project
+merge_request: 17730
+author:
+type: changed
diff --git a/changelogs/unreleased/increase-unicorn-memory-killer-limits.yml b/changelogs/unreleased/increase-unicorn-memory-killer-limits.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6d7d2df4f4a275f9a0d5afa79302ee9d32d2ac35
--- /dev/null
+++ b/changelogs/unreleased/increase-unicorn-memory-killer-limits.yml
@@ -0,0 +1,5 @@
+---
+title: Increase the memory limits used in the unicorn killer
+merge_request: 17948
+author:
+type: other
diff --git a/changelogs/unreleased/issue-39885.yml b/changelogs/unreleased/issue-39885.yml
deleted file mode 100644
index 75bf943415295c5a8355a98649628bde2e32f720..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-39885.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Ensure users cannot create environments with leading or trailing slashes (Fixes #39885)'
-merge_request: 15273
-author:
-type: fixed
diff --git a/changelogs/unreleased/issue-edit-shortcut.yml b/changelogs/unreleased/issue-edit-shortcut.yml
deleted file mode 100644
index 2b29b2bc03fc138fa1e7338413d4eba4f871d523..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-edit-shortcut.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed issue edit shortcut not opening edit form
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/issue_25542.yml b/changelogs/unreleased/issue_25542.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eba491f7e2a396e0ab6ef6272f914b3768832bc1
--- /dev/null
+++ b/changelogs/unreleased/issue_25542.yml
@@ -0,0 +1,5 @@
+---
+title: Improve JIRA event descriptions
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/issue_40915.yml b/changelogs/unreleased/issue_40915.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2b6d98e69a6db05f7642fbdccb81b22caa3a4bfe
--- /dev/null
+++ b/changelogs/unreleased/issue_40915.yml
@@ -0,0 +1,5 @@
+---
+title: Allow assigning and filtering issuables by ancestor group labels
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/issue_42443.yml b/changelogs/unreleased/issue_42443.yml
new file mode 100644
index 0000000000000000000000000000000000000000..954fbd98a4bc8169c0a86ef72e08721a4a1cca3f
--- /dev/null
+++ b/changelogs/unreleased/issue_42443.yml
@@ -0,0 +1,5 @@
+---
+title: Include subgroup issues when searching for group issues using the API
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/issue_44270.yml b/changelogs/unreleased/issue_44270.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6234162be303e4cded6e1ff11a08608737b2a6b0
--- /dev/null
+++ b/changelogs/unreleased/issue_44270.yml
@@ -0,0 +1,5 @@
+---
+title: Show issues of subgroups in group-level issue board
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/jej-commit-api-tracks-lfs.yml b/changelogs/unreleased/jej-commit-api-tracks-lfs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8284abf9f281aee510f4b2b3a841c5180a4001b0
--- /dev/null
+++ b/changelogs/unreleased/jej-commit-api-tracks-lfs.yml
@@ -0,0 +1,5 @@
+---
+title: Create commit API and Web IDE obey LFS filters
+merge_request: 16718
+author:
+type: fixed
diff --git a/changelogs/unreleased/jej-mattermost-notification-confidentiality.yml b/changelogs/unreleased/jej-mattermost-notification-confidentiality.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d5219b5d0190d50319932482c17f2319aff83ed4
--- /dev/null
+++ b/changelogs/unreleased/jej-mattermost-notification-confidentiality.yml
@@ -0,0 +1,5 @@
+---
+title: Adds confidential notes channel for Slack/Mattermost
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/jivl-realtime-update-adding-file.yml b/changelogs/unreleased/jivl-realtime-update-adding-file.yml
new file mode 100644
index 0000000000000000000000000000000000000000..df1bdb1648dca2c22edfacd7025e95ccada73fc2
--- /dev/null
+++ b/changelogs/unreleased/jivl-realtime-update-adding-file.yml
@@ -0,0 +1,5 @@
+---
+title: Add realtime pipeline status for adding/viewing files
+merge_request: 17705
+author:
+type: other
diff --git a/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml b/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5cdbcf7b4079cb0cea5011fc68f47f03cf9bfd2
--- /dev/null
+++ b/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Add average and maximum summary statistics to the prometheus dashboard
+merge_request: 17921
+author:
+type: changed
diff --git a/changelogs/unreleased/jprovazn-issueref.yml b/changelogs/unreleased/jprovazn-issueref.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ee19cac7b19559f998966e886725acf15fd73577
--- /dev/null
+++ b/changelogs/unreleased/jprovazn-issueref.yml
@@ -0,0 +1,6 @@
+---
+title: Display state indicator for issuable references in non-project scope (e.g.
+  when referencing issuables from group scope).
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/jramsay-38830-tarball.yml b/changelogs/unreleased/jramsay-38830-tarball.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6d40c30561433c491c32a706b3b679147b6597e1
--- /dev/null
+++ b/changelogs/unreleased/jramsay-38830-tarball.yml
@@ -0,0 +1,5 @@
+---
+title: Add alternate archive route for simplified packaging
+merge_request: 17225
+author:
+type: added
diff --git a/changelogs/unreleased/label-links-on-project-transfer.yml b/changelogs/unreleased/label-links-on-project-transfer.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fabedb77cb3507bbdecd1de660bd09137430f78e
--- /dev/null
+++ b/changelogs/unreleased/label-links-on-project-transfer.yml
@@ -0,0 +1,5 @@
+---
+title: Fix label links update on project transfer
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/merge-request-widget-source-branch-improvements.yml b/changelogs/unreleased/merge-request-widget-source-branch-improvements.yml
new file mode 100644
index 0000000000000000000000000000000000000000..942eb6062fd00d450b54bcbe1ec36e9d43e763e8
--- /dev/null
+++ b/changelogs/unreleased/merge-request-widget-source-branch-improvements.yml
@@ -0,0 +1,6 @@
+---
+title: Fixes remove source branch checkbox being visible when user cannot remove the
+  branch
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/merge-requests-api-filter-by-branch.yml b/changelogs/unreleased/merge-requests-api-filter-by-branch.yml
deleted file mode 100644
index 03a7e4d0f715eee0e43c306c8243b6e0beafaaad..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/merge-requests-api-filter-by-branch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for filtering by source and target branch to merge requests API
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/minimal-fix-for-artifacts-service.yml b/changelogs/unreleased/minimal-fix-for-artifacts-service.yml
deleted file mode 100644
index 11f5bc17759247f4c5b6b83c8a5adca6ed020341..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/minimal-fix-for-artifacts-service.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent trace artifact migration to incur data loss
-merge_request: 17313
-author:
-type: fixed
diff --git a/changelogs/unreleased/mk-fix-error-code-for-repo-does-not-exist.yml b/changelogs/unreleased/mk-fix-error-code-for-repo-does-not-exist.yml
deleted file mode 100644
index a761d610da12b86feb436f9d79744daac3ed847d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/mk-fix-error-code-for-repo-does-not-exist.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Return a 404 instead of 403 if the repository does not exist on disk
-merge_request: 17341
-author:
-type: fixed
diff --git a/changelogs/unreleased/move-board-blank-state-vue-component.yml b/changelogs/unreleased/move-board-blank-state-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0a278a8c0093455d4822d75e5ef55121099ecaeb
--- /dev/null
+++ b/changelogs/unreleased/move-board-blank-state-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move BoardBlankState vue component
+merge_request: 17666
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/move-email-footer-info-to-single-line.yml b/changelogs/unreleased/move-email-footer-info-to-single-line.yml
new file mode 100644
index 0000000000000000000000000000000000000000..87ed5638056e3c6ce38d5b25f1f5845d8259b2b6
--- /dev/null
+++ b/changelogs/unreleased/move-email-footer-info-to-single-line.yml
@@ -0,0 +1,5 @@
+---
+title: Move email footer info to a single line
+merge_request: 17916
+author:
+type: changed
diff --git a/changelogs/unreleased/move-estimate-only-pane-vue-component.yml b/changelogs/unreleased/move-estimate-only-pane-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b6c538f70b3577ff1856c3a7e1303dfc36bf58be
--- /dev/null
+++ b/changelogs/unreleased/move-estimate-only-pane-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move TimeTrackingEstimateOnlyPane vue component
+merge_request: 18318
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/move-help-state-vue-component.yml b/changelogs/unreleased/move-help-state-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6108368cde02adcd532f792943644bb64a8c606a
--- /dev/null
+++ b/changelogs/unreleased/move-help-state-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move TimeTrackingHelpState vue component
+merge_request: 18319
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/move-pipeline-failed-vue-component.yml b/changelogs/unreleased/move-pipeline-failed-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..38d421348761617ecdb43b5f9b645b29083f3588
--- /dev/null
+++ b/changelogs/unreleased/move-pipeline-failed-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move PipelineFailed vue component
+merge_request: 18277
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/move-registry-after-cicd-project-nav-sidebar.yml b/changelogs/unreleased/move-registry-after-cicd-project-nav-sidebar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..03a6fd42228435991d400b378fc048ceac483ab8
--- /dev/null
+++ b/changelogs/unreleased/move-registry-after-cicd-project-nav-sidebar.yml
@@ -0,0 +1,5 @@
+---
+  title: Move 'Registry' after 'CI/CD' in project navigation sidebar
+  merge_request: 18018
+  author: Elias Werberich
+  type: changed
diff --git a/changelogs/unreleased/optional-api-delimiter.yml b/changelogs/unreleased/optional-api-delimiter.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0bcd07873069757af04ed60366c48c0c41603d01
--- /dev/null
+++ b/changelogs/unreleased/optional-api-delimiter.yml
@@ -0,0 +1,5 @@
+---
+title: Make /-/ delimiter optional for search endpoints
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/osw-41401-render-mr-commit-sha-instead-diffs.yml b/changelogs/unreleased/osw-41401-render-mr-commit-sha-instead-diffs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4497364132521d31cb84c46f7e4425509af7f75b
--- /dev/null
+++ b/changelogs/unreleased/osw-41401-render-mr-commit-sha-instead-diffs.yml
@@ -0,0 +1,5 @@
+---
+title: Render MR commit SHA instead "diffs" when viable
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/osw-44295-adjust-authorization-for-discussions-show.yml b/changelogs/unreleased/osw-44295-adjust-authorization-for-discussions-show.yml
new file mode 100644
index 0000000000000000000000000000000000000000..978c5468bb16bf69d72e3be2a744b9acad6f5f9d
--- /dev/null
+++ b/changelogs/unreleased/osw-44295-adjust-authorization-for-discussions-show.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust 404's for LegacyDiffNote discussion rendering
+merge_request: 18201
+author:
+type: fixed
diff --git a/changelogs/unreleased/pages_force_https.yml b/changelogs/unreleased/pages_force_https.yml
new file mode 100644
index 0000000000000000000000000000000000000000..da7e29087f3a639fe02d9b343a57da19801a7d50
--- /dev/null
+++ b/changelogs/unreleased/pages_force_https.yml
@@ -0,0 +1,5 @@
+---
+title: Add HTTPS-only pages
+merge_request: 16273
+author: rfwatson
+type: added
diff --git a/changelogs/unreleased/poc-upload-hashing-path.yml b/changelogs/unreleased/poc-upload-hashing-path.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7970405bea1defa1184ae96b292ee0da915a7037
--- /dev/null
+++ b/changelogs/unreleased/poc-upload-hashing-path.yml
@@ -0,0 +1,5 @@
+---
+title: File uploads in remote storage now support project renaming.
+merge_request: 4597
+author:
+type: fixed
diff --git a/changelogs/unreleased/reduce-query-count-for-mergerequestscontroller-show.yml b/changelogs/unreleased/reduce-query-count-for-mergerequestscontroller-show.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1f793fe5e7c04380bf157fda92635c26a9575548
--- /dev/null
+++ b/changelogs/unreleased/reduce-query-count-for-mergerequestscontroller-show.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce number of queries when viewing a merge request
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/refactor-move-assignee-title-vue-component.yml b/changelogs/unreleased/refactor-move-assignee-title-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f6521339c3922b35429f363a94bf9765e22cd687
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-assignee-title-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move AssigneeTitle vue component
+merge_request: 17397
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-assignees-vue-component.yml b/changelogs/unreleased/refactor-move-assignees-vue-component.yml
deleted file mode 100644
index 98cfa6b4c8138a0537dd10980f4a21a7df412242..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/refactor-move-assignees-vue-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move Assignees vue component
-merge_request: 16952
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/refactor-move-board-new-issue-vue-component.yml b/changelogs/unreleased/refactor-move-board-new-issue-vue-component.yml
deleted file mode 100644
index 20d055305136e5c9f1e66adc2e752ba1318b09d8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/refactor-move-board-new-issue-vue-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move BoardNewIssue vue component
-merge_request: 16947
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/refactor-move-filtered-search-vue-component.yml b/changelogs/unreleased/refactor-move-filtered-search-vue-component.yml
deleted file mode 100644
index d65318d7ba1a54e2db02dea06136a576fd0d12c4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/refactor-move-filtered-search-vue-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move RecentSearchesDropdownContent vue component
-merge_request: 16951
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/refactor-move-issuable-time-tracker-vue-component.yml b/changelogs/unreleased/refactor-move-issuable-time-tracker-vue-component.yml
deleted file mode 100644
index 5ed06c6181766a084ae9e96850a347157da17e44..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/refactor-move-issuable-time-tracker-vue-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move IssuableTimeTracker vue component
-merge_request: 16948
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-memory-usage-and-graph-components.yml b/changelogs/unreleased/refactor-move-mr-widget-memory-usage-and-graph-components.yml
new file mode 100644
index 0000000000000000000000000000000000000000..96e633439635d522c1f1339954812f1ac21dd169
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-memory-usage-and-graph-components.yml
@@ -0,0 +1,5 @@
+---
+title: Move MemoryGraph and MemoryUsage vue components
+merge_request: 17533
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dc8ff95dc27df197ae7ee55c36cd57971a95a329
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move NothingToMerge vue component
+merge_request: 17544
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-ready-to-merge-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-ready-to-merge-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..90192fae0303e891e64ec069a5863ca200c6d4af
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-ready-to-merge-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move ReadyToMerge vue component
+merge_request: 17545
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-sha-mismatch-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-sha-mismatch-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ac41fe23d3d473b22c54dbe5d458e5b2ff74f7eb
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-sha-mismatch-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move ShaMismatch vue component
+merge_request: 17546
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-unresolved-discussions-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-unresolved-discussions-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a31f1f372a8347df671f84b2453a6c1c1cd5937f
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-unresolved-discussions-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move UnresolvedDiscussions vue component
+merge_request: 17538
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-time-tracking-comparison-pane-vue-component.yml b/changelogs/unreleased/refactor-move-time-tracking-comparison-pane-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..88a4b8ec8c1203e3a0506ac79e11ae28484f055d
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-time-tracking-comparison-pane-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move TimeTrackingComparisonPane vue component
+merge_request: 17931
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-time-tracking-vue-components.yml b/changelogs/unreleased/refactor-move-time-tracking-vue-components.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8151655250a75167749a0435c83f3d6a101783fa
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-time-tracking-vue-components.yml
@@ -0,0 +1,5 @@
+---
+title: Move TimeTrackingCollapsedState vue component
+merge_request: 17399
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/remove-pages-tar-support.yml b/changelogs/unreleased/remove-pages-tar-support.yml
new file mode 100644
index 0000000000000000000000000000000000000000..73448687912d1296433960483122e8ac23874e55
--- /dev/null
+++ b/changelogs/unreleased/remove-pages-tar-support.yml
@@ -0,0 +1,5 @@
+---
+title: Remove support for legacy tar.gz pages artifacts
+merge_request: 18090
+author:
+type: deprecated
diff --git a/changelogs/unreleased/remove-unnecessary-validate-project.yml b/changelogs/unreleased/remove-unnecessary-validate-project.yml
deleted file mode 100644
index ebc8da03dd81f91a01ca4573f167e5db6837df80..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/remove-unnecessary-validate-project.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Remove unecessary validate: true from belongs_to :project'
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/rename-overview-project-sidenav.yml b/changelogs/unreleased/rename-overview-project-sidenav.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3632ef25c00afbfd83205185ea7a02fb0d35d28b
--- /dev/null
+++ b/changelogs/unreleased/rename-overview-project-sidenav.yml
@@ -0,0 +1,5 @@
+---
+title: Renamed Overview to Project in the contextual navigation at a project level
+merge_request: 18295
+author: Constance Okoghenun
+type: changed
diff --git a/changelogs/unreleased/rendering-markdown-multiple-projects.yml b/changelogs/unreleased/rendering-markdown-multiple-projects.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8685772c0892f6dfca4350dccae40e603600d942
--- /dev/null
+++ b/changelogs/unreleased/rendering-markdown-multiple-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Support Markdown rendering using multiple projects
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/revert-project-visibility-changes.yml b/changelogs/unreleased/revert-project-visibility-changes.yml
deleted file mode 100644
index df44fdb79b1370bfaa323b63c6939c2cd5ebad0b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/revert-project-visibility-changes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Revert Project.public_or_visible_to_user changes and only apply to snippets
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-add-cleanup-rpc-gitaly.yml b/changelogs/unreleased/sh-add-cleanup-rpc-gitaly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..81b48fc255bc2bab1c122f2a594e920a65b62db9
--- /dev/null
+++ b/changelogs/unreleased/sh-add-cleanup-rpc-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Automatically cleanup stale worktrees and lock files upon a push
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-appearance-cache-key-version.yml b/changelogs/unreleased/sh-appearance-cache-key-version.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c17a3dc36cd7725848c3232f7882fdacb3f45fe7
--- /dev/null
+++ b/changelogs/unreleased/sh-appearance-cache-key-version.yml
@@ -0,0 +1,5 @@
+---
+title: Use the GitLab version as part of the appearances cache key
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-award-emoji-nplus-one-participants.yml b/changelogs/unreleased/sh-fix-award-emoji-nplus-one-participants.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aee26f9824ad95cb2ac60fdf227cc20aa9eaab6c
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-award-emoji-nplus-one-participants.yml
@@ -0,0 +1,5 @@
+---
+title: Fix N+1 queries when loading participants for a commit note
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-geo-error-500-gpg-commit.yml b/changelogs/unreleased/sh-fix-geo-error-500-gpg-commit.yml
deleted file mode 100644
index 5b4bbe0dc7a532b071179a8768272dd67e824250..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/sh-fix-geo-error-500-gpg-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Error 500 when viewing a commit with a GPG signature in Geo
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-gitlab-sidekiq-logger.yml b/changelogs/unreleased/sh-gitlab-sidekiq-logger.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f68d45d2f38e3bee8ba757c2022226a7eadac06d
--- /dev/null
+++ b/changelogs/unreleased/sh-gitlab-sidekiq-logger.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for Sidekiq JSON logging
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/sh-memoize-repository-empty.yml b/changelogs/unreleased/sh-memoize-repository-empty.yml
new file mode 100644
index 0000000000000000000000000000000000000000..64db3ca0371f85c754c6da577102400f84933a2b
--- /dev/null
+++ b/changelogs/unreleased/sh-memoize-repository-empty.yml
@@ -0,0 +1,5 @@
+---
+title: Memoize Git::Repository#has_visible_content?
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-move-sidekiq-exporter-logs.yml b/changelogs/unreleased/sh-move-sidekiq-exporter-logs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1990f4f61243ed5a2ce537765c2997d456ae135e
--- /dev/null
+++ b/changelogs/unreleased/sh-move-sidekiq-exporter-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Move Sidekiq exporter logs to log/sidekiq_exporter.log
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/tc-re-add-read-only-banner.yml b/changelogs/unreleased/tc-re-add-read-only-banner.yml
new file mode 100644
index 0000000000000000000000000000000000000000..35bcd7e184e8bbcc6cbed8b9e68c0de7d31b59b2
--- /dev/null
+++ b/changelogs/unreleased/tc-re-add-read-only-banner.yml
@@ -0,0 +1,5 @@
+---
+title: Add read-only banner to all pages
+merge_request: 17798
+author:
+type: fixed
diff --git a/changelogs/unreleased/ui-mr-counter-cache.yml b/changelogs/unreleased/ui-mr-counter-cache.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5e241bfe93f7985dee2b9f82fa7d9111de5212cc
--- /dev/null
+++ b/changelogs/unreleased/ui-mr-counter-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Deleting a MR you are assigned to should decrements counter
+merge_request: 17951
+author: m b
+type: fixed
diff --git a/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml b/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d99a9c93c0b0199fc183098e48ffa86574b186f6
--- /dev/null
+++ b/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml
@@ -0,0 +1,5 @@
+---
+title: Add i18n and update specs for UnresolvedDiscussions vue component
+merge_request: 17866
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/update-gitlab-ci-yml-services-docs.yml b/changelogs/unreleased/update-gitlab-ci-yml-services-docs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c76495ec959330f621e0aa9392c79e9f1824a289
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-ci-yml-services-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Update CI services documnetation
+merge_request: 17749
+author:
+type: other
diff --git a/changelogs/unreleased/update-spec-import-path-for-vue-mount-component-helper.yml b/changelogs/unreleased/update-spec-import-path-for-vue-mount-component-helper.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9c13bfbaf6f11e21c03c862d869515602e430302
--- /dev/null
+++ b/changelogs/unreleased/update-spec-import-path-for-vue-mount-component-helper.yml
@@ -0,0 +1,5 @@
+---
+title: Update spec import path for vue mount component helper
+merge_request: 17880
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/update-unresolved-discussions-vue-component.yml b/changelogs/unreleased/update-unresolved-discussions-vue-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..246eaaae2bddf77b7073c1a8c0a4498dedad0f09
--- /dev/null
+++ b/changelogs/unreleased/update-unresolved-discussions-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Add i18n and update specs for ShaMismatch vue component
+merge_request: 17870
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/use-chronic-duration-attribute-for-project-build-timeout.yml b/changelogs/unreleased/use-chronic-duration-attribute-for-project-build-timeout.yml
new file mode 100644
index 0000000000000000000000000000000000000000..675d347b64ce4f9890b0a1cd009a4a8205555684
--- /dev/null
+++ b/changelogs/unreleased/use-chronic-duration-attribute-for-project-build-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Use human readable value build_timeout in Project
+merge_request: 17386
+author:
+type: changed
diff --git a/changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml b/changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml
new file mode 100644
index 0000000000000000000000000000000000000000..14114eca2b21d27afa52f6ff56297bed1eace7e9
--- /dev/null
+++ b/changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Projects and groups badges settings UI
+merge_request: 17114
+author:
+type: added
diff --git a/changelogs/unreleased/winh-deprecate-old-modal.yml b/changelogs/unreleased/winh-deprecate-old-modal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4fae1fafbeab8e2ca6d1bed15b4a23659aeb706c
--- /dev/null
+++ b/changelogs/unreleased/winh-deprecate-old-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Rename modal.vue to deprecated_modal.vue
+merge_request: 17438
+author:
+type: other
diff --git a/changelogs/unreleased/winh-dropdown-entry-unlocking.yml b/changelogs/unreleased/winh-dropdown-entry-unlocking.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fc669af1f5784ca70bce9beffac1ca839153a770
--- /dev/null
+++ b/changelogs/unreleased/winh-dropdown-entry-unlocking.yml
@@ -0,0 +1,5 @@
+---
+title: Remove green background from unlock button in admin area
+merge_request: 18288
+author:
+type: changed
diff --git a/changelogs/unreleased/winh-new-modal-component.yml b/changelogs/unreleased/winh-new-modal-component.yml
deleted file mode 100644
index bcc0d489c8887e16b04b608633efbd4c2ed35320..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/winh-new-modal-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add new modal Vue component
-merge_request: 17108
-author:
-type: changed
diff --git a/changelogs/unreleased/workhorse-gitaly-mandatory.yml b/changelogs/unreleased/workhorse-gitaly-mandatory.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77b62302e863b0cc80dae9717d6395445f3ff8c8
--- /dev/null
+++ b/changelogs/unreleased/workhorse-gitaly-mandatory.yml
@@ -0,0 +1,5 @@
+---
+title: Make all workhorse gitaly calls opt-out, take 2
+merge_request: 18043
+author:
+type: other
diff --git a/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml b/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3d11ee588aeedfaeceb6a073f2096436245181cf
--- /dev/null
+++ b/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml
@@ -0,0 +1,5 @@
+---
+title: Detecting branchnames containing a commit uses Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/zj-bump-gitaly.yml b/changelogs/unreleased/zj-bump-gitaly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eb28bed70e4a0dd03907b20783ca9282b2f4ba69
--- /dev/null
+++ b/changelogs/unreleased/zj-bump-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Gitaly to upgrade its charlock_holmes
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/zj-feature-gate-remove-http-api.yml b/changelogs/unreleased/zj-feature-gate-remove-http-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2095f60146c2a39b4ab9048031b2967d95631efb
--- /dev/null
+++ b/changelogs/unreleased/zj-feature-gate-remove-http-api.yml
@@ -0,0 +1,5 @@
+---
+title: Allow feature gates to be removed through the API
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/zj-find-license-opt-out.yml b/changelogs/unreleased/zj-find-license-opt-out.yml
new file mode 100644
index 0000000000000000000000000000000000000000..be2656601a96a345ef8a894d6dc5ffdd23b6b551
--- /dev/null
+++ b/changelogs/unreleased/zj-find-license-opt-out.yml
@@ -0,0 +1,5 @@
+---
+title: Detect repository license on Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/zj-gitaly-encoding-issue.yml b/changelogs/unreleased/zj-gitaly-encoding-issue.yml
deleted file mode 100644
index 073d8f38e4bd8fff321e0ad10160a3d303c19480..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-gitaly-encoding-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Encode branch name as binary before creating a RPC request to copy attributes
-merge_request: 17291
-author:
-type: fixed
diff --git a/changelogs/unreleased/zj-opt-out-delete-refs.yml b/changelogs/unreleased/zj-opt-out-delete-refs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b02a45eee17a88f4fb979a7b8cda61ac9e5796a2
--- /dev/null
+++ b/changelogs/unreleased/zj-opt-out-delete-refs.yml
@@ -0,0 +1,5 @@
+---
+title: Bulk deleting refs is handled by Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/zj-opt-out-list-commits-by-oid.yml b/changelogs/unreleased/zj-opt-out-list-commits-by-oid.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3871293ee04183f1c62ad9182f6aedfa2f20f297
--- /dev/null
+++ b/changelogs/unreleased/zj-opt-out-list-commits-by-oid.yml
@@ -0,0 +1,5 @@
+---
+title: ListCommitsByOid is executed by Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/zj-ref-exists-opt-out.yml b/changelogs/unreleased/zj-ref-exists-opt-out.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cdffecb0d0ae97a15dba045396df05b6dff89c58
--- /dev/null
+++ b/changelogs/unreleased/zj-ref-exists-opt-out.yml
@@ -0,0 +1,5 @@
+---
+title: Check if a ref exists is done by Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/zj-remote-repo-exists.yml b/changelogs/unreleased/zj-remote-repo-exists.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f024b83159bba50ba16d1cf6d8bc29fb937ff2fa
--- /dev/null
+++ b/changelogs/unreleased/zj-remote-repo-exists.yml
@@ -0,0 +1,5 @@
+---
+title: Test if remote repository exists when importing wikis
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml b/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4774c7811d10216167c60d18d736e20793766f2f
--- /dev/null
+++ b/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml
@@ -0,0 +1,5 @@
+---
+title: Detecting tags containing a commit uses Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/zj-version-string-grouping-ci.yml b/changelogs/unreleased/zj-version-string-grouping-ci.yml
deleted file mode 100644
index 04ef0f65b1e91fdcdabb59a54dbee56dc98d9c7e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-version-string-grouping-ci.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow CI/CD Jobs being grouped on version strings
-merge_request:
-author:
-type: added
diff --git a/config.ru b/config.ru
index de0400f4f67b2a39804f69188f184cf4bbd81adb..405d01863ac4b668f8870e6bd0cabccb4bcc6d95 100644
--- a/config.ru
+++ b/config.ru
@@ -7,8 +7,8 @@ if defined?(Unicorn)
     # Unicorn self-process killer
     require 'unicorn/worker_killer'
 
-    min = (ENV['GITLAB_UNICORN_MEMORY_MIN'] || 300 * 1 << 20).to_i
-    max = (ENV['GITLAB_UNICORN_MEMORY_MAX'] || 350 * 1 << 20).to_i
+    min = (ENV['GITLAB_UNICORN_MEMORY_MIN'] || 400 * 1 << 20).to_i
+    max = (ENV['GITLAB_UNICORN_MEMORY_MAX'] || 650 * 1 << 20).to_i
 
     # Max memory size (RSS) per worker
     use Unicorn::WorkerKiller::Oom, min, max
@@ -23,5 +23,6 @@ warmup do |app|
 end
 
 map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do
+  use Gitlab::Middleware::ReleaseEnv
   run Gitlab::Application
 end
diff --git a/config/application.rb b/config/application.rb
index 918bd4d57cf167a3f8b3f0efe0f7287aeb690d48..ad7338763f720d1dbb80c0228826726648ade2d4 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -26,6 +26,7 @@ module Gitlab
     # This is a nice reference article on autoloading/eager loading:
     # http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
     config.eager_load_paths.push(*%W[#{config.root}/lib
+                                     #{config.root}/app/models/badges
                                      #{config.root}/app/models/hooks
                                      #{config.root}/app/models/members
                                      #{config.root}/app/models/project_services
@@ -69,7 +70,6 @@ module Gitlab
     # - Webhook URLs (:hook)
     # - Sentry DSN (:sentry_dsn)
     # - Deploy keys (:key)
-    # - Secret variable values (:value)
     config.filter_parameters += [/token$/, /password/, /secret/]
     config.filter_parameters += %i(
       certificate
@@ -81,7 +81,6 @@ module Gitlab
       sentry_dsn
       trace
       variables
-      value
     )
 
     # Enable escaping HTML in JSON.
@@ -114,8 +113,15 @@ module Gitlab
     config.assets.precompile << "performance_bar.css"
     config.assets.precompile << "lib/ace.js"
     config.assets.precompile << "test.css"
+    config.assets.precompile << "snippets.css"
     config.assets.precompile << "locale/**/app.js"
 
+    # Import gitlab-svgs directly from vendored directory
+    config.assets.paths << "#{config.root}/node_modules/@gitlab-org/gitlab-svgs/dist"
+    config.assets.precompile << "icons.svg"
+    config.assets.precompile << "icons.json"
+    config.assets.precompile << "illustrations/*.svg"
+
     # Version of your assets, change this if you want to expire all your assets
     config.assets.version = '1.0'
 
@@ -165,7 +171,7 @@ module Gitlab
     ENV['GIT_TERMINAL_PROMPT'] = '0'
 
     # Gitlab Read-only middleware support
-    config.middleware.insert_after ActionDispatch::Flash, 'Gitlab::Middleware::ReadOnly'
+    config.middleware.insert_after ActionDispatch::Flash, '::Gitlab::Middleware::ReadOnly'
 
     config.generators do |g|
       g.factory_bot false
@@ -193,4 +199,10 @@ module Gitlab
       Gitlab::Routing.add_helpers(MilestonesRoutingHelper)
     end
   end
+
+  # This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0.
+  # https://gitlab.com/gitlab-org/gitlab-ce/issues/14286
+  def self.rails5?
+    ENV["RAILS5"].in?(%w[1 true])
+  end
 end
diff --git a/config/boot.rb b/config/boot.rb
index f2830ae3166dc7fc2849feff72258dccca1e5f97..84f390f3228cfdf3639db352842ea9590fada7c1 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -1,6 +1,11 @@
-require 'rubygems'
+def rails5?
+  %w[1 true].include?(ENV["RAILS5"])
+end
 
-# Set up gems listed in the Gemfile.
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+require 'rubygems' unless rails5?
+
+gemfile = rails5? ? "Gemfile.rails5" : "Gemfile"
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__)
 
+# Set up gems listed in the Gemfile.
 require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
diff --git a/config/environment.rb b/config/environment.rb
index df3006d349c2ce2456c6225cb0fcdf1f621c4390..487a4564b4774823e62dbede689863e5c1092dd1 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -1,5 +1,11 @@
 # Load the rails application
-require File.expand_path('../application', __FILE__)
+
+# Remove this condition when upgraded to rails 5.0.
+if %w[1 true].include?(ENV["RAILS5"])
+  require_relative 'application'
+else
+  require File.expand_path('../application', __FILE__)
+end
 
 # Initialize the rails application
 Rails.application.initialize!
diff --git a/config/environments/production.rb b/config/environments/production.rb
index c5cbfcf64cfd3ebfcda210e7b4bcc65956cc7c11..9941987929ca4103dac86a9be3ae9b5d6c6187e1 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -9,7 +9,11 @@ Rails.application.configure do
   config.action_controller.perform_caching = true
 
   # Disable Rails's static asset server (Apache or nginx will already do this)
-  config.serve_static_files = false
+  if Gitlab.rails5?
+    config.public_file_server.enabled = false
+  else
+    config.serve_static_files = false
+  end
 
   # Compress JavaScripts and CSS.
   config.assets.js_compressor = :uglifier
diff --git a/config/environments/test.rb b/config/environments/test.rb
index d09e51e766aa6c6d033398457be97ce142cf1b3a..1849c984351ef5d71f620e154180733e2ec65eff 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -18,7 +18,13 @@ Rails.application.configure do
 
   # Configure static asset server for tests with Cache-Control for performance
   config.assets.compile = false if ENV['CI']
-  config.serve_static_files = true
+
+  if Gitlab.rails5?
+    config.public_file_server.enabled = true
+  else
+    config.serve_static_files = true
+  end
+
   config.static_cache_control = "public, max-age=3600"
 
   # Show full error reports and disable caching
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index bd696a7f2c5f8c5ef279a5ff86c217ba1218de2c..8c39a1f2aa927810f848259523d25d5223656bee 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -145,18 +145,57 @@ production: &base
     enabled: true
     # The location where build artifacts are stored (default: shared/artifacts).
     # path: shared/artifacts
+    # object_store:
+    #   enabled: false
+    #   remote_directory: artifacts # The bucket name
+    #   background_upload: false # Temporary option to limit automatic upload (Default: true)
+    #   proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
+    #   connection:
+    #     provider: AWS # Only AWS supported at the moment
+    #     aws_access_key_id: AWS_ACCESS_KEY_ID
+    #     aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+    #     region: us-east-1
 
   ## Git LFS
   lfs:
     enabled: true
     # The location where LFS objects are stored (default: shared/lfs-objects).
     # storage_path: shared/lfs-objects
+    object_store:
+      enabled: false
+      remote_directory: lfs-objects # Bucket name
+      # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
+      # background_upload: false # Temporary option to limit automatic upload (Default: true)
+      # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
+      connection:
+        provider: AWS
+        aws_access_key_id: AWS_ACCESS_KEY_ID
+        aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+        region: us-east-1
+        # Use the following options to configure an AWS compatible host
+        # host: 'localhost' # default: s3.amazonaws.com
+        # endpoint: 'http://127.0.0.1:9000' # default: nil
+        # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
 
   ## Uploads (attachments, avatars, etc...)
   uploads:
     # The location where uploads objects are stored (default: public/).
     # storage_path: public/
     # base_dir: uploads/-/system
+    object_store:
+      enabled: false
+      # remote_directory: uploads # Bucket name
+      # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
+      # background_upload: false # Temporary option to limit automatic upload (Default: true)
+      # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
+    connection:
+      provider: AWS
+      aws_access_key_id: AWS_ACCESS_KEY_ID
+      aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+      region: us-east-1
+      # host: 'localhost' # default: s3.amazonaws.com
+      # endpoint: 'http://127.0.0.1:9000' # default: nil
+      # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
 
   ## GitLab Pages
   pages:
@@ -189,6 +228,10 @@ production: &base
     # plain_url: "http://..."     # default: https://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
     # ssl_url:   "https://..."    # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
 
+  ## Sidekiq
+  sidekiq:
+    log_format: default # (json is also supported)
+
   ## Auxiliary jobs
   # Periodically executed jobs, to self-heal GitLab, do external synchronizations, etc.
   # Please read here for more information: https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
@@ -481,7 +524,17 @@ production: &base
       # - { name: 'twitter',
       #     app_id: 'YOUR_APP_ID',
       #     app_secret: 'YOUR_APP_SECRET' }
-      #
+      # - { name: 'jwt',
+      #     app_secret: 'YOUR_APP_SECRET',
+      #     args: {
+      #             algorithm: 'HS256',
+      #             uid_claim: 'email',
+      #             required_claims: ["name", "email"],
+      #             info_map: { name: "name", email: "email" },
+      #             auth_url: 'https://example.com/',
+      #             valid_within: nil,
+      #           }
+      #   }
       # - { name: 'saml',
       #     label: 'Our SAML Provider',
       #     groups_attribute: 'Groups',
@@ -655,10 +708,39 @@ test:
     enabled: true
   lfs:
     enabled: false
+    # The location where LFS objects are stored (default: shared/lfs-objects).
+    # storage_path: shared/lfs-objects
+    object_store:
+      enabled: false
+      remote_directory: lfs-objects # The bucket name
+      connection:
+        provider: AWS # Only AWS supported at the moment
+        aws_access_key_id: AWS_ACCESS_KEY_ID
+        aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+        region: us-east-1
   artifacts:
     path: tmp/tests/artifacts
+    enabled: true
+    # The location where build artifacts are stored (default: shared/artifacts).
+    # path: shared/artifacts
+    object_store:
+      enabled: false
+      remote_directory: artifacts # The bucket name
+      background_upload: false
+      connection:
+        provider: AWS # Only AWS supported at the moment
+        aws_access_key_id: AWS_ACCESS_KEY_ID
+        aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+        region: us-east-1
   uploads:
     storage_path: tmp/tests/public
+    object_store:
+      enabled: false
+      connection:
+        provider: AWS # Only AWS supported at the moment
+        aws_access_key_id: AWS_ACCESS_KEY_ID
+        aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+        region: us-east-1
   gitlab:
     host: localhost
     port: 80
@@ -733,6 +815,17 @@ test:
       - { name: 'twitter',
           app_id: 'YOUR_APP_ID',
           app_secret: 'YOUR_APP_SECRET' }
+      - { name: 'jwt',
+          app_secret: 'YOUR_APP_SECRET',
+          args: {
+                  algorithm: 'HS256',
+                  uid_claim: 'email',
+                  required_claims: ["name", "email"],
+                  info_map: { name: "name", email: "email" },
+                  auth_url: 'https://example.com/',
+                  valid_within: nil,
+                }
+        }
       - { name: 'auth0',
           args: {
             client_id: 'YOUR_AUTH0_CLIENT_ID',
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index ea0dee7af5316d4baff47c7f63b0663996c372e4..dc7999ac556d0a2b899576a910d5b02068e37c5d 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -305,6 +305,14 @@ Settings.artifacts['storage_path'] = Settings.absolute(Settings.artifacts.values
 # Settings.artifact['path'] is deprecated, use `storage_path` instead
 Settings.artifacts['path']         = Settings.artifacts['storage_path']
 Settings.artifacts['max_size'] ||= 100 # in megabytes
+Settings.artifacts['object_store'] ||= Settingslogic.new({})
+Settings.artifacts['object_store']['enabled'] = false if Settings.artifacts['object_store']['enabled'].nil?
+Settings.artifacts['object_store']['remote_directory'] ||= nil
+Settings.artifacts['object_store']['direct_upload'] = false if Settings.artifacts['object_store']['direct_upload'].nil?
+Settings.artifacts['object_store']['background_upload'] = true if Settings.artifacts['object_store']['background_upload'].nil?
+Settings.artifacts['object_store']['proxy_download'] = false if Settings.artifacts['object_store']['proxy_download'].nil?
+# Convert upload connection settings to use string keys, to make Fog happy
+Settings.artifacts['object_store']['connection']&.deep_stringify_keys!
 
 #
 # Registry
@@ -340,6 +348,14 @@ Settings.pages['artifacts_server']  ||= Settings.pages['enabled'] if Settings.pa
 Settings['lfs'] ||= Settingslogic.new({})
 Settings.lfs['enabled']      = true if Settings.lfs['enabled'].nil?
 Settings.lfs['storage_path'] = Settings.absolute(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"))
+Settings.lfs['object_store'] ||= Settingslogic.new({})
+Settings.lfs['object_store']['enabled'] = false if Settings.lfs['object_store']['enabled'].nil?
+Settings.lfs['object_store']['remote_directory'] ||= nil
+Settings.lfs['object_store']['direct_upload'] = false if Settings.lfs['object_store']['direct_upload'].nil?
+Settings.lfs['object_store']['background_upload'] = true if Settings.lfs['object_store']['background_upload'].nil?
+Settings.lfs['object_store']['proxy_download'] = false if Settings.lfs['object_store']['proxy_download'].nil?
+# Convert upload connection settings to use string keys, to make Fog happy
+Settings.lfs['object_store']['connection']&.deep_stringify_keys!
 
 #
 # Uploads
@@ -347,6 +363,14 @@ Settings.lfs['storage_path'] = Settings.absolute(Settings.lfs['storage_path'] ||
 Settings['uploads'] ||= Settingslogic.new({})
 Settings.uploads['storage_path'] = Settings.absolute(Settings.uploads['storage_path'] || 'public')
 Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system'
+Settings.uploads['object_store'] ||= Settingslogic.new({})
+Settings.uploads['object_store']['enabled'] = false if Settings.uploads['object_store']['enabled'].nil?
+Settings.uploads['object_store']['remote_directory'] ||= 'uploads'
+Settings.uploads['object_store']['direct_upload'] = false if Settings.uploads['object_store']['direct_upload'].nil?
+Settings.uploads['object_store']['background_upload'] = true if Settings.uploads['object_store']['background_upload'].nil?
+Settings.uploads['object_store']['proxy_download'] = false if Settings.uploads['object_store']['proxy_download'].nil?
+# Convert upload connection settings to use string keys, to make Fog happy
+Settings.uploads['object_store']['connection']&.deep_stringify_keys!
 
 #
 # Mattermost
@@ -431,6 +455,16 @@ Settings.cron_jobs['pages_domain_verification_cron_worker'] ||= Settingslogic.ne
 Settings.cron_jobs['pages_domain_verification_cron_worker']['cron'] ||= '*/15 * * * *'
 Settings.cron_jobs['pages_domain_verification_cron_worker']['job_class'] = 'PagesDomainVerificationCronWorker'
 
+Settings.cron_jobs['issue_due_scheduler_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['issue_due_scheduler_worker']['cron'] ||= '50 00 * * *'
+Settings.cron_jobs['issue_due_scheduler_worker']['job_class'] = 'IssueDueSchedulerWorker'
+
+#
+# Sidekiq
+#
+Settings['sidekiq'] ||= Settingslogic.new({})
+Settings['sidekiq']['log_format'] ||= 'default'
+
 #
 # GitLab Shell
 #
@@ -467,12 +501,7 @@ unless Settings.repositories.storages['default']
 end
 
 Settings.repositories.storages.each do |key, storage|
-  storage = Settingslogic.new(storage)
-
-  # Expand relative paths
-  storage['path'] = Settings.absolute(storage['path'])
-
-  Settings.repositories.storages[key] = storage
+  Settings.repositories.storages[key] = Gitlab::GitalyClient::StorageSettings.new(storage)
 end
 
 #
@@ -486,7 +515,7 @@ repositories_storages          = Settings.repositories.storages.values
 repository_downloads_path      = Settings.gitlab['repository_downloads_path'].to_s.gsub(%r{/$}, '')
 repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
 
-if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(%r{/$}, '')) }
+if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs.legacy_disk_path.gsub(%r{/$}, '')) }
   Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')
 end
 
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index f8e67ce04c9558a7126685fdff58ed7bbcdeb4c4..d92cdb97766b2de38051491deee5a980687c4580 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -5,7 +5,7 @@ end
 def find_parent_path(name, path)
   parent = Pathname.new(path).realpath.parent
   Gitlab.config.repositories.storages.detect do |n, rs|
-    name != n && Pathname.new(rs['path']).realpath == parent
+    name != n && Pathname.new(rs.legacy_disk_path).realpath == parent
   end
 rescue Errno::EIO, Errno::ENOENT => e
   warning = "WARNING: couldn't verify #{path} (#{name}). "\
@@ -33,7 +33,7 @@ def validate_storages_config
         "If you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.\n"
     end
 
-    if !repository_storage.is_a?(Hash) || repository_storage['path'].nil?
+    if !repository_storage.is_a?(Gitlab::GitalyClient::StorageSettings) || repository_storage.legacy_disk_path.nil?
       storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
     end
 
@@ -50,7 +50,7 @@ end
 
 def validate_storages_paths
   Gitlab.config.repositories.storages.each do |name, repository_storage|
-    parent_name, _parent_path = find_parent_path(name, repository_storage['path'])
+    parent_name, _parent_path = find_parent_path(name, repository_storage.legacy_disk_path)
     if parent_name
       storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
     end
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index 45b39b2a38db19e89e2f2f00a7c49d25b875195b..7cdf49159b4d702557238fd715aa86a69c9a0663 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -94,6 +94,7 @@ def instrument_classes(instrumentation)
 
   instrumentation.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker)
 
+  instrumentation.instrument_instance_methods(Rouge::Plugins::CommonMark)
   instrumentation.instrument_instance_methods(Rouge::Plugins::Redcarpet)
   instrumentation.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
 
diff --git a/config/initializers/active_record_array_type_casting.rb b/config/initializers/active_record_array_type_casting.rb
index d94d592add62c05a0c0d8814d72617a90fe47ff7..a149e048ee2e77726d190443b843c204fe820f2e 100644
--- a/config/initializers/active_record_array_type_casting.rb
+++ b/config/initializers/active_record_array_type_casting.rb
@@ -1,20 +1,23 @@
-module ActiveRecord
-  class PredicateBuilder
-    class ArrayHandler
-      module TypeCasting
-        def call(attribute, value)
-          # This is necessary because by default ActiveRecord does not respect
-          # custom type definitions (like our `ShaAttribute`) when providing an
-          # array in `where`, like in `where(commit_sha: [sha1, sha2, sha3])`.
-          model = attribute.relation&.engine
-          type = model.user_provided_columns[attribute.name] if model
-          value = value.map { |value| type.type_cast_for_database(value) } if type
+# Remove this initializer when upgraded to Rails 5.0
+unless Gitlab.rails5?
+  module ActiveRecord
+    class PredicateBuilder
+      class ArrayHandler
+        module TypeCasting
+          def call(attribute, value)
+            # This is necessary because by default ActiveRecord does not respect
+            # custom type definitions (like our `ShaAttribute`) when providing an
+            # array in `where`, like in `where(commit_sha: [sha1, sha2, sha3])`.
+            model = attribute.relation&.engine
+            type = model.user_provided_columns[attribute.name] if model
+            value = value.map { |value| type.type_cast_for_database(value) } if type
 
-          super(attribute, value)
+            super(attribute, value)
+          end
         end
-      end
 
-      prepend TypeCasting
+        prepend TypeCasting
+      end
     end
   end
 end
diff --git a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d9418caf68bdaa1fcce540ec4368a7441557f70a
--- /dev/null
+++ b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
@@ -0,0 +1,98 @@
+# This is a monkey patch which must be removed when migrating to Rails 5.1 from 5.0.
+#
+# In Rails 5.0 there was introduced a bug which casts types in the uniqueness validator.
+# https://github.com/rails/rails/pull/23523/commits/811a4fa8eb6ceea841e61e8ac05747ffb69595ae
+#
+# That causes to bugs like this:
+#
+#     1) API::Users POST /user/:id/gpg_keys/:key_id/revoke when authenticated revokes existing key
+#     Failure/Error: let(:gpg_key) { create(:gpg_key, user: user) }
+#
+#     TypeError:
+#       can't cast Hash
+#     # ./spec/requests/api/users_spec.rb:7:in `block (2 levels) in <top (required)>'
+#     # ./spec/requests/api/users_spec.rb:908:in `block (4 levels) in <top (required)>'
+#     # ------------------
+#     # --- Caused by: ---
+#     # TypeError:
+#     #   TypeError
+#     #   ./spec/requests/api/users_spec.rb:7:in `block (2 levels) in <top (required)>'
+#
+# This bug was fixed in Rails 5.1 by https://github.com/rails/rails/pull/24745/commits/aa062318c451512035c10898a1af95943b1a3803
+
+if Gitlab.rails5?
+  ActiveSupport::Deprecation.warn("#{__FILE__} is a monkey patch which must be removed when upgrading to Rails 5.1")
+
+  if Rails.version.start_with?("5.1")
+    raise "Remove this monkey patch: #{__FILE__}"
+  end
+
+  # Copy-paste from https://github.com/kamipo/rails/blob/aa062318c451512035c10898a1af95943b1a3803/activerecord/lib/active_record/validations/uniqueness.rb
+  # including local fixes to make Rubocop happy again.
+  module ActiveRecord
+    module Validations
+      class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
+        def validate_each(record, attribute, value)
+          finder_class = find_finder_class_for(record)
+          table = finder_class.arel_table
+          value = map_enum_attribute(finder_class, attribute, value)
+
+          relation = build_relation(finder_class, table, attribute, value)
+
+          if record.persisted?
+            if finder_class.primary_key
+              relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
+            else
+              raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
+            end
+          end
+
+          relation = scope_relation(record, table, relation)
+          relation = relation.merge(options[:conditions]) if options[:conditions]
+
+          if relation.exists?
+            error_options = options.except(:case_sensitive, :scope, :conditions)
+            error_options[:value] = value
+
+            record.errors.add(attribute, :taken, error_options)
+          end
+        rescue RangeError
+        end
+
+        protected
+
+        def build_relation(klass, table, attribute, value) #:nodoc:
+          if reflection = klass._reflect_on_association(attribute)
+            attribute = reflection.foreign_key
+            value = value.attributes[reflection.klass.primary_key] unless value.nil?
+          end
+
+          # the attribute may be an aliased attribute
+          if klass.attribute_alias?(attribute)
+            attribute = klass.attribute_alias(attribute)
+          end
+
+          attribute_name = attribute.to_s
+
+          column = klass.columns_hash[attribute_name]
+          cast_type = klass.type_for_attribute(attribute_name)
+
+          comparison =
+            if !options[:case_sensitive] && !value.nil?
+              # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
+              klass.connection.case_insensitive_comparison(table, attribute, column, value)
+            else
+              klass.connection.case_sensitive_comparison(table, attribute, column, value)
+            end
+
+          if value.nil?
+            klass.unscoped.where(comparison)
+          else
+            bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
+            klass.unscoped.where(comparison, bind)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/config/initializers/active_record_locking.rb b/config/initializers/active_record_locking.rb
index 150aaa2a8c22e1e2ab0648445c5c8085ab95274a..3e7111fd0631a0ef1ce2f50dab4d20316ef6b291 100644
--- a/config/initializers/active_record_locking.rb
+++ b/config/initializers/active_record_locking.rb
@@ -1,73 +1,77 @@
 # rubocop:disable Lint/RescueException
 
-# This patch fixes https://github.com/rails/rails/issues/26024
-# TODO: Remove it when it's no longer necessary
-
-module ActiveRecord
-  module Locking
-    module Optimistic
-      # We overwrite this method because we don't want to have default value
-      # for newly created records
-      def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
-        super
-      end
+# Remove this entire initializer when we are at rails 5.0.
+# This file fixes the bug (see below) which has been fixed in the upstream.
+unless Gitlab.rails5?
+  # This patch fixes https://github.com/rails/rails/issues/26024
+  # TODO: Remove it when it's no longer necessary
+
+  module ActiveRecord
+    module Locking
+      module Optimistic
+        # We overwrite this method because we don't want to have default value
+        # for newly created records
+        def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
+          super
+        end
 
-      def _update_record(attribute_names = self.attribute_names) #:nodoc:
-        return super unless locking_enabled?
-        return 0 if attribute_names.empty?
+        def _update_record(attribute_names = self.attribute_names) #:nodoc:
+          return super unless locking_enabled?
+          return 0 if attribute_names.empty?
 
-        lock_col = self.class.locking_column
+          lock_col = self.class.locking_column
 
-        previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend
+          previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend
 
-        # This line is added as a patch
-        previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
+          # This line is added as a patch
+          previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
 
-        increment_lock
+          increment_lock
 
-        attribute_names += [lock_col]
-        attribute_names.uniq!
+          attribute_names += [lock_col]
+          attribute_names.uniq!
 
-        begin
-          relation = self.class.unscoped
+          begin
+            relation = self.class.unscoped
 
-          affected_rows = relation.where(
-            self.class.primary_key => id,
-            lock_col => previous_lock_value
-          ).update_all(
-            attributes_for_update(attribute_names).map do |name|
-              [name, _read_attribute(name)]
-            end.to_h
-          )
+            affected_rows = relation.where(
+              self.class.primary_key => id,
+              lock_col => previous_lock_value
+            ).update_all(
+              attributes_for_update(attribute_names).map do |name|
+                [name, _read_attribute(name)]
+              end.to_h
+            )
 
-          unless affected_rows == 1
-            raise ActiveRecord::StaleObjectError.new(self, "update")
-          end
+            unless affected_rows == 1
+              raise ActiveRecord::StaleObjectError.new(self, "update")
+            end
 
-          affected_rows
+            affected_rows
 
-        # If something went wrong, revert the version.
-        rescue Exception
-          send(lock_col + '=', previous_lock_value)  # rubocop:disable GitlabSecurity/PublicSend
-          raise
+          # If something went wrong, revert the version.
+          rescue Exception
+            send(lock_col + '=', previous_lock_value)  # rubocop:disable GitlabSecurity/PublicSend
+            raise
+          end
         end
-      end
 
-      # This is patched because we need it to query `lock_version IS NULL`
-      # rather than `lock_version = 0` whenever lock_version is NULL.
-      def relation_for_destroy
-        return super unless locking_enabled?
+        # This is patched because we need it to query `lock_version IS NULL`
+        # rather than `lock_version = 0` whenever lock_version is NULL.
+        def relation_for_destroy
+          return super unless locking_enabled?
 
-        column_name = self.class.locking_column
-        super.where(self.class.arel_table[column_name].eq(self[column_name]))
+          column_name = self.class.locking_column
+          super.where(self.class.arel_table[column_name].eq(self[column_name]))
+        end
       end
-    end
 
-    # This is patched because we want `lock_version` default to `NULL`
-    # rather than `0`
-    class LockingType < SimpleDelegator
-      def type_cast_from_database(value)
-        super
+      # This is patched because we want `lock_version` default to `NULL`
+      # rather than `0`
+      class LockingType < SimpleDelegator
+        def type_cast_from_database(value)
+          super
+        end
       end
     end
   end
diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a65f8aecf9ea80fcd265ace29701144441fbe911
--- /dev/null
+++ b/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,12 @@
+# Remove this `if` condition when upgraded to rails 5.0.
+# The body must be kept.
+if Gitlab.rails5?
+  # Be sure to restart your server when you modify this file.
+
+  # ActiveSupport::Reloader.to_prepare do
+  #   ApplicationController.renderer.defaults.merge!(
+  #     http_host: 'example.org',
+  #     https: false
+  #   )
+  # end
+end
diff --git a/config/initializers/ar5_batching.rb b/config/initializers/ar5_batching.rb
index 6ebaf8834d260823e3df422f316666fab408c2c2..874455ce5af5d466d3587528788593ad966642d5 100644
--- a/config/initializers/ar5_batching.rb
+++ b/config/initializers/ar5_batching.rb
@@ -1,41 +1,39 @@
-# Port ActiveRecord::Relation#in_batches from ActiveRecord 5.
-# https://github.com/rails/rails/blob/ac027338e4a165273607dccee49a3d38bc836794/activerecord/lib/active_record/relation/batches.rb#L184
-# TODO: this can be removed once we're using AR5.
-raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5
-
-module ActiveRecord
-  module Batches
-    # Differences from upstream: enumerator support was removed, and custom
-    # order/limit clauses are ignored without a warning.
-    def in_batches(of: 1000, start: nil, finish: nil, load: false)
-      raise "Must provide a block" unless block_given?
-
-      relation = self.reorder(batch_order).limit(of)
-      relation = relation.where(arel_table[primary_key].gteq(start)) if start
-      relation = relation.where(arel_table[primary_key].lteq(finish)) if finish
-      batch_relation = relation
-
-      loop do
-        if load
-          records = batch_relation.records
-          ids = records.map(&:id)
-          yielded_relation = self.where(primary_key => ids)
-          yielded_relation.load_records(records)
-        else
-          ids = batch_relation.pluck(primary_key)
-          yielded_relation = self.where(primary_key => ids)
+# Remove this file when upgraded to rails 5.0.
+unless Gitlab.rails5?
+  module ActiveRecord
+    module Batches
+      # Differences from upstream: enumerator support was removed, and custom
+      # order/limit clauses are ignored without a warning.
+      def in_batches(of: 1000, start: nil, finish: nil, load: false)
+        raise "Must provide a block" unless block_given?
+
+        relation = self.reorder(batch_order).limit(of)
+        relation = relation.where(arel_table[primary_key].gteq(start)) if start
+        relation = relation.where(arel_table[primary_key].lteq(finish)) if finish
+        batch_relation = relation
+
+        loop do
+          if load
+            records = batch_relation.records
+            ids = records.map(&:id)
+            yielded_relation = self.where(primary_key => ids)
+            yielded_relation.load_records(records)
+          else
+            ids = batch_relation.pluck(primary_key)
+            yielded_relation = self.where(primary_key => ids)
+          end
+
+          break if ids.empty?
+
+          primary_key_offset = ids.last
+          raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
+
+          yield yielded_relation
+
+          break if ids.length < of
+
+          batch_relation = relation.where(arel_table[primary_key].gt(primary_key_offset))
         end
-
-        break if ids.empty?
-
-        primary_key_offset = ids.last
-        raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
-
-        yield yielded_relation
-
-        break if ids.length < of
-
-        batch_relation = relation.where(arel_table[primary_key].gt(primary_key_offset))
       end
     end
   end
diff --git a/config/initializers/ar5_pg_10_support.rb b/config/initializers/ar5_pg_10_support.rb
index a529c74a8cea3d4b30b078258c711d0337660567..40548290ce8e8c89495eb3ddc90c5f9e2c93f8f6 100644
--- a/config/initializers/ar5_pg_10_support.rb
+++ b/config/initializers/ar5_pg_10_support.rb
@@ -1,6 +1,5 @@
-raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5
-
-if Gitlab::Database.postgresql?
+# Remove this file when upgraded to rails 5.0.
+if !Gitlab.rails5? && Gitlab::Database.postgresql?
   require 'active_record/connection_adapters/postgresql_adapter'
   require 'active_record/connection_adapters/postgresql/schema_statements'
 
diff --git a/config/initializers/ar_native_database_types.rb b/config/initializers/ar_native_database_types.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3522b1db536c423159af59aa9d9784ca1d72dbad
--- /dev/null
+++ b/config/initializers/ar_native_database_types.rb
@@ -0,0 +1,11 @@
+require 'active_record/connection_adapters/abstract_mysql_adapter'
+
+module ActiveRecord
+  module ConnectionAdapters
+    class AbstractMysqlAdapter
+      NATIVE_DATABASE_TYPES.merge!(
+        bigserial: { name: 'bigint(20) auto_increment PRIMARY KEY' }
+      )
+    end
+  end
+end
diff --git a/config/initializers/artifacts_direct_upload_support.rb b/config/initializers/artifacts_direct_upload_support.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d2bc35ea61306bb50dafa5c7b54126beebcbefec
--- /dev/null
+++ b/config/initializers/artifacts_direct_upload_support.rb
@@ -0,0 +1,7 @@
+artifacts_object_store = Gitlab.config.artifacts.object_store
+
+if artifacts_object_store.enabled &&
+    artifacts_object_store.direct_upload &&
+    artifacts_object_store.connection&.provider.to_s != 'Google'
+  raise "Only 'Google' is supported as a object storage provider when 'direct_upload' of artifacts is used"
+end
diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb
index 59385cdf379bd06a8d2326dcd4de6d5cd5d3f5b0..58941aae1b0ff35681c35ad7d6f29e1685f34e36 100644
--- a/config/initializers/backtrace_silencers.rb
+++ b/config/initializers/backtrace_silencers.rb
@@ -1,7 +1,2 @@
-# Be sure to restart your server when you modify this file.
-
-# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
-# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
-
-# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
-# Rails.backtrace_cleaner.remove_silencers!
+Rails.backtrace_cleaner.remove_silencers!
+Rails.backtrace_cleaner.add_silencer { |line| line !~ Gitlab::APP_DIRS_PATTERN }
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index cd7df44351a60177a0e34aad4312b80d5fffa083..5cde6cbb0ff81caeab5c9340a1604d569cb66ebb 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -28,16 +28,4 @@ if File.exist?(aws_file)
     # when fog_public is false and provider is AWS or Google, defaults to 600
     config.fog_authenticated_url_expiration = 1 << 29
   end
-
-  # Mocking Fog requests, based on: https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Test-Fog-based-uploaders
-  if Rails.env.test?
-    Fog.mock!
-    connection = ::Fog::Storage.new(
-      aws_access_key_id: AWS_CONFIG['access_key_id'],
-      aws_secret_access_key: AWS_CONFIG['secret_access_key'],
-      provider: 'AWS',
-      region: AWS_CONFIG['region']
-    )
-    connection.directories.create(key: AWS_CONFIG['bucket'])
-  end
 end
diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f3f47b2ccf0301f42354e604b331c16422f2dad9
--- /dev/null
+++ b/config/initializers/deprecations.rb
@@ -0,0 +1,5 @@
+deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
+
+if Gitlab.com? || Rails.env.development?
+  ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
+end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index f642e6d47e06552c966908df08bfb92d080bce14..362b9cc9a88c5c2dce796d4e2dfd0f8598ca7952 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -219,49 +219,5 @@ Devise.setup do |config|
     end
   end
 
-  Gitlab.config.omniauth.providers.each do |provider|
-    provider_arguments = []
-
-    %w[app_id app_secret].each do |argument|
-      provider_arguments << provider[argument] if provider[argument]
-    end
-
-    case provider['args']
-    when Array
-      # An Array from the configuration will be expanded.
-      provider_arguments.concat provider['args']
-    when Hash
-      # Add procs for handling SLO
-      if provider['name'] == 'cas3'
-        provider['args'][:on_single_sign_out] = lambda do |request|
-          ticket = request.params[:session_index]
-          raise "Service Ticket not found." unless Gitlab::Auth::OAuth::Session.valid?(:cas3, ticket)
-
-          Gitlab::Auth::OAuth::Session.destroy(:cas3, ticket)
-          true
-        end
-      end
-
-      if provider['name'] == 'authentiq'
-        provider['args'][:remote_sign_out_handler] = lambda do |request|
-          authentiq_session = request.params['sid']
-          if Gitlab::Auth::OAuth::Session.valid?(:authentiq, authentiq_session)
-            Gitlab::Auth::OAuth::Session.destroy(:authentiq, authentiq_session)
-            true
-          else
-            false
-          end
-        end
-      end
-
-      if provider['name'] == 'shibboleth'
-        provider['args'][:fail_with_empty_uid] = true
-      end
-
-      # A Hash from the configuration will be passed as is.
-      provider_arguments << provider['args'].symbolize_keys
-    end
-
-    config.omniauth provider['name'].to_sym, *provider_arguments
-  end
+  Gitlab::OmniauthInitializer.new(config).execute(Gitlab.config.omniauth.providers)
 end
diff --git a/config/initializers/fog_google_https_private_urls.rb b/config/initializers/fog_google_https_private_urls.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f92e623a5d26b98acf6ee3e9b0934152d7885a4e
--- /dev/null
+++ b/config/initializers/fog_google_https_private_urls.rb
@@ -0,0 +1,20 @@
+#
+# Monkey patching the https support for private urls
+# See https://gitlab.com/gitlab-org/gitlab-ee/issues/4879
+#
+module Fog
+  module Storage
+    class GoogleXML
+      class File < Fog::Model
+        module MonkeyPatch
+          def url(expires)
+            requires :key
+            collection.get_https_url(key, expires)
+          end
+        end
+
+        prepend MonkeyPatch
+      end
+    end
+  end
+end
diff --git a/config/initializers/forbid_sidekiq_in_transactions.rb b/config/initializers/forbid_sidekiq_in_transactions.rb
index cb611aa21dff840374c15133d4079816862234c2..4603123665df136a7ac3ff62ec4f8f7e31f7a36c 100644
--- a/config/initializers/forbid_sidekiq_in_transactions.rb
+++ b/config/initializers/forbid_sidekiq_in_transactions.rb
@@ -18,13 +18,18 @@ module Sidekiq
         %i(perform_async perform_at perform_in).each do |name|
           define_method(name) do |*args|
             if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
-              raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
+              begin
+                raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
                 `#{self}.#{name}` cannot be called inside a transaction as this can lead to
                 race conditions when the worker runs before the transaction is committed and
                 tries to access a model that has not been saved yet.
 
                 Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
-              MSG
+                MSG
+              rescue Sidekiq::Worker::EnqueueFromTransactionError => e
+                Rails.logger.error(e.message) if Rails.env.production?
+                Gitlab::Sentry.track_exception(e)
+              end
             end
 
             super(*args)
diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb
index 6dfaceb8427fbaecf826ab273c6acefda2a1c283..81e0577a7c9d0efa079732d7ed8296fa831c193c 100644
--- a/config/initializers/gollum.rb
+++ b/config/initializers/gollum.rb
@@ -7,139 +7,6 @@ module Gollum
 end
 require "gollum-lib"
 
-module Gollum
-  class Committer
-    # Patch for UTF-8 path
-    def method_missing(name, *args)
-      index.send(name, *args)
-    end
-  end
-
-  class Wiki
-    def pages(treeish = nil, limit: nil)
-      tree_list((treeish || @ref), limit: limit)
-    end
-
-    def tree_list(ref, limit: nil)
-      if (sha = @access.ref_to_sha(ref))
-        commit = @access.commit(sha)
-        tree_map_for(sha).inject([]) do |list, entry|
-          next list unless @page_class.valid_page_name?(entry.name)
-
-          list << entry.page(self, commit)
-          break list if limit && list.size >= limit
-
-          list
-        end
-      else
-        []
-      end
-    end
-
-    # Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
-    def update_page(page, name, format, data, commit = {})
-      name = name ? ::File.basename(name) : page.name
-      format ||= page.format
-      dir      = ::File.dirname(page.path)
-      dir      = '' if dir == '.'
-      filename = (rename = page.name != name) ? Gollum::Page.cname(name) : page.filename_stripped
-
-      multi_commit = !!commit[:committer]
-      committer    = multi_commit ? commit[:committer] : Committer.new(self, commit)
-
-      if !rename && page.format == format
-        committer.add(page.path, normalize(data))
-      else
-        committer.delete(page.path)
-        committer.add_to_index(dir, filename, format, data)
-      end
-
-      committer.after_commit do |index, _sha|
-        @access.refresh
-        index.update_working_dir(dir, page.filename_stripped, page.format)
-        index.update_working_dir(dir, filename, format)
-      end
-
-      multi_commit ? committer : committer.commit
-    end
-
-    # Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
-    def rename_page(page, rename, commit = {})
-      return false if page.nil?
-      return false if rename.nil? || rename.empty?
-
-      (target_dir, target_name) = ::File.split(rename)
-      (source_dir, source_name) = ::File.split(page.path)
-      source_name               = page.filename_stripped
-
-      # File.split gives us relative paths with ".", commiter.add_to_index doesn't like that.
-      target_dir                = '' if target_dir == '.'
-      source_dir                = '' if source_dir == '.'
-      target_dir                = target_dir.gsub(/^\//, '') # rubocop:disable Style/RegexpLiteral
-
-      # if the rename is a NOOP, abort
-      if source_dir == target_dir && source_name == target_name
-        return false
-      end
-
-      multi_commit = !!commit[:committer]
-      committer    = multi_commit ? commit[:committer] : Committer.new(self, commit)
-
-      # This piece only works for multi_commit
-      # If we are in a commit batch and one of the previous operations
-      # has updated the page, any information we ask to the page can be outdated.
-      # Therefore, we should ask first to the current committer tree to see if
-      # there is any updated change.
-      raw_data = raw_data_in_committer(committer, source_dir, page.filename) ||
-        raw_data_in_committer(committer, source_dir, "#{target_name}.#{Page.format_to_ext(page.format)}") ||
-        page.raw_data
-
-      committer.delete(page.path)
-      committer.add_to_index(target_dir, target_name, page.format, raw_data)
-
-      committer.after_commit do |index, _sha|
-        @access.refresh
-        index.update_working_dir(source_dir, source_name, page.format)
-        index.update_working_dir(target_dir, target_name, page.format)
-      end
-
-      multi_commit ? committer : committer.commit
-    end
-
-    # Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
-    def raw_data_in_committer(committer, dir, filename)
-      data = nil
-
-      [*dir.split(::File::SEPARATOR), filename].each do |key|
-        data = data ? data[key] : committer.tree[key]
-        break unless data
-      end
-
-      data
-    end
-  end
-
-  module Git
-    class Git
-      def tree_entry(commit, path)
-        pathname = Pathname.new(path)
-        tmp_entry = nil
-
-        pathname.each_filename do |dir|
-          tmp_entry = if tmp_entry.nil?
-                        commit.tree[dir]
-                      else
-                        @repo.lookup(tmp_entry[:oid])[dir]
-                      end
-
-          return nil unless tmp_entry
-        end
-        tmp_entry
-      end
-    end
-  end
-end
-
 Rails.application.configure do
   config.after_initialize do
     Gollum::Page.per_page = Kaminari.config.default_per_page
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 114c1cb512f5b6ee228774c178194c3ba8c82cb7..49fdd23064ce98327c075dfb529cf76ae51f2df9 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -1,3 +1,21 @@
+# Monkey patch lograge until https://github.com/roidrage/lograge/pull/241 is released
+module Lograge
+  class RequestLogSubscriber < ActiveSupport::LogSubscriber
+    def strip_query_string(path)
+      index = path.index('?')
+      index ? path[0, index] : path
+    end
+
+    def extract_location
+      location = Thread.current[:lograge_location]
+      return {} unless location
+
+      Thread.current[:lograge_location] = nil
+      { location: strip_query_string(location) }
+    end
+  end
+end
+
 # Only use Lograge for Rails
 unless Sidekiq.server?
   filename = File.join(Rails.root, 'log', "#{Rails.env}_json.log")
diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2d130bc0bf8a9ee145dbec431f9b7f63c101c62f
--- /dev/null
+++ b/config/initializers/new_framework_defaults.rb
@@ -0,0 +1,29 @@
+# Remove this `if` condition when upgraded to rails 5.0.
+# The body must be kept.
+if Gitlab.rails5?
+  # Be sure to restart your server when you modify this file.
+  #
+  # This file contains migration options to ease your Rails 5.0 upgrade.
+  #
+  # Once upgraded flip defaults one by one to migrate to the new default.
+  #
+  # Read the Guide for Upgrading Ruby on Rails for more info on each option.
+
+  Rails.application.config.action_controller.raise_on_unfiltered_parameters = true
+
+  # Enable per-form CSRF tokens. Previous versions had false.
+  Rails.application.config.action_controller.per_form_csrf_tokens = false
+
+  # Enable origin-checking CSRF mitigation. Previous versions had false.
+  Rails.application.config.action_controller.forgery_protection_origin_check = false
+
+  # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
+  # Previous versions had false.
+  ActiveSupport.to_time_preserves_timezone = false
+
+  # Require `belongs_to` associations by default. Previous versions had false.
+  Rails.application.config.active_record.belongs_to_required_by_default = false
+
+  # Do not halt callback chains when a callback returns false. Previous versions had true.
+  ActiveSupport.halt_callback_chains_on_return_false = true
+end
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index 1175980111272a4f046e871b66b6d5a4213ea3af..ba04a2bf5faf87218bf2a8cf285f241edcb97fd9 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -16,11 +16,11 @@ else
 end
 
 Peek.into PEEK_DB_VIEW
+Peek.into Peek::Views::Gitaly
+Peek.into Peek::Views::Rblineprof
 Peek.into Peek::Views::Redis
 Peek.into Peek::Views::Sidekiq
-Peek.into Peek::Views::Rblineprof
 Peek.into Peek::Views::GC
-Peek.into Peek::Views::Gitaly
 
 # rubocop:disable Naming/ClassAndModuleCamelCase
 class PEEK_DB_CLIENT
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 161fb185c9bbc4b88c3c20d2f21eb9ca90c8736e..f6803eb0b5a5cc8f9a896ec1e19e2686037dde0e 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -5,16 +5,23 @@ queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
 # Default is to retry 25 times with exponential backoff. That's too much.
 Sidekiq.default_worker_options = { retry: 3 }
 
+enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
+
 Sidekiq.configure_server do |config|
   config.redis = queues_config_hash
 
   config.server_middleware do |chain|
-    chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
+    chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] && !enable_json_logs
     chain.add Gitlab::SidekiqMiddleware::Shutdown
     chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
     chain.add Gitlab::SidekiqStatus::ServerMiddleware
   end
 
+  if enable_json_logs
+    Sidekiq.logger.formatter = Gitlab::SidekiqLogging::JSONFormatter.new
+    config.options[:job_logger] = Gitlab::SidekiqLogging::StructuredLogger
+  end
+
   config.client_middleware do |chain|
     chain.add Gitlab::SidekiqStatus::ClientMiddleware
   end
diff --git a/config/karma.config.js b/config/karma.config.js
index 3d95e1622b238e37089aba618babfa7a9435bbcc..61f02294157bfc75073165e48ac1be5622b5ee65 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -1,11 +1,12 @@
 var path = require('path');
 var webpack = require('webpack');
+var argumentsParser = require('commander');
 var webpackConfig = require('./webpack.config.js');
 var ROOT_PATH = path.resolve(__dirname, '..');
 
 // remove problematic plugins
 if (webpackConfig.plugins) {
-  webpackConfig.plugins = webpackConfig.plugins.filter(function (plugin) {
+  webpackConfig.plugins = webpackConfig.plugins.filter(function(plugin) {
     return !(
       plugin instanceof webpack.optimize.CommonsChunkPlugin ||
       plugin instanceof webpack.optimize.ModuleConcatenationPlugin ||
@@ -14,6 +15,24 @@ if (webpackConfig.plugins) {
   });
 }
 
+var testFiles = argumentsParser
+  .option(
+    '-f, --filter-spec [filter]',
+    'Filter run spec files by path. Multiple filters are like a logical OR.',
+    (val, memo) => {
+      memo.push(val);
+      return memo;
+    },
+    []
+  )
+  .parse(process.argv).filterSpec;
+
+webpackConfig.plugins.push(
+  new webpack.DefinePlugin({
+    'process.env.TEST_FILES': JSON.stringify(testFiles),
+  })
+);
+
 webpackConfig.devtool = 'cheap-inline-source-map';
 
 // Karma configuration
@@ -24,7 +43,7 @@ module.exports = function(config) {
 
   var karmaConfig = {
     basePath: ROOT_PATH,
-    browsers:  ['ChromeHeadlessCustom'],
+    browsers: ['ChromeHeadlessCustom'],
     customLaunchers: {
       ChromeHeadlessCustom: {
         base: 'ChromeHeadless',
@@ -34,12 +53,12 @@ module.exports = function(config) {
           // escalated kernel privileges (e.g. docker run --cap-add=CAP_SYS_ADMIN)
           '--no-sandbox',
         ],
-      }
+      },
     },
     frameworks: ['jasmine'],
     files: [
       { pattern: 'spec/javascripts/test_bundle.js', watched: false },
-      { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw)', included: false },
+      { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw|.png)', included: false },
     ],
     preprocessors: {
       'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
@@ -55,7 +74,7 @@ module.exports = function(config) {
       reports: ['html', 'text-summary'],
       dir: 'coverage-javascript/',
       subdir: '.',
-      fixWebpackSourcePaths: true
+      fixWebpackSourcePaths: true,
     };
     karmaConfig.browserNoActivityTimeout = 60000; // 60 seconds
   }
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
index 601a86490d4324baecbcafec752308caa82c40c0..10ca612b24616a4b3c6da4bf1d2e720f42dddc5c 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/additional_metrics.yml
@@ -26,7 +26,7 @@
     weight: 1
     queries:
     - query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
-      label: Average
+      label: Pod average
       unit: ms
   - title: "HTTP Error Rate"
     y_label: "HTTP 500 Errors / Sec"
@@ -139,21 +139,39 @@
 - group: System metrics (Kubernetes)
   priority: 5
   metrics:
-  - title: "Memory Usage"
-    y_label: "Memory Usage (MB)"
+  - title: "Memory Usage (Total)"
+    y_label: "Total Memory Used"
     required_metrics:
       - container_memory_usage_bytes
-    weight: 1
+    weight: 4
     queries:
-    - query_range: '(sum(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job))) / count(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job)) /1024/1024'
-      label: Average
+    - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job)  /1024/1024/1024'
+      label: Total
+      unit: GB
+  - title: "Core Usage (Total)"
+    y_label: "Total Cores"
+    required_metrics:
+     - container_cpu_usage_seconds_total
+    weight: 3
+    queries:
+    - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)'
+      label: Total
+      unit: "cores"
+  - title: "Memory Usage (Pod average)"
+    y_label: "Memory Used per Pod"
+    required_metrics:
+      - container_memory_usage_bytes
+    weight: 2
+    queries:
+    - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
+      label: Pod average
       unit: MB
-  - title: "CPU Utilization"
-    y_label: "CPU Utilization (%)"
+  - title: "Core Usage (Pod average)"
+    y_label: "Cores per Pod"
     required_metrics:
      - container_cpu_usage_seconds_total
     weight: 1
     queries:
-    - query_range: 'sum(avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="%{ci_environment_slug}"}[2m])) without (job)) * 100'
-      label: Average
-      unit: "%"
\ No newline at end of file
+    - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
+      label: Pod average
+      unit: "cores"
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index e72ea1881cdbd13b56e2154516d735f9f364306f..52726f947531818f1f2e77fd25d37179df7a6117 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -43,10 +43,8 @@ Rails.application.routes.draw do
     get 'liveness' => 'health#liveness'
     get 'readiness' => 'health#readiness'
     post 'storage_check' => 'health#storage_check'
-    get 'ide' => 'ide#index'
-    get 'ide/*vueroute' => 'ide#index', format: false
     resources :metrics, only: [:index]
-    mount Peek::Railtie => '/peek'
+    mount Peek::Railtie => '/peek', as: 'peek_routes'
 
     # Boards resources shared between group and projects
     resources :boards, only: [] do
@@ -63,6 +61,9 @@ Rails.application.routes.draw do
 
     # UserCallouts
     resources :user_callouts, only: [:create]
+
+    get 'ide' => 'ide#index'
+    get 'ide/*vueroute' => 'ide#index', format: false
   end
 
   # Koding route
diff --git a/config/routes/ci.rb b/config/routes/ci.rb
index 60c1724bc052d312877f388326f9161915611b30..ebd321ed097be4d975859477a13e380bfdacea18 100644
--- a/config/routes/ci.rb
+++ b/config/routes/ci.rb
@@ -1,5 +1,5 @@
 namespace :ci do
-  resource :lint, only: [:show, :create]
+  resource :lint, only: :show
 
   root to: redirect('')
 end
diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb
index ff51823897dbe0e55ebf9d61127401929ff11d52..ec5c68f81df068f1ea8775af3901a5cc68ee649f 100644
--- a/config/routes/git_http.rb
+++ b/config/routes/git_http.rb
@@ -40,7 +40,7 @@ scope(path: '*namespace_id/:project_id',
     # /info/refs?service=git-receive-pack, but nothing else.
     #
     git_http_handshake = lambda do |request|
-      ProjectUrlConstrainer.new.matches?(request) &&
+      ::Constraints::ProjectUrlConstrainer.new.matches?(request) &&
         (request.query_string.blank? ||
          request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
     end
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 7a4740a4df7d3646c6da62fa2ef161db43b74630..170508e893dba2ab6281b8d14ed3b41ad27ee212 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -1,10 +1,8 @@
-require 'constraints/group_url_constrainer'
-
 resources :groups, only: [:index, :new, :create] do
   post :preview_markdown
 end
 
-constraints(GroupUrlConstrainer.new) do
+constraints(::Constraints::GroupUrlConstrainer.new) do
   scope(path: 'groups/*id',
         controller: :groups,
         constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
@@ -26,6 +24,7 @@ constraints(GroupUrlConstrainer.new) do
         constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
     namespace :settings do
       resource :ci_cd, only: [:show], controller: 'ci_cd'
+      resources :badges, only: [:index]
     end
 
     resource :variables, only: [:show, :update]
@@ -56,6 +55,9 @@ constraints(GroupUrlConstrainer.new) do
         get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
       end
     end
+
+    # On CE only index and show actions are needed
+    resources :boards, only: [:index, :show]
   end
 
   scope(path: '*id',
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 34636285c51259ada7c7e31c786869a19d967a9f..2a1bcb8cde274100233d5968b9563ef5a1a37bf3 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -1,10 +1,8 @@
-require 'constraints/project_url_constrainer'
-
 resources :projects, only: [:index, :new, :create]
 
 draw :git_http
 
-constraints(ProjectUrlConstrainer.new) do
+constraints(::Constraints::ProjectUrlConstrainer.new) do
   # If the route has a wildcard segment, the segment has a regex constraint,
   # the segment is potentially followed by _another_ wildcard segment, and
   # the `format` option is not set to false, we need to specify that
@@ -54,7 +52,7 @@ constraints(ProjectUrlConstrainer.new) do
         end
       end
 
-      resource :pages, only: [:show, :destroy] do
+      resource :pages, only: [:show, :update, :destroy] do
         resources :domains, except: :index, controller: 'pages_domains', constraints: { id: %r{[^/]+} } do
           member do
             post :verify
@@ -69,7 +67,7 @@ constraints(ProjectUrlConstrainer.new) do
         end
       end
 
-      resources :services, constraints: { id: %r{[^/]+} }, only: [:index, :edit, :update] do
+      resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
         member do
           put :test
         end
@@ -90,6 +88,12 @@ constraints(ProjectUrlConstrainer.new) do
         end
       end
 
+      resources :deploy_tokens, constraints: { id: /\d+/ }, only: [] do
+        member do
+          put :revoke
+        end
+      end
+
       resources :forks, only: [:index, :new, :create]
       resource :import, only: [:new, :create, :show]
 
@@ -132,7 +136,7 @@ constraints(ProjectUrlConstrainer.new) do
           post :bulk_update
         end
 
-        resources :discussions, only: [], constraints: { id: /\h{40}/ } do
+        resources :discussions, only: [:show], constraints: { id: /\h{40}/ } do
           member do
             post :resolve
             delete :resolve, action: :unresolve
@@ -251,6 +255,8 @@ constraints(ProjectUrlConstrainer.new) do
       end
 
       scope '-' do
+        get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
+
         resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
           collection do
             post :cancel_all
@@ -282,6 +288,10 @@ constraints(ProjectUrlConstrainer.new) do
             post :keep
           end
         end
+
+        namespace :ci do
+          resource :lint, only: [:show, :create]
+        end
       end
 
       draw :legacy_builds
@@ -381,7 +391,8 @@ constraints(ProjectUrlConstrainer.new) do
 
       get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
 
-      resources :boards, only: [:index, :show, :create, :update, :destroy]
+      # On CE only index and show are needed
+      resources :boards, only: [:index, :show]
 
       resources :todos, only: [:create]
 
@@ -417,11 +428,14 @@ constraints(ProjectUrlConstrainer.new) do
       end
       namespace :settings do
         get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
-        resource :ci_cd, only: [:show], controller: 'ci_cd' do
+        resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
           post :reset_cache
         end
         resource :integrations, only: [:show]
-        resource :repository, only: [:show], controller: :repository
+        resource :repository, only: [:show], controller: :repository do
+          post :create_deploy_token, path: 'deploy_token/create'
+        end
+        resources :badges, only: [:index]
       end
 
       # Since both wiki and repository routing contains wildcard characters
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 9ffdebbcff1c3d507ac61eca6ac23424671579d7..9e506a1a43adba9df127c24a99e59abe292c6085 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -2,10 +2,11 @@
 
 resource :repository, only: [:create] do
   member do
-    get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
-
     # deprecated since GitLab 9.5
-    get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
+    get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative', defaults: { append_sha: true }
+
+    # deprecated since GitLab 10.7
+    get ':id/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+/ }, action: 'archive', as: 'archive_deprecated', defaults: { append_sha: true }
   end
 end
 
@@ -49,6 +50,7 @@ scope format: false do
       end
     end
 
+    get '/branches/:state', to: 'branches#index', as: :branches_filtered, constraints: { state: /active|stale|all/ }
     resources :branches, only: [:index, :new, :create, :destroy]
     delete :merged_branches, controller: 'branches', action: :destroy_all_merged
     resources :tags, only: [:index, :show, :new, :create, :destroy] do
diff --git a/config/routes/user.rb b/config/routes/user.rb
index 733a3f6ce9a7e42595004f140c55767ac7e52ad1..57fb37530bbac90b3e5c2c626acc5f37829e430c 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -1,5 +1,3 @@
-require 'constraints/user_url_constrainer'
-
 devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks,
                                   registrations: :registrations,
                                   passwords: :passwords,
@@ -35,7 +33,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
   get '/u/:username/contributed', to: redirect('users/%{username}/contributed')
 end
 
-constraints(UserUrlConstrainer.new) do
+constraints(::Constraints::UserUrlConstrainer.new) do
   # Get all keys of user
   get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
 
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 4845dc28a4a5d6f7052ee1ee86ba2b3b630e9d08..47fbbed44cf6a723c8a4fd4814d225b49366e2b1 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -34,6 +34,7 @@
   - [email_receiver, 2]
   - [emails_on_push, 2]
   - [mailers, 2]
+  - [mail_scheduler, 2]
   - [invalid_gpg_signature_update, 2]
   - [create_gpg_signature, 2]
   - [rebase, 2]
@@ -68,4 +69,7 @@
   - [project_migrate_hashed_storage, 1]
   - [storage_migrator, 1]
   - [pages_domain_verification, 1]
+  - [object_storage_upload, 1]
+  - [object_storage, 1]
   - [plugin, 1]
+  - [pipeline_background, 1]
diff --git a/config/spring.rb b/config/spring.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c9119b40c08eff8aef2d607f5bf42424483cbd15
--- /dev/null
+++ b/config/spring.rb
@@ -0,0 +1,6 @@
+%w(
+  .ruby-version
+  .rbenv-vars
+  tmp/restart.txt
+  tmp/caching-dev.txt
+).each { |path| Spring.watch(path) }
diff --git a/config/svg.config.js b/config/svg.config.js
deleted file mode 100644
index bb27f0caeefcac4686bbaf240231b10a1373be20..0000000000000000000000000000000000000000
--- a/config/svg.config.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* eslint-disable no-commonjs */
-const path = require('path');
-const fs = require('fs');
-
-const sourcePath = path.join('node_modules', '@gitlab-org/gitlab-svgs', 'dist');
-const sourcePathIllustrations = path.join('node_modules', '@gitlab-org/gitlab-svgs', 'dist', 'illustrations');
-const destPath = path.normalize(path.join('app', 'assets', 'images'));
-
-// Actual Task copying the 2 files + all illustrations
-copyFileSync(path.join(sourcePath, 'icons.svg'), destPath);
-copyFileSync(path.join(sourcePath, 'icons.json'), destPath);
-copyFolderRecursiveSync(sourcePathIllustrations, destPath);
-
-// Helper Functions
-function copyFileSync(source, target) {
-  var targetFile = target;
-  //if target is a directory a new file with the same name will be created
-  if (fs.existsSync(target)) {
-    if (fs.lstatSync(target).isDirectory()) {
-      targetFile = path.join(target, path.basename(source));
-    }
-  }
-  console.log(`Copy SVG File : ${targetFile}`);
-  fs.writeFileSync(targetFile, fs.readFileSync(source));
-}
-
-function copyFolderRecursiveSync(source, target) {
-  var files = [];
-
-  //check if folder needs to be created or integrated
-  var targetFolder = path.join(target, path.basename(source));
-  if (!fs.existsSync(targetFolder)) {
-    fs.mkdirSync(targetFolder);
-  }
-
-  //copy
-  if (fs.lstatSync(source).isDirectory()) {
-    files = fs.readdirSync(source);
-    files.forEach(function (file) {
-      var curSource = path.join(source, file);
-      if (fs.lstatSync(curSource).isDirectory()) {
-        copyFolderRecursiveSync(curSource, targetFolder);
-      } else {
-        copyFileSync(curSource, targetFolder);
-      }
-    });
-  }
-}
diff --git a/config/webpack.config.js b/config/webpack.config.js
index cc098c8a3f9c70be2008bea48b7fd6af09c0c6ec..39e9fbbd5304eee4a54b022d54087e2ace428c4c 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -1,5 +1,3 @@
-'use strict';
-
 const crypto = require('crypto');
 const fs = require('fs');
 const path = require('path');
@@ -27,10 +25,10 @@ let watchAutoEntries = [];
 function generateEntries() {
   // generate automatic entry points
   const autoEntries = {};
-  const pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') });
-  watchAutoEntries = [
-    path.join(ROOT_PATH, 'app/assets/javascripts/pages/'),
-  ];
+  const pageEntries = glob.sync('pages/**/index.js', {
+    cwd: path.join(ROOT_PATH, 'app/assets/javascripts'),
+  });
+  watchAutoEntries = [path.join(ROOT_PATH, 'app/assets/javascripts/pages/')];
 
   function generateAutoEntries(path, prefix = '.') {
     const chunkPath = path.replace(/\/index\.js$/, '');
@@ -38,18 +36,16 @@ function generateEntries() {
     autoEntries[chunkName] = `${prefix}/${path}`;
   }
 
-  pageEntries.forEach(( path ) => generateAutoEntries(path));
+  pageEntries.forEach(path => generateAutoEntries(path));
 
   autoEntriesCount = Object.keys(autoEntries).length;
 
   const manualEntries = {
-    common:               './commons/index.js',
-    common_vue:           './vue_shared/vue_resource_interceptor.js',
-    locale:               './locale/index.js',
-    main:                 './main.js',
-    ide:                  './ide/index.js',
-    raven:                './raven/index.js',
-    webpack_runtime:      './webpack.js',
+    common: './commons/index.js',
+    main: './main.js',
+    raven: './raven/index.js',
+    webpack_runtime: './webpack.js',
+    ide: './ide/index.js',
   };
 
   return Object.assign(manualEntries, autoEntries);
@@ -93,8 +89,8 @@ const config = {
           {
             loader: 'worker-loader',
             options: {
-              inline: true
-            }
+              inline: true,
+            },
           },
           { loader: 'babel-loader' },
         ],
@@ -105,18 +101,18 @@ const config = {
         loader: 'file-loader',
         options: {
           name: '[name].[hash].[ext]',
-        }
+        },
       },
       {
-        test: /katex.css$/,
+        test: /katex.min.css$/,
         include: /node_modules\/katex\/dist/,
         use: [
           { loader: 'style-loader' },
           {
             loader: 'css-loader',
             options: {
-              name: '[name].[hash].[ext]'
-            }
+              name: '[name].[hash].[ext]',
+            },
           },
         ],
       },
@@ -126,7 +122,7 @@ const config = {
         loader: 'file-loader',
         options: {
           name: '[name].[hash].[ext]',
-        }
+        },
       },
       {
         test: /monaco-editor\/\w+\/vs\/loader\.js$/,
@@ -134,7 +130,7 @@ const config = {
           { loader: 'exports-loader', options: 'l.global' },
           { loader: 'imports-loader', options: 'l=>{},this=>l,AMDLoader=>this,module=>undefined' },
         ],
-      }
+      },
     ],
 
     noParse: [/monaco-editor\/\w+\/vs\//],
@@ -152,10 +148,10 @@ const config = {
           source: false,
           chunks: false,
           modules: false,
-          assets: true
+          assets: true,
         });
         return JSON.stringify(stats, null, 2);
-      }
+      },
     }),
 
     // prevent pikaday from including moment.js
@@ -172,7 +168,7 @@ const config = {
     new NameAllModulesPlugin(),
 
     // assign deterministic chunk ids
-    new webpack.NamedChunksPlugin((chunk) => {
+    new webpack.NamedChunksPlugin(chunk => {
       if (chunk.name) {
         return chunk.name;
       }
@@ -189,9 +185,12 @@ const config = {
         const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
 
         if (m.resource.indexOf(pagesBase) === 0) {
-          moduleNames.push(path.relative(pagesBase, m.resource)
-            .replace(/\/index\.[a-z]+$/, '')
-            .replace(/\//g, '__'));
+          moduleNames.push(
+            path
+              .relative(pagesBase, m.resource)
+              .replace(/\/index\.[a-z]+$/, '')
+              .replace(/\//g, '__')
+          );
         } else {
           moduleNames.push(path.relative(m.context, m.resource));
         }
@@ -199,7 +198,8 @@ const config = {
 
       chunk.forEachModule(collectModuleNames);
 
-      const hash = crypto.createHash('sha256')
+      const hash = crypto
+        .createHash('sha256')
         .update(moduleNames.join('_'))
         .digest('hex');
 
@@ -211,13 +211,13 @@ const config = {
       names: ['main', 'common', 'webpack_runtime'],
     }),
 
-    // enable scope hoisting
-    new webpack.optimize.ModuleConcatenationPlugin(),
-
     // copy pre-compiled vendor libraries verbatim
     new CopyWebpackPlugin([
       {
-        from: path.join(ROOT_PATH, `node_modules/monaco-editor/${IS_PRODUCTION ? 'min' : 'dev'}/vs`),
+        from: path.join(
+          ROOT_PATH,
+          `node_modules/monaco-editor/${IS_PRODUCTION ? 'min' : 'dev'}/vs`
+        ),
         to: 'monaco-editor/vs',
         transform: function(content, path) {
           if (/\.js$/.test(path) && !/worker/i.test(path) && !/typescript/i.test(path)) {
@@ -230,23 +230,23 @@ const config = {
             );
           }
           return content;
-        }
-      }
+        },
+      },
     ]),
   ],
 
   resolve: {
     extensions: ['.js'],
     alias: {
-      '~':              path.join(ROOT_PATH, 'app/assets/javascripts'),
-      'emojis':         path.join(ROOT_PATH, 'fixtures/emojis'),
-      'empty_states':   path.join(ROOT_PATH, 'app/views/shared/empty_states'),
-      'icons':          path.join(ROOT_PATH, 'app/views/shared/icons'),
-      'images':         path.join(ROOT_PATH, 'app/assets/images'),
-      'vendor':         path.join(ROOT_PATH, 'vendor/assets/javascripts'),
-      'vue$':           'vue/dist/vue.esm.js',
-      'spec':           path.join(ROOT_PATH, 'spec/javascripts'),
-    }
+      '~': path.join(ROOT_PATH, 'app/assets/javascripts'),
+      emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
+      empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
+      icons: path.join(ROOT_PATH, 'app/views/shared/icons'),
+      images: path.join(ROOT_PATH, 'app/assets/images'),
+      vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
+      vue$: 'vue/dist/vue.esm.js',
+      spec: path.join(ROOT_PATH, 'spec/javascripts'),
+    },
   },
 
   // sqljs requires fs
@@ -261,13 +261,14 @@ if (IS_PRODUCTION) {
     new webpack.NoEmitOnErrorsPlugin(),
     new webpack.LoaderOptionsPlugin({
       minimize: true,
-      debug: false
+      debug: false,
     }),
+    new webpack.optimize.ModuleConcatenationPlugin(),
     new webpack.optimize.UglifyJsPlugin({
-      sourceMap: true
+      sourceMap: true,
     }),
     new webpack.DefinePlugin({
-      'process.env': { NODE_ENV: JSON.stringify('production') }
+      'process.env': { NODE_ENV: JSON.stringify('production') },
     })
   );
 
@@ -286,7 +287,7 @@ if (IS_DEV_SERVER) {
     headers: { 'Access-Control-Allow-Origin': '*' },
     stats: 'errors-only',
     hot: DEV_SERVER_LIVERELOAD,
-    inline: DEV_SERVER_LIVERELOAD
+    inline: DEV_SERVER_LIVERELOAD,
   };
   config.plugins.push(
     // watch node_modules for changes if we encounter a missing module compile error
@@ -302,10 +303,12 @@ if (IS_DEV_SERVER) {
           ];
 
           // report our auto-generated bundle count
-          console.log(`${autoEntriesCount} entries from '/pages' automatically added to webpack output.`);
+          console.log(
+            `${autoEntriesCount} entries from '/pages' automatically added to webpack output.`
+          );
 
           callback();
-        })
+        });
       },
     }
   );
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index dfb50c195c139c3455255d1f93396c829064c58f..1e260236dc5667351cc6c72a6c10e695fa30d3d2 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -1,14 +1,14 @@
 require './spec/support/sidekiq'
 
 Gitlab::Seeder.quiet do
-  User.seed do |s|
-    s.id = 1
-    s.name = 'Administrator'
-    s.email = 'admin@example.com'
-    s.notification_email = 'admin@example.com'
-    s.username = 'root'
-    s.password = '5iveL!fe'
-    s.admin = true
-    s.confirmed_at = DateTime.now
-  end
+  User.create!(
+    name: 'Administrator',
+    email: 'admin@example.com',
+    username: 'root',
+    password: '5iveL!fe',
+    admin: true,
+    confirmed_at: DateTime.now
+  )
+
+  print '.'
 end
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 30244ee4431802b3cc6337a6e8a4d0b2327105af..bcfdd058a1c765d909db9d81d54db61fef7ef4f4 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do
   # Limit the number of merge requests per project to avoid long seeds
   MAX_NUM_MERGE_REQUESTS = 10
 
-  Project.all.reject(&:empty_repo?).each do |project|
+  Project.non_archived.with_merge_requests_enabled.reject(&:empty_repo?).each do |project|
     branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2)
 
     branches.each do |branch_name|
@@ -21,7 +21,11 @@ Gitlab::Seeder.quiet do
         assignee: project.team.users.sample
       }
 
-      MergeRequests::CreateService.new(project, project.team.users.sample, params).execute
+      # Only create MRs with users that are allowed to create MRs
+      developer = project.team.developers.sample
+      break unless developer
+
+      MergeRequests::CreateService.new(project, developer, params).execute
       print '.'
     end
   end
diff --git a/db/fixtures/development/19_environments.rb b/db/fixtures/development/19_environments.rb
index c1bbc9af6d6f84d4e3cb3e408bf317edab054336..00a14f458d17e04edb597d3f61cd1d8a73703c8d 100644
--- a/db/fixtures/development/19_environments.rb
+++ b/db/fixtures/development/19_environments.rb
@@ -28,7 +28,11 @@ class Gitlab::Seeder::Environments
   end
 
   def create_merge_request_review_deployments!
-    @project.merge_requests.sample(4).map do |merge_request|
+    @project
+      .merge_requests
+      .select { |mr| mr.source_branch.match(/\p{Alnum}+/) }
+      .sample(4)
+      .each do |merge_request|
       next unless merge_request.diff_head_sha
 
       create_deployment!(
diff --git a/db/fixtures/development/22_labeled_issues_seed.rb b/db/fixtures/development/22_labeled_issues_seed.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3730e9c79585ffe575af7bffb1721159f4beaec9
--- /dev/null
+++ b/db/fixtures/development/22_labeled_issues_seed.rb
@@ -0,0 +1,103 @@
+# Creates a project with labeled issues for an user.
+# Run this single seed file using: rake db:seed_fu FILTER=labeled USER_ID=74.
+# If an USER_ID is not provided it will use the last created user.
+require './spec/support/sidekiq'
+
+class Gitlab::Seeder::LabeledIssues
+  include ::Gitlab::Utils
+
+  def initialize(user)
+    @user = user
+  end
+
+  def seed!
+    Sidekiq::Testing.inline! do
+      group = create_group
+
+      create_projects(group)
+      create_labels(group)
+      create_issues(group)
+    end
+
+    print '.'
+  end
+
+  private
+
+  def create_group
+    group_name = "group_of_#{@user.username}_#{SecureRandom.hex(4)}"
+
+    group_params = {
+      name: group_name,
+      path: group_name,
+      description: FFaker::Lorem.sentence
+    }
+
+    Groups::CreateService.new(@user, group_params).execute
+  end
+
+  def create_projects(group)
+    5.times do
+      project_name = "project_#{SecureRandom.hex(6)}"
+
+      params = {
+        namespace_id: group.id,
+        name: project_name,
+        description: FFaker::Lorem.sentence,
+        visibility_level: Gitlab::VisibilityLevel.values.sample
+      }
+
+      Projects::CreateService.new(@user, params).execute
+    end
+  end
+
+  def create_labels(group)
+    group.projects.each do |project|
+      5.times do
+        label_title = FFaker::Vehicle.model
+        Labels::CreateService.new(title: label_title, color: "#69D100").execute(project: project)
+      end
+    end
+
+    10.times do
+      label_title = FFaker::Product.brand
+      Labels::CreateService.new(title: label_title).execute(group: group)
+    end
+  end
+
+  def create_issues(group)
+    # Get only group labels
+    group_labels =
+      LabelsFinder.new(@user, group_id: group.id).execute.where.not(group_id: nil)
+
+    group.projects.each do |project|
+      label_ids = project.labels.pluck(:id).sample(5)
+      label_ids.push(*group.labels.sample(4))
+
+      20.times do
+        issue_params = {
+          title: FFaker::Lorem.sentence(6),
+          description: FFaker::Lorem.sentence,
+          state: 'opened',
+          label_ids: label_ids
+
+        }
+
+        Issues::CreateService.new(project, @user, issue_params).execute if project.project_feature.present?
+      end
+    end
+  end
+end
+
+Gitlab::Seeder.quiet do
+  user_id = ENV['USER_ID']
+
+  user =
+    if user_id.present?
+      User.find(user_id)
+    else
+      User.last
+    end
+
+  Gitlab::Seeder::LabeledIssues.new(user).seed!
+end
diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
index bcdae272209b34552749182325a5be0501fc02e0..a96ea7d9db44aa8715e94ea462b66dfa1ea12ea9 100644
--- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
+++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
@@ -12,7 +12,7 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
     end
 
     def repository_storage_path
-      Gitlab.config.repositories.storages[repository_storage]['path']
+      Gitlab.config.repositories.storages[repository_storage].legacy_disk_path
     end
 
     def repository_path
diff --git a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
index 8fb1f9d5e737e0230ade8412fb49553731d072ce..bddc234db251055544c782bc0fba03e74552395e 100644
--- a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
+++ b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
@@ -60,7 +60,7 @@ class RemoveDotGitFromGroupNames < ActiveRecord::Migration
 
   def move_namespace(group_id, path_was, path)
     repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{group_id}").map do |row|
-      Gitlab.config.repositories.storages[row['repository_storage']]['path']
+      Gitlab.config.repositories.storages[row['repository_storage']].legacy_disk_path
     end.compact
 
     # Move the namespace directory in all storages paths used by member projects
diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
index 61dcc8c54f5844bda2b52163091ed10cfd367ec9..7c28d934c290fb7de35862e2bfdf4e7f14290035 100644
--- a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
+++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
@@ -71,7 +71,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
     route_exists = route_exists?(path)
 
     Gitlab.config.repositories.storages.each_value do |storage|
-      if route_exists || path_exists?(path, storage['path'])
+      if route_exists || path_exists?(path, storage.legacy_disk_path)
         counter += 1
         path = "#{base}#{counter}"
 
@@ -84,7 +84,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
 
   def move_namespace(namespace_id, path_was, path)
     repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row|
-      Gitlab.config.repositories.storages[row['repository_storage']]['path']
+      Gitlab.config.repositories.storages[row['repository_storage']].legacy_disk_path
     end.compact
 
     # Move the namespace directory in all storages paths used by member projects
diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
index af6d10b51581121ae1a5b5c9dc7376710938ef65..1199073ed3a55a462707bc9c5acd23cd95925d8b 100644
--- a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
+++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
@@ -154,7 +154,7 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
   end
 
   def add_foreign_key_if_not_exists(source, target, column:)
-    return if foreign_key_exists?(source, column)
+    return if foreign_key_exists?(source, target, column: column)
 
     add_concurrent_foreign_key(source, target, column: column)
   end
@@ -175,12 +175,6 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
   rescue ArgumentError
   end
 
-  def foreign_key_exists?(table, column)
-    foreign_keys(table).any? do |key|
-      key.options[:column] == column.to_s
-    end
-  end
-
   def connection
     # Rails memoizes connection objects, but this causes them to be shared
     # amongst threads; we don't want that.
diff --git a/db/migrate/20170601163708_add_artifacts_store_to_ci_build.rb b/db/migrate/20170601163708_add_artifacts_store_to_ci_build.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e82109190a72be58775aa15075635ff107679841
--- /dev/null
+++ b/db/migrate/20170601163708_add_artifacts_store_to_ci_build.rb
@@ -0,0 +1,10 @@
+class AddArtifactsStoreToCiBuild < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column(:ci_builds, :artifacts_file_store, :integer)
+    add_column(:ci_builds, :artifacts_metadata_store, :integer)
+  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
index 68b947583d3cb9ecce7ef840eff8e79bd6c604a7..a89d348b127fe30927235a7642e0b8e395b94d6c 100644
--- a/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
+++ b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
@@ -10,13 +10,13 @@ class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
       add_concurrent_index(:ci_builds, :stage_id)
     end
 
-    unless foreign_key_exists?(:ci_builds, :stage_id)
+    unless foreign_key_exists?(:ci_builds, :ci_stages, column: :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)
+    if foreign_key_exists?(:ci_builds, column: :stage_id)
       remove_foreign_key(:ci_builds, column: :stage_id)
     end
 
@@ -24,12 +24,4 @@ class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
       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/20170713104829_add_foreign_key_to_merge_requests.rb b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb
index c25d4fd59865e7569b612daba2d6ece24854e6fb..c409915ceedb2b68999a10d5b54322d44781b024 100644
--- a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb
+++ b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb
@@ -23,23 +23,15 @@ class AddForeignKeyToMergeRequests < ActiveRecord::Migration
       merge_requests.update_all(head_pipeline_id: nil)
     end
 
-    unless foreign_key_exists?(:merge_requests, :head_pipeline_id)
+    unless foreign_key_exists?(:merge_requests, column: :head_pipeline_id)
       add_concurrent_foreign_key(:merge_requests, :ci_pipelines,
                                  column: :head_pipeline_id, on_delete: :nullify)
     end
   end
 
   def down
-    if foreign_key_exists?(:merge_requests, :head_pipeline_id)
+    if foreign_key_exists?(:merge_requests, column: :head_pipeline_id)
       remove_foreign_key(:merge_requests, column: :head_pipeline_id)
     end
   end
-
-  private
-
-  def foreign_key_exists?(table, column)
-    foreign_keys(table).any? do |key|
-      key.options[:column] == column.to_s
-    end
-  end
 end
diff --git a/db/migrate/20170825015534_add_file_store_to_lfs_objects.rb b/db/migrate/20170825015534_add_file_store_to_lfs_objects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..41bb031014f6b3945705f5cc60119add395c5cb4
--- /dev/null
+++ b/db/migrate/20170825015534_add_file_store_to_lfs_objects.rb
@@ -0,0 +1,31 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddFileStoreToLfsObjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  # DOWNTIME_REASON = ''
+
+  # When using the methods "add_concurrent_index", "remove_concurrent_index" or
+  # "add_column_with_default" you must disable the use of transactions
+  # as these methods can not run in an existing transaction.
+  # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
+  # that either of them is the _only_ method called in the migration,
+  # any other changes should go in a separate migration.
+  # This ensures that upon failure _only_ the index creation or removing fails
+  # and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    add_column(:lfs_objects, :file_store, :integer)
+  end
+end
diff --git a/db/migrate/20170918072949_add_file_store_job_artifacts.rb b/db/migrate/20170918072949_add_file_store_job_artifacts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b1f1bea6debd64561d701d5bb5312f2bc0f22306
--- /dev/null
+++ b/db/migrate/20170918072949_add_file_store_job_artifacts.rb
@@ -0,0 +1,10 @@
+class AddFileStoreJobArtifacts < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  disable_ddl_transaction!
+  DOWNTIME = false
+
+  def change
+    add_column(:ci_job_artifacts, :file_store, :integer)
+  end
+end
diff --git a/db/migrate/20171214144320_add_store_column_to_uploads.rb b/db/migrate/20171214144320_add_store_column_to_uploads.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e35798e2c41095a98bb315cb9fe9f6bd6994c96c
--- /dev/null
+++ b/db/migrate/20171214144320_add_store_column_to_uploads.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddStoreColumnToUploads < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column(:uploads, :store, :integer)
+  end
+end
diff --git a/db/migrate/20171222115326_add_confidential_note_events_to_web_hooks.rb b/db/migrate/20171222115326_add_confidential_note_events_to_web_hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..900a6386922cff15437c6f4861c256a0215a1d58
--- /dev/null
+++ b/db/migrate/20171222115326_add_confidential_note_events_to_web_hooks.rb
@@ -0,0 +1,15 @@
+class AddConfidentialNoteEventsToWebHooks < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column :web_hooks, :confidential_note_events, :boolean
+  end
+
+  def down
+    remove_column :web_hooks, :confidential_note_events
+  end
+end
diff --git a/db/migrate/20180102220145_add_pages_https_only_to_projects.rb b/db/migrate/20180102220145_add_pages_https_only_to_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ef6bc6896c04c0ab6e0786c1ffbef826e222bcd4
--- /dev/null
+++ b/db/migrate/20180102220145_add_pages_https_only_to_projects.rb
@@ -0,0 +1,9 @@
+class AddPagesHttpsOnlyToProjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :projects, :pages_https_only, :boolean
+  end
+end
diff --git a/db/migrate/20180103123548_add_confidential_note_events_to_services.rb b/db/migrate/20180103123548_add_confidential_note_events_to_services.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b54ad88df437dff73d50113eb27b23904a2676a6
--- /dev/null
+++ b/db/migrate/20180103123548_add_confidential_note_events_to_services.rb
@@ -0,0 +1,16 @@
+class AddConfidentialNoteEventsToServices < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column :services, :confidential_note_events, :boolean
+    change_column_default :services, :confidential_note_events, true
+  end
+
+  def down
+    remove_column :services, :confidential_note_events
+  end
+end
diff --git a/db/migrate/20180109183319_change_default_value_for_pages_https_only.rb b/db/migrate/20180109183319_change_default_value_for_pages_https_only.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c242e1b0d24c102defbe479c5eb23d7a5996ba75
--- /dev/null
+++ b/db/migrate/20180109183319_change_default_value_for_pages_https_only.rb
@@ -0,0 +1,13 @@
+class ChangeDefaultValueForPagesHttpsOnly < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    change_column_default :projects, :pages_https_only, true
+  end
+
+  def down
+    change_column_default :projects, :pages_https_only, nil
+  end
+end
diff --git a/db/migrate/20180209165249_add_closed_by_to_issues.rb b/db/migrate/20180209165249_add_closed_by_to_issues.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e251afd7b49894facb79425e66a06efc1c3ffa5d
--- /dev/null
+++ b/db/migrate/20180209165249_add_closed_by_to_issues.rb
@@ -0,0 +1,20 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddClosedByToIssues < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  disable_ddl_transaction!
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  def up
+    add_column :issues, :closed_by_id, :integer
+    add_concurrent_foreign_key :issues, :users, column: :closed_by_id, on_delete: :nullify
+  end
+
+  def down
+    remove_foreign_key :issues, column: :closed_by_id
+    remove_column :issues, :closed_by_id
+  end
+end
diff --git a/db/migrate/20180214093516_create_badges.rb b/db/migrate/20180214093516_create_badges.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6559f8344840ff49b6f25afd057f8a76f889c348
--- /dev/null
+++ b/db/migrate/20180214093516_create_badges.rb
@@ -0,0 +1,17 @@
+class CreateBadges < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    create_table :badges do |t|
+      t.string     :link_url, null: false
+      t.string     :image_url, null: false
+      t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: true
+      t.integer    :group_id, index: true, null: true
+      t.string     :type, null: false
+
+      t.timestamps_with_timezone null: false
+    end
+
+    add_foreign_key :badges, :namespaces, column: :group_id, on_delete: :cascade
+  end
+end
diff --git a/db/migrate/20180219153455_add_maximum_timeout_to_ci_runners.rb b/db/migrate/20180219153455_add_maximum_timeout_to_ci_runners.rb
new file mode 100644
index 0000000000000000000000000000000000000000..072e696a43e5bffd04a0624a0a5112449f2938e5
--- /dev/null
+++ b/db/migrate/20180219153455_add_maximum_timeout_to_ci_runners.rb
@@ -0,0 +1,9 @@
+class AddMaximumTimeoutToCiRunners < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :ci_runners, :maximum_timeout, :integer
+  end
+end
diff --git a/db/migrate/20180221151752_add_allow_maintainer_to_push_to_merge_requests.rb b/db/migrate/20180221151752_add_allow_maintainer_to_push_to_merge_requests.rb
new file mode 100644
index 0000000000000000000000000000000000000000..81acfbc3655789e3a1df91b00a2c89b08b1a5a47
--- /dev/null
+++ b/db/migrate/20180221151752_add_allow_maintainer_to_push_to_merge_requests.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddAllowMaintainerToPushToMergeRequests < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column :merge_requests, :allow_maintainer_to_push, :boolean
+  end
+
+  def down
+    remove_column :merge_requests, :allow_maintainer_to_push
+  end
+end
diff --git a/db/migrate/20180223120443_create_user_interacted_projects_table.rb b/db/migrate/20180223120443_create_user_interacted_projects_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8da8cf6808821e8c0311711f5d64d946ef3d7d6b
--- /dev/null
+++ b/db/migrate/20180223120443_create_user_interacted_projects_table.rb
@@ -0,0 +1,20 @@
+class CreateUserInteractedProjectsTable < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  INDEX_NAME = 'user_interacted_projects_non_unique_index'
+
+  def up
+    create_table :user_interacted_projects, id: false do |t|
+      t.references :user, null: false
+      t.references :project, null: false
+    end
+
+    add_index :user_interacted_projects, [:project_id, :user_id], name: INDEX_NAME
+  end
+
+  def down
+    drop_table :user_interacted_projects
+  end
+end
diff --git a/db/migrate/20180223144945_add_allow_local_requests_from_hooks_and_services_to_application_settings.rb b/db/migrate/20180223144945_add_allow_local_requests_from_hooks_and_services_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c994a54698b1522d21b8d012f87f3882e24e33ba
--- /dev/null
+++ b/db/migrate/20180223144945_add_allow_local_requests_from_hooks_and_services_to_application_settings.rb
@@ -0,0 +1,18 @@
+class AddAllowLocalRequestsFromHooksAndServicesToApplicationSettings < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default(:application_settings, :allow_local_requests_from_hooks_and_services,
+                            :boolean,
+                            default: false,
+                            allow_null: false)
+  end
+
+  def down
+    remove_column(:application_settings, :allow_local_requests_from_hooks_and_services)
+  end
+end
diff --git a/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..54e6e35449e49a0437df83f32d1c6fbbbaa7039e
--- /dev/null
+++ b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb
@@ -0,0 +1,7 @@
+class AddChecksumToCiJobArtifacts < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    add_column :ci_job_artifacts, :file_sha256, :binary
+  end
+end
diff --git a/db/migrate/20180227182112_add_group_id_to_boards_ce.rb b/db/migrate/20180227182112_add_group_id_to_boards_ce.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f54dd8d76870bd96f7de249086fe289fdec7b449
--- /dev/null
+++ b/db/migrate/20180227182112_add_group_id_to_boards_ce.rb
@@ -0,0 +1,34 @@
+class AddGroupIdToBoardsCe < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  DOWNTIME = false
+
+  def up
+    return if group_id_exists?
+
+    add_column :boards, :group_id, :integer
+    add_foreign_key :boards, :namespaces, column: :group_id, on_delete: :cascade
+    add_concurrent_index :boards, :group_id
+
+    change_column_null :boards, :project_id, true
+  end
+
+  def down
+    return unless group_id_exists?
+
+    remove_foreign_key :boards, column: :group_id
+    remove_index :boards, :group_id if index_exists? :boards, :group_id
+    remove_column :boards, :group_id
+
+    execute "DELETE from boards WHERE project_id IS NULL"
+    change_column_null :boards, :project_id, false
+  end
+
+  private
+
+  def group_id_exists?
+    column_exists?(:boards, :group_id)
+  end
+end
diff --git a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ce73744409233ce04672f370f11baa5d6835a8a5
--- /dev/null
+++ b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb
@@ -0,0 +1,20 @@
+class CreateCiBuildsMetadataTable < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    create_table :ci_builds_metadata do |t|
+      t.integer :build_id, null: false
+      t.integer :project_id, null: false
+      t.integer :timeout
+      t.integer :timeout_source, null: false, default: 1
+
+      t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade
+      t.foreign_key :projects, column: :project_id, on_delete: :cascade
+
+      t.index :build_id, unique: true
+      t.index :project_id
+    end
+  end
+end
diff --git a/db/migrate/20180302152117_ensure_foreign_keys_on_clusters_applications.rb b/db/migrate/20180302152117_ensure_foreign_keys_on_clusters_applications.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8298979e96abca26c7e16a4487b27a17c1746422
--- /dev/null
+++ b/db/migrate/20180302152117_ensure_foreign_keys_on_clusters_applications.rb
@@ -0,0 +1,50 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class EnsureForeignKeysOnClustersApplications < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    existing = Clusters::Cluster
+      .joins(:application_ingress)
+      .where('clusters.id = clusters_applications_ingress.cluster_id')
+
+    Clusters::Applications::Ingress.where('NOT EXISTS (?)', existing).in_batches do |batch|
+      batch.delete_all
+    end
+
+    unless foreign_keys_for(:clusters_applications_ingress, :cluster_id).any?
+      add_concurrent_foreign_key :clusters_applications_ingress, :clusters,
+        column: :cluster_id,
+        on_delete: :cascade
+    end
+
+    existing = Clusters::Cluster
+      .joins(:application_prometheus)
+      .where('clusters.id = clusters_applications_prometheus.cluster_id')
+
+    Clusters::Applications::Ingress.where('NOT EXISTS (?)', existing).in_batches do |batch|
+      batch.delete_all
+    end
+
+    unless foreign_keys_for(:clusters_applications_prometheus, :cluster_id).any?
+      add_concurrent_foreign_key :clusters_applications_prometheus, :clusters,
+        column: :cluster_id,
+        on_delete: :cascade
+    end
+  end
+
+  def down
+    if foreign_keys_for(:clusters_applications_ingress, :cluster_id).any?
+      remove_foreign_key :clusters_applications_ingress, column: :cluster_id
+    end
+
+    if foreign_keys_for(:clusters_applications_prometheus, :cluster_id).any?
+      remove_foreign_key :clusters_applications_prometheus, column: :cluster_id
+    end
+  end
+end
diff --git a/db/migrate/20180305095250_create_internal_ids_table.rb b/db/migrate/20180305095250_create_internal_ids_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..432086fe98b8de9c7446e95205efa3f8ef54dd1f
--- /dev/null
+++ b/db/migrate/20180305095250_create_internal_ids_table.rb
@@ -0,0 +1,15 @@
+class CreateInternalIdsTable < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    create_table :internal_ids, id: :bigserial do |t|
+      t.references :project, null: false, foreign_key: { on_delete: :cascade }
+      t.integer :usage, null: false
+      t.integer :last_value, null: false
+
+      t.index [:usage, :project_id], unique: true
+    end
+  end
+end
diff --git a/db/migrate/20180305144721_add_privileged_to_runner.rb b/db/migrate/20180305144721_add_privileged_to_runner.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32e73dba8d54bf564cad1bc564d2ffd095d24a23
--- /dev/null
+++ b/db/migrate/20180305144721_add_privileged_to_runner.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddPrivilegedToRunner < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default :clusters_applications_runners, :privileged, :boolean, default: true, allow_null: false
+  end
+
+  def down
+    remove_column :clusters_applications_runners, :privileged
+  end
+end
diff --git a/db/migrate/20180306134842_add_missing_indexes_acts_as_taggable_on_engine.rb b/db/migrate/20180306134842_add_missing_indexes_acts_as_taggable_on_engine.rb
new file mode 100644
index 0000000000000000000000000000000000000000..06e402adcd71c223844a9f894c473e8d1af90a57
--- /dev/null
+++ b/db/migrate/20180306134842_add_missing_indexes_acts_as_taggable_on_engine.rb
@@ -0,0 +1,21 @@
+# This migration comes from acts_as_taggable_on_engine (originally 6)
+#
+# It has been modified to handle no-downtime GitLab migrations. Several
+# indexes have been removed since they are not needed for GitLab.
+class AddMissingIndexesActsAsTaggableOnEngine < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :taggings, :tag_id unless index_exists? :taggings, :tag_id
+    add_concurrent_index :taggings, [:taggable_id, :taggable_type] unless index_exists? :taggings, [:taggable_id, :taggable_type]
+  end
+
+  def down
+    remove_concurrent_index :taggings, :tag_id
+    remove_concurrent_index :taggings, [:taggable_id, :taggable_type]
+  end
+end
diff --git a/db/migrate/20180308052825_add_section_name_id_index_on_ci_build_trace_sections.rb b/db/migrate/20180308052825_add_section_name_id_index_on_ci_build_trace_sections.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b616cc2fd30aab4cbcfa2fba1546fd0152e23947
--- /dev/null
+++ b/db/migrate/20180308052825_add_section_name_id_index_on_ci_build_trace_sections.rb
@@ -0,0 +1,23 @@
+class AddSectionNameIdIndexOnCiBuildTraceSections < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+  INDEX_NAME = 'index_ci_build_trace_sections_on_section_name_id'
+
+  disable_ddl_transaction!
+
+  def up
+    # MySQL may already have this as a foreign key
+    unless index_exists?(:ci_build_trace_sections, :section_name_id, name: INDEX_NAME)
+      add_concurrent_index :ci_build_trace_sections, :section_name_id, name: INDEX_NAME
+    end
+  end
+
+  def down
+    # We cannot remove index for MySQL because it's needed for foreign key
+    if Gitlab::Database.postgresql?
+      remove_concurrent_index :ci_build_trace_sections, :section_name_id, name: INDEX_NAME
+    end
+  end
+end
diff --git a/db/migrate/20180309121820_reschedule_commits_count_for_merge_request_diff.rb b/db/migrate/20180309121820_reschedule_commits_count_for_merge_request_diff.rb
new file mode 100644
index 0000000000000000000000000000000000000000..990759104b0112ee94ac9d6229a53b9d625cbc85
--- /dev/null
+++ b/db/migrate/20180309121820_reschedule_commits_count_for_merge_request_diff.rb
@@ -0,0 +1,30 @@
+class RescheduleCommitsCountForMergeRequestDiff < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  MIGRATION = 'AddMergeRequestDiffCommitsCount'.freeze
+  BATCH_SIZE = 5000
+  DELAY_INTERVAL = 5.minutes.to_i
+
+  class MergeRequestDiff < ActiveRecord::Base
+    self.table_name = 'merge_request_diffs'
+
+    include ::EachBatch
+  end
+
+  disable_ddl_transaction!
+
+  def up
+    say 'Populating the MergeRequestDiff `commits_count` (reschedule)'
+
+    execute("SET statement_timeout TO '60s'") if Gitlab::Database.postgresql?
+
+    MergeRequestDiff.where(commits_count: nil).each_batch(of: BATCH_SIZE) do |relation, index|
+      start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
+      delay = index * DELAY_INTERVAL
+
+      BackgroundMigrationWorker.perform_in(delay, MIGRATION, [start_id, end_id])
+    end
+  end
+end
diff --git a/db/migrate/20180309160427_add_partial_indexes_on_todos.rb b/db/migrate/20180309160427_add_partial_indexes_on_todos.rb
new file mode 100644
index 0000000000000000000000000000000000000000..18a5c69df1bc9d21794c7248a366c599954d9fc8
--- /dev/null
+++ b/db/migrate/20180309160427_add_partial_indexes_on_todos.rb
@@ -0,0 +1,28 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddPartialIndexesOnTodos < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+   disable_ddl_transaction!
+
+   INDEX_NAME_PENDING="index_todos_on_user_id_and_id_pending"
+   INDEX_NAME_DONE="index_todos_on_user_id_and_id_done"
+   
+  def up
+    unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_PENDING)
+      add_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING)
+    end
+    unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_DONE)
+      add_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE)
+    end
+  end
+
+  def down
+    remove_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING)
+    remove_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE)
+  end    
+end
diff --git a/db/migrate/20180319190020_create_deploy_tokens.rb b/db/migrate/20180319190020_create_deploy_tokens.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d129459ea0ac50c962135e9b114120cdf7f11bab
--- /dev/null
+++ b/db/migrate/20180319190020_create_deploy_tokens.rb
@@ -0,0 +1,19 @@
+class CreateDeployTokens < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    create_table :deploy_tokens do |t|
+      t.boolean :revoked, default: false
+      t.boolean :read_repository, null: false, default: false
+      t.boolean :read_registry, null: false, default: false
+
+      t.datetime_with_timezone :expires_at, null: false
+      t.datetime_with_timezone :created_at, null: false
+
+      t.string :name, null: false
+      t.string :token, index: { unique: true }, null: false
+
+      t.index [:token, :expires_at, :id], where: "(revoked IS FALSE)"
+    end
+  end
+end
diff --git a/db/migrate/20180320182229_add_indexes_for_user_activity_queries.rb b/db/migrate/20180320182229_add_indexes_for_user_activity_queries.rb
new file mode 100644
index 0000000000000000000000000000000000000000..824bbb3ac05a2cb6f722cd2e19e570019b69893f
--- /dev/null
+++ b/db/migrate/20180320182229_add_indexes_for_user_activity_queries.rb
@@ -0,0 +1,40 @@
+class AddIndexesForUserActivityQueries < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :events, [:author_id, :project_id] unless index_exists?(:events, [:author_id, :project_id])
+    add_concurrent_index :user_interacted_projects, :user_id unless index_exists?(:user_interacted_projects, :user_id)
+  end
+
+  def down
+    remove_concurrent_index :events, [:author_id, :project_id] if index_exists?(:events, [:author_id, :project_id])
+
+    patch_foreign_keys do
+      remove_concurrent_index :user_interacted_projects, :user_id if index_exists?(:user_interacted_projects, :user_id)
+    end
+  end
+
+  private
+
+  def patch_foreign_keys
+    return yield if Gitlab::Database.postgresql?
+
+    # MySQL doesn't like to remove the index with a foreign key using it.
+    remove_foreign_key :user_interacted_projects, :users if fk_exists?(:user_interacted_projects, :user_id)
+
+    yield
+
+    # Let's re-add the foreign key using the existing index on (user_id, project_id)
+    add_concurrent_foreign_key :user_interacted_projects, :users, column: :user_id unless fk_exists?(:user_interacted_projects, :user_id)
+  end
+
+  def fk_exists?(table, column)
+    foreign_keys(table).any? do |key|
+      key.options[:column] == column.to_s
+    end
+  end
+end
diff --git a/db/migrate/20180323150945_add_push_to_merge_request_to_notification_settings.rb b/db/migrate/20180323150945_add_push_to_merge_request_to_notification_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..12b8875d8dcd618deff23e57bb3cdbb6f90fb829
--- /dev/null
+++ b/db/migrate/20180323150945_add_push_to_merge_request_to_notification_settings.rb
@@ -0,0 +1,9 @@
+class AddPushToMergeRequestToNotificationSettings < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :notification_settings, :push_to_merge_request, :boolean
+  end
+end
diff --git a/db/migrate/20180327101207_remove_index_from_events_table.rb b/db/migrate/20180327101207_remove_index_from_events_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..172441da65b58329aa4f37c8624d36d0bd2d054d
--- /dev/null
+++ b/db/migrate/20180327101207_remove_index_from_events_table.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveIndexFromEventsTable < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    remove_concurrent_index :events, :author_id
+  end
+
+  def down
+    add_concurrent_index :events, :author_id
+  end
+end
diff --git a/db/migrate/20180330121048_add_issue_due_to_notification_settings.rb b/db/migrate/20180330121048_add_issue_due_to_notification_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c64a481fcf08e516e7f22df04cb04244a32603e0
--- /dev/null
+++ b/db/migrate/20180330121048_add_issue_due_to_notification_settings.rb
@@ -0,0 +1,9 @@
+class AddIssueDueToNotificationSettings < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :notification_settings, :issue_due, :boolean
+  end
+end
diff --git a/db/migrate/20180405142733_create_project_deploy_tokens.rb b/db/migrate/20180405142733_create_project_deploy_tokens.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9d8f89243a82f31053ee19fec40c5937111ec3a8
--- /dev/null
+++ b/db/migrate/20180405142733_create_project_deploy_tokens.rb
@@ -0,0 +1,16 @@
+class CreateProjectDeployTokens < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    create_table :project_deploy_tokens do |t|
+      t.integer :project_id, null: false
+      t.integer :deploy_token_id, null: false
+      t.datetime_with_timezone :created_at, null: false
+
+      t.foreign_key :deploy_tokens, column: :deploy_token_id, on_delete: :cascade
+      t.foreign_key :projects, column: :project_id, on_delete: :cascade
+
+      t.index [:project_id, :deploy_token_id], unique: true
+    end
+  end
+end
diff --git a/db/migrate/20180418053107_add_index_to_ci_job_artifacts_file_store.rb b/db/migrate/20180418053107_add_index_to_ci_job_artifacts_file_store.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1084ca14a345136b1f3bdab7b0c99c1f73cb8f8d
--- /dev/null
+++ b/db/migrate/20180418053107_add_index_to_ci_job_artifacts_file_store.rb
@@ -0,0 +1,15 @@
+class AddIndexToCiJobArtifactsFileStore < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :ci_job_artifacts, :file_store
+  end
+
+  def down
+    remove_index :ci_job_artifacts, :file_store if index_exists?(:ci_job_artifacts, :file_store)
+  end
+end
diff --git a/db/post_migrate/20180104131052_schedule_set_confidential_note_events_on_webhooks.rb b/db/post_migrate/20180104131052_schedule_set_confidential_note_events_on_webhooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fa51ac8361907ad5214f4807d9f3c2b459febf4b
--- /dev/null
+++ b/db/post_migrate/20180104131052_schedule_set_confidential_note_events_on_webhooks.rb
@@ -0,0 +1,23 @@
+class ScheduleSetConfidentialNoteEventsOnWebhooks < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+  BATCH_SIZE = 1_000
+  INTERVAL = 5.minutes
+
+  disable_ddl_transaction!
+
+  def up
+    migration = Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks
+    migration_name = migration.to_s.demodulize
+    relation = migration::WebHook.hooks_to_update
+
+    queue_background_migration_jobs_by_range_at_intervals(relation,
+                                                          migration_name,
+                                                          INTERVAL,
+                                                          batch_size: BATCH_SIZE)
+  end
+
+  def down
+  end
+end
diff --git a/db/post_migrate/20180122154930_schedule_set_confidential_note_events_on_services.rb b/db/post_migrate/20180122154930_schedule_set_confidential_note_events_on_services.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a3ff9f1719eeb324d94820aa2770b236ed4316c5
--- /dev/null
+++ b/db/post_migrate/20180122154930_schedule_set_confidential_note_events_on_services.rb
@@ -0,0 +1,23 @@
+class ScheduleSetConfidentialNoteEventsOnServices < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+  BATCH_SIZE = 1_000
+  INTERVAL = 20.minutes
+
+  disable_ddl_transaction!
+
+  def up
+    migration = Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices
+    migration_name = migration.to_s.demodulize
+    relation = migration::Service.services_to_update
+
+    queue_background_migration_jobs_by_range_at_intervals(relation,
+                                                          migration_name,
+                                                          INTERVAL,
+                                                          batch_size: BATCH_SIZE)
+  end
+
+  def down
+  end
+end
diff --git a/db/post_migrate/20180212101928_schedule_build_stage_migration.rb b/db/post_migrate/20180212101928_schedule_build_stage_migration.rb
index df15b2cd9d484eeddbe69c4708ba9bed7d53529b..0f61fa8183238538fe0d564012187c579089b934 100644
--- a/db/post_migrate/20180212101928_schedule_build_stage_migration.rb
+++ b/db/post_migrate/20180212101928_schedule_build_stage_migration.rb
@@ -1,26 +1,11 @@
 class ScheduleBuildStageMigration < ActiveRecord::Migration
-  include Gitlab::Database::MigrationHelpers
-
-  DOWNTIME = false
-  MIGRATION = 'MigrateBuildStage'.freeze
-  BATCH_SIZE = 500
-
-  disable_ddl_transaction!
-
-  class Build < ActiveRecord::Base
-    include EachBatch
-    self.table_name = 'ci_builds'
-  end
+  ##
+  # This migration has been rescheduled to run again, see
+  # `20180405101928_reschedule_builds_stages_migration.rb`
+  #
 
   def up
-    disable_statement_timeout
-
-    Build.where('stage_id IS NULL').tap do |relation|
-      queue_background_migration_jobs_by_range_at_intervals(relation,
-                                                            MIGRATION,
-                                                            5.minutes,
-                                                            batch_size: BATCH_SIZE)
-    end
+    # noop
   end
 
   def down
diff --git a/db/post_migrate/20180220150310_remove_empty_extern_uid_auth0_identities.rb b/db/post_migrate/20180220150310_remove_empty_extern_uid_auth0_identities.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2d5a8617169c5d1df52a0dcc9b2b06397fe09e65
--- /dev/null
+++ b/db/post_migrate/20180220150310_remove_empty_extern_uid_auth0_identities.rb
@@ -0,0 +1,25 @@
+class RemoveEmptyExternUidAuth0Identities < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  class Identity < ActiveRecord::Base
+    self.table_name = 'identities'
+    include EachBatch
+  end
+
+  def up
+    broken_auth0_identities.each_batch do |identity|
+      identity.delete_all
+    end
+  end
+
+  def broken_auth0_identities
+    Identity.where(provider: 'auth0', extern_uid: [nil, ''])
+  end
+
+  def down
+  end
+end
diff --git a/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9addd36dca6cd90970c00412deedeab67a3ddb13
--- /dev/null
+++ b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
@@ -0,0 +1,170 @@
+require_relative '../migrate/20180223120443_create_user_interacted_projects_table.rb'
+# rubocop:disable AddIndex
+# rubocop:disable AddConcurrentForeignKey
+class BuildUserInteractedProjectsTable < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  UNIQUE_INDEX_NAME = 'index_user_interacted_projects_on_project_id_and_user_id'
+
+  disable_ddl_transaction!
+
+  def up
+    if Gitlab::Database.postgresql?
+      PostgresStrategy.new
+    else
+      MysqlStrategy.new
+    end.up
+
+    if index_exists_by_name?(:user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME)
+      remove_concurrent_index_by_name :user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME
+    end
+  end
+
+  def down
+    execute "TRUNCATE user_interacted_projects"
+
+    if foreign_key_exists?(:user_interacted_projects, :users)
+      remove_foreign_key :user_interacted_projects, :users
+    end
+
+    if foreign_key_exists?(:user_interacted_projects, :projects)
+      remove_foreign_key :user_interacted_projects, :projects
+    end
+
+    if index_exists_by_name?(:user_interacted_projects, UNIQUE_INDEX_NAME)
+      remove_concurrent_index_by_name :user_interacted_projects, UNIQUE_INDEX_NAME
+    end
+
+    unless index_exists_by_name?(:user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME)
+      add_concurrent_index :user_interacted_projects, [:project_id, :user_id], name: CreateUserInteractedProjectsTable::INDEX_NAME
+    end
+  end
+
+  private
+
+  class PostgresStrategy < ActiveRecord::Migration
+    include Gitlab::Database::MigrationHelpers
+
+    BATCH_SIZE = 100_000
+    SLEEP_TIME = 5
+
+    def up
+      with_index(:events, [:author_id, :project_id], name: 'events_user_interactions_temp', where: 'project_id IS NOT NULL') do
+        insert_missing_records
+
+        # Do this once without lock to speed up the second invocation
+        remove_duplicates
+        with_table_lock(:user_interacted_projects) do
+          remove_duplicates
+          create_unique_index
+        end
+
+        remove_without_project
+        with_table_lock(:user_interacted_projects, :projects) do
+          remove_without_project
+          create_fk :user_interacted_projects, :projects, :project_id
+        end
+
+        remove_without_user
+        with_table_lock(:user_interacted_projects, :users) do
+          remove_without_user
+          create_fk :user_interacted_projects, :users, :user_id
+        end
+      end
+
+      execute "ANALYZE user_interacted_projects"
+    end
+
+    private
+    def insert_missing_records
+      iteration = 0
+      records = 0
+      begin
+        Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}"
+        result = execute <<~SQL
+            INSERT INTO user_interacted_projects (user_id, project_id)
+            SELECT e.user_id, e.project_id
+            FROM (SELECT DISTINCT author_id AS user_id, project_id FROM events WHERE project_id IS NOT NULL) AS e
+            LEFT JOIN user_interacted_projects ucp USING (user_id, project_id)
+            WHERE ucp.user_id IS NULL
+            LIMIT #{BATCH_SIZE}
+        SQL
+        iteration += 1
+        records += result.cmd_tuples
+        Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall"
+        Kernel.sleep(SLEEP_TIME) if result.cmd_tuples > 0
+      end while result.cmd_tuples > 0
+    end
+
+    def remove_duplicates
+      execute <<~SQL
+        WITH numbered AS (select ctid, ROW_NUMBER() OVER (PARTITION BY (user_id, project_id)) as row_number, user_id, project_id from user_interacted_projects)
+        DELETE FROM user_interacted_projects WHERE ctid IN (SELECT ctid FROM numbered WHERE row_number > 1);
+      SQL
+    end
+
+    def remove_without_project
+      execute "DELETE FROM user_interacted_projects WHERE NOT EXISTS (SELECT 1 FROM projects WHERE id = user_interacted_projects.project_id)"
+    end
+
+    def remove_without_user
+      execute "DELETE FROM user_interacted_projects WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = user_interacted_projects.user_id)"
+    end
+
+    def create_fk(table, target, column)
+      return if foreign_key_exists?(table, target, column: column)
+
+      add_foreign_key table, target, column: column, on_delete: :cascade
+    end
+
+    def create_unique_index
+      return if index_exists_by_name?(:user_interacted_projects, UNIQUE_INDEX_NAME)
+
+      add_index :user_interacted_projects, [:project_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
+    end
+
+    # Protect table against concurrent data changes while still allowing reads
+    def with_table_lock(*tables)
+      ActiveRecord::Base.connection.transaction do
+        execute "LOCK TABLE #{tables.join(", ")} IN SHARE MODE"
+        yield
+      end
+    end
+
+    def with_index(*args)
+      add_concurrent_index(*args) unless index_exists?(*args)
+      yield
+    ensure
+      remove_concurrent_index(*args) if index_exists?(*args)
+    end
+  end
+
+  class MysqlStrategy < ActiveRecord::Migration
+    include Gitlab::Database::MigrationHelpers
+
+    def up
+      execute <<~SQL
+        INSERT INTO user_interacted_projects (user_id, project_id)
+        SELECT e.user_id, e.project_id
+        FROM (SELECT DISTINCT author_id AS user_id, project_id FROM events WHERE project_id IS NOT NULL) AS e
+        LEFT JOIN user_interacted_projects ucp USING (user_id, project_id)
+        WHERE ucp.user_id IS NULL
+      SQL
+
+      unless index_exists?(:user_interacted_projects, [:project_id, :user_id])
+        add_concurrent_index :user_interacted_projects, [:project_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
+      end
+
+      unless foreign_key_exists?(:user_interacted_projects, :users, column: :user_id)
+        add_concurrent_foreign_key :user_interacted_projects, :users, column: :user_id, on_delete: :cascade
+      end
+
+      unless foreign_key_exists?(:user_interacted_projects, :projects, column: :project_id)
+        add_concurrent_foreign_key :user_interacted_projects, :projects, column: :project_id, on_delete: :cascade
+      end
+    end
+  end
+end
diff --git a/db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb b/db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..db5165dbe701e87216bdaae61c59bfac871a1d6f
--- /dev/null
+++ b/db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb
@@ -0,0 +1,37 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemovePermanentFromRedirectRoutes < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+  disable_ddl_transaction!
+
+  INDEX_NAME_PERM = "index_redirect_routes_on_path_text_pattern_ops_where_permanent"
+  INDEX_NAME_TEMP = "index_redirect_routes_on_path_text_pattern_ops_where_temporary"
+
+  def up
+    # These indexes were created on Postgres only in:
+    # ReworkRedirectRoutesIndexes:
+    # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16211
+    if Gitlab::Database.postgresql?
+      disable_statement_timeout
+
+      execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_PERM};"
+      execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_TEMP};"
+    end
+
+    remove_column(:redirect_routes, :permanent)
+  end
+
+  def down
+    add_column(:redirect_routes, :permanent, :boolean)
+
+    if Gitlab::Database.postgresql?
+      disable_statement_timeout
+
+      execute("CREATE INDEX CONCURRENTLY #{INDEX_NAME_PERM} ON redirect_routes (lower(path) varchar_pattern_ops) where (permanent);")
+      execute("CREATE INDEX CONCURRENTLY #{INDEX_NAME_TEMP} ON redirect_routes (lower(path) varchar_pattern_ops) where (not permanent or permanent is null) ;")
+    end
+  end
+end
diff --git a/db/post_migrate/20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb b/db/post_migrate/20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0af1c3bc0a5d40c5aa0d9ebe36ac0ee6614c4f84
--- /dev/null
+++ b/db/post_migrate/20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb
@@ -0,0 +1,13 @@
+class MigrateCreateTraceArtifactSidekiqQueue < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    sidekiq_queue_migrate 'pipeline_default:create_trace_artifact', to: 'pipeline_background:archive_trace'
+  end
+
+  def down
+    sidekiq_queue_migrate 'pipeline_background:archive_trace', to: 'pipeline_default:create_trace_artifact'
+  end
+end
diff --git a/db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb b/db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d6fb4f06695bd36e0a92d8aba4653d118c641c57
--- /dev/null
+++ b/db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb
@@ -0,0 +1,38 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddPathIndexToRedirectRoutes < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+  disable_ddl_transaction!
+
+  INDEX_NAME = 'index_redirect_routes_on_path_unique_text_pattern_ops'
+
+  # Indexing on LOWER(path) varchar_pattern_ops speeds up the LIKE query in
+  # RedirectRoute.matching_path_and_descendants
+  #
+  # This same index is also added in the `ReworkRedirectRoutesIndexes` so this
+  # is a no-op in most cases. But this migration is also called from the
+  # `setup_postgresql.rake` task when setting up a new database, in which case
+  # we want to create the index.
+  def up
+    return unless Gitlab::Database.postgresql?
+
+    disable_statement_timeout
+
+    unless index_exists_by_name?(:redirect_routes, INDEX_NAME)
+      execute("CREATE UNIQUE INDEX CONCURRENTLY #{INDEX_NAME} ON redirect_routes (lower(path) varchar_pattern_ops);")
+    end
+  end
+
+  def down
+    # Do nothing in the DOWN. Since the index above is originally created in the
+    # `ReworkRedirectRoutesIndexes`. This migration wouldn't have actually
+    # created any new index.
+    #
+    # This migration is only here to be called form `setup_postgresql.rake` so
+    # any newly created database would have this index.
+  end
+end
diff --git a/db/post_migrate/20180307012445_migrate_update_head_pipeline_for_merge_request_sidekiq_queue.rb b/db/post_migrate/20180307012445_migrate_update_head_pipeline_for_merge_request_sidekiq_queue.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9728df6d409f52092d13eb02cdf48c89d3acde84
--- /dev/null
+++ b/db/post_migrate/20180307012445_migrate_update_head_pipeline_for_merge_request_sidekiq_queue.rb
@@ -0,0 +1,15 @@
+class MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    sidekiq_queue_migrate 'pipeline_default:update_head_pipeline_for_merge_request',
+      to: 'pipeline_processing:update_head_pipeline_for_merge_request'
+  end
+
+  def down
+    sidekiq_queue_migrate 'pipeline_processing:update_head_pipeline_for_merge_request',
+      to: 'pipeline_default:update_head_pipeline_for_merge_request'
+  end
+end
diff --git a/db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb b/db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e19387bce1ee9a874155ffb28eccd72a629948db
--- /dev/null
+++ b/db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb
@@ -0,0 +1,33 @@
+class RescheduleBuildsStagesMigration < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  ##
+  # Rescheduled `20180212101928_schedule_build_stage_migration.rb`
+  #
+
+  DOWNTIME = false
+  MIGRATION = 'MigrateBuildStage'.freeze
+  BATCH_SIZE = 500
+
+  disable_ddl_transaction!
+
+  class Build < ActiveRecord::Base
+    include EachBatch
+    self.table_name = 'ci_builds'
+  end
+
+  def up
+    disable_statement_timeout
+
+    Build.where('stage_id IS NULL').tap do |relation|
+      queue_background_migration_jobs_by_range_at_intervals(relation,
+                                                            MIGRATION,
+                                                            5.minutes,
+                                                            batch_size: BATCH_SIZE)
+    end
+  end
+
+  def down
+    # noop
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index db8bafe967795f5ded08c8687cfb85f82735e88b..1d272bdb77918561c3eaf8937c4ecf454cc09131 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: 20180301084653) do
+ActiveRecord::Schema.define(version: 20180418053107) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -157,6 +157,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.boolean "authorized_keys_enabled", default: true, null: false
     t.string "auto_devops_domain"
     t.boolean "pages_domain_verification_enabled", default: true, null: false
+    t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false
   end
 
   create_table "audit_events", force: :cascade do |t|
@@ -183,12 +184,27 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree
   add_index "award_emoji", ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree
 
+  create_table "badges", force: :cascade do |t|
+    t.string "link_url", null: false
+    t.string "image_url", null: false
+    t.integer "project_id"
+    t.integer "group_id"
+    t.string "type", null: false
+    t.datetime_with_timezone "created_at", null: false
+    t.datetime_with_timezone "updated_at", null: false
+  end
+
+  add_index "badges", ["group_id"], name: "index_badges_on_group_id", using: :btree
+  add_index "badges", ["project_id"], name: "index_badges_on_project_id", using: :btree
+
   create_table "boards", force: :cascade do |t|
-    t.integer "project_id", null: false
+    t.integer "project_id"
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
+    t.integer "group_id"
   end
 
+  add_index "boards", ["group_id"], name: "index_boards_on_group_id", using: :btree
   add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree
 
   create_table "broadcast_messages", force: :cascade do |t|
@@ -249,6 +265,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
 
   add_index "ci_build_trace_sections", ["build_id", "section_name_id"], name: "index_ci_build_trace_sections_on_build_id_and_section_name_id", unique: true, using: :btree
   add_index "ci_build_trace_sections", ["project_id"], name: "index_ci_build_trace_sections_on_project_id", using: :btree
+  add_index "ci_build_trace_sections", ["section_name_id"], name: "index_ci_build_trace_sections_on_section_name_id", using: :btree
 
   create_table "ci_builds", force: :cascade do |t|
     t.string "status"
@@ -290,6 +307,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.integer "auto_canceled_by_id"
     t.boolean "retried"
     t.integer "stage_id"
+    t.integer "artifacts_file_store"
+    t.integer "artifacts_metadata_store"
     t.boolean "protected"
     t.integer "failure_reason"
   end
@@ -310,6 +329,16 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
   add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
 
+  create_table "ci_builds_metadata", force: :cascade do |t|
+    t.integer "build_id", null: false
+    t.integer "project_id", null: false
+    t.integer "timeout"
+    t.integer "timeout_source", default: 1, null: false
+  end
+
+  add_index "ci_builds_metadata", ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree
+  add_index "ci_builds_metadata", ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree
+
   create_table "ci_group_variables", force: :cascade do |t|
     t.string "key", null: false
     t.text "value"
@@ -328,14 +357,17 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.integer "project_id", null: false
     t.integer "job_id", null: false
     t.integer "file_type", null: false
+    t.integer "file_store"
     t.integer "size", limit: 8
     t.datetime_with_timezone "created_at", null: false
     t.datetime_with_timezone "updated_at", null: false
     t.datetime_with_timezone "expire_at"
     t.string "file"
+    t.binary "file_sha256"
   end
 
   add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree
+  add_index "ci_job_artifacts", ["file_store"], name: "index_ci_job_artifacts_on_file_store", using: :btree
   add_index "ci_job_artifacts", ["job_id", "file_type"], name: "index_ci_job_artifacts_on_job_id_and_file_type", unique: true, using: :btree
   add_index "ci_job_artifacts", ["project_id"], name: "index_ci_job_artifacts_on_project_id", using: :btree
 
@@ -438,6 +470,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.boolean "locked", default: false, null: false
     t.integer "access_level", default: 0, null: false
     t.string "ip_address"
+    t.integer "maximum_timeout"
   end
 
   add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
@@ -590,6 +623,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.datetime_with_timezone "updated_at", null: false
     t.string "version", null: false
     t.text "status_reason"
+    t.boolean "privileged", default: true, null: false
   end
 
   add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
@@ -650,6 +684,19 @@ ActiveRecord::Schema.define(version: 20180301084653) do
 
   add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
 
+  create_table "deploy_tokens", force: :cascade do |t|
+    t.boolean "revoked", default: false
+    t.boolean "read_repository", default: false, null: false
+    t.boolean "read_registry", default: false, null: false
+    t.datetime_with_timezone "expires_at", null: false
+    t.datetime_with_timezone "created_at", null: false
+    t.string "name", null: false
+    t.string "token", null: false
+  end
+
+  add_index "deploy_tokens", ["token", "expires_at", "id"], name: "index_deploy_tokens_on_token_and_expires_at_and_id", where: "(revoked IS FALSE)", using: :btree
+  add_index "deploy_tokens", ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree
+
   create_table "deployments", force: :cascade do |t|
     t.integer "iid", null: false
     t.integer "project_id", null: false
@@ -709,7 +756,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   end
 
   add_index "events", ["action"], name: "index_events_on_action", using: :btree
-  add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree
+  add_index "events", ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id", using: :btree
   add_index "events", ["project_id", "id"], name: "index_events_on_project_id_and_id", using: :btree
   add_index "events", ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id", using: :btree
 
@@ -848,6 +895,14 @@ ActiveRecord::Schema.define(version: 20180301084653) do
 
   add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
 
+  create_table "internal_ids", id: :bigserial, force: :cascade do |t|
+    t.integer "project_id", null: false
+    t.integer "usage", null: false
+    t.integer "last_value", null: false
+  end
+
+  add_index "internal_ids", ["usage", "project_id"], name: "index_internal_ids_on_usage_and_project_id", unique: true, using: :btree
+
   create_table "issue_assignees", id: false, force: :cascade do |t|
     t.integer "user_id", null: false
     t.integer "issue_id", null: false
@@ -891,6 +946,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.integer "last_edited_by_id"
     t.boolean "discussion_locked"
     t.datetime_with_timezone "closed_at"
+    t.integer "closed_by_id"
   end
 
   add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
@@ -981,6 +1037,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string "file"
+    t.integer "file_store"
   end
 
   add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
@@ -1128,6 +1185,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.boolean "discussion_locked"
     t.integer "latest_merge_request_diff_id"
     t.string "rebase_commit_sha"
+    t.boolean "allow_maintainer_to_push"
   end
 
   add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -1267,6 +1325,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.boolean "merge_merge_request"
     t.boolean "failed_pipeline"
     t.boolean "success_pipeline"
+    t.boolean "push_to_merge_request"
+    t.boolean "issue_due"
   end
 
   add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
@@ -1385,6 +1445,14 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_index "project_custom_attributes", ["key", "value"], name: "index_project_custom_attributes_on_key_and_value", using: :btree
   add_index "project_custom_attributes", ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true, using: :btree
 
+  create_table "project_deploy_tokens", force: :cascade do |t|
+    t.integer "project_id", null: false
+    t.integer "deploy_token_id", null: false
+    t.datetime_with_timezone "created_at", null: false
+  end
+
+  add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
+
   create_table "project_features", force: :cascade do |t|
     t.integer "project_id"
     t.integer "merge_requests_access_level"
@@ -1484,6 +1552,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.boolean "merge_requests_ff_only_enabled", default: false
     t.boolean "merge_requests_rebase_enabled", default: false, null: false
     t.integer "jobs_cache_index"
+    t.boolean "pages_https_only", default: true
   end
 
   add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
@@ -1571,7 +1640,6 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.string "path", null: false
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
-    t.boolean "permanent"
   end
 
   add_index "redirect_routes", ["path"], name: "index_redirect_routes_on_path", unique: true, using: :btree
@@ -1639,6 +1707,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.boolean "confidential_issues_events", default: true, null: false
     t.boolean "commit_events", default: true, null: false
     t.boolean "job_events", default: false, null: false
+    t.boolean "confidential_note_events", default: true
   end
 
   add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
@@ -1715,7 +1784,9 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   end
 
   add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree
+  add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
   add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
+  add_index "taggings", ["taggable_id", "taggable_type"], name: "index_taggings_on_taggable_id_and_taggable_type", using: :btree
 
   create_table "tags", force: :cascade do |t|
     t.string "name"
@@ -1757,6 +1828,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree
   add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree
   add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
+  add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_done", where: "((state)::text = 'done'::text)", using: :btree
+  add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_pending", where: "((state)::text = 'pending'::text)", using: :btree
   add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
 
   create_table "trending_projects", force: :cascade do |t|
@@ -1789,6 +1862,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.datetime "created_at", null: false
     t.string "mount_point"
     t.string "secret"
+    t.integer "store"
   end
 
   add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
@@ -1826,6 +1900,14 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_index "user_custom_attributes", ["key", "value"], name: "index_user_custom_attributes_on_key_and_value", using: :btree
   add_index "user_custom_attributes", ["user_id", "key"], name: "index_user_custom_attributes_on_user_id_and_key", unique: true, using: :btree
 
+  create_table "user_interacted_projects", id: false, force: :cascade do |t|
+    t.integer "user_id", null: false
+    t.integer "project_id", null: false
+  end
+
+  add_index "user_interacted_projects", ["project_id", "user_id"], name: "index_user_interacted_projects_on_project_id_and_user_id", unique: true, using: :btree
+  add_index "user_interacted_projects", ["user_id"], name: "index_user_interacted_projects_on_user_id", using: :btree
+
   create_table "user_synced_attributes_metadata", force: :cascade do |t|
     t.boolean "name_synced", default: false
     t.boolean "email_synced", default: false
@@ -1964,11 +2046,15 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.boolean "confidential_issues_events", default: false, null: false
     t.boolean "repository_update_events", default: false, null: false
     t.boolean "job_events", default: false, null: false
+    t.boolean "confidential_note_events"
   end
 
   add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
   add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree
 
+  add_foreign_key "badges", "namespaces", column: "group_id", on_delete: :cascade
+  add_foreign_key "badges", "projects", on_delete: :cascade
+  add_foreign_key "boards", "namespaces", column: "group_id", on_delete: :cascade
   add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade
   add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
   add_foreign_key "ci_build_trace_section_names", "projects", on_delete: :cascade
@@ -1978,6 +2064,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
   add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
   add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
+  add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade
+  add_foreign_key "ci_builds_metadata", "projects", on_delete: :cascade
   add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
   add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade
   add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
@@ -2001,6 +2089,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_foreign_key "cluster_providers_gcp", "clusters", on_delete: :cascade
   add_foreign_key "clusters", "users", on_delete: :nullify
   add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
+  add_foreign_key "clusters_applications_ingress", "clusters", name: "fk_753a7b41c1", on_delete: :cascade
+  add_foreign_key "clusters_applications_prometheus", "clusters", name: "fk_557e773639", on_delete: :cascade
   add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify
   add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
   add_foreign_key "container_repositories", "projects"
@@ -2023,6 +2113,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
   add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
   add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade
+  add_foreign_key "internal_ids", "projects", on_delete: :cascade
   add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
   add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
   add_foreign_key "issue_metrics", "issues", on_delete: :cascade
@@ -2030,6 +2121,7 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_foreign_key "issues", "milestones", name: "fk_96b1dd429c", on_delete: :nullify
   add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade
   add_foreign_key "issues", "users", column: "author_id", name: "fk_05f1e72feb", on_delete: :nullify
+  add_foreign_key "issues", "users", column: "closed_by_id", name: "fk_c63cbf6c25", on_delete: :nullify
   add_foreign_key "issues", "users", column: "updated_by_id", name: "fk_ffed080f01", on_delete: :nullify
   add_foreign_key "label_priorities", "labels", on_delete: :cascade
   add_foreign_key "label_priorities", "projects", on_delete: :cascade
@@ -2068,6 +2160,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_foreign_key "project_authorizations", "users", on_delete: :cascade
   add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
   add_foreign_key "project_custom_attributes", "projects", on_delete: :cascade
+  add_foreign_key "project_deploy_tokens", "deploy_tokens", on_delete: :cascade
+  add_foreign_key "project_deploy_tokens", "projects", on_delete: :cascade
   add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
   add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
   add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
@@ -2095,6 +2189,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_foreign_key "u2f_registrations", "users"
   add_foreign_key "user_callouts", "users", on_delete: :cascade
   add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
+  add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade
+  add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade
   add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
   add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
   add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index fb7a23e2750d1096720cfc16fdc4a0ac0f2fd58b..a841a4cfbf1e011a433fe10e023ebbdc31ec721b 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -11,35 +11,62 @@ GitLab offers the most scalable Git-based fully integrated platform for
 software development, with flexible products and subscriptions.
 To understand what features you have access to, check the [GitLab subscriptions](#gitlab-subscriptions) below.
 
-## Shortcuts to GitLab's most visited docs
+**Shortcuts to GitLab's most visited docs:**
 
-| [GitLab CI/CD](ci/README.md) | Other |
+| General documentation | GitLab CI/CD docs |
 | :----- | :----- |
-| [Quick start guide](ci/quick_start/README.md) | [API](api/README.md) |
-| [Configuring `.gitlab-ci.yml`](ci/yaml/README.md) | [SSH authentication](ssh/README.md) |
-| [Using Docker images](ci/docker/using_docker_images.md) | [GitLab Pages](user/project/pages/index.md) |
-
-- [User documentation](user/index.md)
-- [Administrator documentation](administration/index.md)
-- [Contributor documentation](#contributor-documentation)
+| [User documentation](user/index.md) | [GitLab CI/CD](ci/README.md) |
+| [Administrator documentation](administration/index.md) | [GitLab CI/CD quick start guide](ci/quick_start/README.md) |
+| [Contributor documentation](#contributor-documentation) | [Configuring `.gitlab-ci.yml`](ci/yaml/README.md) |
+| [Getting started with GitLab](#getting-started-with-gitlab) | [Using Docker images](ci/docker/using_docker_images.md) |
+| [API](api/README.md) | [Auto DevOps](topics/autodevops/index.md) |
+| [SSH authentication](ssh/README.md) | [Kubernetes integration](user/project/clusters/index.md)|
+| [GitLab Pages](user/project/pages/index.md) | [GitLab Container Registry](user/project/container_registry.md) |
+
+## Complete DevOps with GitLab
+
+GitLab is the first single application for software development, security,
+and operations that enables Concurrent DevOps, making the software lifecycle
+three times faster and radically improving the speed of business. GitLab
+provides solutions for all the stages of the DevOps lifecycle:
+[plan](#plan), [create](#create), [verify](#verify), [package](#package),
+[release](#release), [configure](#configure), [monitor](#monitor).
+
+![DevOps Lifecycle](img/devops_lifecycle.png)
+
+### Plan
+
+Whether you use Waterfall, Agile, or Conversational Development,
+GitLab streamlines your collaborative workflows. Visualize, prioritize,
+coordinate, and track your progress your way with GitLab鈥檚 flexible project
+management tools.
+
+- Chat operations
+  - [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md)
+  - [Slack slash commands](user/project/integrations/slack_slash_commands.md)
+- [Discussions](user/discussions/index.md): Threads, comments, and resolvable discussions in issues, commits, and  merge requests.
+- [Issues](user/project/issues/index.md)
+- [Project Issue Board](user/project/issue_board.md)
+- [Issues and merge requests templates](user/project/description_templates.md): Create templates for submitting new issues and merge requests.
+- [Labels](user/project/labels.md): Categorize your issues or merge requests based on descriptive titles.
+- [Milestones](user/project/milestones/index.md): Organize issues and merge requests into a cohesive group, optionally setting a due date.
+- [Todos](workflow/todos.md): A chronological list of to-dos that are waiting for your input, all in a simple dashboard.
+- [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.
 
-## Getting started with GitLab
+#### Migrate and import your projects from other platforms
 
-- [GitLab Basics](gitlab-basics/README.md): Start working on your command line and on GitLab.
-- [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 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.
-- [Auto DevOps](topics/autodevops/index.md)
+- [Importing to GitLab](user/project/import/index.md): Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
+- [Migrating from SVN](workflow/importing/migrating_from_svn.md): Convert a SVN repository to Git and GitLab.
 
-### User account
+### Create
 
-- [User account](user/profile/index.md): Manage your account
-  - [Authentication](topics/authentication/index.md): Account security with two-factor authentication, setup your ssh keys and deploy keys for secure access to your projects.
-  - [Profile settings](user/profile/index.md#profile-settings): Manage your profile settings, two factor authentication and more.
-- [User permissions](user/permissions.md): Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
+Consolidate source code into a single [DVCS](https://en.wikipedia.org/wiki/Distributed_version_control)
+that鈥檚 easily managed and controlled without disrupting your workflow.
+GitLab鈥檚 git repositories come complete with branching tools and access
+controls, providing a scalable, single source of truth for collaborating
+on projects and code.
 
-### Projects and groups
+#### Projects and groups
 
 - [Projects](user/project/index.md):
   - [Project settings](user/project/settings/index.md)
@@ -53,8 +80,9 @@ To understand what features you have access to, check the [GitLab subscriptions]
 - [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards.
 - [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
 - [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis.
+- [Web IDE](user/project/web_ide/index.md)
 
-### Repository
+#### Repositories
 
 Manage your [repositories](user/project/repository/index.md) from the UI (user interface):
 
@@ -72,50 +100,92 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
 - [Commits](user/project/repository/index.md#commits)
   - [Signing commits](user/project/repository/gpg_signed_commits/index.md): use GPG to sign your commits.
 
-### Issues and Merge Requests (MRs)
+#### Integrations
+
+- [Project Services](user/project/integrations/project_services.md): Integrate a project with external services, such as CI and chat.
+- [GitLab Integration](integration/README.md): Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication.
+- [Trello Power-Up](integration/trello_power_up.md): Integrate with GitLab's Trello Power-Up
+
+#### Automation
+
+- [API](api/README.md): Automate GitLab via a simple and powerful API.
+- [GitLab Webhooks](user/project/integrations/webhooks.md): Let GitLab notify you when new code has been pushed to your project.
+
+### Verify
+
+Spot errors sooner and shorten feedback cycles with built-in code review, code testing,
+Code Quality, and Review Apps. Customize your approval workflow controls, automatically
+test the quality of your code, and spin up a staging environment for every code change.
+GitLab Continuous Integration is the most popular next generation testing system that
+auto scales to run your tests faster.
 
-- [Discussions](user/discussions/index.md): Threads, comments, and resolvable discussions in issues, commits, and  merge requests.
-- [Issues](user/project/issues/index.md)
-- [Project issue Board](user/project/issue_board.md)
-- [Issues and merge requests templates](user/project/description_templates.md): Create templates for submitting new issues and merge requests.
-- [Labels](user/project/labels.md): Categorize your issues or merge requests based on descriptive titles.
 - [Merge Requests](user/project/merge_requests/index.md)
   - [Work In Progress Merge Requests](user/project/merge_requests/work_in_progress_merge_requests.md)
   - [Merge Request discussion resolution](user/discussions/index.md#moving-a-single-discussion-to-a-new-issue): Resolve discussions, move discussions in a merge request to an issue, only allow merge requests to be merged if all discussions are resolved.
   - [Checkout merge requests locally](user/project/merge_requests/index.md#checkout-merge-requests-locally)
   - [Cherry-pick](user/project/merge_requests/cherry_pick_changes.md)
-- [Milestones](user/project/milestones/index.md): Organize issues and merge requests into a cohesive group, optionally setting a due date.
-- [Todos](workflow/todos.md): A chronological list of to-dos that are waiting for your input, all in a simple dashboard.
+- [Review Apps](ci/review_apps/index.md): Preview changes to your app right from a merge request.
 
-### Git and GitLab
+### Package
 
-- [Git](topics/git/index.md): Getting started with Git, branching strategies, Git LFS, advanced use.
-- [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf): Download a PDF describing the most used Git operations.
-- [GitLab Flow](workflow/gitlab_flow.md): explore the best of Git with the GitLab Flow strategy.
+GitLab Container Registry gives you the enhanced security and access controls of
+custom Docker images without 3rd party add-ons. Easily upload and download images
+from GitLab CI/CD with full Git repository management integration.
 
-### Migrate and import your projects from other platforms
+- [GitLab CI/CD](ci/README.md): Explore the features and capabilities of Continuous Integration, Continuous Delivery, and Continuous Deployment with GitLab.
+- [GitLab Container Registry](user/project/container_registry.md): Learn how to use GitLab's built-in Container Registry.
 
-- [Importing to GitLab](user/project/import/index.md): Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
-- [Migrating from SVN](workflow/importing/migrating_from_svn.md): Convert a SVN repository to Git and GitLab.
+### Release
+
+Spend less time configuring your tools, and more time creating. Whether you鈥檙e
+deploying to one server or thousands, build, test, and release your code
+confidently and securely with GitLab鈥檚 built-in Continuous Delivery and Deployment.
 
-### Continuous Integration, Delivery, and Deployment
+- [GitLab Pages](user/project/pages/index.md): Build, test, and deploy a static site directly from GitLab.
+- [Auto Deploy](topics/autodevops/index.md#auto-deploy): Configure GitLab CI for the deployment of your application.
+- [Environments and deployments](ci/environments.md): With environments, you can control the continuous deployment of your software within GitLab.
 
-- [GitLab CI](ci/README.md): Explore the features and capabilities of Continuous Integration, Continuous Delivery, and Continuous Deployment with GitLab.
-  - [Auto Deploy](ci/autodeploy/index.md): Configure GitLab CI for the deployment of your application.
-  - [Review Apps](ci/review_apps/index.md): Preview changes to your app right from a merge request.
+### Configure
+
+Automate your entire workflow from build to deploy and monitoring with GitLab
+Auto Devops. Best practice templates get you started with minimal to zero
+configuration. Then customize everything from buildpacks to CI/CD.
+
+- [Auto DevOps](topics/autodevops/index.md)
+
+### Monitor
+
+Measure how long it takes to go from planning to monitoring and ensure your
+applications are always responsive and available. GitLab collects and displays
+performance metrics for deployed apps using Prometheus so you can know in an
+instant how code changes impact your production environment.
+
+- [GitLab Prometheus](administration/monitoring/prometheus/index.md): Configure the bundled Prometheus to collect various metrics from your GitLab instance.
+- [Prometheus project integration](user/project/integrations/prometheus.md): Configure the Prometheus integration per project and monitor your CI/CD environments.
+- [Prometheus metrics](user/project/integrations/prometheus_library/metrics.md): Let Prometheus collect metrics from various services, like Kubernetes, NGINX, NGINX ingress controller, HAProxy, and Amazon Cloud Watch.
+- [GitLab Performance Monitoring](administration/monitoring/performance/index.md): Use InfluxDB and Grafana to monitor the performance of your GitLab instance (will be eventually replaced by Prometheus).
+- [Health check](user/admin_area/monitoring/health_check.md): GitLab provides liveness and readiness probes to indicate service health and reachability to required services.
 - [GitLab Cycle Analytics](user/project/cycle_analytics.md): Cycle Analytics measures the time it takes to go from an [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have.
-- [GitLab Container Registry](user/project/container_registry.md): Learn how to use GitLab's built-in Container Registry.
 
-### Automation
+## Getting started with GitLab
 
-- [API](api/README.md): Automate GitLab via a simple and powerful API.
-- [GitLab Webhooks](user/project/integrations/webhooks.md): Let GitLab notify you when new code has been pushed to your project.
+- [GitLab Basics](gitlab-basics/README.md): Start working on your command line and on GitLab.
+- [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).
 
-### Integrations
+### User account
 
-- [Project Services](user/project/integrations/project_services.md): Integrate a project with external services, such as CI and chat.
-- [GitLab Integration](integration/README.md): Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication.
-- [Trello Power-Up](integration/trello_power_up.md): Integrate with GitLab's Trello Power-Up
+- [User account](user/profile/index.md): Manage your account
+  - [Authentication](topics/authentication/index.md): Account security with two-factor authentication, setup your ssh keys and deploy keys for secure access to your projects.
+  - [Profile settings](user/profile/index.md#profile-settings): Manage your profile settings, two factor authentication and more.
+- [User permissions](user/permissions.md): Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
+
+### Git and GitLab
+
+- [Git](topics/git/index.md): Getting started with Git, branching strategies, Git LFS, advanced use.
+- [Git cheatsheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf): Download a PDF describing the most used Git operations.
+- [GitLab Flow](workflow/gitlab_flow.md): explore the best of Git with the GitLab Flow strategy.
 
 ## Administrator documentation
 
@@ -146,9 +216,9 @@ straight away.
 
 ### GitLab self-hosted
 
-With GitLab self-hosted, you deploy your own GitLab instance on-premises or on a private cloud of your choice. GitLab self-hosted is available for [free and with paid subscriptions](https://about.gitlab.com/products/): Libre, Starter, Premium, and Ultimate.
+With GitLab self-hosted, you deploy your own GitLab instance on-premises or on a private cloud of your choice. GitLab self-hosted is available for [free and with paid subscriptions](https://about.gitlab.com/products/): Core, Starter, Premium, and Ultimate.
 
-Every feature available in Libre is also available in Starter, Premium, and Ultimate.
+Every feature available in Core is also available in Starter, Premium, and Ultimate.
 Starter features are also available in Premium and Ultimate, and Premium features are also
 available in Ultimate.
 
@@ -162,7 +232,7 @@ GitLab.com subscriptions grants access
 to the same features available in GitLab self-hosted, **expect
 [administration](administration/index.md) tools and settings**:
 
-- GitLab.com Free includes the same features available in GitLab Libre
+- GitLab.com Free includes the same features available in Core
 - GitLab.com Bronze includes the same features available in GitLab Starter
 - GitLab.com Silver includes the same features available in GitLab Premium
 - GitLab.com Gold includes the same features available in GitLab Ultimate
diff --git a/doc/administration/auth/jwt.md b/doc/administration/auth/jwt.md
new file mode 100644
index 0000000000000000000000000000000000000000..b51e705ab52577090b50635a920ba544f95c134e
--- /dev/null
+++ b/doc/administration/auth/jwt.md
@@ -0,0 +1,72 @@
+# JWT OmniAuth provider
+
+To enable the JWT OmniAuth provider, you must register your application with JWT.
+JWT will provide you with a secret key for you to use.
+
+1.  On your GitLab server, open the configuration file.
+
+    For Omnibus GitLab:
+
+    ```sh
+    sudo editor /etc/gitlab/gitlab.rb
+    ```
+
+    For installations from source:
+
+    ```sh
+    cd /home/git/gitlab
+    sudo -u git -H editor config/gitlab.yml
+    ```
+
+1.  See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
+1.  Add the provider configuration.
+
+    For Omnibus GitLab:
+
+    ```ruby
+    gitlab_rails['omniauth_providers'] = [
+      { name: 'jwt',
+        app_secret: 'YOUR_APP_SECRET',
+        args: {
+                algorithm: 'HS256',
+                uid_claim: 'email',
+                required_claims: ["name", "email"],
+                info_maps: { name: "name", email: "email" },
+                auth_url: 'https://example.com/',
+                valid_within: nil,
+              }
+      }
+    ]
+    ```
+
+    For installation from source:
+
+    ```
+    - { name: 'jwt',
+        app_secret: 'YOUR_APP_SECRET',
+        args: {
+                algorithm: 'HS256',
+                uid_claim: 'email',
+                required_claims: ["name", "email"],
+                info_map: { name: "name", email: "email" },
+                auth_url: 'https://example.com/',
+                valid_within: nil,
+              }
+      }
+    ```
+
+    NOTE: **Note:** For more information on each configuration option refer to
+    the [OmniAuth JWT usage documentation](https://github.com/mbleigh/omniauth-jwt#usage).
+
+1.  Change `YOUR_APP_SECRET` to the client secret and set `auth_url` to your redirect URL.
+1.  Save the configuration file.
+1.  [Reconfigure GitLab][] or [restart GitLab][] for the changes to take effect if you
+    installed GitLab via Omnibus or from source respectively.
+
+On the sign in page there should now be a JWT icon below the regular sign in form.
+Click the icon to begin the authentication process. JWT will ask the user to
+sign in and authorize the GitLab application. If everything goes well, the user
+will be redirected to GitLab and will be signed in.
+
+[reconfigure GitLab]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../restart_gitlab.md#installations-from-source
diff --git a/doc/administration/img/circuitbreaker_config.png b/doc/administration/img/circuitbreaker_config.png
index e811d1736346bcb8e8b6ab03a9a293b94355d07a..693b2ee9c6921537ee002470ef081728c427e35e 100644
Binary files a/doc/administration/img/circuitbreaker_config.png and b/doc/administration/img/circuitbreaker_config.png differ
diff --git a/doc/administration/img/repository_storages_admin_ui.png b/doc/administration/img/repository_storages_admin_ui.png
index 3e76c5b282cf4426b20eea4c298da5a237862de2..036e708cdac66737bb92b584294da3eb7ffc63d4 100644
Binary files a/doc/administration/img/repository_storages_admin_ui.png and b/doc/administration/img/repository_storages_admin_ui.png differ
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 6c5a466ced502a120ba2a4caeafde2e019ed0dac..27a3710632da6ce505e9071aace4170dd670e03d 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -187,6 +187,7 @@ for a real-world example of this exploit.
 
     ```sh
     sudo gitlab-ctl reconfigure
+    sudo gitlab-ctl restart
     ```
 
 1. Verify that everything is configured correctly:
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 69efaf7514019ffc5f8ad056dcc9a12d00f396de..b472ca5b4d89b6c3b539253f2b0f141829314071 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -1,4 +1,4 @@
-# Administrator documentation
+# Administrator documentation **[CORE ONLY]**
 
 Learn how to administer your GitLab instance (Community Edition and
 Enterprise Edition).
@@ -11,8 +11,8 @@ available through [different subscriptions](https://about.gitlab.com/products/).
 
 You can [install GitLab CE or GitLab EE](https://about.gitlab.com/installation/ce-or-ee/),
 but the features you'll have access to depend on the subscription you choose
-(Libre, Starter, Premium, or Ultimate). GitLab Community Edition installations
-only have access to Libre features.
+(Core, Starter, Premium, or Ultimate). GitLab Community Edition installations
+only have access to Core features.
 
 GitLab.com is administered by GitLab, Inc., therefore, only GitLab team members have
 access to its admin configurations. If you're a GitLab.com user, please check the
@@ -39,6 +39,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
 - [GitLab Pages configuration for GitLab source installations](pages/source.md): Enable and configure GitLab Pages on
 [source installations](../install/installation.md#installation-from-source).
 - [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
+- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
 
 #### Customizing GitLab's appearance
 
@@ -85,7 +86,6 @@ created in snippets, wikis, and repos.
   - [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a
   basic Postfix mail server with IMAP authentication on Ubuntu for incoming
   emails.
-server with IMAP authentication on Ubuntu, to be used with Reply by email.
 - [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time.
 
 [reply by email]: reply_by_email.md
@@ -111,6 +111,7 @@ server with IMAP authentication on Ubuntu, to be used with Reply by email.
 - [Enable/disable GitLab CI/CD](../ci/enable_or_disable_ci.md#site-wide-admin-setting): Enable or disable GitLab CI/CD for your instance.
 - [GitLab CI/CD admin settings](../user/admin_area/settings/continuous_integration.md): Define max artifacts size and expiration time.
 - [Job artifacts](job_artifacts.md): Enable, disable, and configure job artifacts (a set of files and directories which are outputted by a job when it completes successfully).
+- [Job traces](job_traces.md): Information about the job traces (logs).
 - [Artifacts size and expiration](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size): Define maximum artifacts limits and expiration date.
 - [Register Shared and specific Runners](../ci/runners/README.md#registering-a-shared-runner): Learn how to register and configure Shared and specific Runners to your own instance.
 - [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota): Limit the usage of pipeline minutes for Shared Runners.
diff --git a/doc/administration/issue_closing_pattern.md b/doc/administration/issue_closing_pattern.md
index 28e1fd4e12e2d09b628a4a560a7634f392cdd125..466bb1f851e9058e5cb3fa5bc14edb6036c87d62 100644
--- a/doc/administration/issue_closing_pattern.md
+++ b/doc/administration/issue_closing_pattern.md
@@ -24,11 +24,11 @@ Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
 **For Omnibus installations**
 
 1. Open `/etc/gitlab/gitlab.rb` with your editor.
-1. Change the value of `gitlab_rails['issue_closing_pattern']` to a regular
+1. Change the value of `gitlab_rails['gitlab_issue_closing_pattern']` to a regular
    expression of your liking:
 
     ```ruby
-    gitlab_rails['issue_closing_pattern'] = "((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
+    gitlab_rails['gitlab_issue_closing_pattern'] = "((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
     ```
 1. [Reconfigure] GitLab for the changes to take effect.
 
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index d86a54daaddfb8404f97fc5d99e1fe8efa82567a..896cb93e5ed7e7c9e4ea7c4e11eba0905cb28a47 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -87,10 +87,125 @@ _The artifacts are stored by default in
 
 ### Using object storage
 
+>**Notes:**
+- [Introduced][ee-1762] in [GitLab Premium][eep] 9.4.
+- Since version 9.5, artifacts are [browsable], when object storage is enabled. 
+  9.4 lacks this feature.
 > Available in [GitLab Premium](https://about.gitlab.com/products/) and
 [GitLab.com Silver](https://about.gitlab.com/gitlab-com/).
+> Since version 10.6, available in [GitLab CE](https://about.gitlab.com/products/)
+
+If you don't want to use the local disk where GitLab is installed to store the
+artifacts, you can use an object storage like AWS S3 instead.
+This configuration relies on valid AWS credentials to be configured already.
+Use an [Object storage option][os] like AWS S3 to store job artifacts.
+
+### Object Storage Settings
+
+For source installations the following settings are nested under `artifacts:` and then `object_store:`. On omnibus installs they are prefixed by `artifacts_object_store_`.
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `enabled` | Enable/disable object storage | `false` |
+| `remote_directory` | The bucket name where Artfacts will be stored| |
+| `direct_upload` | Set to true to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. Currently only `Google` provider is supported | `false` |
+| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` |
+| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
+| `connection` | Various connection options described below | |
+
+#### S3 compatible connection settings
+
+The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `provider` | Always `AWS` for compatible hosts | AWS |
+| `aws_access_key_id` | AWS credentials, or compatible | |
+| `aws_secret_access_key` | AWS credentials, or compatible | |
+| `region` | AWS region | us-east-1 |
+| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
+| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
+| `path_style` | Set to true to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as false for AWS S3 | false |
+
+**In Omnibus installations:**
+
+_The artifacts are stored by default in
+`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
+   the values you want:
+
+    ```ruby
+    gitlab_rails['artifacts_enabled'] = true
+    gitlab_rails['artifacts_object_store_enabled'] = true
+    gitlab_rails['artifacts_object_store_remote_directory'] = "artifacts"
+    gitlab_rails['artifacts_object_store_connection'] = {
+      'provider' => 'AWS',
+      'region' => 'eu-central-1',
+      'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+      'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
+    }
+    ```
+
+    NOTE: For GitLab 9.4+, if you are using AWS IAM profiles, be sure to omit the
+    AWS access key and secret acces key/value pairs. For example:
+
+    ```ruby
+    gitlab_rails['artifacts_object_store_connection'] = {
+      'provider' => 'AWS',
+      'region' => 'eu-central-1',
+      'use_iam_profile' => true
+    }
+    ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+1. Migrate any existing local artifacts to the object storage:
+
+      ```bash
+      gitlab-rake gitlab:artifacts:migrate
+      ```
+
+      Currently this has to be executed manually and it will allow you to
+      migrate the existing artifacts to the object storage, but all new
+      artifacts will still be stored on the local disk. In the future
+      you will be given an option to define a default storage artifacts for all
+      new files.
+
+---
+
+**In installations from source:**
+
+_The artifacts are stored by default in
+`/home/git/gitlab/shared/artifacts`._
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
+   lines:
+
+    ```yaml
+    artifacts:
+      enabled: true
+      object_store:
+        enabled: true
+        remote_directory: "artifacts" # The bucket name
+        connection:
+          provider: AWS # Only AWS supported at the moment
+          aws_access_key_id: AWS_ACESS_KEY_ID
+          aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+          region: eu-central-1
+    ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+1. Migrate any existing local artifacts to the object storage:
+
+      ```bash
+      sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production
+      ```
 
-Use an [Object storage option][ee-os] like AWS S3 to store job artifacts.
+      Currently this has to be executed manually and it will allow you to
+      migrate the existing artifacts to the object storage, but all new
+      artifacts will still be stored on the local disk. In the future
+      you will be given an option to define a default storage artifacts for all
+      new files.
 
 ## Expiring artifacts
 
@@ -194,7 +309,7 @@ When clicking on a specific file, [GitLab Workhorse] extracts it
 from the archive and the download begins. This implementation saves space,
 memory and disk I/O.
 
-[reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
-[restart gitlab]: restart_gitlab.md "How to restart GitLab"
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
+[restart gitlab]: restart_gitlab.md#installations-from-source "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
+[os]: https://docs.gitlab.com/administration/job_artifacts.html#using-object-storage
diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md
new file mode 100644
index 0000000000000000000000000000000000000000..84a1ffeec98ba6c5a41420518c455ff078b7f2e2
--- /dev/null
+++ b/doc/administration/job_traces.md
@@ -0,0 +1,42 @@
+# Job traces (logs)
+
+By default, all job traces (logs) are saved to `/var/opt/gitlab/gitlab-ci/builds`
+and `/home/git/gitlab/builds` for Omnibus packages and installations from source
+respectively. The job logs are organized by year and month (for example, `2017_03`),
+and then by project ID.
+
+There isn't a way to automatically expire old job logs, but it's safe to remove
+them if they're taking up too much space. If you remove the logs manually, the
+job output in the UI will be empty.
+
+## Changing the job traces location
+
+To change the location where the job logs will be stored, follow the steps below.
+
+**In Omnibus installations:**
+
+1.  Edit `/etc/gitlab/gitlab.rb` and add or amend the following line:
+
+    ```
+    gitlab_ci['builds_directory'] = '/mnt/to/gitlab-ci/builds'
+    ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+    ```yaml
+    gitlab_ci:
+      # The location where build traces are stored (default: builds/).
+      # Relative paths are relative to Rails.root.
+      builds_path: path/to/builds/
+    ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
+[restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab"
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 00a2f3d01b84281140ab45e0bb94d0d8a090f0db..c8a3ef80e8f84ce81ea58ac07d8ce1945f3a4763 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -146,6 +146,28 @@ this file. For example:
 2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"}
 ```
 
+Instead of the format above, you can opt to generate JSON logs for
+Sidekiq. For example:
+
+```json
+{"severity":"INFO","time":"2018-04-03T22:57:22.071Z","queue":"cronjob:update_all_mirrors","args":[],"class":"UpdateAllMirrorsWorker","retry":false,"queue_namespace":"cronjob","jid":"06aeaa3b0aadacf9981f368e","created_at":"2018-04-03T22:57:21.930Z","enqueued_at":"2018-04-03T22:57:21.931Z","pid":10077,"message":"UpdateAllMirrorsWorker JID-06aeaa3b0aadacf9981f368e: done: 0.139 sec","job_status":"done","duration":0.139,"completed_at":"2018-04-03T22:57:22.071Z"}
+```
+
+For Omnibus GitLab installations, add the configuration option:
+
+```ruby
+sidekiq['log_format'] = 'json'
+```
+
+For source installations, edit the `gitlab.yml` and set the Sidekiq
+`log_format` configuration option:
+
+```yaml
+  ## Sidekiq
+  sidekiq:
+    log_format: json
+```
+
 ## `gitlab-shell.log`
 
 This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for
@@ -206,4 +228,12 @@ is populated whenever `gitlab-ctl reconfigure` is run manually or as part of an
 Reconfigure logs files are named according to the UNIX timestamp of when the reconfigure
 was initiated, such as `1509705644.log`
 
+## `sidekiq_exporter.log`
+
+If Prometheus metrics and the Sidekiq Exporter are both enabled, Sidekiq will
+start a Web server and listen to the defined port (default: 3807). Access logs
+will be generated in `/var/log/gitlab/gitlab-rails/sidekiq_exporter.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/sidekiq_exporter.log` for
+installations from source.
+
 [repocheck]: repository_checks.md
diff --git a/doc/administration/monitoring/index.md b/doc/administration/monitoring/index.md
index b6320aba83ec6009eeaadc85d9776832b18de042..d18dddf09c096e45c3528ed62bfe43be397b0072 100644
--- a/doc/administration/monitoring/index.md
+++ b/doc/administration/monitoring/index.md
@@ -7,3 +7,4 @@ Explore our features to monitor your GitLab instance:
 - [GitHub imports](github_imports.md): Monitor the health and progress of GitLab's GitHub importer with various Prometheus metrics.
 - [Monitoring uptime](../../user/admin_area/monitoring/health_check.md): Check the server status using the health check endpoint.
   - [IP whitelists](ip_whitelist.md): Configure GitLab for monitoring endpoints that provide health check information when probed.
+- [nginx_status](https://docs.gitlab.com/omnibus/settings/nginx.html#enabling-disabling-nginx_status): Monitor your Nginx server status
diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png
index b3c6bc474e36f0d1412794214f48e292ee1d50db..48212f6276a2961195d910c311709bcec712989a 100644
Binary files a/doc/administration/monitoring/performance/img/performance_bar.png and b/doc/administration/monitoring/performance/img/performance_bar.png differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
new file mode 100644
index 0000000000000000000000000000000000000000..52176df9ecd39794670504ae5d333c94761521d4
Binary files /dev/null and b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index b9464945ceaf21ed3bf689d84bda1ebd77b81e94..dc4f685d843751d64dcb919f07bde2da7672921e 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -11,12 +11,18 @@ It allows you to see (from left to right):
 - the timing of the page (backend, frontend)
 - time taken and number of DB queries, click through for details of these queries
 ![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
+- time taken and number of [Gitaly] calls, click through for details of these calls
+![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png)
+- profile of the code used to generate the page, line by line. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)).
+![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
 - time taken and number of calls to Redis
 - time taken and number of background jobs created by Sidekiq
-- profile of the code used to generate the page, line by line for either _all_, _app & lib_ , or _views_. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)).
-![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
 - time taken and number of Ruby GC calls
 
+On the far right is a request selector that allows you to view the same metrics
+(excluding the page timing and line profiler) for any requests made while the
+page was open. Only the first two requests per unique URL are captured.
+
 ## Enable the Performance Bar via the Admin panel
 
 GitLab Performance Bar is disabled by default. To enable it for a given group,
@@ -39,3 +45,5 @@ You can toggle the Bar using the same shortcut.
 ![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
 
 ---
+
+[Gitaly]: ../../gitaly/index.md
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index f43c89dad87b937af782fdb8c34ed7a673f7d550..3d24812c66ae683a81591b03f35d99a6ab9f22f0 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -62,7 +62,14 @@ To change the address/port that Prometheus listens on:
     ```
 
     Replace `localhost:9090` with the address/port you want Prometheus to
-    listen on.
+    listen on. If you would like to allow access to Prometheus to hosts other
+    than `localhost`, leave out the host, or use `0.0.0.0` to allow public access:
+
+    ```ruby
+    prometheus['listen_address'] = ':9090'
+    # or
+    prometheus['listen_address'] = '0.0.0.0:9090'
+    ```
 
 1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
    take effect
diff --git a/doc/administration/plugins.md b/doc/administration/plugins.md
index c91ac3012b9445203b02fe9ea1f0c24dca791696..4302667caf5eaf145cd3514b495dda59552823c8 100644
--- a/doc/administration/plugins.md
+++ b/doc/administration/plugins.md
@@ -1,66 +1,85 @@
-# Plugins
+# GitLab Plugin system
 
-**Note:** Plugins must be configured on the filesystem of the GitLab
-server. Only GitLab server administrators will be able to complete these tasks.
-Please explore [system hooks] or [webhooks] as an option if you do not
-have filesystem access. 
+> Introduced in GitLab 10.6.
 
-Introduced in GitLab 10.6.
+With custom plugins, GitLab administrators can introduce custom integrations
+without modifying GitLab's source code.
 
-A plugin will run on each event so it's up to you to filter events or projects within a plugin code. You can have as many plugins as you want. Each plugin will be triggered by GitLab asynchronously in case of an event. For a list of events please see [system hooks] documentation.
+NOTE: **Note:**
+Instead of writing and supporting your own plugin you can make changes
+directly to the GitLab source code and contribute back upstream. This way we can
+ensure functionality is preserved across versions and covered by tests.
+
+NOTE: **Note:**
+Plugins must be configured on the filesystem of the GitLab server. Only GitLab
+server administrators will be able to complete these tasks. Explore
+[system hooks] or [webhooks] as an option if you do not have filesystem access.
+
+A plugin will run on each event so it's up to you to filter events or projects
+within a plugin code. You can have as many plugins as you want. Each plugin will
+be triggered by GitLab asynchronously in case of an event. For a list of events
+see the [system hooks] documentation.
 
 ## Setup
 
-Plugins must be placed directly into `plugins` directory, subdirectories will be ignored. 
-There is an `example` directory inside `plugins` where you can find some basic examples. 
+The plugins must be placed directly into the `plugins` directory, subdirectories
+will be ignored. There is an
+[`example` directory inside `plugins`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/plugins/examples)
+where you can find some basic examples.
 
 Follow the steps below to set up a custom hook:
 
-1. On the GitLab server, navigate to the project's plugin directory.
+1. On the GitLab server, navigate to the plugin directory.
    For an installation from source the path is usually
    `/home/git/gitlab/plugins/`. For Omnibus installs the path is
    usually `/opt/gitlab/embedded/service/gitlab-rails/plugins`.
-1. Inside the `plugins` directory, create a file with a name of your choice, but without spaces or special characters.
+
+    For [highly available] configurations, your hook file should exist on each
+    application server.
+
+1. Inside the `plugins` directory, create a file with a name of your choice,
+   without spaces or special characters.
 1. Make the hook file executable and make sure it's owned by the git user.
-1. Write the code to make the plugin function as expected. Plugin can be
-   in any language. Ensure the 'shebang' at the top properly reflects the language
-   type. For example, if the script is in Ruby the shebang will probably be
-   `#!/usr/bin/env ruby`.
-1. The data to the plugin will be provided as JSON on STDIN. It will be exactly same as one for [system hooks]
+1. Write the code to make the plugin function as expected. That can be
+   in any language, and ensure the 'shebang' at the top properly reflects the
+   language type. For example, if the script is in Ruby the shebang will
+   probably be `#!/usr/bin/env ruby`.
+1. The data to the plugin will be provided as JSON on STDIN. It will be exactly
+   same as for [system hooks]
 
-That's it! Assuming the plugin code is properly implemented the hook will fire
-as appropriate. Plugins file list is updated for each event. There is no need to restart GitLab to apply a new plugin.
+That's it! Assuming the plugin code is properly implemented, the hook will fire
+as appropriate. The plugins file list is updated for each event, there is no
+need to restart GitLab to apply a new plugin.
 
 If a plugin executes with non-zero exit code or GitLab fails to execute it, a
 message will be logged to `plugin.log`.
 
 ## Validation
 
-Writing own plugin can be tricky and its easier if you can check it without altering the system. 
-We provided a rake task you can use with staging environment to test your plugin before using it in production. 
-The rake task will use a sample data and execute each of plugins. By output you should be able to determine if 
-system sees your plugin and if it was executed without errors.
+Writing your own plugin can be tricky and it's easier if you can check it
+without altering the system. A rake task is provided so that you can use it
+in a staging environment to test your plugin before using it in production.
+The rake task will use a sample data and execute each of plugin. The output
+should be enough to determine if the system sees your plugin and if it was
+executed without errors.
 
 ```bash
 # Omnibus installations
 sudo gitlab-rake plugins:validate
 
 # Installations from source
+cd /home/git/gitlab
 bundle exec rake plugins:validate RAILS_ENV=production
 ```
 
-Example of output can be next: 
+Example of output:
 
 ```
--> bundle exec rake plugins:validate RAILS_ENV=production
 Validating plugins from /plugins directory
 * /home/git/gitlab/plugins/save_to_file.clj succeed (zero exit code)
 * /home/git/gitlab/plugins/save_to_file.rb failure (non-zero exit code)
 ```
 
-[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks
 [system hooks]: ../system_hooks/system_hooks.md
 [webhooks]: ../user/project/integrations/webhooks.md
-[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073
-[93]: https://gitlab.com/gitlab-org/gitlab-shell/merge_requests/93
-
+[highly available]: ./high_availability/README.md
\ No newline at end of file
diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md
index d1ed152b58c095e847d0efd396340b2d81ba9195..7d34d35e7d13f729c000a845fa073d9f2bcac056 100644
--- a/doc/administration/raketasks/check.md
+++ b/doc/administration/raketasks/check.md
@@ -78,34 +78,45 @@ Example output:
 
 ## Uploaded Files Integrity
 
-The uploads check Rake task will loop through all uploads in the database
-and run two checks to determine the integrity of each file:
+Various types of file can be uploaded to a GitLab installation by users.
+Checksums are generated and stored in the database upon upload, and integrity
+checks using those checksums can be run. These checks also detect missing files.
 
-1. Check if the file exist on the file system.
-1. Check if the checksum of the file on the file system matches the checksum in the database.
+Currently, integrity checks are supported for the following types of file:
+
+* CI artifacts (Available from version 10.7.0)
+* LFS objects (Available from version 10.6.0)
+* User uploads (Available from version 10.6.0)
 
 **Omnibus Installation**
 
 ```
+sudo gitlab-rake gitlab:artifacts:check
+sudo gitlab-rake gitlab:lfs:check
 sudo gitlab-rake gitlab:uploads:check
 ```
 
 **Source Installation**
 
 ```bash
+sudo -u git -H bundle exec rake gitlab:artifacts:check RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:lfs:check RAILS_ENV=production
 sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production
 ```
 
-This task also accepts some environment variables which you can use to override
+These tasks also accept some environment variables which you can use to override
 certain values:
 
-Variable | Type | Description
--------- | ---- | -----------
-`BATCH`   | integer  | Specifies the size of the batch. Defaults to 200.
-`ID_FROM` | integer  | Specifies the ID to start from, inclusive of the value.
-`ID_TO`   | integer  | Specifies the ID value to end at, inclusive of the value.
+Variable  | Type    | Description
+--------- | ------- | -----------
+`BATCH`   | integer | Specifies the size of the batch. Defaults to 200.
+`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value.
+`ID_TO`   | integer | Specifies the ID value to end at, inclusive of the value.
+`VERBOSE` | boolean | Causes failures to be listed individually, rather than being summarized.
 
 ```bash
+sudo gitlab-rake gitlab:artifacts:check BATCH=100 ID_FROM=50 ID_TO=250
+sudo gitlab-rake gitlab:lfs:check BATCH=100 ID_FROM=50 ID_TO=250
 sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250
 ```
 
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index ecf92c379fd6497e4066c3a2fd5ef61f4962288c..2b4252a7b1d6c84653c847350556911311ab23b1 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -240,3 +240,31 @@ sudo gitlab-rake gitlab:tcp_check[example.com,80]
 cd /home/git/gitlab
 sudo -u git -H bundle exec rake gitlab:tcp_check[example.com,80] RAILS_ENV=production
 ```
+
+## Clear exclusive lease (DANGER)
+
+GitLab uses a shared lock mechanism: `ExclusiveLease` to prevent simultaneous operations
+in a shared resource. An example is running periodic garbage collection on repositories.
+
+In very specific situations, a operation locked by an Exclusive Lease can fail without
+releasing the lock. If you can't wait for it to expire, you can run this task to manually
+clear it.
+
+To clear all exclusive leases:
+
+DANGER: **DANGER**: 
+Don't run it while GitLab or Sidekiq is running
+
+```bash
+sudo gitlab-rake gitlab:exclusive_lease:clear
+```
+
+To specify a lease `type` or lease `type + id`, specify a scope:
+
+```bash
+# to clear all leases for repository garbage collection:
+sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:*]
+
+# to clear a lease for repository garbage collection in a specific project: (id=4)
+sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:4]
+```
diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md
new file mode 100644
index 0000000000000000000000000000000000000000..0cd33ffc122a71853575aff17c14387c6e52f901
--- /dev/null
+++ b/doc/administration/raketasks/uploads/migrate.md
@@ -0,0 +1,74 @@
+# Uploads Migrate Rake Task
+
+## Migrate to Object Storage
+
+After [configuring the object storage](../../uploads.md#using-object-storage) for GitLab's uploads, you may use this task to migrate existing uploads from the local storage to the remote storage.
+
+>**Note:**
+All of the processing will be done in a background worker and requires **no downtime**.
+
+This tasks uses 3 parameters to find uploads to migrate.
+
+>**Note:**
+These parameters are mainly internal to GitLab's structure, you may want to refer to the task list instead below.
+
+Parameter | Type | Description
+--------- | ---- | -----------
+`uploader_class` | string | Type of the uploader to migrate from
+`model_class` | string | Type of the model to migrate from
+`mount_point` | string/symbol | Name of the model's column on which the uploader is mounted on.
+
+This task also accepts some environment variables which you can use to override
+certain values:
+
+Variable | Type | Description
+-------- | ---- | -----------
+`BATCH`   | integer  | Specifies the size of the batch. Defaults to 200.
+
+** Omnibus Installation**
+
+```bash
+# gitlab-rake gitlab:uploads:migrate[uploader_class, model_class, mount_point]
+
+# Avatars
+gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]"
+gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]"
+gitlab-rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]"
+
+# Attachments
+gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]"
+gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]"
+gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]"
+
+# Markdown
+gitlab-rake "gitlab:uploads:migrate[FileUploader, Project]"
+gitlab-rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]"
+gitlab-rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]"
+gitlab-rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
+```
+
+**Source Installation**
+
+>**Note:**
+Use `RAILS_ENV=production` for every task.
+
+```bash
+# sudo -u git -H bundle exec rake gitlab:uploads:migrate
+
+# Avatars
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]"
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]"
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]"
+
+# Attachments
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]"
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]"
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]"
+
+# Markdown
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, Project]"
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]"
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]"
+sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
+
+```
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
new file mode 100644
index 0000000000000000000000000000000000000000..2fa3284b6be13c77e3d334e56144ae25565c2599
--- /dev/null
+++ b/doc/administration/uploads.md
@@ -0,0 +1,210 @@
+# Uploads administration
+
+>**Notes:**
+Uploads represent all user data that may be sent to GitLab as a single file. As an example, avatars and notes' attachments are uploads. Uploads are integral to GitLab functionality, and therefore cannot be disabled.
+
+### Using local storage
+
+>**Notes:**
+This is the default configuration
+
+To change the location where the uploads are stored locally, follow the steps
+below.
+
+---
+
+**In Omnibus installations:**
+
+>**Notes:**
+For historical reasons, uploads are stored into a base directory, which by default is `uploads/-/system`. It is strongly discouraged to change this configuration option on an existing GitLab installation.
+
+_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/public/uploads/-/system`._
+
+1. To change the storage path for example to `/mnt/storage/uploads`, edit
+   `/etc/gitlab/gitlab.rb` and add the following line:
+
+    ```ruby
+    gitlab_rails['uploads_storage_path'] = "/mnt/storage/"
+	gitlab_rails['uploads_base_dir'] = "uploads"
+    ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+_The uploads are stored by default in
+`/home/git/gitlab/public/uploads/-/system`._
+
+1. To change the storage path for example to `/mnt/storage/uploads`, edit
+   `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+    ```yaml
+	uploads:
+	  storage_path: /mnt/storage
+	  base_dir: uploads
+    ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+### Using object storage
+
+>**Notes:**
+- [Introduced][ee-3867] in [GitLab Enterprise Edition Premium][eep] 10.5.
+
+If you don't want to use the local disk where GitLab is installed to store the
+uploads, you can use an object storage provider like AWS S3 instead.
+This configuration relies on valid AWS credentials to be configured already.
+
+### Object Storage Settings
+
+For source installations the following settings are nested under `uploads:` and then `object_store:`. On omnibus installs they are prefixed by `uploads_object_store_`.
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `enabled` | Enable/disable object storage | `false` |
+| `remote_directory` | The bucket name where Uploads will be stored| |
+| `direct_upload` | Set to true to enable direct upload of Uploads without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. This is beta option as it uses inefficient way of uploading data (via Unicorn). The accelerated uploads gonna be implemented in future releases | `false` |
+| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` |
+| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
+| `connection` | Various connection options described below | |
+
+#### S3 compatible connection settings
+
+The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `provider` | Always `AWS` for compatible hosts | AWS |
+| `aws_access_key_id` | AWS credentials, or compatible | |
+| `aws_secret_access_key` | AWS credentials, or compatible | |
+| `region` | AWS region | us-east-1 |
+| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
+| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
+| `path_style` | Set to true to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as false for AWS S3 | false |
+
+**In Omnibus installations:**
+
+_The uploads are stored by default in
+`/var/opt/gitlab/gitlab-rails/public/uploads/-/system`._
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
+   the values you want:
+
+    ```ruby
+    gitlab_rails['uploads_object_store_enabled'] = true
+    gitlab_rails['uploads_object_store_remote_directory'] = "uploads"
+    gitlab_rails['uploads_object_store_connection'] = {
+      'provider' => 'AWS',
+      'region' => 'eu-central-1',
+      'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+      'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
+    }
+    ```
+
+>**Note:**
+If you are using AWS IAM profiles, be sure to omit the AWS access key and secret acces key/value pairs.
+
+    ```ruby
+    gitlab_rails['uploads_object_store_connection'] = {
+      'provider' => 'AWS',
+      'region' => 'eu-central-1',
+      'use_iam_profile' => true
+    }
+    ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+1. Migrate any existing local uploads to the object storage:
+
+>**Notes:**
+These task complies with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**.
+
+      ```bash
+      # gitlab-rake gitlab:uploads:migrate[uploader_class, model_class, mount_point]
+	  
+	  # Avatars
+	  gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]"
+	  gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]"
+	  gitlab-rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]"
+
+      # Attachments
+	  gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]"
+	  gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]"
+	  gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]"
+
+      # Markdown
+	  gitlab-rake "gitlab:uploads:migrate[FileUploader, Project]"
+	  gitlab-rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]"
+	  gitlab-rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]"
+	  gitlab-rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
+      ```
+
+      Currently this has to be executed manually and it will allow you to
+      migrate the existing uploads to the object storage, but all new
+      uploads will still be stored on the local disk. In the future
+      you will be given an option to define a default storage for all
+      new files.
+
+---
+
+**In installations from source:**
+
+_The uploads are stored by default in
+`/home/git/gitlab/public/uploads/-/system`._
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
+   lines:
+
+    ```yaml
+    uploads:
+      object_store:
+        enabled: true
+        remote_directory: "uploads" # The bucket name
+        connection:
+          provider: AWS # Only AWS supported at the moment
+          aws_access_key_id: AWS_ACESS_KEY_ID
+          aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+          region: eu-central-1
+    ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+1. Migrate any existing local uploads to the object storage:
+
+>**Notes:**
+
+- These task comply with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**.
+
+- To migrate in production use `RAILS_ENV=production` environment variable.
+
+      ```bash
+      # sudo -u git -H bundle exec rake gitlab:uploads:migrate
+	  
+	  # Avatars
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]"
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]"
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]"
+
+      # Attachments
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]"
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]"
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]"
+
+      # Markdown
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, Project]"
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]"
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]"
+	  sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
+	  
+      ```
+
+      Currently this has to be executed manually and it will allow you to
+      migrate the existing uploads to the object storage, but all new
+      uploads will still be stored on the local disk. In the future
+      you will be given an option to define a default storage for all
+      new files.
+
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
+[restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab"
+[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition Premium"
+[ee-3867]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3867
diff --git a/doc/api/README.md b/doc/api/README.md
index b193ef4ab7f408f724fccbc410eb57ee574f4b2d..ae4481b400e39fa2f2897316243ab5a16b8832ac 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -24,9 +24,11 @@ following locations:
 - [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
 - [Groups](groups.md)
 - [Group Access Requests](access_requests.md)
+- [Group Badges](group_badges.md)
 - [Group Members](members.md)
 - [Issues](issues.md)
 - [Issue Boards](boards.md)
+- [Group Issue Boards](group_boards.md)
 - [Jobs](jobs.md)
 - [Keys](keys.md)
 - [Labels](labels.md)
@@ -35,6 +37,7 @@ following locations:
 - [Group milestones](group_milestones.md)
 - [Namespaces](namespaces.md)
 - [Notes](notes.md) (comments)
+- [Discussions](discussions.md) (threaded comments)
 - [Notification settings](notification_settings.md)
 - [Open source license templates](templates/licenses.md)
 - [Pages Domains](pages_domains.md)
@@ -43,6 +46,7 @@ following locations:
 - [Pipeline Schedules](pipeline_schedules.md)
 - [Projects](projects.md) including setting Webhooks
 - [Project Access Requests](access_requests.md)
+- [Project Badges](project_badges.md)
 - [Project import/export](project_import_export.md)
 - [Project Members](members.md)
 - [Project Snippets](project_snippets.md)
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 80744258acb07f29dd7f32126ab8d584e3d7ab70..01bb30c385946db11b2b5efd8be31ac29c5fa774 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -13,6 +13,7 @@ GET /projects/:id/repository/branches
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
 | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `search` | string | no | Return list of branches matching the search criteria.  |
 
 ```bash
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 55c673fd06a167792d1a65e234e07c2e2de2a70d..d1584cf64de6a3b853cb03e1a8ba9b42ffdc982d 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -412,9 +412,10 @@ Example response:
 
 Since GitLab 8.1, this is the new commit status API.
 
-### Get the status of a commit
+### List the statuses of a commit
 
-Get the statuses of a commit in a project.
+List the statuses of a commit in a project.
+The pagination parameters `page` and `per_page` can be used to restrict the list of references.
 
 ```
 GET /projects/:id/repository/commits/:sha/statuses
@@ -536,6 +537,77 @@ Example response:
 }
 ```
 
+## List Merge Requests associated with a commit
+
+> [Introduced][ce-18004] in GitLab 10.7.
+
+Get a list of Merge Requests related to the specified commit.
+
+```
+GET /projects/:id/repository/commits/:sha/merge_requests
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+| `sha`     | string  | yes   | The commit SHA
+
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/af5b13261899fb2c0db30abdd0af8b07cb44fdc5/merge_requests"
+```
+
+Example response:
+
+```json
+[
+   {
+      "id":45,
+      "iid":1,
+      "project_id":35,
+      "title":"Add new file",
+      "description":"",
+      "state":"opened",
+      "created_at":"2018-03-26T17:26:30.916Z",
+      "updated_at":"2018-03-26T17:26:30.916Z",
+      "target_branch":"master",
+      "source_branch":"test-branch",
+      "upvotes":0,
+      "downvotes":0,
+      "author" : {
+        "web_url" : "https://gitlab.example.com/thedude",
+        "name" : "Jeff Lebowski",
+        "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
+        "username" : "thedude",
+        "state" : "active",
+        "id" : 28
+      },
+      "assignee":null,
+      "source_project_id":35,
+      "target_project_id":35,
+      "labels":[ ],
+      "work_in_progress":false,
+      "milestone":null,
+      "merge_when_pipeline_succeeds":false,
+      "merge_status":"can_be_merged",
+      "sha":"af5b13261899fb2c0db30abdd0af8b07cb44fdc5",
+      "merge_commit_sha":null,
+      "user_notes_count":0,
+      "discussion_locked":null,
+      "should_remove_source_branch":null,
+      "force_remove_source_branch":false,
+      "web_url":"http://https://gitlab.example.com/root/test-project/merge_requests/1",
+      "time_stats":{
+         "time_estimate":0,
+         "total_time_spent":0,
+         "human_time_estimate":null,
+         "human_total_time_spent":null
+      }
+   }
+]
+```
+
 [ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit"
 [ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047
 [ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026
+[ce-18004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18004
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
new file mode 100644
index 0000000000000000000000000000000000000000..c341b7f200912df815fab663fefe2caa93048749
--- /dev/null
+++ b/doc/api/discussions.md
@@ -0,0 +1,411 @@
+# Discussions API
+
+Discussions are set of related notes on snippets or issues.
+
+## Issues
+
+### List project issue discussions
+
+Gets a list of all discussions for a single issue.
+
+```
+GET /projects/:id/issues/:issue_iid/discussions
+```
+
+| Attribute           | Type             | Required   | Description  |
+| ------------------- | ---------------- | ---------- | ------------ |
+| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid`         | integer          | yes        | The IID of an issue |
+
+```json
+[
+  {
+    "id": "6a9c1750b37d513a43987b574953fceb50b03ce7",
+    "individual_note": false,
+    "notes": [
+      {
+        "id": 1126,
+        "type": "DiscussionNote",
+        "body": "discussion text",
+        "attachment": null,
+        "author": {
+          "id": 1,
+          "name": "root",
+          "username": "root",
+          "state": "active",
+          "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+          "web_url": "http://localhost:3000/root"
+        },
+        "created_at": "2018-03-03T21:54:39.668Z",
+        "updated_at": "2018-03-03T21:54:39.668Z",
+        "system": false,
+        "noteable_id": 3,
+        "noteable_type": "Issue",
+        "noteable_iid": null
+      },
+      {
+        "id": 1129,
+        "type": "DiscussionNote",
+        "body": "reply to the discussion",
+        "attachment": null,
+        "author": {
+          "id": 1,
+          "name": "root",
+          "username": "root",
+          "state": "active",
+          "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+          "web_url": "http://localhost:3000/root"
+        },
+        "created_at": "2018-03-04T13:38:02.127Z",
+        "updated_at": "2018-03-04T13:38:02.127Z",
+        "system": false,
+        "noteable_id": 3,
+        "noteable_type": "Issue",
+        "noteable_iid": null
+      }
+    ]
+  },
+  {
+    "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6",
+    "individual_note": true,
+    "notes": [
+      {
+        "id": 1128,
+        "type": null,
+        "body": "a single comment",
+        "attachment": null,
+        "author": {
+          "id": 1,
+          "name": "root",
+          "username": "root",
+          "state": "active",
+          "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+          "web_url": "http://localhost:3000/root"
+        },
+        "created_at": "2018-03-04T09:17:22.520Z",
+        "updated_at": "2018-03-04T09:17:22.520Z",
+        "system": false,
+        "noteable_id": 3,
+        "noteable_type": "Issue",
+        "noteable_iid": null
+      }
+    ]
+  }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions
+```
+
+### Get single issue discussion
+
+Returns a single discussion for a specific project issue
+
+```
+GET /projects/:id/issues/:issue_iid/discussions/:discussion_id
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid`     | integer        | yes      | The IID of an issue |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+```
+
+### Create new issue discussion
+
+Creates a new discussion to a single project issue. This is similar to creating
+a note but but another comments (replies) can be added to it later.
+
+```
+POST /projects/:id/issues/:issue_iid/discussions
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid`     | integer        | yes      | The IID of an issue |
+| `body`          | string         | yes      | The content of a discussion |
+| `created_at`    | string         | no       | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions?body=comment
+```
+
+### Add note to existing issue discussion
+
+Adds a new note to the discussion.
+
+```
+POST /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid`     | integer        | yes      | The IID of an issue |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+| `note_id`       | integer        | yes      | The ID of a discussion note |
+| `body`          | string         | yes      | The content of a discussion |
+| `created_at`    | string         | no       | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+```
+
+### Modify existing issue discussion note
+
+Modify existing discussion note of an issue.
+
+```
+PUT /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid`     | integer        | yes      | The IID of an issue |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+| `note_id`       | integer        | yes      | The ID of a discussion note |
+| `body`          | string         | yes      | The content of a discussion |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+```
+
+### Delete an issue discussion note
+
+Deletes an existing discussion note of an issue.
+
+```
+DELETE /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid`     | integer        | yes      | The IID of an issue |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+| `note_id`       | integer        | yes      | The ID of a discussion note |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/636
+```
+
+## Snippets
+
+### List project snippet discussions
+
+Gets a list of all discussions for a single snippet.
+
+```
+GET /projects/:id/snippets/:snippet_id/discussions
+```
+
+| Attribute           | Type             | Required   | Description |
+| ------------------- | ---------------- | ---------- | ------------|
+| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `snippet_id`        | integer          | yes        | The ID of an snippet |
+
+```json
+[
+  {
+    "id": "6a9c1750b37d513a43987b574953fceb50b03ce7",
+    "individual_note": false,
+    "notes": [
+      {
+        "id": 1126,
+        "type": "DiscussionNote",
+        "body": "discussion text",
+        "attachment": null,
+        "author": {
+          "id": 1,
+          "name": "root",
+          "username": "root",
+          "state": "active",
+          "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+          "web_url": "http://localhost:3000/root"
+        },
+        "created_at": "2018-03-03T21:54:39.668Z",
+        "updated_at": "2018-03-03T21:54:39.668Z",
+        "system": false,
+        "noteable_id": 3,
+        "noteable_type": "Snippet",
+        "noteable_id": null
+      },
+      {
+        "id": 1129,
+        "type": "DiscussionNote",
+        "body": "reply to the discussion",
+        "attachment": null,
+        "author": {
+          "id": 1,
+          "name": "root",
+          "username": "root",
+          "state": "active",
+          "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+          "web_url": "http://localhost:3000/root"
+        },
+        "created_at": "2018-03-04T13:38:02.127Z",
+        "updated_at": "2018-03-04T13:38:02.127Z",
+        "system": false,
+        "noteable_id": 3,
+        "noteable_type": "Snippet",
+        "noteable_id": null
+      }
+    ]
+  },
+  {
+    "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6",
+    "individual_note": true,
+    "notes": [
+      {
+        "id": 1128,
+        "type": null,
+        "body": "a single comment",
+        "attachment": null,
+        "author": {
+          "id": 1,
+          "name": "root",
+          "username": "root",
+          "state": "active",
+          "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+          "web_url": "http://localhost:3000/root"
+        },
+        "created_at": "2018-03-04T09:17:22.520Z",
+        "updated_at": "2018-03-04T09:17:22.520Z",
+        "system": false,
+        "noteable_id": 3,
+        "noteable_type": "Snippet",
+        "noteable_id": null
+      }
+    ]
+  }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions
+```
+
+### Get single snippet discussion
+
+Returns a single discussion for a specific project snippet
+
+```
+GET /projects/:id/snippets/:snippet_id/discussions/:discussion_id
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `snippet_id`    | integer        | yes      | The ID of an snippet |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+```
+
+### Create new snippet discussion
+
+Creates a new discussion to a single project snippet. This is similar to creating
+a note but but another comments (replies) can be added to it later.
+
+```
+POST /projects/:id/snippets/:snippet_id/discussions
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `snippet_id`    | integer        | yes      | The ID of an snippet |
+| `body`          | string         | yes      | The content of a discussion |
+| `created_at`    | string         | no       | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions?body=comment
+```
+
+### Add note to existing snippet discussion
+
+Adds a new note to the discussion.
+
+```
+POST /projects/:id/snippets/:snippet_id/discussions/:discussion_id/notes
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `snippet_id`    | integer        | yes      | The ID of an snippet |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+| `note_id`       | integer        | yes      | The ID of a discussion note |
+| `body`          | string         | yes      | The content of a discussion |
+| `created_at`    | string         | no       | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+```
+
+### Modify existing snippet discussion note
+
+Modify existing discussion note of an snippet.
+
+```
+PUT /projects/:id/snippets/:snippet_id/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `snippet_id`    | integer        | yes      | The ID of an snippet |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+| `note_id`       | integer        | yes      | The ID of a discussion note |
+| `body`          | string         | yes      | The content of a discussion |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+```
+
+### Delete an snippet discussion note
+
+Deletes an existing discussion note of an snippet.
+
+```
+DELETE /projects/:id/snippets/:snippet_id/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute       | Type           | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id`            | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `snippet_id`    | integer        | yes      | The ID of an snippet |
+| `discussion_id` | integer        | yes      | The ID of a discussion |
+| `note_id`       | integer        | yes      | The ID of a discussion note |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/636
+```
diff --git a/doc/api/events.md b/doc/api/events.md
index 129af0afa35faa58061ec5af1b654347411f4b80..f4d26c4de1cfa8f7f6d04973ab10d5a1456bbaec 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -42,6 +42,10 @@ Dates for the `before` and `after` parameters should be supplied in the followin
 YYYY-MM-DD
 ```
 
+### Event Time Period Limit
+
+GitLab removes events older than 1 year from the events table for performance reasons. The range of 1 year was chosen because user contribution calendars only show contributions of the past year.
+
 ## List currently authenticated user's events
 
 >**Note:** This endpoint was introduced in GitLab 9.3.
diff --git a/doc/api/features.md b/doc/api/features.md
index 6861dbf00a2e0723215a82547bc09eb4d6244645..6ee1c36ef5b8209ffa269e945a48f3f3315a93dc 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -86,3 +86,11 @@ Example response:
   ]
 }
 ```
+
+## Delete a feature
+
+Removes a feature gate. Response is equal when the gate exists, or doesn't.
+
+```
+DELETE /features/:name
+```
diff --git a/doc/api/group_badges.md b/doc/api/group_badges.md
new file mode 100644
index 0000000000000000000000000000000000000000..0d7d0fd9c421540f26793ca621a2f5d10ffafd55
--- /dev/null
+++ b/doc/api/group_badges.md
@@ -0,0 +1,194 @@
+# Group badges API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17082)
+in GitLab 10.6.
+
+## Placeholder tokens
+
+Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
+
+- **%{project_path}**: will be replaced by the project path.
+- **%{project_id}**: will be replaced by the project id.
+- **%{default_branch}**: will be replaced by the project default branch.
+- **%{commit_sha}**: will be replaced by the last project's commit sha.
+
+Because these enpoints aren't inside a project's context, the information used to replace the placeholders will be
+from the first group's project by creation date. If the group hasn't got any project the original URL with the placeholders will be returned.
+
+## List all badges of a group
+
+Gets a list of a group's badges.
+
+```
+GET /groups/:id/badges
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges
+```
+
+Example response:
+
+```json
+[
+  {
+    "id": 1,
+    "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+    "image_url": "https://shields.io/my/badge",
+    "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+    "rendered_image_url": "https://shields.io/my/badge",
+    "kind": "group"
+  },
+  {
+    "id": 2,
+    "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+    "image_url": "https://shields.io/my/badge",
+    "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+    "rendered_image_url": "https://shields.io/my/badge",
+    "kind": "group"
+  },
+]
+```
+
+## Get a badge of a group
+
+Gets a badge of a group.
+
+```
+GET /groups/:id/badges/:badge_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `badge_id` | integer | yes   | The badge ID |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+  "image_url": "https://shields.io/my/badge",
+  "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+  "rendered_image_url": "https://shields.io/my/badge",
+  "kind": "group"
+}
+```
+
+## Add a badge to a group
+
+Adds a badge to a group.
+
+```
+POST /groups/:id/badges
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `link_url` | string         | yes | URL of the badge link |
+| `image_url` | string | yes | URL of the badge image |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/groups/:id/badges
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "image_url": "https://shields.io/my/badge1",
+  "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "rendered_image_url": "https://shields.io/my/badge1",
+  "kind": "group"
+}
+```
+
+## Edit a badge of a group
+
+Updates a badge of a group.
+
+```
+PUT /groups/:id/badges/:badge_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `badge_id` | integer | yes   | The badge ID |
+| `link_url` | string         | no | URL of the badge link |
+| `image_url` | string | no | URL of the badge image |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "image_url": "https://shields.io/my/badge",
+  "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "rendered_image_url": "https://shields.io/my/badge",
+  "kind": "group"
+}
+```
+
+## Remove a badge from a group
+
+Removes a badge from a group.
+
+```
+DELETE /groups/:id/badges/:badge_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `badge_id` | integer | yes   | The badge ID |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
+```
+
+## Preview a badge from a group
+
+Returns how the `link_url` and `image_url` final URLs would be after resolving the placeholder interpolation.
+
+```
+GET /groups/:id/badges/render
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `link_url` | string         | yes | URL of the badge link|
+| `image_url` | string | yes | URL of the badge image |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
+```
+
+Example response:
+
+```json
+{
+  "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+  "image_url": "https://shields.io/my/badge",
+  "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+  "rendered_image_url": "https://shields.io/my/badge",
+}
+```
diff --git a/doc/api/group_boards.md b/doc/api/group_boards.md
new file mode 100644
index 0000000000000000000000000000000000000000..45a8544d6b16a311761ddcd4bb0e276643f501ba
--- /dev/null
+++ b/doc/api/group_boards.md
@@ -0,0 +1,284 @@
+# Group Issue Boards API
+
+Every API call to group boards must be authenticated.
+
+If a user is not a member of a group and the group is private, a `GET`
+request will result in `404` status code.
+
+## Group Board
+
+Lists Issue Boards in the given group.
+
+```
+GET /groups/:id/boards
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards
+```
+
+Example response:
+
+```json
+[
+  {
+    "id": 1,
+    "group_id": 5,
+    "lists" : [
+      {
+        "id" : 1,
+        "label" : {
+          "name" : "Testing",
+          "color" : "#F0AD4E",
+          "description" : null
+        },
+        "position" : 1
+      },
+      {
+        "id" : 2,
+        "label" : {
+          "name" : "Ready",
+          "color" : "#FF0000",
+          "description" : null
+        },
+        "position" : 2
+      },
+      {
+        "id" : 3,
+        "label" : {
+          "name" : "Production",
+          "color" : "#FF5F00",
+          "description" : null
+        },
+        "position" : 3
+      }
+    ]
+  }
+]
+```
+
+## Single board
+
+Gets a single board.
+
+```
+GET /groups/:id/boards/:board_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1
+```
+
+Example response:
+
+```json
+  {
+    "id": 1,
+    "group_id": 5,
+    "lists" : [
+      {
+        "id" : 1,
+        "label" : {
+          "name" : "Testing",
+          "color" : "#F0AD4E",
+          "description" : null
+        },
+        "position" : 1
+      },
+      {
+        "id" : 2,
+        "label" : {
+          "name" : "Ready",
+          "color" : "#FF0000",
+          "description" : null
+        },
+        "position" : 2
+      },
+      {
+        "id" : 3,
+        "label" : {
+          "name" : "Production",
+          "color" : "#FF5F00",
+          "description" : null
+        },
+        "position" : 3
+      }
+    ]
+  }
+```
+
+## List board lists
+
+Get a list of the board's lists.
+Does not include `backlog` and `closed` lists
+
+```
+GET /groups/:id/boards/:board_id/lists
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists
+```
+
+Example response:
+
+```json
+[
+  {
+    "id" : 1,
+    "label" : {
+      "name" : "Testing",
+      "color" : "#F0AD4E",
+      "description" : null
+    },
+    "position" : 1
+  },
+  {
+    "id" : 2,
+    "label" : {
+      "name" : "Ready",
+      "color" : "#FF0000",
+      "description" : null
+    },
+    "position" : 2
+  },
+  {
+    "id" : 3,
+    "label" : {
+      "name" : "Production",
+      "color" : "#FF5F00",
+      "description" : null
+    },
+    "position" : 3
+  }
+]
+```
+
+## Single board list
+
+Get a single board list.
+
+```
+GET /groups/:id/boards/:board_id/lists/:list_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+| `list_id` | integer | yes | The ID of a board's list |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists/1
+```
+
+Example response:
+
+```json
+{
+  "id" : 1,
+  "label" : {
+    "name" : "Testing",
+    "color" : "#F0AD4E",
+    "description" : null
+  },
+  "position" : 1
+}
+```
+
+## New board list
+
+Creates a new Issue Board list.
+
+```
+POST /groups/:id/boards/:board_id/lists
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+| `label_id` | integer | yes | The ID of a label |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists?label_id=5
+```
+
+Example response:
+
+```json
+{
+  "id" : 1,
+  "label" : {
+    "name" : "Testing",
+    "color" : "#F0AD4E",
+    "description" : null
+  },
+  "position" : 1
+}
+```
+
+## Edit board list
+
+Updates an existing Issue Board list. This call is used to change list position.
+
+```
+PUT /groups/:id/boards/:board_id/lists/:list_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`            | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+| `list_id` | integer | yes | The ID of a board's list |
+| `position` | integer | yes | The position of the list |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/group/5/boards/1/lists/1?position=2
+```
+
+Example response:
+
+```json
+{
+  "id" : 1,
+  "label" : {
+    "name" : "Testing",
+    "color" : "#F0AD4E",
+    "description" : null
+  },
+  "position" : 1
+}
+```
+
+## Delete a board list
+
+Only for admins and group owners. Soft deletes the board list in question.
+
+```
+DELETE /groups/:id/boards/:board_id/lists/:list_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+| `list_id` | integer | yes | The ID of a board's list |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists/1
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index f50558b58a6324c546a9a191d604de27b4856840..1aed8aac64e6c32d417aead24a1e93ad1c414723 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -525,3 +525,7 @@ And to switch pages add:
 ```
 
 [ce-15142]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15142
+
+## Group badges
+
+Read more in the [Group Badges](group_badges.md) documentation.
diff --git a/doc/api/issues.md b/doc/api/issues.md
index da89db17cd973c289ace6e9d638a90ca2bdaa84d..7479c1d2f93bd93de3aef5c203a525ced3784f55 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -46,6 +46,10 @@ GET /issues?my_reaction_emoji=star
 | `order_by`          | string           | no         | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at`                                                               |
 | `sort`              | string           | no         | Return issues sorted in `asc` or `desc` order. Default is `desc`                                                                                    |
 | `search`            | string           | no         | Search issues against their `title` and `description`                                                                                               |
+| `created_after`     | datetime         | no         | Return issues created on or after the given time                                                                                                    |
+| `created_before`    | datetime         | no         | Return issues created on or before the given time                                                                                                   |
+| `updated_after`     | datetime         | no         | Return issues updated on or after the given time                                                                                                    |
+| `updated_before`    | datetime         | no         | Return issues updated on or before the given time                                                                                                   |
 
 ```bash
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues
@@ -96,6 +100,7 @@ Example response:
       },
       "updated_at" : "2016-01-04T15:31:51.081Z",
       "closed_at" : null,
+      "closed_by" : null,
       "id" : 76,
       "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
       "created_at" : "2016-01-04T15:31:51.081Z",
@@ -118,6 +123,8 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## List group issues
 
 Get a list of a group's issues.
@@ -152,6 +159,10 @@ GET /groups/:id/issues?my_reaction_emoji=star
 | `order_by`          | string           | no         | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at`                                         |
 | `sort`              | string           | no         | Return issues sorted in `asc` or `desc` order. Default is `desc`                                                              |
 | `search`            | string           | no         | Search group issues against their `title` and `description`                                                                   |
+| `created_after`     | datetime         | no         | Return issues created on or after the given time                                                                              |
+| `created_before`    | datetime         | no         | Return issues created on or before the given time                                                                             |
+| `updated_after`     | datetime         | no         | Return issues updated on or after the given time                                                                              |
+| `updated_before`    | datetime         | no         | Return issues updated on or before the given time                                                                             |
 
 
 ```bash
@@ -208,6 +219,7 @@ Example response:
       "updated_at" : "2016-01-04T15:31:46.176Z",
       "created_at" : "2016-01-04T15:31:46.176Z",
       "closed_at" : null,
+      "closed_by" : null,
       "user_notes_count": 1,
       "due_date": null,
       "web_url": "http://example.com/example/example/issues/1",
@@ -225,6 +237,8 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## List project issues
 
 Get a list of a project's issues.
@@ -259,8 +273,10 @@ GET /projects/:id/issues?my_reaction_emoji=star
 | `order_by`          | string           | no         | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at`                                         |
 | `sort`              | string           | no         | Return issues 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)                                                                       |
+| `created_after`     | datetime         | no         | Return issues created on or after the given time                                                                              |
+| `created_before`    | datetime         | no         | Return issues created on or before the given time                                                                             |
+| `updated_after`     | datetime         | no         | Return issues updated on or after the given time                                                                              |
+| `updated_before`    | datetime         | no         | Return issues updated on or before the given time                                                                             |
 
 ```bash
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
@@ -316,6 +332,14 @@ Example response:
       "updated_at" : "2016-01-04T15:31:46.176Z",
       "created_at" : "2016-01-04T15:31:46.176Z",
       "closed_at" : "2016-01-05T15:31:46.176Z",
+      "closed_by" : {
+         "state" : "active",
+         "web_url" : "https://gitlab.example.com/root",
+         "avatar_url" : null,
+         "username" : "root",
+         "id" : 1,
+         "name" : "Administrator"
+      },
       "user_notes_count": 1,
       "due_date": "2016-07-22",
       "web_url": "http://example.com/example/example/issues/1",
@@ -333,6 +357,8 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## Single issue
 
 Get a single project issue.
@@ -399,6 +425,8 @@ Example response:
    "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
    "updated_at" : "2016-01-04T15:31:46.176Z",
    "created_at" : "2016-01-04T15:31:46.176Z",
+   "closed_at" : null,
+   "closed_by" : null,
    "subscribed": false,
    "user_notes_count": 1,
    "due_date": null,
@@ -422,6 +450,8 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## New issue
 
 Creates a new project issue.
@@ -474,6 +504,7 @@ Example response:
    "description" : null,
    "updated_at" : "2016-01-07T12:44:33.959Z",
    "closed_at" : null,
+   "closed_by" : null,
    "milestone" : null,
    "subscribed" : true,
    "user_notes_count": 0,
@@ -498,6 +529,8 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## Edit issue
 
 Updates an existing project issue. This call is also used to mark an issue as
@@ -546,6 +579,14 @@ Example response:
    "description" : null,
    "updated_at" : "2016-01-07T12:55:16.213Z",
    "closed_at" : "2016-01-08T12:55:16.213Z",
+   "closed_by" : {
+      "state" : "active",
+      "web_url" : "https://gitlab.example.com/root",
+      "avatar_url" : null,
+      "username" : "root",
+      "id" : 1,
+      "name" : "Administrator"
+    },
    "iid" : 15,
    "labels" : [
       "bug"
@@ -577,6 +618,8 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## Delete an issue
 
 Only for admins and project owners. Soft deletes the issue in question.
@@ -630,6 +673,7 @@ Example response:
   "created_at": "2016-04-05T21:41:45.652Z",
   "updated_at": "2016-04-07T12:20:17.596Z",
   "closed_at": null,
+  "closed_by": null,
   "labels": [],
   "milestone": null,
   "assignees": [{
@@ -677,6 +721,8 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## Subscribe to an issue
 
 Subscribes the authenticated user to an issue to receive notifications.
@@ -709,6 +755,7 @@ Example response:
   "created_at": "2016-04-05T21:41:45.652Z",
   "updated_at": "2016-04-07T12:20:17.596Z",
   "closed_at": null,
+  "closed_by": null,
   "labels": [],
   "milestone": null,
   "assignees": [{
@@ -756,6 +803,9 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## Unsubscribe from an issue
 
 Unsubscribes the authenticated user from the issue to not receive notifications
@@ -797,6 +847,8 @@ Example response:
     "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon",
     "web_url": "https://gitlab.example.com/keyon"
   },
+  "closed_at": null,
+  "closed_by": null,
   "author": {
     "name": "Vivian Hermann",
     "username": "orville",
@@ -917,6 +969,9 @@ Example response:
 
 **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
 
+
+**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
+
 ## Set a time estimate for an issue
 
 Sets an estimated time of work for this issue.
@@ -1102,6 +1157,8 @@ Example response:
     "assignee": null,
     "source_project_id": 1,
     "target_project_id": 1,
+    "closed_at": null,
+    "closed_by": null,
     "labels": [],
     "work_in_progress": false,
     "milestone": null,
@@ -1196,3 +1253,4 @@ Example response:
 
 [ce-13004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13004
 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
+[ce-17042]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17042
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index e7060e154f414300c1e915e249a30eb6da57e8dc..db4fe2f688012a5cbe4c178fc0e1f39e5cd61442 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -294,9 +294,10 @@ Example of response
 
 ## Get job artifacts
 
-> [Introduced][ce-2893] in GitLab 8.5
+> **Notes**:
+- [Introduced][ce-2893] in GitLab 8.5.
 
-Get job artifacts of a project
+Get job artifacts of a project.
 
 ```
 GET /projects/:id/jobs/:job_id/artifacts
@@ -307,8 +308,10 @@ GET /projects/:id/jobs/:job_id/artifacts
 | `id`       | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
 | `job_id` | integer | yes      | The ID of a job   |
 
+Example requests:
+
 ```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
+curl --location --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
 ```
 
 Response:
@@ -322,7 +325,8 @@ Response:
 
 ## Download the artifacts archive
 
-> [Introduced][ce-5347] in GitLab 8.10.
+> **Notes**:
+- [Introduced][ce-5347] in GitLab 8.10.
 
 Download the artifacts archive from the given reference name and job provided the
 job finished successfully.
@@ -339,7 +343,7 @@ Parameters
 | `ref_name`  | string  | yes      | The ref from a repository (can only be branch or tag name, not HEAD or SHA) |
 | `job`       | string  | yes      | The name of the job       |
 
-Example request:
+Example requests:
 
 ```
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index cb9b06187676c0d35531c16c9c5be136e68dc771..b9a4f66177761eccaa41939c1f6a16d37409687c 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -41,8 +41,10 @@ Parameters:
 | `milestone`         | string   | no       | Return merge requests for a specific milestone                                                                         |
 | `view`              | string   | no       | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request                              |
 | `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)                                                        |
+| `created_after`     | datetime | no       | Return merge requests created on or after the given time                                                               |
+| `created_before`    | datetime | no       | Return merge requests created on or before the given time                                                              |
+| `updated_after`     | datetime | no       | Return merge requests updated on or after the given time                                                               |
+| `updated_before`    | datetime | no       | Return merge requests updated on or before the given time                                                              |
 | `scope`             | string   | no       | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me`     |
 | `author_id`         | integer  | no       | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`              |
 | `assignee_id`       | integer  | no       | Returns merge requests assigned to the given user `id`                                                                 |
@@ -158,8 +160,10 @@ Parameters:
 | `milestone`         | string         | no       | Return merge requests for a specific milestone                                                                                 |
 | `view`              | string         | no       | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request                                      |
 | `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)                                                                |
+| `created_after`     | datetime       | no       | Return merge requests created on or after the given time                                                                       |
+| `created_before`    | datetime       | no       | Return merge requests created on or before the given time                                                                      |
+| `updated_after`     | datetime       | no       | Return merge requests updated on or after the given time                                                                       |
+| `updated_before`    | datetime       | no       | Return merge requests updated on or before the given time                                                                      |
 | `scope`             | string         | no       | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ |
 | `author_id`         | integer        | no       | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_                                 |
 | `assignee_id`       | integer        | no       | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_                                |
@@ -494,6 +498,8 @@ Parameters:
 
 ## List MR pipelines
 
+> [Introduced][ce-15454] in GitLab 10.5.0.
+
 Get a list of merge request pipelines.
 
 ```
@@ -523,18 +529,19 @@ Creates a new merge request.
 POST /projects/:id/merge_requests
 ```
 
-| Attribute              | Type    | Required | Description                                                                     |
-| ---------              | ----    | -------- | -----------                                                                     |
-| `id`                   | integer/string  | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `source_branch`        | string  | yes      | The source branch                                                               |
-| `target_branch`        | string  | yes      | The target branch                                                               |
-| `title`                | string  | yes      | Title of MR                                                                     |
-| `assignee_id`          | integer | no       | Assignee user ID                                                                |
-| `description`          | string  | no       | Description of MR                                                               |
-| `target_project_id`    | integer | no       | The target project (numeric id)                                                 |
-| `labels`               | string  | no       | Labels for MR as a comma-separated list                                         |
-| `milestone_id`         | integer | no       | The ID of a milestone                                                           |
-| `remove_source_branch` | boolean | no       | Flag indicating if a merge request should remove the source branch when merging |
+| Attribute                  | Type    | Required | Description                                                                     |
+| ---------                  | ----    | -------- | -----------                                                                     |
+| `id`                       | integer/string  | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `source_branch`            | string  | yes      | The source branch                                                               |
+| `target_branch`            | string  | yes      | The target branch                                                               |
+| `title`                    | string  | yes      | Title of MR                                                                     |
+| `assignee_id`              | integer | no       | Assignee user ID                                                                |
+| `description`              | string  | no       | Description of MR                                                               |
+| `target_project_id`        | integer | no       | The target project (numeric id)                                                 |
+| `labels`                   | string  | no       | Labels for MR as a comma-separated list                                         |
+| `milestone_id`             | integer | no       | The ID of a milestone                                                           |
+| `remove_source_branch`     | boolean | no       | Flag indicating if a merge request should remove the source branch when merging |
+| `allow_maintainer_to_push` | boolean | no       | Whether or not a maintainer of the target project can push to the source branch  |
 
 ```json
 {
@@ -542,7 +549,7 @@ POST /projects/:id/merge_requests
   "iid": 1,
   "target_branch": "master",
   "source_branch": "test1",
-  "project_id": 3,
+  "project_id": 4,
   "title": "test1",
   "state": "opened",
   "upvotes": 0,
@@ -563,7 +570,7 @@ POST /projects/:id/merge_requests
     "state": "active",
     "created_at": "2012-04-29T08:46:00Z"
   },
-  "source_project_id": 4,
+  "source_project_id": 3,
   "target_project_id": 4,
   "labels": [ ],
   "description": "fixed login page css paddings",
@@ -590,6 +597,7 @@ POST /projects/:id/merge_requests
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
   "discussion_locked": false,
+  "allow_maintainer_to_push": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -607,19 +615,20 @@ Updates an existing merge request. You can change the target branch, title, or e
 PUT /projects/:id/merge_requests/:merge_request_iid
 ```
 
-| Attribute              | Type    | Required | Description                                                                     |
-| ---------              | ----    | -------- | -----------                                                                     |
-| `id`                   | integer/string | yes  | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `merge_request_iid`    | integer | yes      | The ID of a merge request                                                       |
-| `target_branch`        | string  | no       | The target branch                                                               |
-| `title`                | string  | no       | Title of MR                                                                     |
-| `assignee_id`          | integer | no       | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees.  |
-| `milestone_id`         | integer | no       | The ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
-| `labels`               | string  | no       | Comma-separated label names for a merge request. Set to an empty string to unassign all labels.                    |
-| `description`          | string  | no       | Description of MR                                                               |
-| `state_event`          | string  | no       | New state (close/reopen)                                                        |
-| `remove_source_branch` | boolean | no       | Flag indicating if a merge request should remove the source branch when merging |
-| `discussion_locked`    | boolean | no       | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. |
+| Attribute                  | Type    | Required | Description                                                                     |
+| ---------                  | ----    | -------- | -----------                                                                     |
+| `id`                       | integer/string | yes  | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `merge_request_iid`        | integer | yes      | The ID of a merge request                                                       |
+| `target_branch`            | string  | no       | The target branch                                                               |
+| `title`                    | string  | no       | Title of MR                                                                     |
+| `assignee_id`              | integer | no       | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees.  |
+| `milestone_id`             | integer | no       | The ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
+| `labels`                   | string  | no       | Comma-separated label names for a merge request. Set to an empty string to unassign all labels.                    |
+| `description`              | string  | no       | Description of MR                                                               |
+| `state_event`              | string  | no       | New state (close/reopen)                                                        |
+| `remove_source_branch`     | boolean | no       | Flag indicating if a merge request should remove the source branch when merging |
+| `discussion_locked`        | boolean | no       | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. |
+| `allow_maintainer_to_push` | boolean | no       | Whether or not a maintainer of the target project can push to the source branch |
 
 Must include at least one non-required attribute from above.
 
@@ -628,7 +637,7 @@ Must include at least one non-required attribute from above.
   "id": 1,
   "iid": 1,
   "target_branch": "master",
-  "project_id": 3,
+  "project_id": 4,
   "title": "test1",
   "state": "opened",
   "upvotes": 0,
@@ -649,7 +658,7 @@ Must include at least one non-required attribute from above.
     "state": "active",
     "created_at": "2012-04-29T08:46:00Z"
   },
-  "source_project_id": 4,
+  "source_project_id": 3,
   "target_project_id": 4,
   "labels": [ ],
   "description": "description1",
@@ -676,6 +685,7 @@ Must include at least one non-required attribute from above.
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
   "discussion_locked": false,
+  "allow_maintainer_to_push": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -1449,3 +1459,4 @@ Example response:
 
 [ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060
 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
+[ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 1b68bd99ce25e4d4fbbdc136ef103c1a976e9d39..aa38d22845c378f72974aefb758b75face0854f8 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -15,7 +15,7 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at
 
 | Attribute           | Type             | Required   | Description                                                                                                                                         |
 | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 | `issue_iid`         | integer          | yes        | The IID of an issue
 | `sort`              | string           | no         | Return issue notes sorted in `asc` or `desc` order. Default is `desc`
 | `order_by`          | string           | no         | Return issue notes ordered by `created_at` or `updated_at` fields. Default is `created_at`
@@ -63,6 +63,10 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at
 ]
 ```
 
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes
+```
+
 ### Get single issue note
 
 Returns a single note for a specific project issue
@@ -73,14 +77,17 @@ GET /projects/:id/issues/:issue_iid/notes/:note_id
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `issue_iid` (required) - The IID of a project issue
 - `note_id` (required) - The ID of an issue note
 
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes/1
+```
+
 ### Create new issue note
 
-Creates a new note to a single project issue. If you create a note where the body
-only contains an Award Emoji, you'll receive this object back.
+Creates a new note to a single project issue.
 
 ```
 POST /projects/:id/issues/:issue_iid/notes
@@ -88,11 +95,15 @@ POST /projects/:id/issues/:issue_iid/notes
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `issue_id` (required) - The IID of an issue
 - `body` (required) - The content of a note
 - `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
 
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
+```
+
 ### Modify existing issue note
 
 Modify existing note of an issue.
@@ -103,11 +114,15 @@ PUT /projects/:id/issues/:issue_iid/notes/:note_id
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `issue_iid` (required) - The IID of an issue
 - `note_id` (required) - The ID of a note
 - `body` (required) - The content of a note
 
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
+```
+
 ### Delete an issue note
 
 Deletes an existing note of an issue.
@@ -120,7 +135,7 @@ Parameters:
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
 | `issue_iid` | integer | yes | The IID of an issue |
 | `note_id` | integer | yes | The ID of a note |
 
@@ -141,11 +156,15 @@ GET /projects/:id/snippets/:snippet_id/notes?sort=asc&order_by=updated_at
 
 | Attribute           | Type             | Required   | Description                                                                                                                                         |
 | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 | `snippet_id`        | integer          | yes        | The ID of a project snippet
 | `sort`              | string           | no         | Return snippet notes sorted in `asc` or `desc` order. Default is `desc`
 | `order_by`          | string           | no         | Return snippet notes ordered by `created_at` or `updated_at` fields. Default is `created_at`
 
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes
+```
+
 ### Get single snippet note
 
 Returns a single note for a given snippet.
@@ -156,7 +175,7 @@ GET /projects/:id/snippets/:snippet_id/notes/:note_id
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `snippet_id` (required) - The ID of a project snippet
 - `note_id` (required) - The ID of a snippet note
 
@@ -179,6 +198,10 @@ Parameters:
 }
 ```
 
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes/11
+```
+
 ### Create new snippet note
 
 Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
@@ -190,10 +213,14 @@ POST /projects/:id/snippets/:snippet_id/notes
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `snippet_id` (required) - The ID of a snippet
 - `body` (required) - The content of a note
 
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
+```
+
 ### Modify existing snippet note
 
 Modify existing note of a snippet.
@@ -204,11 +231,15 @@ PUT /projects/:id/snippets/:snippet_id/notes/:note_id
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `snippet_id` (required) - The ID of a snippet
 - `note_id` (required) - The ID of a note
 - `body` (required) - The content of a note
 
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes?body=note
+```
+
 ### Delete a snippet note
 
 Deletes an existing note of a snippet.
@@ -221,7 +252,7 @@ Parameters:
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
 | `snippet_id` | integer | yes | The ID of a snippet |
 | `note_id` | integer | yes | The ID of a note |
 
@@ -242,11 +273,15 @@ GET /projects/:id/merge_requests/:merge_request_iid/notes?sort=asc&order_by=upda
 
 | Attribute           | Type             | Required   | Description                                                                                                                                         |
 | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+| `id`                | integer/string   | yes        | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 | `merge_request_iid` | integer          | yes        | The IID of a project merge request
 | `sort`              | string           | no         | Return merge request notes sorted in `asc` or `desc` order. Default is `desc`
 | `order_by`          | string           | no         | Return merge request notes ordered by `created_at` or `updated_at` fields. Default is `created_at`
 
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes
+```
+
 ### Get single merge request note
 
 Returns a single note for a given merge request.
@@ -257,7 +292,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/notes/:note_id
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `merge_request_iid` (required) - The IID of a project merge request
 - `note_id` (required) - The ID of a merge request note
 
@@ -283,6 +318,10 @@ Parameters:
 }
 ```
 
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes/1
+```
+
 ### Create new merge request note
 
 Creates a new note for a single merge request.
@@ -295,7 +334,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/notes
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `merge_request_iid` (required) - The IID of a merge request
 - `body` (required) - The content of a note
 
@@ -309,11 +348,15 @@ PUT /projects/:id/merge_requests/:merge_request_iid/notes/:note_id
 
 Parameters:
 
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
 - `merge_request_iid` (required) - The IID of a merge request
 - `note_id` (required) - The ID of a note
 - `body` (required) - The content of a note
 
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes?body=note
+```
+
 ### Delete a merge request note
 
 Deletes an existing note of a merge request.
@@ -326,7 +369,7 @@ Parameters:
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
 | `merge_request_iid` | integer | yes | The IID of a merge request |
 | `note_id` | integer | yes | The ID of a note |
 
diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md
index 3a2c398e35571592f602468f600efb551bff635c..682b90361bdc725fe93a7a4c67fb55e07a0a4717 100644
--- a/doc/api/notification_settings.md
+++ b/doc/api/notification_settings.md
@@ -23,7 +23,9 @@ new_issue
 reopen_issue
 close_issue
 reassign_issue
+issue_due
 new_merge_request
+push_to_merge_request
 reopen_merge_request
 close_merge_request
 reassign_merge_request
@@ -74,7 +76,9 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
 | `reopen_issue` | boolean | no | Enable/disable this notification |
 | `close_issue` | boolean | no | Enable/disable this notification |
 | `reassign_issue` | boolean | no | Enable/disable this notification |
+| `issue_due` | boolean | no | Enable/disable this notification |
 | `new_merge_request` | boolean | no | Enable/disable this notification |
+| `push_to_merge_request` | boolean | no | Enable/disable this notification |
 | `reopen_merge_request` | boolean | no | Enable/disable this notification |
 | `close_merge_request` | boolean | no | Enable/disable this notification |
 | `reassign_merge_request` | boolean | no | Enable/disable this notification |
@@ -140,7 +144,9 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
 | `reopen_issue` | boolean | no | Enable/disable this notification |
 | `close_issue` | boolean | no | Enable/disable this notification |
 | `reassign_issue` | boolean | no | Enable/disable this notification |
+| `issue_due` | boolean | no | Enable/disable this notification |
 | `new_merge_request` | boolean | no | Enable/disable this notification |
+| `push_to_merge_request` | boolean | no | Enable/disable this notification |
 | `reopen_merge_request` | boolean | no | Enable/disable this notification |
 | `close_merge_request` | boolean | no | Enable/disable this notification |
 | `reassign_merge_request` | boolean | no | Enable/disable this notification |
@@ -163,7 +169,9 @@ Example responses:
     "reopen_issue": false,
     "close_issue": false,
     "reassign_issue": false,
+    "issue_due": false,
     "new_merge_request": false,
+    "push_to_merge_request": false,
     "reopen_merge_request": false,
     "close_merge_request": false,
     "reassign_merge_request": false,
diff --git a/doc/api/project_badges.md b/doc/api/project_badges.md
new file mode 100644
index 0000000000000000000000000000000000000000..94389273e9c162beff377ef894b58ee056e9dfac
--- /dev/null
+++ b/doc/api/project_badges.md
@@ -0,0 +1,191 @@
+# Project badges API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17082)
+in GitLab 10.6.
+
+## Placeholder tokens
+
+Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
+
+- **%{project_path}**: will be replaced by the project path.
+- **%{project_id}**: will be replaced by the project id.
+- **%{default_branch}**: will be replaced by the project default branch.
+- **%{commit_sha}**: will be replaced by the last project's commit sha.
+
+## List all badges of a project
+
+Gets a list of a project's badges and its group badges.
+
+```
+GET /projects/:id/badges
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges
+```
+
+Example response:
+
+```json
+[
+  {
+    "id": 1,
+    "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+    "image_url": "https://shields.io/my/badge",
+    "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+    "rendered_image_url": "https://shields.io/my/badge",
+    "kind": "project"
+  },
+  {
+    "id": 2,
+    "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+    "image_url": "https://shields.io/my/badge",
+    "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+    "rendered_image_url": "https://shields.io/my/badge",
+    "kind": "group"
+  },
+]
+```
+
+## Get a badge of a project
+
+Gets a badge of a project.
+
+```
+GET /projects/:id/badges/:badge_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `badge_id` | integer | yes   | The badge ID |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+  "image_url": "https://shields.io/my/badge",
+  "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+  "rendered_image_url": "https://shields.io/my/badge",
+  "kind": "project"
+}
+```
+
+## Add a badge to a project
+
+Adds a badge to a project.
+
+```
+POST /projects/:id/badges
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the project ](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `link_url` | string         | yes | URL of the badge link |
+| `image_url` | string | yes | URL of the badge image |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/projects/:id/badges
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "image_url": "https://shields.io/my/badge1",
+  "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "rendered_image_url": "https://shields.io/my/badge1",
+  "kind": "project"
+}
+```
+
+## Edit a badge of a project
+
+Updates a badge of a project.
+
+```
+PUT /projects/:id/badges/:badge_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `badge_id` | integer | yes   | The badge ID |
+| `link_url` | string         | no | URL of the badge link |
+| `image_url` | string | no | URL of the badge image |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "image_url": "https://shields.io/my/badge",
+  "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
+  "rendered_image_url": "https://shields.io/my/badge",
+  "kind": "project"
+}
+```
+
+## Remove a badge from a project
+
+Removes a badge from a project. Only project's badges will be removed by using this endpoint.
+
+```
+DELETE /projects/:id/badges/:badge_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `badge_id` | integer | yes   | The badge ID |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
+```
+
+## Preview a badge from a project
+
+Returns how the `link_url` and `image_url` final URLs would be after resolving the placeholder interpolation.
+
+```
+GET /projects/:id/badges/render
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `link_url` | string         | yes | URL of the badge link|
+| `image_url` | string | yes | URL of the badge image |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
+```
+
+Example response:
+
+```json
+{
+  "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
+  "image_url": "https://shields.io/my/badge",
+  "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
+  "rendered_image_url": "https://shields.io/my/badge",
+}
+```
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index e442442c750e8e281a7e70bfdaaf7501dce081e3..d8f61852b11373421b5ccacdbbc5793ec818196f 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -1,9 +1,105 @@
-# Project import API
+# Project import/export API
 
 [Introduced][ce-41899] in GitLab 10.6
 
 [See also the project import/export documentation](../user/project/settings/import_export.md)
 
+## Schedule an export
+
+Start a new export.
+
+The endpoint also accepts an `upload` param. This param is a hash that contains
+all the necessary information to upload the exported project to a web server or
+to any S3-compatible platform. At the moment we only support binary
+data file uploads to the final server.
+
+If the `upload` params is present, `upload[url]` param is required.
+ (**Note:** This feature was introduced in GitLab 10.7)
+
+```http
+POST /projects/:id/export
+```
+
+| Attribute | Type           | Required | Description                              |
+| --------- | -------------- | -------- | ---------------------------------------- |
+| `id`      | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `description`      | string | no | Overrides the project description |
+| `upload`      | hash | no | Hash that contains the information to upload the exported project to a web server |
+| `upload[url]`      | string | yes      | The URL to upload the project |
+| `upload[http_method]`      | string | no      | The HTTP method to upload the exported project. Only `PUT` and `POST` methods allowed. Default is `PUT` |
+
+```console
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export --data "description=FooBar&upload[http_method]=PUT&upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd"
+```
+
+```json
+{
+  "message": "202 Accepted"
+}
+```
+
+## Export status
+
+Get the status of export.
+
+```http
+GET /projects/:id/export
+```
+
+| Attribute | Type           | Required | Description                              |
+| --------- | -------------- | -------- | ---------------------------------------- |
+| `id`      | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```console
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export
+```
+
+Status can be one of `none`, `started`, `after_export_action` or `finished`. The
+`after_export_action` state represents that the export process has been completed successfully and
+the platform is performing some actions on the resulted file. For example, sending
+an email notifying the user to download the file, uploading the exported file
+to a web server, etc.
+
+`_links` are only present when export has finished.
+
+```json
+{
+  "id": 1,
+  "description": "Itaque perspiciatis minima aspernatur corporis consequatur.",
+  "name": "Gitlab Test",
+  "name_with_namespace": "Gitlab Org / Gitlab Test",
+  "path": "gitlab-test",
+  "path_with_namespace": "gitlab-org/gitlab-test",
+  "created_at": "2017-08-29T04:36:44.383Z",
+  "export_status": "finished",
+  "_links": {
+    "api_url": "https://gitlab.example.com/api/v4/projects/1/export/download",
+    "web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/download_export",
+  }
+}
+```
+
+## Export download
+
+Download the finished export.
+
+```http
+GET /projects/:id/export/download
+```
+
+| Attribute | Type           | Required | Description                              |
+| --------- | -------------- | -------- | ---------------------------------------- |
+| `id`      | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```console
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --remote-header-name --remote-name https://gitlab.example.com/api/v4/projects/5/export/download
+```
+
+```console
+ls *export.tar.gz
+2017-12-05_22-11-148_namespace_project_export.tar.gz
+```
+
 ## Import a file
 
 ```http
@@ -15,6 +111,10 @@ POST /projects/import
 | `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
 | `file` | string | yes | The file to be uploaded |
 | `path` | string | yes | Name and path for new project |
+| `overwrite` | boolean | no | If there is a project with the same path the import will overwrite it. Default to false |
+| `override_params` | Hash | no | Supports all fields defined in the [Project API](projects.md)] |
+
+The override params passed will take precendence over all values defined inside the export file.
 
 To upload a file from your filesystem, use the `--form` argument. This causes
 cURL to post data using the header `Content-Type: multipart/form-data`.
diff --git a/doc/api/projects.md b/doc/api/projects.md
index b6442cfac22906dc64cbb86a4728a429ac0966ad..7ffe380e275d2005160c759db8f1b05588230726 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -16,6 +16,21 @@ Values for the project visibility level are:
 * `public`:
   The project can be cloned without any authentication.
 
+## Project merge method
+
+There are currently three options for `merge_method` to choose from:
+
+* `merge`:
+  A merge commit is created for every merge, and merging is allowed as long as there are no conflicts.
+
+* `rebase_merge`:
+  A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible.
+  This way you could make sure that if this merge request would build, after merging to target branch it would also build.
+
+* `ff`:
+  No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.
+
+
 ## List all projects
 
 Get a list of all visible projects across GitLab for the authenticated user.
@@ -94,6 +109,7 @@ GET /projects
     "only_allow_merge_if_pipeline_succeeds": false,
     "only_allow_merge_if_all_discussions_are_resolved": false,
     "request_access_enabled": false,
+    "merge_method": "merge",
     "statistics": {
       "commit_count": 37,
       "storage_size": 1038090,
@@ -173,6 +189,7 @@ GET /projects
     "only_allow_merge_if_pipeline_succeeds": false,
     "only_allow_merge_if_all_discussions_are_resolved": false,
     "request_access_enabled": false,
+    "merge_method": "merge",
     "statistics": {
       "commit_count": 12,
       "storage_size": 2066080,
@@ -278,6 +295,7 @@ GET /users/:user_id/projects
     "only_allow_merge_if_pipeline_succeeds": false,
     "only_allow_merge_if_all_discussions_are_resolved": false,
     "request_access_enabled": false,
+    "merge_method": "merge",
     "statistics": {
       "commit_count": 37,
       "storage_size": 1038090,
@@ -357,6 +375,7 @@ GET /users/:user_id/projects
     "only_allow_merge_if_pipeline_succeeds": false,
     "only_allow_merge_if_all_discussions_are_resolved": false,
     "request_access_enabled": false,
+    "merge_method": "merge",
     "statistics": {
       "commit_count": 12,
       "storage_size": 2066080,
@@ -467,6 +486,7 @@ GET /projects/:id
   "only_allow_merge_if_all_discussions_are_resolved": false,
   "printing_merge_requests_link_enabled": true,
   "request_access_enabled": false,
+  "merge_method": "merge",
   "statistics": {
     "commit_count": 37,
     "storage_size": 1038090,
@@ -550,6 +570,7 @@ POST /projects
 | `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 |
 | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
+| `merge_method` | string | no | Set the merge method used |
 | `lfs_enabled` | boolean | no | Enable LFS |
 | `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 |
@@ -586,6 +607,7 @@ POST /projects/user/:user_id
 | `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 |
 | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
+| `merge_method` | string | no | Set the merge method used |
 | `lfs_enabled` | boolean | no | Enable LFS |
 | `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 |
@@ -621,6 +643,7 @@ PUT /projects/:id
 | `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 |
 | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
+| `merge_method` | string | no | Set the merge method used |
 | `lfs_enabled` | boolean | no | Enable LFS |
 | `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 |
@@ -724,6 +747,7 @@ Example responses:
     "only_allow_merge_if_pipeline_succeeds": false,
     "only_allow_merge_if_all_discussions_are_resolved": false,
     "request_access_enabled": false,
+    "merge_method": "merge",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -801,6 +825,7 @@ Example response:
   "only_allow_merge_if_pipeline_succeeds": false,
   "only_allow_merge_if_all_discussions_are_resolved": false,
   "request_access_enabled": false,
+  "merge_method": "merge",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -877,6 +902,7 @@ Example response:
   "only_allow_merge_if_pipeline_succeeds": false,
   "only_allow_merge_if_all_discussions_are_resolved": false,
   "request_access_enabled": false,
+  "merge_method": "merge",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -889,6 +915,29 @@ Example response:
 }
 ```
 
+## Languages
+
+Get languages used in a project with percentage value.
+
+```
+GET /projects/:id/languages
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/languages"
+```
+
+Example response:
+
+```json
+{
+  "Ruby": 66.69,
+  "JavaScript": 22.98,
+  "HTML": 7.91,
+  "CoffeeScript": 2.42
+}
+```
+
 ## Archive a project
 
 Archives the project if the user is either admin or the project owner of this project. This action is
@@ -971,6 +1020,7 @@ Example response:
   "only_allow_merge_if_pipeline_succeeds": false,
   "only_allow_merge_if_all_discussions_are_resolved": false,
   "request_access_enabled": false,
+  "merge_method": "merge",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -1065,6 +1115,7 @@ Example response:
   "only_allow_merge_if_pipeline_succeeds": false,
   "only_allow_merge_if_all_discussions_are_resolved": false,
   "request_access_enabled": false,
+  "merge_method": "merge",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -1340,3 +1391,11 @@ Read more in the [Project import/export](project_import_export.md) documentation
 ## Project members
 
 Read more in the [Project members](members.md) documentation.
+
+## Project badges
+
+Read more in the [Project Badges](project_badges.md) documentation.
+
+## Issue and merge request description templates
+
+The non-default [issue and merge request description templates](../user/project/description_templates.md) are managed inside the project's repository. So you can manage them via the API through the [Repositories API](repositories.md) and the [Repository Files API](repository_files.md).
\ No newline at end of file
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 96609cd530fbc903b541b7ce8bc4c195843f55ad..5aff255c20a2499b3ebe2371780a87077e44cef4 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -183,7 +183,7 @@ GET /projects/:id/repository/contributors
 Parameters:
 
 - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-- `order_by` (optional) - Return contributors ordered by `name`, `email`, or `commits` fields. If not given contributors are ordered by commit date.
+- `order_by` (optional) - Return contributors ordered by `name`, `email`, or `commits` (orders by commit date) fields. Default is `commits`
 - `sort` (optional) - Return contributors sorted in `asc` or `desc` order. Default is `asc`
 
 Response:
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 7495c6cdedb0f8d4ad2ced1a5f9fc5c913673181..f384ac57bfe3e03b12aa8af46e0da3b3b8b07760 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -153,7 +153,8 @@ Example response:
         "mysql"
     ],
     "version": null,
-    "access_level": "ref_protected"
+    "access_level": "ref_protected",
+    "maximum_timeout": 3600
 }
 ```
 
@@ -174,6 +175,7 @@ PUT /runners/:id
 | `run_untagged`    | boolean   | no       | Flag indicating the runner can execute untagged jobs |
 | `locked`    | boolean   | no       | Flag indicating the runner is locked |
 | `access_level`    | string   | no       | The access_level of the runner; `not_protected` or `ref_protected` |
+| `maximum_timeout` | integer | no | Maximum timeout set when this Runner will handle the job |
 
 ```
 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
@@ -211,7 +213,8 @@ Example response:
         "tag2"
     ],
     "version": null,
-    "access_level": "ref_protected"
+    "access_level": "ref_protected",
+    "maximum_timeout": null
 }
 ```
 
diff --git a/doc/api/search.md b/doc/api/search.md
index d441b556186d7af83c97e413111cdfdf36fa6d8f..107ddaffa6ab1a262d66ee0a9c20ab1518e9a15b 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -289,7 +289,7 @@ Search within the specified group.
 If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
 
 ```
-GET /groups/:id/-/search
+GET /groups/:id/search
 ```
 
 | Attribute     | Type     | Required   | Description            |
@@ -305,7 +305,7 @@ The response depends on the requested scope.
 ### Scope: projects
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=projects&search=flight
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=projects&search=flight
 ```
 
 Example response:
@@ -336,7 +336,7 @@ Example response:
 ### Scope: issues
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=issues&search=file
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=issues&search=file
 ```
 
 Example response:
@@ -401,7 +401,7 @@ Example response:
 ### Scope: merge_requests
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=merge_requests&search=file
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=merge_requests&search=file
 ```
 
 Example response:
@@ -478,7 +478,7 @@ Example response:
 ### Scope: milestones
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=milestones&search=release
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=milestones&search=release
 ```
 
 Example response:
@@ -507,7 +507,7 @@ Search within the specified project.
 If a user is not a member of a project and the project is private, a `GET` request on that project will result to a `404` status code.
 
 ```
-GET /projects/:id/-/search
+GET /projects/:id/search
 ```
 
 | Attribute     | Type     | Required   | Description            |
@@ -524,7 +524,7 @@ The response depends on the requested scope.
 ### Scope: issues
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/-/search?scope=issues&search=file
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/search?scope=issues&search=file
 ```
 
 Example response:
@@ -589,7 +589,7 @@ Example response:
 ### Scope: merge_requests
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=merge_requests&search=file
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=merge_requests&search=file
 ```
 
 Example response:
@@ -666,7 +666,7 @@ Example response:
 ### Scope: milestones
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/-/search?scope=milestones&search=release
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/search?scope=milestones&search=release
 ```
 
 Example response:
@@ -691,7 +691,7 @@ Example response:
 ### Scope: notes
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=notes&search=maxime
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=notes&search=maxime
 ```
 
 Example response:
@@ -723,7 +723,7 @@ Example response:
 ### Scope: wiki_blobs
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=wiki_blobs&search=bye
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=wiki_blobs&search=bye
 ```
 
 Example response:
@@ -746,7 +746,7 @@ Example response:
 ### Scope: commits
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=commits&search=bye
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=commits&search=bye
 ```
 
 Example response:
@@ -777,7 +777,7 @@ Example response:
 ### Scope: blobs
 
 ```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=blobs&search=installation
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
 ```
 
 Example response:
diff --git a/doc/api/tags.md b/doc/api/tags.md
index fa25dc7645204455d8df9226df0c12ed6c966b1b..4af096c3c0cb5ffae7672d88da423157d97b7c11 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -42,6 +42,7 @@ Parameters:
       "description": "Amazing release. Wow"
     },
     "name": "v1.0.0",
+    "target": "2695effb5807a22ff3d138d593fd856244e155e7",
     "message": null
   }
 ]
@@ -73,6 +74,7 @@ Example Response:
 {
   "name": "v5.0.0",
   "message": null,
+  "target": "60a8ff033665e1207714d6670fcd7b65304ec02f",
   "commit": {
     "id": "60a8ff033665e1207714d6670fcd7b65304ec02f",
     "short_id": "60a8ff03",
@@ -132,12 +134,16 @@ Parameters:
     "description": "Amazing release. Wow"
   },
   "name": "v1.0.0",
+  "target: "2695effb5807a22ff3d138d593fd856244e155e7",
   "message": null
 }
 ```
 The message will be `null` when creating a lightweight tag otherwise
 it will contain the annotation.
 
+The target will contain the tag objects ID when creating annotated tags,
+otherwise it will contain the commit ID when creating lightweight tags.
+
 In case of an error,
 status code `405` with an explaining error message is returned.
 
diff --git a/doc/api/todos.md b/doc/api/todos.md
index dd4c737b729a676bd332a59036cc89eaee07b286..27e623007cc50a087ebd08aac544025ad756eb98 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -15,7 +15,7 @@ Parameters:
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, or `approval_required`. |
+| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, `approval_required`, `unmergeable` or `directly_addressed`. |
 | `author_id` | integer | no | The ID of an author |
 | `project_id` | integer | no | The ID of a project |
 | `state` | string | no | The state of the todo. Can be either `pending` or `done` |
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 532ae52a184c8fd3773e1e2dddc2f4a3ae24f544..6aa0e5885db70d2640f544fc5cbd1d74c03e91e8 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -65,7 +65,8 @@ learn how to leverage its potential even more.
   environments and use them for different purposes like testing, building and
   deploying
 - [Job artifacts](../user/project/pipelines/job_artifacts.md)
-- [Git submodules](git_submodules.md): How to run your CI jobs when Git
+- [Caching dependencies](caching/index.md)
+- [Git submodules](git_submodules.md) - How to run your CI jobs when Git
   submodules are involved
 - [Use SSH keys in your build environment](ssh_keys/README.md)
 - [Trigger pipelines through the GitLab API](triggers/README.md)
diff --git a/doc/ci/caching/img/clear_runners_cache.png b/doc/ci/caching/img/clear_runners_cache.png
new file mode 100644
index 0000000000000000000000000000000000000000..e5db4a47b3eaac4633d77d596833458aa8f005aa
Binary files /dev/null and b/doc/ci/caching/img/clear_runners_cache.png differ
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..c159198d16babef4a857b12cea147c48e00ce0ef
--- /dev/null
+++ b/doc/ci/caching/index.md
@@ -0,0 +1,518 @@
+# Cache dependencies in GitLab CI/CD
+
+GitLab CI/CD provides a caching mechanism that can be used to save time
+when your jobs are running.
+
+Caching is about speeding the time a job is executed by reusing the same
+content of a previous job. It can be particularly useful when your are
+developing software that depends on other libraries which are fetched via the
+internet during build time.
+
+If caching is enabled, it's shared between pipelines and jobs by default,
+starting from GitLab 9.0.
+
+Make sure you read the [`cache` reference](../yaml/README.md#cache) to learn
+how it is defined in `.gitlab-ci.yml`.
+
+## Good caching practices
+
+We have the cache from the perspective of the developers (who consume a cache
+within the job) and the cache from the perspective of the Runner. Depending on
+which type of Runner you are using, cache can act differently.
+
+From the perspective of the developer, to ensure maximum availability of the
+cache, when declaring `cache` in your jobs, use one or a mix of the following:
+
+- [Tag your Runners](../runners/README.md#using-tags) and use the tag on jobs
+  that share their cache.
+- [Use sticky Runners](../runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
+  that will be only available to a particular project.
+- [Use a `key`](../yaml/README.md#cache-key) that fits your workflow (e.g.,
+  different caches on each branch). For that, you can take advantage of the
+  [CI/CD predefined variables](../variables/README.md#predefined-variables-environment-variables).
+
+TIP: **Tip:**
+Using the same Runner for your pipeline, is the most simple and efficient way to
+cache files in one stage or pipeline, and pass this cache to subsequent stages
+or pipelines in a guaranteed manner.
+
+From the perspective of the Runner, in order for cache to work effectively, one
+of the following must be true:
+
+- Use a single Runner for all your jobs
+- Use multiple Runners (in autoscale mode or not) that use
+  [distributed caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching),
+  where the cache is stored in S3 buckets (like shared Runners on GitLab.com)
+- Use multiple Runners (not in autoscale mode) of the same architecture that
+  share a common network-mounted directory (using NFS or something similar)
+  where the cache will be stored
+
+TIP: **Tip:**
+Read about the [availability of the cache](#availability-of-the-cache)
+to learn more about the internals and get a better idea how cache works.
+
+### Sharing caches across the same branch
+
+Define a cache with the `key: ${CI_COMMIT_REF_SLUG}` so that jobs of each
+branch always use the same cache:
+
+```yaml
+cache:
+  key: ${CI_COMMIT_REF_SLUG}
+```
+
+While this feels like it might be safe from accidentally overwriting the cache,
+it means merge requests get slow first pipelines, which might be a bad
+developer experience. The next time a new commit is pushed to the branch, the
+cache will be re-used.
+
+To enable per-job and per-branch caching:
+
+```yaml
+cache:
+  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
+```
+
+To enable per-branch and per-stage caching:
+
+```yaml
+cache:
+  key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
+```
+
+### Sharing caches across different branches
+
+If the files you are caching need to be shared across all branches and all jobs,
+you can use the same key for all of them:
+
+```yaml
+cache:
+  key: one-key-to-rull-them-all
+```
+
+To share the same cache between branches, but separate them by job:
+
+```yaml
+cache:
+  key: ${CI_JOB_NAME}
+```
+
+### Disabling cache on specific jobs
+
+If you have defined the cache globally, it means that each job will use the
+same definition. You can override this behavior per-job, and if you want to
+disable it completely, use an empty hash:
+
+```yaml
+job:
+  cache: {}
+```
+
+For more fine tuning, read also about the
+[`cache: policy`](../yaml/README.md#cache-policy).
+
+## Common use cases
+
+The most common use case of cache is to preserve contents between subsequent
+runs of jobs for things like dependencies and commonly used libraries
+(Nodejs packages, PHP packages, rubygems, python libraries, etc.),
+so they don't have to be re-fetched from the public internet.
+
+NOTE: **Note:**
+For more examples, check the [GitLab CI Yml](https://gitlab.com/gitlab-org/gitlab-ci-yml)
+project.
+
+### Caching Nodejs dependencies
+
+Assuming your project is using [npm](https://www.npmjs.com/) or
+[Yarn](https://yarnpkg.com/en/) to install the Nodejs dependencies, the
+following example defines `cache` globally so that all jobs inherit it.
+Nodejs modules are installed in `node_modules/` and are cached per-branch:
+
+```yaml
+#
+# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Nodejs.gitlab-ci.yml
+#
+image: node:latest
+
+# Cache modules in between jobs
+cache:
+  key: ${CI_COMMIT_REF_SLUG}
+  paths:
+  - node_modules/
+
+before_script:
+  - npm install
+
+test_async:
+  script:
+  - node ./specs/start.js ./specs/async.spec.js
+```
+
+### Caching PHP dependencies
+
+Assuming your project is using [Composer](https://getcomposer.org/) to install
+the PHP dependencies, the following example defines `cache` globally so that
+all jobs inherit it. PHP libraries modules are installed in `vendor/` and
+are cached per-branch:
+
+```yaml
+#
+# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/PHP.gitlab-ci.yml
+#
+image: php:7.2
+
+# Cache libraries in between jobs
+cache:
+  key: ${CI_COMMIT_REF_SLUG}
+  paths:
+  - vendor/
+
+before_script:
+# Install and run Composer
+- curl --show-error --silent https://getcomposer.org/installer | php
+- php composer.phar install
+
+test:
+  script:
+  - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
+```
+
+### Caching Python dependencies
+
+Assuming your project is using [pip](https://pip.pypa.io/en/stable/) to install
+the python dependencies, the following example defines `cache` globally so that
+all jobs inherit it. Python libraries are installed in a virtualenv under `venv/`,
+pip's cache is defined under `.cache/pip/` and both are cached per-branch:
+
+```yaml
+#
+# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Python.gitlab-ci.yml
+#
+image: python:latest
+
+# Change pip's cache directory to be inside the project directory since we can
+# only cache local items.
+variables:
+    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache"
+
+# Pip's cache doesn't store the python packages
+# https://pip.pypa.io/en/stable/reference/pip_install/#caching
+#
+# If you want to also cache the installed packages, you have to install
+# them in a virtualenv and cache it as well.
+cache:
+  paths:
+    - .cache/
+    - venv/
+
+before_script:
+  - python -V               # Print out python version for debugging
+  - pip install virtualenv
+  - virtualenv venv
+  - source venv/bin/activate
+
+test:
+  script:
+  - python setup.py test
+  - pip install flake8
+  - flake8 .
+```
+
+### Caching Ruby dependencies
+
+Assuming your project is using [Bundler](https://bundler.io) to install the
+gem dependencies, the following example defines `cache` globally so that all
+jobs inherit it. Gems are installed in `vendor/ruby/` and are cached per-branch:
+
+```yaml
+#
+# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Ruby.gitlab-ci.yml
+#
+image: ruby:2.5
+
+# Cache gems in between builds
+cache:
+  key: ${CI_COMMIT_REF_SLUG}
+  paths:
+    - vendor/ruby
+
+before_script:
+  - ruby -v                                   # Print out ruby version for debugging
+  - gem install bundler  --no-ri --no-rdoc    # Bundler is not installed with the image
+  - bundle install -j $(nproc) --path vendor  # Install dependencies into ./vendor/ruby
+
+rspec:
+  script:
+  - rspec spec
+```
+
+## Availability of the cache
+
+Caching is an optimization, but isn't guaranteed to always work, so you need to
+be prepared to regenerate any cached files in each job that needs them.
+
+Assuming you have properly [defined `cache` in `.gitlab-ci.yml`](../yaml/README.md#cache)
+according to your workflow, the availability of the cache ultimately depends on
+how the Runner has been configured (the executor type and whether different
+Runners are used for passing the cache between jobs).
+
+### Where the caches are stored
+
+Since the Runner is the one responsible for storing the cache, it's essential
+to know **where** it's stored. All the cache paths defined under a job in
+`.gitlab-ci.yml` are archived in a single `cache.zip` file and stored in the
+Runner's configured cache location. By default, they are stored locally in the
+machine where the Runner is installed and depends on the type of the executor.
+
+| GitLab Runner executor | Default path of the cache |
+| ---------------------- | ------------------------- |
+| [Shell](https://docs.gitlab.com/runner/executors/shell.html) | Locally, stored under the `gitlab-runner` user's home directory: `/home/gitlab-runner/cache/<user>/<project>/<cache-key>/cache.zip`. |
+| [Docker](https://docs.gitlab.com/runner/executors/docker.html) | Locally, stored under [Docker volumes](https://docs.gitlab.com/runner/executors/docker.html#the-builds-and-cache-storage): `/var/lib/docker/volumes/<volume-id>/_data/<user>/<project>/<cache-key>/cache.zip`. |
+| [Docker machine](https://docs.gitlab.com/runner/executors/docker_machine.html) (autoscale Runners) | Behaves the same as the Docker executor. |
+
+### How archiving and extracting works
+
+In the most simple scenario, consider that you use only one machine where the
+Runner is installed, and all jobs of your project run on the same host.
+
+Let's see the following example of two jobs that belong to two consecutive
+stages:
+
+```yaml
+stages:
+- build
+- test
+
+before_script:
+- echo "Hello"
+
+job A:
+  stage: build
+  script:
+  - mkdir vendor/
+  - echo "build" > vendor/hello.txt
+  cache:
+    key: build-cache
+    paths:
+    - vendor/
+  after_script:
+  - echo "World"
+
+job B:
+  stage: test
+  script:
+  - cat vendor/hello.txt
+  cache:
+    key: build-cache
+```
+
+Here's what happens behind the scenes:
+
+1. Pipeline starts
+1. `job A` runs
+1. `before_script` is executed
+1. `script` is executed
+1. `after_script` is executed
+1. `cache` runs and the `vendor/` directory is zipped into `cache.zip`.
+    This file is then saved in the directory based on the
+    [Runner's setting](#where-the-caches-are-stored) and the `cache: key`.
+1. `job B` runs
+1. The cache is extracted (if found)
+1. `before_script` is executed
+1. `script` is executed
+1. Pipeline finishes
+
+By using a single Runner on a single machine, you'll not have the issue where
+`job B` might execute on a Runner different from `job A`, thus guaranteeing the
+cache between stages. That will only work if the build goes from stage `build`
+to `test` in the same Runner/machine, otherwise, you [might not have the cache
+available](#cache-mismatch).
+
+During the caching process, there's also a couple of things to consider:
+
+- If some other job, with another cache configuration had saved its
+  cache in the same zip file, it is overwritten. If the S3 based shared cache is
+  used, the file is additionally uploaded to S3 to an object based on the cache
+  key. So, two jobs with different paths, but the same cache key, will overwrite
+  their cache.
+- When extracting the cache from `cache.zip`, everything in the zip file is
+  extracted in the job's working directory (usually the repository which is
+  pulled down), and the Runner doesn't mind if the archive of `job A` overwrites
+  things in the archive of `job B`.
+
+The reason why it works this way is because the cache created for one Runner
+often will not be valid when used by a different one which can run on a
+**different architecture** (e.g., when the cache includes binary files). And
+since the different steps might be executed by Runners running on different
+machines, it is a safe default.
+
+### Cache mismatch
+
+In the following table, you can see some reasons where you might hit a cache
+mismatch and a few ideas how to fix it.
+
+| Reason of a cache mismatch | How to fix it |
+| -------------------------- | ------------- |
+| You use multiple standalone Runners (not in autoscale mode) attached to one project without a shared cache | Use only one Runner for your project or use multiple Runners with distributed cache enabled |
+| You use Runners in autoscale mode without a distributed cache enabled | Configure the autoscale Runner to use a distributed cache |
+| The machine the Runner is installed on is low on disk space or, if you've set up distributed cache, the S3 bucket where the cache is stored doesn't have enough space | Make sure you clear some space to allow new caches to be stored. Currently, there's no automatic way to do this. |
+| You use the same `key` for jobs where they cache different paths. | Use different cache keys to that the cache archive is stored to a different location and doesn't overwrite wrong caches. |
+
+Let's explore some examples.
+
+---
+
+Let's assume you have only one Runner assigned to your project, so the cache
+will be stored in the Runner's machine by default. If two jobs, A and B,
+have the same cache key, but they cache different paths, cache B would overwrite
+cache A, even if their `paths` don't match:
+
+We want `job A` and `job B` to re-use their
+cache when the pipeline is run for a second time.
+
+```yaml
+stages:
+- build
+- test
+
+job A:
+  stage: build
+  script: make build
+  cache:
+    key: same-key
+    paths:
+    - public/
+
+job B:
+  stage: test
+  script: make test
+  cache:
+    key: same-key
+    paths:
+    - vendor/
+```
+
+1. `job A` runs
+1. `public/` is cached as cache.zip
+1. `job B` runs
+1. The previous cache, if any, is unzipped
+1. `vendor/` is cached as cache.zip and overwrites the previous one
+1. The next time `job A` runs it will use the cache of `job B` which is different
+   and thus will be ineffective
+
+To fix that, use different `keys` for each job.
+
+---
+
+In another case, let's assume you have more than one Runners assigned to your
+project, but the distributed cache is not enabled. We want the second time the
+pipeline is run, `job A` and `job B` to re-use their cache (which in this case
+will be different):
+
+```yaml
+stages:
+- build
+- test
+
+job A:
+  stage: build
+  script: build
+  cache:
+    key: keyA
+    paths:
+    - vendor/
+
+job B:
+  stage: test
+  script: test
+  cache:
+    key: keyB
+    paths:
+    - vendor/
+```
+
+In that case, even if the `key` is different (no fear of overwriting), you
+might experience the cached files to "get cleaned" before each stage if the
+jobs run on different Runners in the subsequent pipelines.
+
+## Clearing the cache
+
+GitLab Runners use [cache](../yaml/README.md#cache) to speed up the execution
+of your jobs by reusing existing data. This however, can sometimes lead to an
+inconsistent behavior.
+
+To start with a fresh copy of the cache, there are two ways to do that.
+
+### Clearing the cache by changing `cache:key`
+
+All you have to do is set a new `cache: key` in your `.gitlab-ci.yml`. In the
+next run of the pipeline, the cache will be stored in a different location.
+
+### Clearing the cache manually
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41249) in GitLab 10.4.
+
+If you want to avoid editing `.gitlab-ci.yml`, you can easily clear the cache
+via GitLab's UI:
+
+1. Navigate to your project's **CI/CD > Pipelines** page
+1. Click on the **Clear Runner caches** button to clean up the cache
+
+    ![Clear Runners cache](img/clear_runners_cache.png)
+
+1. On the next push, your CI/CD job will use a new cache
+
+Behind the scenes, this works by increasing a counter in the database, and the
+value of that counter is used to create the key for the cache by appending an
+integer to it: `-1`, `-2`, etc. After a push, a new key is generated and the
+old cache is not valid anymore.
+
+## Cache vs artifacts
+
+NOTE: **Note:**
+Be careful if you use cache and artifacts to store the same path in your jobs
+as **caches are restored before artifacts** and the content would be overwritten.
+
+Don't mix the caching with passing artifacts between stages. Caching is not
+designed to pass artifacts between stages. Cache is for runtime dependencies
+needed to compile the project:
+
+- `cache` - **Use for temporary storage for project dependencies.** Not useful
+  for keeping intermediate build results, like `jar` or `apk` files.
+  Cache was designed to be used to speed up invocations of subsequent runs of a
+  given job, by keeping things like dependencies (e.g., npm packages, Go vendor
+  packages, etc.) so they don't have to be re-fetched from the public internet.
+  While the cache can be abused to pass intermediate build results between stages,
+  there may be cases where artifacts are a better fit.
+- `artifacts` - **Use for stage results that will be passed between stages.**
+  Artifacts were designed to upload some compiled/generated bits of the build,
+  and they can be fetched by any number of concurrent Runners. They are
+  guaranteed to be available and are there to pass data between jobs. They are
+  also exposed to be downloaded from the UI.
+
+It's sometimes confusing because the name artifact sounds like something that
+is only useful outside of the job, like for downloading a final image. But
+artifacts are also available in between stages within a pipeline. So if you
+build your application by downloading all the required modules, you might want
+to declare them as artifacts so that each subsequent stage can depend on them
+being there. There are some optimizations like declaring an
+[expiry time](../yaml/README.md#artifacts-expire_in) so you don't keep artifacts
+around too long, and using [dependencies](../yaml/README.md#dependencies) to
+control exactly where artifacts are passed around.
+
+So, to sum up:
+- Caches are disabled if not defined globally or per job (using `cache:`)
+- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally
+- Caches can be used by subsequent pipelines of that very same job (a script in
+  a stage) in which the cache was created (if not defined globally).
+- Caches are stored where the Runner is installed **and** uploaded to S3 if
+  [distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching)
+- Caches defined per job are only used either a) for the next pipeline of that job,
+  or b) if that same cache is also defined in a subsequent job of the same pipeline
+- Artifacts are disabled if not defined per job (using `artifacts:`)
+- Artifacts can only be enabled per job, not globally
+- Artifacts are created during a pipeline and can be used by the subsequent
+  jobs of that currently active pipeline
+- Artifacts are always uploaded to GitLab (known as coordinator)
+- Artifacts can have an expiration value for controlling disk usage (30 days by default)
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 22afcb9199d3f6e68f026e4237a9c0c4767ee138..07b144f6ddd3da44388d1bb2cd902179aa711172 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -1,26 +1,29 @@
-# Using Docker Build
+# Building Docker images with GitLab CI/CD
 
-GitLab CI allows you to use Docker Engine to build and test docker-based projects.
+GitLab CI/CD allows you to use Docker Engine to build and test docker-based projects.
 
-**This also allows to you to use `docker-compose` and other docker-enabled tools.**
+TIP: **Tip:**
+This also allows to you to use `docker-compose` and other docker-enabled tools.
 
 One of the new trends in Continuous Integration/Deployment is to:
 
-1. create an application image,
-1. run tests against the created image,
-1. push image to a remote registry, and
-1. deploy to a server from the pushed image.
+1. Create an application image
+1. Run tests against the created image
+1. Push image to a remote registry
+1. Deploy to a server from the pushed image
 
-It's also useful when your application already has the `Dockerfile` that can be used to create and test an image:
+It's also useful when your application already has the `Dockerfile` that can be
+used to create and test an image:
 
 ```bash
-$ docker build -t my-image dockerfiles/
-$ docker run my-docker-image /script/to/run/tests
-$ docker tag my-image my-registry:5000/my-image
-$ docker push my-registry:5000/my-image
+docker build -t my-image dockerfiles/
+docker run my-docker-image /script/to/run/tests
+docker tag my-image my-registry:5000/my-image
+docker push my-registry:5000/my-image
 ```
 
-This requires special configuration of GitLab Runner to enable `docker` support during jobs.
+This requires special configuration of GitLab Runner to enable `docker` support
+during jobs.
 
 ## Runner Configuration
 
@@ -74,8 +77,8 @@ GitLab Runner then executes job scripts as the `gitlab-runner` user.
 
 5. You can now use `docker` command and install `docker-compose` if needed.
 
-> **Note:**
-* By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions.
+NOTE: **Note:**
+By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions.
 For more information please read [On Docker security: `docker` group considered harmful](https://www.andreas-jung.com/contents/on-docker-security-docker-group-considered-harmful).
 
 ### Use docker-in-docker executor
@@ -98,12 +101,12 @@ In order to do that, follow the steps:
       --registration-token REGISTRATION_TOKEN \
       --executor docker \
       --description "My Docker Runner" \
-      --docker-image "docker:latest" \
+      --docker-image "docker:stable" \
       --docker-privileged
     ```
 
     The above command will register a new Runner to use the special
-    `docker:latest` image which is provided by Docker. **Notice that it's using
+    `docker:stable` image which is provided by Docker. **Notice that it's using
     the `privileged` mode to start the build and service containers.** If you
     want to use [docker-in-docker] mode, you always have to use `privileged = true`
     in your Docker containers.
@@ -117,7 +120,7 @@ In order to do that, follow the steps:
       executor = "docker"
       [runners.docker]
         tls_verify = false
-        image = "docker:latest"
+        image = "docker:stable"
         privileged = true
         disable_cache = false
         volumes = ["/cache"]
@@ -129,7 +132,7 @@ In order to do that, follow the steps:
    `docker:dind` service):
 
     ```yaml
-    image: docker:latest
+    image: docker:stable
 
     # When using dind, it's wise to use the overlayfs driver for
     # improved performance.
@@ -198,12 +201,12 @@ In order to do that, follow the steps:
       --registration-token REGISTRATION_TOKEN \
       --executor docker \
       --description "My Docker Runner" \
-      --docker-image "docker:latest" \
+      --docker-image "docker:stable" \
       --docker-volumes /var/run/docker.sock:/var/run/docker.sock
     ```
 
     The above command will register a new Runner to use the special
-    `docker:latest` image which is provided by Docker. **Notice that it's using
+    `docker:stable` image which is provided by Docker. **Notice that it's using
     the Docker daemon of the Runner itself, and any containers spawned by docker
     commands will be siblings of the Runner rather than children of the runner.**
     This may have complications and limitations that are unsuitable for your workflow.
@@ -217,7 +220,7 @@ In order to do that, follow the steps:
       executor = "docker"
       [runners.docker]
         tls_verify = false
-        image = "docker:latest"
+        image = "docker:stable"
         privileged = false
         disable_cache = false
         volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
@@ -229,7 +232,7 @@ In order to do that, follow the steps:
    include the `docker:dind` service as when using the Docker in Docker executor):
 
     ```yaml
-    image: docker:latest
+    image: docker:stable
 
     before_script:
     - docker info
@@ -259,8 +262,66 @@ aware of the following implications:
     docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
     ```
 
+## Making docker-in-docker builds faster with Docker layer caching
+
+When using docker-in-docker, Docker will download all layers of your image every
+time you create a build. Recent versions of Docker (Docker 1.13 and above) can
+use a pre-existing image as a cache during the `docker build` step, considerably
+speeding up the build process.
+
+### How Docker caching works
+
+When running `docker build`, each command in `Dockerfile` results in a layer.
+These layers are kept around as a cache and can be reused if there haven't been
+any changes. Change in one layer causes all subsequent layers to be recreated.
+
+You can specify a tagged image to be used as a cache source for the `docker build`
+command by using the `--cache-from` argument. Multiple images can be specified
+as a cache source by using multiple `--cache-from` arguments. Keep in mind that
+any image that's used with the `--cache-from` argument must first be pulled
+(using `docker pull`) before it can be used as a cache source.
+
+### Using Docker caching
+
+Here's a simple `.gitlab-ci.yml` file showing how Docker caching can be utilized:
+
+```yaml
+image: docker:stable
+
+services:
+  - docker:dind
+
+variables:
+  CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
+  DOCKER_DRIVER: overlay2
+
+before_script:
+  - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
+
+build:
+  stage: build
+  script:
+    - docker pull $CONTAINER_IMAGE:latest || true
+    - docker build --cache-from $CONTAINER_IMAGE:latest --tag $CONTAINER_IMAGE:$CI_BUILD_REF --tag $CONTAINER_IMAGE:latest .
+    - docker push $CONTAINER_IMAGE:$CI_BUILD_REF
+    - docker push $CONTAINER_IMAGE:latest
+```
+
+The steps in the `script` section for the `build` stage can be summed up to:
+
+1. The first command tries to pull the image from the registry so that it can be
+   used as a cache for the `docker build` command.
+1. The second command builds a Docker image using the pulled image as a
+   cache (notice the `--cache-from $CONTAINER_IMAGE:latest` argument) if
+   available, and tags it.
+1. The last two commands push the tagged Docker images to the container registry
+   so that they may also be used as cache for subsequent builds.
+
 ## Using the OverlayFS driver
 
+NOTE: **Note:**
+The shared Runners on GitLab.com use the `overlay2` driver by default.
+
 By default, when using `docker:dind`, Docker uses the `vfs` storage driver which
 copies the filesystem on every run. This is a very disk-intensive operation
 which can be avoided if a different driver is used, for example `overlay2`.
@@ -327,7 +388,7 @@ could look like:
 
 ```yaml
  build:
-   image: docker:latest
+   image: docker:stable
    services:
    - docker:dind
    stage: build
@@ -373,7 +434,7 @@ when needed. Changes to `master` also get tagged as `latest` and deployed using
 an application-specific deploy script:
 
 ```yaml
-image: docker:latest
+image: docker:stable
 services:
 - docker:dind
 
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index fb5bfe26bb0678b0a5dff253f18120ee4fec98c3..7c0f837ea9c1e851079713f3faa11f5beac2619e 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -58,7 +58,7 @@ 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
-run a database container, eg. `mysql`. It's easier and faster to use an
+run a database container, e.g., `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.
 
@@ -83,6 +83,67 @@ So, in order to access your database service you have to connect to the host
 named `mysql` instead of a socket or `localhost`. Read more in [accessing the
 services](#accessing-the-services).
 
+### How the health check of services works
+
+Services are designed to provide additional functionality which is **network accessible**.
+It may be a database like MySQL, or Redis, and even `docker:stable-dind` which
+allows you to use Docker in Docker. It can be practically anything that is
+required for the CI/CD job to proceed and is accessed by network.
+
+To make sure this works, the Runner:
+
+1. checks which ports are exposed from the container by default
+1. starts a special container that waits for these ports to be accessible
+
+When the second stage of the check fails, either because there is no opened port in the
+service, or the service was not started properly before the timeout and the port is not
+responding, it prints the warning: `*** WARNING: Service XYZ probably didn't start properly`.
+
+In most cases it will affect the job, but there may be situations when the job
+will still succeed even if that warning was printed. For example:
+
+- The service was started a little after the warning was raised, and the job is
+  not using the linked service from the very beginning. In that case, when the
+  job needed to access the service, it may have been already there waiting for
+  connections.
+- The service container is not providing any networking service, but it's doing
+  something with the job's directory (all services have the job directory mounted
+  as a volume under `/builds`). In that case, the service will do its job, and
+  since the job is not trying to connect to it, it won't fail.
+
+### What services are not for
+
+As it was mentioned before, this feature is designed to provide **network accessible**
+services. A database is the simplest example of such a service.
+
+NOTE: **Note:**
+The services feature is not designed to, and will not add any software from the
+defined `services` image(s) to the job's container.
+
+For example, if you have the following `services` defined in your job, the `php`,
+`node` or `go` commands will **not** be available for your script, and thus
+the job will fail:
+
+```yaml
+job:
+  services:
+  - php:7
+  - node:latest
+  - golang:1.10
+  image: alpine:3.7
+  script:
+  - php -v
+  - node -v
+  - go version
+```
+
+If you need to have `php`, `node` and `go` available for your script, you should
+either:
+
+- choose an existing Docker image that contains all required tools, or
+- create your own Docker image, which will have all the required tools included
+  and use that in your job
+
 ### Accessing the services
 
 Let's say that you need a Wordpress instance to test some API integration with
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 58c4a71cef9092b6f230d0fb166ae7492fd09d68..b3d9f0bc96cbda3477a07d5d2ac51570296fef5d 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -247,10 +247,19 @@ declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is
 the basis of [Review apps](review_apps/index.md).
 
 >**Note:**
-The `name` and `url` parameters can use any of the defined CI variables,
+The `name` and `url` parameters can use most of the defined CI variables,
 including predefined, secure variables and `.gitlab-ci.yml`
-[`variables`](yaml/README.md#variables).
-You however cannot use variables defined under `script` or on the Runner's side.
+[`variables`](yaml/README.md#variables). You however cannot use variables
+defined under `script` or on the Runner's side. There are other variables that
+are unsupported in environment name context:
+- `CI_JOB_ID`
+- `CI_JOB_TOKEN`
+- `CI_BUILD_ID`
+- `CI_BUILD_TOKEN`
+- `CI_REGISTRY_USER`
+- `CI_REGISTRY_PASSWORD`
+- `CI_REPOSITORY_URL`
+- `CI_ENVIRONMENT_URL`
 
 GitLab Runner exposes various [environment variables][variables] when a job runs,
 and as such, you can use them as environment names. Let's add another job in
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index ffebe1618d3f93542f064d9e87d17bacdb29d682..de60cd27cd1a33e8f155aa15610d4f82250378a7 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -32,34 +32,45 @@ There's also a collection of repositories with [example projects](https://gitlab
 - **Debian**: [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
 - **Maven**: [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
 
+### Game development
+
+- [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md)
+
 ### Miscellaneous
 
 - [Using `dpl` as deployment tool](deployment/README.md)
 - [The `.gitlab-ci.yml` file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
 
-### Code quality analysis
+## Code quality analysis
 
 [Analyze code quality with the Code Climate CLI](code_climate.md).
 
-### Static Application Security Testing (SAST)
+## Static Application Security Testing (SAST)
+
+**(Ultimate)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html)
+
+## Dependency Scanning
+
+**(Ultimate)** [Scan your dependencies for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/dependency_scanning.html)
+
+## Container Scanning
 
-- **(Ultimate)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html)
-- [Scan your Docker images for vulnerabilities](sast_docker.md)
+[Scan your Docker images for vulnerabilities](container_scanning.md)
 
-### Dynamic Application Security Testing (DAST)
+## Dynamic Application Security Testing (DAST)
 
 Scan your app for vulnerabilities with GitLab [Dynamic Application Security Testing (DAST)](dast.md).
 
-### Browser Performance Testing with Sitespeed.io
+## Browser Performance Testing with Sitespeed.io
 
 Analyze your [browser performance with Sitespeed.io](browser_performance.md).
 
-### GitLab CI/CD for Review Apps
+## GitLab CI/CD for Review Apps
 
 - [Example project](https://gitlab.com/gitlab-examples/review-apps-nginx/) that shows how to use GitLab CI/CD for [Review Apps](../review_apps/index.html).
 - [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/)
 
-### GitLab CI/CD for GitLab Pages
+## GitLab CI/CD for GitLab Pages
 
 See the documentation on [GitLab Pages](../../user/project/pages/index.md) for a complete overview.
 
diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md
index 42dc6ef36ba20ccf90ccfd5f12ec01bdcb2693a8..0dab07a7f80ed760feed2daf8e03096d22635836 100644
--- a/doc/ci/examples/browser_performance.md
+++ b/doc/ci/examples/browser_performance.md
@@ -1,22 +1,28 @@
 # Browser Performance Testing with the Sitespeed.io container
 
-This example shows how to run the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) on your code by using
-GitLab CI/CD and [Sitespeed.io](https://www.sitespeed.io) using Docker-in-Docker.
+This example shows how to run the
+[Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) on
+your code by using GitLab CI/CD and [Sitespeed.io](https://www.sitespeed.io)
+using Docker-in-Docker.
 
-First, you need a GitLab Runner with the [docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
-
-Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `performance`:
+First, you need a GitLab Runner with the
+[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
+Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called
+`performance`:
 
 ```yaml
+performance:
   stage: performance
   image: docker:git
+  variables:
+    URL: https://example.com
   services:
-    - docker:dind
+    - docker:stable-dind
   script:
     - mkdir gitlab-exporter
-    - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-5/index.js
+    - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
     - mkdir sitespeed-results
-    - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results https://my.website.com
+    - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
     - mv sitespeed-results/data/performance.json performance.json
   artifacts:
     paths:
@@ -24,37 +30,84 @@ Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `performan
     - sitespeed-results/
 ```
 
-This will create a `performance` job in your CI/CD pipeline and will run Sitespeed.io against the webpage you define. The GitLab plugin for Sitespeed.io is downloaded in order to export key metrics to JSON. The full HTML Sitespeed.io report will also be saved as an artifact, and if you have Pages enabled it can be viewed directly in your browser. For further customization options of Sitespeed.io, including the ability to provide a list of URLs to test, please consult their [documentation](https://www.sitespeed.io/documentation/sitespeed.io/configuration/).
+The above example will:
+
+1. Create a `performance` job in your CI/CD pipeline and will run
+   Sitespeed.io against the webpage you defined in `URL`.
+1. The [GitLab plugin](https://gitlab.com/gitlab-org/gl-performance) for
+   Sitespeed.io is downloaded in order to export key metrics to JSON. The full
+   HTML Sitespeed.io report will also be saved as an artifact, and if you have
+   [GitLab Pages](../../user/project/pages/index.md) enabled, it can be viewed
+   directly in your browser.
+
+For further customization options of Sitespeed.io, including the ability to
+provide a list of URLs to test, please consult
+[their documentation](https://www.sitespeed.io/documentation/sitespeed.io/configuration/).
 
-For [GitLab Premium](https://about.gitlab.com/products/) users, key metrics are automatically
-extracted and shown right in the merge request widget. Learn more about [Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
+TIP: **Tip:**
+For [GitLab Premium](https://about.gitlab.com/pricing/) users, key metrics are automatically
+extracted and shown right in the merge request widget. Learn more about
+[Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
 
 ## Performance testing on Review Apps
 
-The above CI YML is great for testing against static environments, and it can be extended for dynamic environments. There are a few extra steps to take to set this up:
-1. The `performance` job should run after the environment has started.
-1. In the `deploy` job, persist the hostname so it is available to the `performance` job. The same can be done for static environments like staging and production to unify the code path. Saving it as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`.
-1. In the `performance` job read the artifact into an environment variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test URL's.
-1. Now you can run the Sitespeed.io container against the desired hostname and paths.
+The above CI YML is great for testing against static environments, and it can
+be extended for dynamic environments. There are a few extra steps to take to
+set this up:
 
-A simple `performance` job would look like:
+1. The `performance` job should run after the dynamic environment has started.
+1. In the `review` job, persist the hostname and upload it as an artifact so
+   it's available to the `performance` job (the same can be done for static
+   environments like staging and production to unify the code path). Saving it
+   as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`
+   in your job's `script`.
+1. In the `performance` job, read the previous artifact into an environment
+   variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test
+   URLs.
+1. You can now run the Sitespeed.io container against the desired hostname and
+   paths.
+
+Your `.gitlab-ci.yml` file would look like:
 
 ```yaml
+stages:
+  - deploy
+  - performance
+
+review:
+  stage: deploy
+  environment:
+    name: review/$CI_COMMIT_REF_SLUG
+    url: http://$CI_COMMIT_REF_SLUG.$APPS_DOMAIN
+  script:
+    - run_deploy_script
+    - echo $CI_ENVIRONMENT_URL > environment_url.txt
+  artifacts:
+    paths:
+      - environment_url.txt
+  only:
+    - branches
+  except:
+    - master
+
+performance:
   stage: performance
   image: docker:git
   services:
-    - docker:dind
+    - docker:stable-dind
+  dependencies:
+    - review
   script:
     - export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
     - mkdir gitlab-exporter
-    - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-5/index.js
+    - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
     - mkdir sitespeed-results
     - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
     - mv sitespeed-results/data/performance.json performance.json
   artifacts:
     paths:
-    - performance.json
-    - sitespeed-results/
+      - performance.json
+      - sitespeed-results/
 ```
 
-A complete example can be found in our [Auto DevOps CI YML](https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Auto-DevOps.gitlab-ci.yml).
\ No newline at end of file
+A complete example can be found in our [Auto DevOps CI YML](https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Auto-DevOps.gitlab-ci.yml).
diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md
index d7df53494ed0bdf4e0fcba2564cdecaa6df6d2cb..d1aa783cc9cb60a8d831678e151c78c1d76f2514 100644
--- a/doc/ci/examples/code_climate.md
+++ b/doc/ci/examples/code_climate.md
@@ -9,25 +9,33 @@ Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `codequali
 
 ```yaml
 codequality:
-  image: docker:latest
+  image: docker:stable
   variables:
-    DOCKER_DRIVER: overlay
+    DOCKER_DRIVER: overlay2
+  allow_failure: true
   services:
-    - docker:dind
+    - docker:stable-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:0.69.0 init
-    - 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:0.69.0 analyze -f json > codeclimate.json || true
+    - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+    - docker run
+        --env SOURCE_CODE="$PWD"
+        --volume "$PWD":/code
+        --volume /var/run/docker.sock:/var/run/docker.sock
+        "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
   artifacts:
     paths: [codeclimate.json]
 ```
 
-This will create a `codequality` job in your CI pipeline and will allow you to
-download and analyze the report artifact in JSON format.
+The above example will create a `codequality` job in your CI/CD pipeline which
+will scan your source code for code quality issues. The report will be saved
+as an artifact that you can later download and analyze.
 
-For [GitLab Starter][ee] users, this information can be automatically
-extracted and shown right in the merge request widget. [Learn more on code quality
-diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html).
+TIP: **Tip:**
+Starting with [GitLab Starter][ee] 9.3, this information will
+be automatically extracted and shown right in the merge request widget. To do
+so, the CI/CD job must be named `codequality` and the artifact path must be
+`codeclimate.json`.
+[Learn more on code quality diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html).
 
 [cli]: https://github.com/codeclimate/codeclimate
 [dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor
diff --git a/doc/ci/examples/container_scanning.md b/doc/ci/examples/container_scanning.md
new file mode 100644
index 0000000000000000000000000000000000000000..eb76521cc0230cc3ba58eebcd11ce1eb45c1f709
--- /dev/null
+++ b/doc/ci/examples/container_scanning.md
@@ -0,0 +1,59 @@
+# Container Scanning with GitLab CI/CD
+
+You can check your Docker images (or more precisely the containers) for known
+vulnerabilities by using [Clair](https://github.com/coreos/clair) and
+[clair-scanner](https://github.com/arminc/clair-scanner), two open source tools
+for Vulnerability Static Analysis for containers.
+
+All you need is a GitLab Runner with the Docker executor (the shared Runners on
+GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
+called `sast:container`:
+
+```yaml
+sast:container:
+  image: docker:stable
+  variables:
+    DOCKER_DRIVER: overlay2
+    ## Define two new variables based on GitLab's CI/CD predefined variables
+    ## https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
+    CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
+    CI_APPLICATION_TAG: $CI_COMMIT_SHA
+  allow_failure: true
+  services:
+    - docker:stable-dind
+  script:
+    - docker run -d --name db arminc/clair-db:latest
+    - docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
+    - apk add -U wget ca-certificates
+    - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
+    - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+    - mv clair-scanner_linux_amd64 clair-scanner
+    - chmod +x clair-scanner
+    - touch clair-whitelist.yml
+    - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
+    - retries=0
+    - echo "Waiting for clair daemon to start"
+    - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
+    - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
+  artifacts:
+    paths: [gl-sast-container-report.json]
+```
+
+The above example will create a `sast:container` job in your CI/CD pipeline, pull
+the image from the [Container Registry](../../user/project/container_registry.md)
+(whose name is defined from the two `CI_APPLICATION_` variables) and scan it
+for possible vulnerabilities. The report will be saved as an artifact that you
+can later download and analyze.
+
+If you want to whitelist some specific vulnerabilities, you can do so by defining
+them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file),
+in our case its named `clair-whitelist.yml`.
+
+TIP: **Tip:**
+Starting with [GitLab Ultimate][ee] 10.4, this information will
+be automatically extracted and shown right in the merge request widget. To do
+so, the CI/CD job must be named `sast:container` and the artifact path must be
+`gl-sast-container-report.json`.
+[Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
+
+[ee]: https://about.gitlab.com/products/
diff --git a/doc/ci/examples/dast.md b/doc/ci/examples/dast.md
index 96de0f5ff5c73ded2fe689c5f417e42aa9dd58dd..a8720f0b7eabc94ce4c30672501c029da36d329c 100644
--- a/doc/ci/examples/dast.md
+++ b/doc/ci/examples/dast.md
@@ -14,9 +14,10 @@ called `dast`:
 
 ```yaml
 dast:
-  image: owasp/zap2docker-stable
+  image: registry.gitlab.com/gitlab-org/security-products/zaproxy
   variables:
     website: "https://example.com"
+  allow_failure: true
   script:
     - mkdir /zap/wrk/
     - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
@@ -30,6 +31,28 @@ the tests on the URL defined in the `website` variable (change it to use your
 own) and finally write the results in the `gl-dast-report.json` file. You can
 then download and analyze the report artifact in JSON format.
 
+It's also possible to authenticate the user before performing DAST checks:
+
+```yaml
+dast:
+  image: registry.gitlab.com/gitlab-org/security-products/zaproxy
+  variables:
+    website: "https://example.com"
+    login_url: "https://example.com/sign-in"
+  allow_failure: true
+  script:
+    - mkdir /zap/wrk/
+    - /zap/zap-baseline.py -J gl-dast-report.json -t $website
+        --auth-url $login_url
+        --auth-username "john.doe@example.com"
+        --auth-password "john-doe-password" || true
+    - cp /zap/wrk/gl-dast-report.json .
+  artifacts:
+    paths: [gl-dast-report.json]
+```
+See [zaproxy documentation](https://gitlab.com/gitlab-org/security-products/zaproxy)
+to learn more about authentication settings.
+
 TIP: **Tip:**
 Starting with [GitLab Ultimate][ee] 10.4, this information will
 be automatically extracted and shown right in the merge request widget. To do
diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md
index e80e246c5ddf6913a6174ba1e6f4160af6483594..2dcdc2d41ecbf55aff6aa8b559724f017617373b 100644
--- a/doc/ci/examples/deployment/README.md
+++ b/doc/ci/examples/deployment/README.md
@@ -111,7 +111,7 @@ We also use two secure variables:
 ## Storing API keys
 
 Secure Variables can added by going to your project's
-**Settings 鉃� Pipelines 鉃� Secret variables**. The variables that are defined
+**Settings 鉃� CI / CD 鉃� Secret variables**. The variables that are defined
 in the project settings are sent along with the build script to the Runner.
 The secure variables are stored out of the repository. Never store secrets in
 your project's `.gitlab-ci.yml`. It is also important that the secret's value
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png
new file mode 100644
index 0000000000000000000000000000000000000000..76e0295722b4ef6706f10615202459def67b6324
Binary files /dev/null and b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png
new file mode 100644
index 0000000000000000000000000000000000000000..050a97d2726309524cbaf07ced5448795783c6b0
Binary files /dev/null and b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png
new file mode 100644
index 0000000000000000000000000000000000000000..4ab5d5f401aab2829ab5a92d3b3c2296ad140d01
Binary files /dev/null and b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..bfc8558a5802a036d34f15f3be4eb6f417b3431a
--- /dev/null
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -0,0 +1,526 @@
+---
+author: Ryan Hall
+author_gitlab: blitzgren
+level: intermediary
+article_type: tutorial
+date: 2018-03-07
+---
+
+# DevOps and Game Dev with GitLab CI/CD
+
+With advances in WebGL and WebSockets, browsers are extremely viable as game development
+platforms without the use of plugins like Adobe Flash. Furthermore, by using GitLab and [AWS](https://aws.amazon.com/),
+single game developers, as well as game dev teams, can easily host browser-based games online.
+
+In this tutorial, we'll focus on DevOps, as well as testing and hosting games with Continuous
+Integration/Deployment methods. We assume you are familiar with GitLab, javascript,
+and the basics of game development.
+
+## The game
+
+Our [demo game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/) consists of a simple spaceship traveling in space that shoots by clicking the mouse in a given direction.
+
+Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/about),
+was essential for the fast pace the team worked at. This tutorial will build upon my
+[previous introductory article](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) and go through the following steps:
+
+1. Using code from the previous article to start with a barebones [Phaser](https://phaser.io) game built by a gulp file
+1. Adding and running unit tests
+1. Creating a `Weapon` class that can be triggered to spawn a `Bullet` in a given direction
+1. Adding a `Player` class that uses this weapon and moves around the screen
+1. Adding the sprites we will use for the `Player` and `Weapon`
+1. Testing and deploying with Continuous Integration and Continuous Deployment methods
+
+By the end, we'll have the core of a [playable game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/)
+that's tested and deployed on every push to the `master` branch of the [codebase](https://gitlab.com/blitzgren/gitlab-game-demo).
+This will also provide
+boilerplate code for starting a browser-based game with the following components:
+
+- Written in [Typescript](https://www.typescriptlang.org/) and [PhaserJs](https://phaser.io)
+- Building, running, and testing with [Gulp](http://gulpjs.com/)
+- Unit tests with [Chai](http://chaijs.com/) and [Mocha](https://mochajs.org/)
+- CI/CD with GitLab
+- Hosting the codebase on GitLab.com
+- Hosting the game on AWS
+- Deploying to AWS
+
+## Requirements and setup
+
+Please refer to my previous article [DevOps and Game Dev](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) to learn the foundational
+development tools, running a Hello World-like game, and building this game using GitLab
+CI/CD from every new push to master. The `master` branch for this game's [repository](https://gitlab.com/blitzgren/gitlab-game-demo)
+contains a completed version with all configurations. If you would like to follow along
+with this article, you can clone and work from the `devops-article` branch:
+
+```sh
+git clone git@gitlab.com:blitzgren/gitlab-game-demo.git
+git checkout devops-article
+```
+
+Next, we'll create a small subset of tests that exemplify most of the states I expect
+this `Weapon` class to go through. To get started, create a folder called `lib/tests`
+and add the following code to a new file `weaponTests.ts`:
+
+```ts
+import { expect } from 'chai';
+import { Weapon, BulletFactory } from '../lib/weapon';
+
+describe('Weapon', () => {
+    var subject: Weapon;
+    var shotsFired: number = 0;
+    // Mocked bullet factory
+    var bulletFactory: BulletFactory = <BulletFactory>{
+        generate: function(px, py, vx, vy, rot) {
+            shotsFired++;
+        }
+    };
+    var parent: any = { x: 0, y: 0 };
+
+    beforeEach(() => {
+        shotsFired = 0;
+        subject = new Weapon(bulletFactory, parent, 0.25, 1);
+    });
+
+    it('should shoot if not in cooldown', () => {
+        subject.trigger(true);
+        subject.update(0.1);
+        expect(shotsFired).to.equal(1);
+    });
+
+    it('should not shoot during cooldown', () => {
+        subject.trigger(true);
+        subject.update(0.1);
+        subject.update(0.1);
+        expect(shotsFired).to.equal(1);
+    });
+
+    it('should shoot after cooldown ends', () => {
+        subject.trigger(true);
+        subject.update(0.1);
+        subject.update(0.3); // longer than timeout
+        expect(shotsFired).to.equal(2);
+    });
+
+    it('should not shoot if not triggered', () => {
+        subject.update(0.1);
+        subject.update(0.1);
+        expect(shotsFired).to.equal(0);
+    });
+});
+```
+
+To build and run these tests using gulp, let's also add the following gulp functions
+to the existing `gulpfile.js` file:
+
+```ts
+gulp.task('build-test', function () {
+    return gulp.src('src/tests/**/*.ts', { read: false })
+    .pipe(tap(function (file) {
+      // replace file contents with browserify's bundle stream
+      file.contents = browserify(file.path, { debug: true })
+        .plugin(tsify, { project: "./tsconfig.test.json" })
+        .bundle();
+    }))
+    .pipe(buffer())
+    .pipe(sourcemaps.init({loadMaps: true}) )
+    .pipe(gulp.dest('built/tests'));
+});
+
+gulp.task('run-test', function() {
+    gulp.src(['./built/tests/**/*.ts']).pipe(mocha());
+});
+```
+
+We will start implementing the first part of our game and get these `Weapon` tests to pass.
+The `Weapon` class will expose a method to trigger the generation of a bullet at a given
+direction and speed. Later we will implement a `Player` class that ties together the user input
+to trigger the weapon. In the `src/lib` folder create a `weapon.ts` file. We'll add two classes
+to it: `Weapon` and `BulletFactory` which will encapsulate Phaser's **sprite** and
+**group** objects, and the logic specific to our game.
+
+```ts
+export class Weapon {
+    private isTriggered: boolean = false;
+    private currentTimer: number = 0;
+
+    constructor(private bulletFactory: BulletFactory, private parent: Phaser.Sprite, private cooldown: number, private bulletSpeed: number) {
+    }
+
+    public trigger(on: boolean): void {
+        this.isTriggered = on;
+    }
+
+    public update(delta: number): void {
+        this.currentTimer -= delta;
+
+        if (this.isTriggered && this.currentTimer <= 0) {
+            this.shoot();
+        }
+    }
+
+    private shoot(): void {
+        // Reset timer
+        this.currentTimer = this.cooldown;
+
+        // Get velocity direction from player rotation
+        var parentRotation = this.parent.rotation + Math.PI / 2;
+        var velx = Math.cos(parentRotation);
+        var vely = Math.sin(parentRotation);
+
+        // Apply a small forward offset so bullet shoots from head of ship instead of the middle
+        var posx = this.parent.x - velx * 10
+        var posy = this.parent.y - vely * 10;
+
+        this.bulletFactory.generate(posx, posy, -velx * this.bulletSpeed, -vely * this.bulletSpeed, this.parent.rotation);
+    }
+}
+
+export class BulletFactory {
+
+    constructor(private bullets: Phaser.Group, private poolSize: number) {
+        // Set all the defaults for this BulletFactory's bullet object
+        this.bullets.enableBody = true;
+        this.bullets.physicsBodyType = Phaser.Physics.ARCADE;
+        this.bullets.createMultiple(30, 'bullet');
+        this.bullets.setAll('anchor.x', 0.5);
+        this.bullets.setAll('anchor.y', 0.5);
+        this.bullets.setAll('outOfBoundsKill', true);
+        this.bullets.setAll('checkWorldBounds', true);
+    }
+
+    public generate(posx: number, posy: number, velx: number, vely: number, rot: number): Phaser.Sprite {
+        // Pull a bullet from Phaser's Group pool
+        var bullet = this.bullets.getFirstExists(false);
+
+        // Set the few unique properties about this bullet: rotation, position, and velocity
+        if (bullet) {
+            bullet.reset(posx, posy);
+            bullet.rotation = rot;
+            bullet.body.velocity.x = velx;
+            bullet.body.velocity.y = vely;
+        }
+
+        return bullet;
+    }
+}
+```
+
+Lastly, we'll redo our entry point, `game.ts`, to tie together both `Player` and `Weapon` objects
+as well as add them to the update loop. Here is what the updated `game.ts` file looks like:
+
+```ts
+import { Player } from "./player";
+import { Weapon, BulletFactory } from "./weapon";
+
+window.onload = function() {
+    var game = new Phaser.Game(800, 600, Phaser.AUTO, 'gameCanvas', { preload: preload, create: create, update: update });
+    var player: Player;
+    var weapon: Weapon;
+
+    // Import all assets prior to loading the game
+    function preload () {
+        game.load.image('player', 'assets/player.png');
+        game.load.image('bullet', 'assets/bullet.png');
+    }
+
+    // Create all entities in the game, after Phaser loads
+    function create () {
+        // Create and position the player
+        var playerSprite = game.add.sprite(400, 550, 'player');
+        playerSprite.anchor.setTo(0.5);
+        player = new Player(game.input, playerSprite, 150);
+
+        var bulletFactory = new BulletFactory(game.add.group(), 30);
+        weapon = new Weapon(bulletFactory, player.sprite, 0.25, 1000);
+
+        player.loadWeapon(weapon);
+    }
+
+    // This function is called once every tick, default is 60fps
+    function update() {
+        var deltaSeconds = game.time.elapsedMS / 1000; // convert to seconds
+        player.update(deltaSeconds);
+        weapon.update(deltaSeconds);
+    }
+}
+```
+
+Run `gulp serve` and you can run around and shoot. Wonderful! Let's update our CI
+pipeline to include running the tests along with the existing build job.
+
+## Continuous Integration
+
+To ensure our changes don't break the build and all tests still pass, we utilize
+Continuous Integration (CI) to run these checks automatically for every push.
+Read through this article to understand [Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/),
+and how these methods are leveraged by GitLab.
+From the [last tutorial](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) we already have a `gitlab-ci.yml` file set up for building our app from
+every push. We need to set up a new CI job for testing, which GitLab CI/CD will run after the build job using our generated artifacts from gulp.
+
+Please read through the [documentation on CI/CD configuration](../../../ci/yaml/README.md) file to explore its contents and adjust it to your needs.
+
+### Build your game with GitLab CI/CD
+
+We need to update our build job to ensure tests get run as well. Add `gulp build-test`
+to the end of the `script` array for the existing `build` job. Once these commands run,
+we know we will need to access everything in the `built` folder, given by GitLab CI/CD's `artifacts`.
+We'll also cache `node_modules` to avoid having to do a full re-pull of those dependencies:
+just pack them up in the cache. Here is the full `build` job:
+
+```yml
+build:
+    stage: build
+    script:
+        - npm i gulp -g
+        - npm i
+        - gulp
+        - gulp build-test
+    cache:
+        policy: push
+        paths:
+        - node_modules
+    artifacts:
+        paths:
+        - built
+```
+
+### Test your game with GitLab CI/CD
+
+For testing locally, we simply run `gulp run-tests`, which requires gulp to be installed
+globally like in the `build` job. We pull `node_modules` from the cache, so the `npm i`
+command won't have to do much. In preparation for deployment, we know we will still need
+the `built` folder in the artifacts, which will be brought over as default behavior from
+the previous job. Lastly, by convention, we let GitLab CI/CD know this needs to be run after
+the `build` job by giving it a `test` [stage](../../../ci/yaml/README.md#stages).
+Following the YAML structure, the `test` job should look like this:
+
+```yml
+test:
+    stage: test
+    script:
+        - npm i gulp -g
+        - npm i
+        - gulp run-test
+    cache:
+        policy: push
+        paths:
+        - node_modules/
+    artifacts:
+        paths:
+        - built/
+```
+
+We have added unit tests for a `Weapon` class that shoots on a specified interval.
+The `Player` class implements `Weapon` along with the ability to move around and shoot. Also,
+we've added test artifacts and a test stage to our GitLab CI/CD pipeline using `.gitlab-ci.yml`,
+allowing us to run our tests by every push.
+Our entire `.gitlab-ci.yml` file should now look like this:
+
+```yml
+image: node:6
+
+build:
+    stage: build
+    script:
+        - npm i gulp -g
+        - npm i
+        - gulp
+        - gulp build-test
+    cache:
+        policy: push
+        paths:
+        - node_modules/
+    artifacts:
+        paths:
+        - built/
+
+test:
+    stage: test
+    script:
+        - npm i gulp -g
+        - npm i
+        - gulp run-test
+    cache:
+        policy: pull
+        paths:
+        - node_modules/
+    artifacts:
+        paths:
+        - built/
+```
+
+### Run your CI/CD pipeline
+
+That's it! Add all your new files, commit, and push. For a reference of what our repo should
+look like at this point, please refer to the [final commit related to this article on my sample repository](https://gitlab.com/blitzgren/gitlab-game-demo/commit/8b36ef0ecebcf569aeb251be4ee13743337fcfe2).
+By applying both build and test stages, GitLab will run them sequentially at every push to
+our repository. If all goes well you'll end up with a green check mark on each job for the pipeline:
+
+![Passing Pipeline](img/test_pipeline_pass.png)
+
+You can confirm that the tests passed by clicking on the `test` job to enter the full build logs.
+Scroll to the bottom and observe, in all its passing glory:
+
+```sh
+$ gulp run-test
+[18:37:24] Using gulpfile /builds/blitzgren/gitlab-game-demo/gulpfile.js
+[18:37:24] Starting 'run-test'...
+[18:37:24] Finished 'run-test' after 21 ms
+
+
+  Weapon
+    鉁� should shoot if not in cooldown
+    鉁� should not shoot during cooldown
+    鉁� should shoot after cooldown ends
+    鉁� should not shoot if not triggered
+
+
+  4 passing (18ms)
+
+Uploading artifacts...
+built/: found 17 matching files
+Uploading artifacts to coordinator... ok            id=17095874 responseStatus=201 Created token=aaaaaaaa Job succeeded
+```
+
+## Continuous Deployment
+
+We have our codebase built and tested on every push. To complete the full pipeline with Continuous Deployment,
+let's set up [free web hosting with AWS S3](https://aws.amazon.com/s/dm/optimization/server-side-test/free-tier/free_np/) and a job through which our build artifacts get
+deployed. GitLab also has a free static site hosting service we could use, [GitLab Pages](https://about.gitlab.com/features/pages/),
+however Dark Nova specifically uses other AWS tools that necessitates using `AWS S3`.
+Read through this article that describes [deploying to both S3 and GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+and further delves into the principles of GitLab CI/CD than discussed in this article.
+
+### Set up S3 Bucket
+
+1. Log into your AWS account and go to [S3](https://console.aws.amazon.com/s3/home)
+1. Click the **Create Bucket** link at the top
+1. Enter a name of your choosing and click next
+1. Keep the default **Properties** and click next
+1. Click the **Manage group permissions** and allow **Read** for the **Everyone** group, click next
+1. Create the bucket, and select it in your S3 bucket list
+1. On the right side, click **Properties** and enable the **Static website hosting** category
+1. Update the radio button to the **Use this bucket to host a website** selection. Fill in `index.html` and `error.html` respectively
+
+### Set up AWS Secrets
+
+We need to be able to deploy to AWS with our AWS account credentials, but we certainly
+don't want to put secrets into source code. Luckily GitLab provides a solution for this
+with [Secret Variables](../../../ci/variables/README.md). This can get complicated
+due to [IAM](https://aws.amazon.com/iam/) management. As a best practice, you shouldn't
+use root security credentials. Proper IAM credential management is beyond the scope of this
+article, but AWS will remind you that using root credentials is unadvised and against their
+best practices, as they should. Feel free to follow best practices and use a custom IAM user's
+credentials, which will be the same two credentials (Key ID and Secret). It's a good idea to
+fully understand [IAM Best Practices in AWS](http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
+
+1. Log into your AWS account and go to the [Security Credentials page](https://console.aws.amazon.com/iam/home#/security_credential)
+1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the id and secret around, you'll need them later
+    ![AWS Access Key Config](img/aws_config_window.png)
+1. Go to your GitLab project, click **Settings > CI/CD** on the left sidebar
+1. Expand the **Secret Variables** section
+    ![GitLab Secret Config](img/gitlab_config.png)
+1. Add a key named `AWS_KEY_ID` and copy the key id from Step 2 into the **Value** textbox
+1. Add a key named `AWS_KEY_SECRET` and copy the key secret from Step 2 into the **Value** textbox
+
+### Deploy your game with GitLab CI/CD
+
+To deploy our build artifacts, we need to install the [AWS CLI](https://aws.amazon.com/cli/) on
+the Shared Runner. The Shared Runner also needs to be able to authenticate with your AWS
+account to deploy the artifacts. By convention, AWS CLI will look for `AWS_ACCESS_KEY_ID`
+and `AWS_SECRET_ACCESS_KEY`. GitLab's CI gives us a way to pass the secret variables we
+set up in the prior section using the `variables` portion of the `deploy` job. At the end,
+we add directives to ensure deployment `only` happens on pushes to `master`. This way, every
+single branch still runs through CI, and only merging (or committing directly) to master will
+trigger the `deploy` job of our pipeline. Put these together to get the following:
+
+```yml
+deploy:
+    stage: deploy
+    variables:
+        AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+        AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+    script:
+        - apt-get update
+        - apt-get install -y python3-dev python3-pip
+        - easy_install3 -U pip
+        - pip3 install --upgrade awscli
+        - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+    only:
+        - master
+```
+
+Be sure to update the region and S3 URL in that last script command to fit your setup.
+Our final configuration file `.gitlab-ci.yml` looks like:
+
+```yml
+image: node:6
+
+build:
+    stage: build
+    script:
+        - npm i gulp -g
+        - npm i
+        - gulp
+        - gulp build-test
+    cache:
+        policy: push
+        paths:
+        - node_modules/
+    artifacts:
+        paths:
+        - built/
+
+test:
+    stage: test
+    script:
+        - npm i gulp -g
+        - gulp run-test
+    cache:
+        policy: pull
+        paths:
+        - node_modules/
+    artifacts:
+        paths:
+        - built/
+
+deploy:
+    stage: deploy
+    variables:
+        AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+        AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+    script:
+        - apt-get update
+        - apt-get install -y python3-dev python3-pip
+        - easy_install3 -U pip
+        - pip3 install --upgrade awscli
+        - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+    only:
+        - master
+```
+
+## Conclusion
+
+Within the [demo repository](https://gitlab.com/blitzgren/gitlab-game-demo) you can also find a handful of boilerplate code to get
+[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](http://gulpjs.com/) and [Phaser](https://phaser.io) all playing
+together nicely with GitLab CI/CD, which is the result of lessons learned while making [Dark Nova](http://darknova.io/).
+Using a combination of free and open source software, we have a full CI/CD pipeline, a game foundation,
+and unit tests, all running and deployed at every push to master - with shockingly little code.
+Errors can be easily debugged through GitLab's build logs, and within minutes of a successful commit,
+you can see the changes live on your game.
+
+Setting up Continous Integration and Continuous Deployment from the start with Dark Nova enables
+rapid but stable development. We can easily test changes in a separate [environment](../../../ci/environments.md#introduction-to-environments-and-deployments),
+or multiple environments if needed. Balancing and updating a multiplayer game can be ongoing
+and tedious, but having faith in a stable deployment with GitLab CI/CD allows
+a lot of breathing room in quickly getting changes to players.
+
+## Further settings
+
+Here are some ideas to further investigate that can speed up or improve your pipeline:
+
+- [Yarn](https://yarnpkg.com) instead of npm
+- Setup a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ci-yml) image that can preload dependencies and tools (like AWS CLI)
+- Forward a [custom domain](http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
+- Combine jobs if you find it unnecessary for a small project
+- Avoid the queues and set up your own [custom GitLab CI/CD runner](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index b62874ef0295b544a5746e140151b3b04c193731..1f9b9d53fc10434ea219f9a070ae8d4ba798740d 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -190,7 +190,7 @@ To start, we create an `Envoy.blade.php` in the root of our app with a simple ta
 ```php
 @servers(['web' => 'remote_username@remote_host'])
 
-@task('list', [on => 'web'])
+@task('list', ['on' => 'web'])
     ls -l
 @endtask
 ```
diff --git a/doc/ci/examples/sast_docker.md b/doc/ci/examples/sast_docker.md
index 57a9c4bcfc1d878fde73ea6c8686ed3e33cb87c9..9f4a63e296d7fd2f42ad0991aeea11b94559c445 100644
--- a/doc/ci/examples/sast_docker.md
+++ b/doc/ci/examples/sast_docker.md
@@ -1,55 +1 @@
-# Static Application Security Testing for Docker containers with GitLab CI/CD
-
-You can check your Docker images (or more precisely the containers) for known
-vulnerabilities by using [Clair](https://github.com/coreos/clair) and
-[clair-scanner](https://github.com/arminc/clair-scanner), two open source tools
-for Vulnerability Static Analysis for containers.
-
-All you need is a GitLab Runner with the Docker executor (the shared Runners on
-GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
-called `sast:container`:
-
-```yaml
-sast:container:
-  image: docker:latest
-  variables:
-    DOCKER_DRIVER: overlay2
-    ## Define two new variables based on GitLab's CI/CD predefined variables
-    ## https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
-    CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
-    CI_APPLICATION_TAG: $CI_COMMIT_SHA
-  allow_failure: true
-  services:
-    - docker:dind
-  script:
-    - docker run -d --name db arminc/clair-db:latest
-    - docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
-    - apk add -U wget ca-certificates
-    - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
-    - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
-    - mv clair-scanner_linux_amd64 clair-scanner
-    - chmod +x clair-scanner
-    - touch clair-whitelist.yml
-    - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
-  artifacts:
-    paths: [gl-sast-container-report.json]
-```
-
-The above example will create a `sast:container` job in your CI/CD pipeline, pull
-the image from the [Container Registry](../../user/project/container_registry.md)
-(whose name is defined from the two `CI_APPLICATION_` variables) and scan it
-for possible vulnerabilities. The report will be saved as an artifact that you
-can later download and analyze.
-
-If you want to whitelist some specific vulnerabilities, you can do so by defining
-them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file),
-in our case its named `clair-whitelist.yml`.
-
-TIP: **Tip:**
-Starting with [GitLab Ultimate][ee] 10.4, this information will
-be automatically extracted and shown right in the merge request widget. To do
-so, the CI/CD job must be named `sast:container` and the artifact path must be
-`gl-sast-container-report.json`.
-[Learn more on application security testing results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html).
-
-[ee]: https://about.gitlab.com/products/
+This document was moved to [another location](./container_scanning.md).
\ No newline at end of file
diff --git a/doc/ci/img/job_failure_reason.png b/doc/ci/img/job_failure_reason.png
new file mode 100644
index 0000000000000000000000000000000000000000..a60ce1fb21cbca35e07ea06dc6d0cee64b1e21ce
Binary files /dev/null and b/doc/ci/img/job_failure_reason.png differ
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 856d7f264e4269b4a82e5f938b39b5a28b33c682..b16cbc61d141e55333b1596d86827dc18f7173fa 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -2,6 +2,11 @@
 
 > Introduced in GitLab 8.8.
 
+NOTE: **Note:**
+If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository),
+you may need to enable pipeline triggering in your project's
+**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
+
 ## Pipelines
 
 A pipeline is a group of [jobs][] that get executed in [stages][](batches).
@@ -68,6 +73,23 @@ cancel the job, retry it, or erase the job trace.
 
 ![Pipelines example](img/pipelines.png)
 
+## Seeing the failure reason for jobs
+
+> [Introduced][ce-17782] in GitLab 10.7.
+
+When a pipeline fails or is allowed to fail, there are several places where you
+can quickly check the reason it failed:
+
+- **In the pipeline graph** present on the pipeline detail view.
+- **In the pipeline widgets** present in the merge requests and commit pages.
+- **In the job views** present in the global and detailed views of a job.
+
+In any case, if you hover over the failed job you can see the reason it failed.
+
+![Pipeline detail](img/job_failure_reason.png)
+
+From [GitLab 10.8][ce-17814] you can also see the reason it failed on the Job detail page.
+
 ## Pipeline graphs
 
 > [Introduced][ce-5742] in GitLab 8.11.
@@ -121,9 +143,8 @@ The basic requirements is that there are two numbers separated with one of
 the following (you can even use them interchangeably):
 
 - a space
-- a forward slash (`/`)
+- a slash (`/`)
 - a colon (`:`)
-- a dot (`.`)
 
 >**Note:**
 More specifically, [it uses][regexp] this regular expression: `\d+[\s:\/\\]+\d+\s*`.
@@ -259,4 +280,6 @@ runners will not use regular runners, they must be tagged accordingly.
 [ce-6242]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242
 [ce-7931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931
 [ce-9760]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760
+[ce-17782]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782
+[ce-17814]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17814
 [regexp]: https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index e504b81eae806920f2ab1d0a9136eb8ed798f012..fec0ff87326449d8ceccfda6361773e719e30e5b 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -104,8 +104,8 @@ Jobs are used to create jobs, which are then picked by
 
 What is important is that each job is run independently from each other.
 
-If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
-Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
+If you want to check whether the `.gitlab-ci.yml` of your project is valid, there is a
+Lint tool under the page `/ci/lint` of your project namespace. You can also find
 a "CI Lint" button to go to this page under **CI/CD 鉃� Pipelines** and
 **Pipelines 鉃� Jobs** in your project.
 
@@ -126,6 +126,11 @@ git push origin master
 Now if you go to the **Pipelines** page you will see that the pipeline is
 pending.
 
+NOTE: **Note:**
+If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository),
+you may need to enable pipeline triggering in your project's
+**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
+
 You can also go to the **Commits** page and notice the little pause icon next
 to the commit SHA.
 
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 03aa6ff8e7ca808c1841a6f439b7e72bcfe59b3d..60dc2ef9ac5f124fec52cb1d2f41dd9e0da48100 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -35,7 +35,7 @@ are:
 
 A Runner that is specific only runs for the specified project(s). A shared Runner
 can run jobs for every project that has enabled the option **Allow shared Runners**
-under **Settings 鉃� CI/CD**.
+under **Settings > CI/CD**.
 
 Projects with high demand of CI activity can also benefit from using specific
 Runners. By having dedicated Runners you are guaranteed that the Runner is not
@@ -76,7 +76,7 @@ Registering a specific can be done in two ways:
 To create a specific Runner without having admin rights to the GitLab instance,
 visit the project you want to make the Runner work for in GitLab:
 
-1. Go to **Settings 鉃� CI/CD** to obtain the token
+1. Go to **Settings > CI/CD** to obtain the token
 1. [Register the Runner][register]
 
 ### Making an existing shared Runner specific
@@ -85,7 +85,7 @@ If you are an admin on your GitLab instance, you can turn any shared Runner into
 a specific one, but not the other way around. Keep in mind that this is a one
 way transition.
 
-1. Go to the Runners in the admin area **Overview 鉃� Runners** (`/admin/runners`)
+1. Go to the Runners in the admin area **Overview > Runners** (`/admin/runners`)
    and find your Runner
 1. Enable any projects under **Restrict projects for this Runner** to be used
    with the Runner
@@ -101,7 +101,7 @@ can be changed afterwards under each Runner's settings.
 
 To lock/unlock a Runner:
 
-1. Visit your project's **Settings 鉃� CI/CD**
+1. Visit your project's **Settings > CI/CD**
 1. Find the Runner you wish to lock/unlock and make sure it's enabled
 1. Click the pencil button
 1. Check the **Lock to current projects** option
@@ -115,7 +115,7 @@ you can enable the Runner also on any other project where you have Master permis
 
 To enable/disable a Runner in your project:
 
-1. Visit your project's **Settings 鉃� CI/CD**
+1. Visit your project's **Settings > CI/CD**
 1. Find the Runner you wish to enable/disable
 1. Click **Enable for this project** or **Disable for this project**
 
@@ -124,6 +124,13 @@ Consider that if you don't lock your specific Runner to a specific project, any
 user with Master role in you project can assign your runner to another arbitrary
 project without requiring your authorization, so use it with caution.
 
+An admin can enable/disable a specific Runner for projects:
+
+1. Navigate to **Admin > Runners**
+2. Find the Runner you wish to enable/disable
+3. Click edit on the Runner
+4. Click **Enable** or **Disable** on the project
+
 ## Protected Runners
 
 >
@@ -136,7 +143,7 @@ Whenever a Runner is protected, the Runner picks only jobs created on
 
 To protect/unprotect Runners:
 
-1. Visit your project's **Settings 鉃� CI/CD**
+1. Visit your project's **Settings > CI/CD**
 1. Find a Runner you want to protect/unprotect and make sure it's enabled
 1. Click the pencil button besides the Runner name
 1. Check the **Protected** option
@@ -146,25 +153,7 @@ To protect/unprotect Runners:
 
 ## Manually clearing the Runners cache
 
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41249) in GitLab 10.4.
-
-GitLab Runners use [cache](../yaml/README.md#cache) to speed up the execution
-of your jobs by reusing existing data. This however, can sometimes lead to an
-inconsistent behavior.
-
-To start with a fresh copy of the cache, you can easily do it via GitLab's UI:
-
-1. Navigate to your project's **CI/CD > Pipelines** page.
-1. Click on the **Clear Runner caches** to clean up the cache.
-1. On the next push, your CI/CD job will use a new cache.
-
-That way, you don't have to change the [cache key](../yaml/README.md#cache-key)
-in your `.gitlab-ci.yml`.
-
-Behind the scenes, this works by increasing a counter in the database, and the
-value of that counter is used to create the key for the cache. After a push, a
-new key is generated and the old cache is not valid anymore. Eventually, the
-Runner's garbage collector will remove it form the filesystem.
+Read [clearing the cache](../caching/index.md#clearing-the-cache).
 
 ## How shared Runners pick jobs
 
@@ -228,15 +217,16 @@ that it may encounter on the projects it's shared over. This would be
 problematic for large amounts of projects, if it wasn't for tags.
 
 By tagging a Runner for the types of jobs it can handle, you can make sure
-shared Runners will only run the jobs they are equipped to run.
+shared Runners will [only run the jobs they are equipped to run](../yaml/README.md#tags).
 
 For instance, at GitLab we have Runners tagged with "rails" if they contain
 the appropriate dependencies to run Rails test suites.
 
 ### Preventing Runners with tags from picking jobs without tags
 
-You can configure a Runner to prevent it from picking jobs with tags when
-the Runner does not have tags assigned. This setting can be enabled the first
+You can configure a Runner to prevent it from picking
+[jobs with tags](../yaml/README.md#tags) when the Runner does not have tags
+assigned. This setting can be enabled the first
 time you [register a Runner][register] and can be changed afterwards under
 each Runner's settings.
 
@@ -248,6 +238,38 @@ To make a Runner pick tagged/untagged jobs:
 1. Check the **Run untagged jobs** option
 1. Click **Save changes** for the changes to take effect
 
+### Setting maximum job timeout for a Runner
+
+For each Runner you can specify a _maximum job timeout_. Such timeout,
+if smaller than [project defined timeout], will take the precedence. This
+feature can be used to prevent Shared Runner from being appropriated
+by a project by setting a ridiculous big timeout (e.g. one week).
+
+When not configured, Runner will not override project timeout.
+
+How this feature will work:
+
+**Example 1 - Runner timeout bigger than project timeout**
+
+1. You set the _maximum job timeout_ for a Runner to 24 hours
+1. You set the _CI/CD Timeout_ for a project to **2 hours**
+1. You start a job
+1. The job, if running longer, will be timeouted after **2 hours**
+
+**Example 2 - Runner timeout not configured**
+
+1. You remove the _maximum job timeout_ configuration from a Runner
+1. You set the _CI/CD Timeout_ for a project to **2 hours**
+1. You start a job
+1. The job, if running longer, will be timeouted after **2 hours**
+
+**Example 3 - Runner timeout smaller than project timeout**
+
+1. You set the _maximum job timeout_ for a Runner to **30 minutes**
+1. You set the _CI/CD Timeout_ for a project to 2 hours
+1. You start a job
+1. The job, if running longer, will be timeouted after **30 minutes**
+
 ### Be careful with sensitive information
 
 With some [Runner Executors](https://docs.gitlab.com/runner/executors/README.html),
@@ -276,8 +298,42 @@ Mentioned briefly earlier, but the following things of Runners can be exploited.
 We're always looking for contributions that can mitigate these
 [Security Considerations](https://docs.gitlab.com/runner/security/).
 
+## Determining the IP address of a Runner
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17286) in GitLab 10.6.
+
+It may be useful to know the IP address of a Runner so you can troubleshoot
+issues with that Runner. GitLab stores and displays the IP address by viewing
+the source of the HTTP requests it makes to GitLab when polling for jobs. The
+IP address is always kept up to date so if the Runner IP changes it will be
+automatically updated in GitLab.
+
+The IP address for shared Runners and specific Runners can be found in
+different places.
+
+### Shared Runners
+
+To view the IP address of a shared Runner you must have admin access to
+the GitLab instance. To determine this:
+
+1. Visit **Admin area 鉃� Overview 鉃� Runners**
+1. Look for the Runner in the table and you should see a column for "IP Address"
+
+![shared Runner IP address](img/shared_runner_ip_address.png)
+
+### Specific Runners
+
+You can find the IP address of a Runner for a specific project by:
+
+1. Visit your project's **Settings 鉃� CI/CD**
+1. Find the Runner and click on it's ID which links you to the details page
+1. On the details page you should see a row for "IP Address"
+
+![specific Runner IP address](img/specific_runner_ip_address.png)
+
 [install]: http://docs.gitlab.com/runner/install/
 [fifo]: https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)
 [register]: http://docs.gitlab.com/runner/register/
 [protected branches]: ../../user/project/protected_branches.md
 [protected tags]: ../../user/project/protected_tags.md
+[project defined timeout]: ../../user/project/pipelines/settings.html#timeout
diff --git a/doc/ci/runners/img/shared_runner_ip_address.png b/doc/ci/runners/img/shared_runner_ip_address.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b1542d59d39d9b36003549b47a350109a5bde2c
Binary files /dev/null and b/doc/ci/runners/img/shared_runner_ip_address.png differ
diff --git a/doc/ci/runners/img/specific_runner_ip_address.png b/doc/ci/runners/img/specific_runner_ip_address.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b4c3e9f2ebc30f18ebbd9fc3c34bac35a99907b
Binary files /dev/null and b/doc/ci/runners/img/specific_runner_ip_address.png differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 23ce6a5f2103c72f1d4ffb912f8ccd4ea8687bad..4a504a98902cbc5823c8c7529a24f5a0b34a2d7c 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -12,7 +12,7 @@ this order:
 1. [Trigger variables][triggers] or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables) (take precedence over all)
 1. Project-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
 1. Group-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
-1. YAML-defined [job-level variables](../yaml/README.md#job-variables)
+1. YAML-defined [job-level variables](../yaml/README.md#variables)
 1. YAML-defined [global variables](../yaml/README.md#variables)
 1. [Deployment variables](#deployment-variables)
 1. [Predefined variables](#predefined-variables-environment-variables) (are the
@@ -449,6 +449,107 @@ export CI_REGISTRY_USER="gitlab-ci-token"
 export CI_REGISTRY_PASSWORD="longalfanumstring"
 ```
 
+## Variables expressions
+
+> Variables expressions were added in GitLab 10.7.
+
+It is possible to use variables expressions with only / except policies in
+`.gitlab-ci.yml`. By using this approach you can limit what jobs are going to
+be created within a pipeline after pushing a code to GitLab.
+
+This is particularly useful in combination with secret variables and triggered
+pipeline variables.
+
+```yaml
+deploy:
+  script: cap staging deploy
+  environment: staging
+  only:
+    variables:
+      - $RELEASE == "staging"
+      - $STAGING
+```
+
+Each expression provided is going to be evaluated before creating a pipeline.
+
+If any of the conditions in `variables` evaluates to truth when using `only`,
+a new job is going to be created. If any of the expressions evaluates to truth
+when `except` is being used, a job is not going to be created.
+
+This follows usual rules for [`only` / `except` policies][builds-policies].
+
+### Supported syntax
+
+Below you can find supported syntax reference:
+
+1. Equality matching using a string
+
+    > Example: `$VARIABLE == "some value"`
+
+    You can use equality operator `==` to compare a variable content to a
+    string. We support both, double quotes and single quotes to define a string
+    value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
+    are supported. `"some value" == $VARIABLE` is correct too.
+
+1. Checking for an undefined value
+
+    > Example: `$VARIABLE == null`
+
+    It sometimes happens that you want to check whether a variable is defined
+    or not. To do that, you can compare a variable to `null` keyword, like
+    `$VARIABLE == null`. This expression is going to evaluate to truth if
+    variable is not defined.
+
+1. Checking for an empty variable
+
+    > Example: `$VARIABLE == ""`
+
+    If you want to check whether a variable is defined, but is empty, you can
+    simply compare it against an empty string, like `$VAR == ''`.
+
+1. Comparing two variables
+
+    > Example: `$VARIABLE_1 == $VARIABLE_2`
+
+    It is possible to compare two variables. This is going to compare values
+    of these variables.
+
+1. Variable presence check
+
+    > Example: `$STAGING`
+
+    If you only want to create a job when there is some variable present,
+    which means that it is defined and non-empty, you can simply use
+    variable name as an expression, like `$STAGING`. If `$STAGING` variable
+    is defined, and is non empty, expression will evaluate to truth.
+    `$STAGING` value needs to a string, with length higher than zero.
+    Variable that contains only whitespace characters is not an empty variable.
+
+### Unsupported predefined variables
+
+Because GitLab evaluates variables before creating jobs, we do not support a
+few variables that depend on persistence layer, like `$CI_JOB_ID`.
+
+Environments (like `production` or `staging`) are also being created based on
+what jobs pipeline consists of, thus some environment-specific variables are
+not supported as well.
+
+We do not support variables containing tokens because of security reasons.
+
+You can find a full list of unsupported variables below:
+
+- `CI_JOB_ID`
+- `CI_JOB_TOKEN`
+- `CI_BUILD_ID`
+- `CI_BUILD_TOKEN`
+- `CI_REGISTRY_USER`
+- `CI_REGISTRY_PASSWORD`
+- `CI_REPOSITORY_URL`
+- `CI_ENVIRONMENT_URL`
+
+These variables are also not supported in a contex of a
+[dynamic environment name][dynamic-environments].
+
 [ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI secret variables"
 [eep]: https://about.gitlab.com/products/ "Available only in GitLab Premium"
 [envs]: ../environments.md
@@ -459,3 +560,5 @@ export CI_REGISTRY_PASSWORD="longalfanumstring"
 [triggered]: ../triggers/README.md
 [triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
 [subgroups]: ../../user/group/subgroups/index.md
+[builds-policies]: ../yaml/README.md#only-and-except-complex
+[dynamic-environments]: ../environments.md#dynamic-environments
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 80ab63468f2dbaadba961e1bcd3e3d38af17c15c..623e7d662a358202ff0e8c4c1a8d0d3514bc5bb2 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -3,18 +3,24 @@
 This document describes the usage of `.gitlab-ci.yml`, the file that is used by
 GitLab Runner to manage your project's jobs.
 
+From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML)
+file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root
+of your repository and contains definitions of how your project should be built.
+
 If you want a quick introduction to GitLab CI, follow our
 [quick start guide](../quick_start/README.md).
 
-## .gitlab-ci.yml
+NOTE: **Note:**
+If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository),
+you may need to enable pipeline triggering in your project's
+**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
 
-From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML)
-file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root
-of your repository and contains definitions of how your project should be built.
+## Jobs
 
 The YAML file defines a set of jobs with constraints stating when they should
-be run. The jobs are defined as top-level elements with a name and always have
-to contain at least the `script` clause:
+be run. You can specify an unlimited number of jobs which are defined as
+top-level elements with an arbitrary name and always have to contain at least
+the `script` clause.
 
 ```yaml
 job1:
@@ -24,9 +30,8 @@ job2:
   script: "execute-script-for-job2"
 ```
 
-The above example is the simplest possible CI configuration with two separate
+The above example is the simplest possible CI/CD configuration with two separate
 jobs, where each of the jobs executes a different command.
-
 Of course a command can execute code directly (`./configure;make;make install`)
 or run a script (`test.sh`) in the repository.
 
@@ -34,78 +39,115 @@ Jobs are picked up by [Runners](../runners/README.md) and executed within the
 environment of the Runner. What is important, is that each job is run
 independently from each other.
 
-The YAML syntax allows for using more complex job specifications than in the
-above example:
+Each job must have a unique name, but there are a few **reserved `keywords` that
+cannot be used as job names**:
 
-```yaml
-image: ruby:2.1
-services:
-  - postgres
+- `image`
+- `services`
+- `stages`
+- `types`
+- `before_script`
+- `after_script`
+- `variables`
+- `cache`
 
-before_script:
-  - bundle install
+A job is defined by a list of parameters that define the job behavior.
 
-after_script:
-  - rm secrets
+| Keyword       | Required | Description |
+|---------------|----------|-------------|
+| script        | yes      | Defines a shell script which is executed by Runner |
+| image         | no       | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| services      | no       | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| stage         | no       | Defines a job stage (default: `test`) |
+| type          | no       | Alias for `stage` |
+| variables     | no       | Define job variables on a job level |
+| only          | no       | Defines a list of git refs for which job is created |
+| except        | no       | Defines a list of git refs for which job is not created |
+| tags          | no       | Defines a list of tags which are used to select Runner |
+| allow_failure | no       | Allow job to fail. Failed job doesn't contribute to commit status |
+| when          | no       | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
+| dependencies  | no       | Define other jobs that a job depends on so that you can pass artifacts between them|
+| artifacts     | no       | Define list of [job artifacts](#artifacts) |
+| cache         | no       | Define list of files that should be cached between subsequent runs |
+| before_script | no       | Override a set of commands that are executed before job |
+| after_script  | no       | Override a set of commands that are executed after job |
+| environment   | no       | Defines a name of environment to which deployment is done by this job |
+| coverage      | no       | Define code coverage settings for a given job |
+| retry         | no       | Define how many times a job can be auto-retried in case of a failure |
 
-stages:
-  - build
-  - test
-  - deploy
+### `pages`
 
-job1:
-  stage: build
+`pages` is a special job that is used to upload static content to GitLab that
+can be used to serve your website. It has a special syntax, so the two
+requirements below must be met:
+
+1. Any static content must be placed under a `public/` directory
+1. `artifacts` with a path to the `public/` directory must be defined
+
+The example below simply moves all files from the root of the project to the
+`public/` directory. The `.public` workaround is so `cp` doesn't also copy
+`public/` to itself in an infinite loop:
+
+```
+pages:
+  stage: deploy
   script:
-    - execute-script-for-job1
+  - mkdir .public
+  - cp -r * .public
+  - mv .public public
+  artifacts:
+    paths:
+    - public
   only:
-    - master
-  tags:
-    - docker
+  - master
 ```
 
-There are a few reserved `keywords` that **cannot** be used as job names:
-
-| Keyword       | Required | Description |
-|---------------|----------|-------------|
-| image         | no | Use docker image, covered in [Use Docker](../docker/README.md) |
-| services      | no | Use docker services, covered in [Use Docker](../docker/README.md) |
-| stages        | no | Define build stages |
-| types         | no | Alias for `stages` (deprecated) |
-| before_script | no | Define commands that run before each job's script |
-| after_script  | no | Define commands that run after each job's script |
-| variables     | no | Define build variables |
-| cache         | no | Define list of files that should be cached between subsequent runs |
+Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
 
-### image and services
+## `image` and `services`
 
 This allows to specify a custom Docker image and a list of services that can be
 used for time of the job. The configuration of this feature is covered in
 [a separate document](../docker/README.md).
 
-### before_script
-
-`before_script` is used to define the command that should be run before all
-jobs, including deploy jobs, but after the restoration of artifacts. This can
-be an array or a multi-line string.
-
-### after_script
+## `before_script` and `after_script`
 
 > Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
 
+`before_script` is used to define the command that should be run before all
+jobs, including deploy jobs, but after the restoration of [artifacts](#artifacts).
+This can be an array or a multi-line string.
+
 `after_script` is used to define the command that will be run after for all
 jobs, including failed ones. This has to be an array or a multi-line string.
 
-> **Note:**
 The `before_script` and the main `script` are concatenated and run in a single context/container.
 The `after_script` is run separately, so depending on the executor, changes done
 outside of the working tree might not be visible, e.g. software installed in the
 `before_script`.
 
-### stages
+It's possible to overwrite the globally defined `before_script` and `after_script`
+if you set it per-job:
 
-`stages` is used to define stages that can be used by jobs.
-The specification of `stages` allows for having flexible multi stage pipelines.
+```yaml
+before_script:
+- global before script
+
+job:
+  before_script:
+  - execute this instead of global before script
+  script:
+  - my command
+  after_script:
+  - execute this after my script
+```
 
+## `stages`
+
+`stages` is used to define stages that can be used by jobs and is defined
+globally.
+
+The specification of `stages` allows for having flexible multi stage pipelines.
 The ordering of elements in `stages` defines the ordering of jobs' execution:
 
 1. Jobs of the same stage are run in parallel.
@@ -134,280 +176,45 @@ There are also two edge cases worth mentioning:
    `test` and `deploy` are allowed to be used as job's stage by default.
 2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
 
-### types
-
-> Deprecated, and could be removed in one of the future releases. Use [stages](#stages) instead.
-
-Alias for [stages](#stages).
-
-### variables
-
-> Introduced in GitLab Runner v0.5.0.
-
-GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the
-job environment. The variables are stored in the Git repository and are meant
-to store non-sensitive project configuration, for example:
-
-```yaml
-variables:
-  DATABASE_URL: "postgres://postgres@postgres/my_database"
-```
-
->**Note:**
-Integers (as well as strings) are legal both for variable's name and value.
-Floats are not legal and cannot be used.
-
-These variables can be later used in all executed commands and scripts.
-The YAML-defined variables are also set to all created service containers,
-thus allowing to fine tune them. Variables can be also defined on a
-[job level](#job-variables).
-
-Except for the user defined variables, there are also the ones set up by the
-Runner itself. One example would be `CI_COMMIT_REF_NAME` which has the value of
-the branch or tag name for which project is built. Apart from the variables
-you can set in `.gitlab-ci.yml`, there are also the so called secret variables
-which can be set in GitLab's UI.
-
-[Learn more about variables.][variables]
-
-### cache
-
->
-**Notes:**
-- Introduced in GitLab Runner v0.7.0.
-- Prior to GitLab 9.2, caches were restored after artifacts.
-- From GitLab 9.2, caches are restored before artifacts.
-
-`cache` is used to specify a list of files and directories which should be
-cached between jobs. You can only use paths that are within the project
-workspace.
-
-**By default caching is enabled and shared between pipelines and jobs,
-starting from GitLab 9.0**
-
-If `cache` is defined outside the scope of jobs, it means it is set
-globally and all jobs will use that definition.
-
-Cache all files in `binaries` and `.config`:
-
-```yaml
-rspec:
-  script: test
-  cache:
-    paths:
-    - binaries/
-    - .config
-```
-
-Cache all Git untracked files:
-
-```yaml
-rspec:
-  script: test
-  cache:
-    untracked: true
-```
-
-Cache all Git untracked files and files in `binaries`:
-
-```yaml
-rspec:
-  script: test
-  cache:
-    untracked: true
-    paths:
-    - binaries/
-```
-
-Locally defined cache overrides globally defined options. The following `rspec`
-job will cache only `binaries/`:
-
-```yaml
-cache:
-  paths:
-  - my/files
-
-rspec:
-  script: test
-  cache:
-    key: rspec
-    paths:
-    - binaries/
-```
-
-Note that since cache is shared between jobs, if you're using different
-paths for different jobs, you should also set a different **cache:key**
-otherwise cache content can be overwritten.
-
-The cache is provided on a best-effort basis, so don't expect that the cache
-will be always present. For implementation details, please check GitLab Runner.
-
-#### cache:key
-
-> Introduced in GitLab Runner v1.0.0.
-
-The `key` directive allows you to define the affinity of caching
-between jobs, allowing to have a single cache for all jobs,
-cache per-job, cache per-branch or any other way you deem proper.
-
-This allows you to fine tune caching, allowing you to cache data between
-different jobs or even different branches.
-
-The `cache:key` variable can use any of the [predefined variables](../variables/README.md).
-
-The default key is **default** across the project, therefore everything is
-shared between each pipelines and jobs by default, starting from GitLab 9.0.
-
->**Note:** The `cache:key` variable cannot contain the `/` character, or the equivalent URI encoded `%2F`; a value made only of dots (`.`, `%2E`) is also forbidden.
-
----
-
-**Example configurations**
-
-To enable per-job caching:
-
-```yaml
-cache:
-  key: "$CI_JOB_NAME"
-  untracked: true
-```
-
-To enable per-branch caching:
-
-```yaml
-cache:
-  key: "$CI_COMMIT_REF_SLUG"
-  untracked: true
-```
-
-To enable per-job and per-branch caching:
-
-```yaml
-cache:
-  key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
-  untracked: true
-```
-
-To enable per-branch and per-stage caching:
-
-```yaml
-cache:
-  key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
-  untracked: true
-```
-
-If you use **Windows Batch** to run your shell scripts you need to replace
-`$` with `%`:
-
-```yaml
-cache:
-  key: "%CI_JOB_STAGE%-%CI_COMMIT_REF_SLUG%"
-  untracked: true
-```
-
-If you use **Windows PowerShell** to run your shell scripts you need to replace
-`$` with `$env:`:
-
-```yaml
-cache:
-  key: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_SLUG"
-  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.
+## `stage`
 
-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:
+`stage` is defined per-job and relies on [`stages`](#stages) which is defined
+globally. It allows to group jobs into different stages, and jobs of the same
+`stage` are executed in `parallel`. For example:
 
 ```yaml
 stages:
-  - setup
+  - build
   - test
+  - deploy
 
-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
+job 1:
+  stage: build
+  script: make build dependencies
 
-`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
-must have a unique name, which is not one of the keywords mentioned above.
-A job is defined by a list of parameters that define the job behavior.
+job 2:
+  stage: build
+  script: make build artifacts
 
-```yaml
-job_name:
-  script:
-    - rake spec
-    - coverage
+job 3:
   stage: test
-  only:
-    - master
-  except:
-    - develop
-  tags:
-    - ruby
-    - postgres
-  allow_failure: true
+  script: make test
+
+job 4:
+  stage: deploy
+  script: make deploy
 ```
 
-| Keyword       | Required | Description |
-|---------------|----------|-------------|
-| script        | yes      | Defines a shell script which is executed by Runner |
-| image         | no       | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| services      | no       | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| stage         | no       | Defines a job stage (default: `test`) |
-| type          | no       | Alias for `stage` |
-| variables     | no       | Define job variables on a job level |
-| only          | no       | Defines a list of git refs for which job is created |
-| except        | no       | Defines a list of git refs for which job is not created |
-| tags          | no       | Defines a list of tags which are used to select Runner |
-| allow_failure | no       | Allow job to fail. Failed job doesn't contribute to commit status |
-| when          | no       | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
-| dependencies  | no       | Define other jobs that a job depends on so that you can pass artifacts between them|
-| artifacts     | no       | Define list of [job artifacts](../../user/project/pipelines/job_artifacts.md) |
-| cache         | no       | Define list of files that should be cached between subsequent runs |
-| before_script | no       | Override a set of commands that are executed before job |
-| after_script  | no       | Override a set of commands that are executed after job |
-| environment   | no       | Defines a name of environment to which deployment is done by this job |
-| coverage      | no       | Define code coverage settings for a given job |
-| retry         | no       | Define how many times a job can be auto-retried in case of a failure |
+## `types`
 
-### script
+CAUTION: **Deprecated:**
+`types` is deprecated, and could be removed in one of the future releases.
+Use [stages](#stages) instead.
 
-`script` is a shell script which is executed by the Runner. For example:
+## `script`
+
+`script` is the only required keyword that a job needs. It's a shell script
+which is executed by the Runner. For example:
 
 ```yaml
 job:
@@ -429,13 +236,7 @@ that the YAML parser knows to interpret the whole thing as a string rather than
 a "key: value" pair. Be careful when using special characters:
 `:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``.
 
-### stage
-
-`stage` allows to group jobs into different stages. Jobs of the same `stage`
-are executed in `parallel`. For more info about the use of `stage` please check
-[stages](#stages).
-
-### only and except (simplified)
+## `only` and `except` (simplified)
 
 `only` and `except` are two parameters that set a job policy to limit when
 jobs are created:
@@ -505,12 +306,15 @@ job:
 The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
 except master.
 
-### only and except (complex)
+## `only` and `except` (complex)
+
+> `refs` and `kubernetes` policies introduced in GitLab 10.0
 
-> Introduced in GitLab 10.0
+> `variables` policy introduced in 10.7
 
-> This an _alpha_ feature, and it it subject to change at any time without
-  prior notice!
+CAUTION: **Warning:**
+This an _alpha_ feature, and it it subject to change at any time without
+prior notice!
 
 Since GitLab 10.0 it is possible to define a more elaborate only/except job
 policy configuration.
@@ -518,9 +322,14 @@ policy configuration.
 GitLab now supports both, simple and complex strategies, so it is possible to
 use an array and a hash configuration scheme.
 
-Two keys are now available: `refs` and `kubernetes`. Refs strategy equals to
-simplified only/except configuration, whereas kubernetes strategy accepts only
-`active` keyword.
+Three keys are now available: `refs`, `kubernetes` and `variables`.
+Refs strategy equals to simplified only/except configuration, whereas
+kubernetes strategy accepts only `active` keyword.
+
+`variables` keyword is used to define variables expressions. In other words
+you can use predefined variables / secret variables / project / group or
+environment-scoped variables to define an expression GitLab is going to
+evaluate in order to decide whether a job should be created or not.
 
 See the example below. Job is going to be created only when pipeline has been
 scheduled or runs for a `master` branch, and only if kubernetes service is
@@ -535,24 +344,21 @@ job:
     kubernetes: active
 ```
 
-### Job variables
-
-It is possible to define job variables using a `variables` keyword on a job
-level. It works basically the same way as its [global-level equivalent](#variables),
-but allows you to define job-specific variables.
-
-When the `variables` keyword is used on a job level, it overrides the global YAML
-job variables and predefined ones. To turn off global defined variables
-in your job, define an empty hash:
+Example of using variables expressions:
 
 ```yaml
-job_name:
-  variables: {}
+deploy:
+  only:
+    refs:
+      - branches
+    variables:
+      - $RELEASE == "staging"
+      - $STAGING
 ```
 
-Job variables priority is defined in the [variables documentation][variables].
+Learn more about variables expressions on [a separate page][variables-expressions].
 
-### tags
+## `tags`
 
 `tags` is used to select specific Runners from the list of all Runners that are
 allowed to run this project.
@@ -573,7 +379,7 @@ job:
 The specification above, will make sure that `job` is built by a Runner that
 has both `ruby` AND `postgres` tags defined.
 
-### allow_failure
+## `allow_failure`
 
 `allow_failure` is used when you want to allow a job to fail without impacting
 the rest of the CI suite. Failed jobs don't contribute to the commit status.
@@ -606,7 +412,7 @@ job3:
   - deploy_to_staging
 ```
 
-### when
+## `when`
 
 `when` is used to implement jobs that are run in case of failure or despite the
 failure.
@@ -619,7 +425,7 @@ failure.
     fails.
 1. `always` - execute job regardless of the status of jobs from prior stages.
 1. `manual` - execute job manually (added in GitLab 8.10). Read about
-    [manual actions](#manual-actions) below.
+    [manual actions](#when-manual) below.
 
 For example:
 
@@ -667,42 +473,41 @@ The above script will:
    success or failure.
 3. Allow you to manually execute `deploy_job` from GitLab's UI.
 
-#### Manual actions
+### `when:manual`
 
-> Introduced in GitLab 8.10.
-> Blocking manual actions were introduced in GitLab 9.0
-> Protected actions were introduced in GitLab 9.2
+> **Notes:**
+- Introduced in GitLab 8.10.
+- Blocking manual actions were introduced in GitLab 9.0.
+- Protected actions were introduced in GitLab 9.2.
 
-Manual actions are a special type of job that are not executed automatically;
-they need to be explicitly started by a user. Manual actions can be started
-from pipeline, build, environment, and deployment views.
+Manual actions are a special type of job that are not executed automatically,
+they need to be explicitly started by a user. An example usage of manual actions
+would be a deployment to a production environment. Manual actions can be started
+from the pipeline, job, environment, and deployment views. Read more at the
+[environments documentation][env-manual].
 
-An example usage of manual actions is deployment to production.
-
-Read more at the [environments documentation][env-manual].
-
-Manual actions can be either optional or blocking. Blocking manual action will
-block execution of the pipeline at stage this action is defined in. It is
+Manual actions can be either optional or blocking. Blocking manual actions will
+block the execution of the pipeline at the stage this action is defined in. It's
 possible to resume execution of the pipeline when someone executes a blocking
-manual actions by clicking a _play_ button.
+manual action by clicking a _play_ button.
 
-When pipeline is blocked it will not be merged if Merge When Pipeline Succeeds
+When a pipeline is blocked, it will not be merged if Merge When Pipeline Succeeds
 is set. Blocked pipelines also do have a special status, called _manual_.
-
 Manual actions are non-blocking by default. If you want to make manual action
 blocking, it is necessary to add `allow_failure: false` to the job's definition
 in `.gitlab-ci.yml`.
 
-Optional manual actions have `allow_failure: true` set by default.
-
-**Statuses of optional actions do not contribute to overall pipeline status.**
+Optional manual actions have `allow_failure: true` set by default and their
+Statuses do not contribute to the overall pipeline status. So, if a manual
+action fails, the pipeline will eventually succeed.
 
-**Manual actions are considered to be write actions, so permissions for
-protected branches are used when user wants to trigger an action. In other
-words, in order to trigger a manual action assigned to a branch that the
-pipeline is running for, user needs to have ability to merge to this branch.**
+Manual actions are considered to be write actions, so permissions for
+[protected branches](../../user/project/protected_branches.md) are used when
+user wants to trigger an action. In other words, in order to trigger a manual
+action assigned to a branch that the pipeline is running for, user needs to
+have ability to merge to this branch.
 
-### environment
+## `environment`
 
 >
 **Notes:**
@@ -727,7 +532,7 @@ deploy to production:
 In the above example, the `deploy to production` job will be marked as doing a
 deployment to the `production` environment.
 
-#### environment:name
+### `environment:name`
 
 >
 **Notes:**
@@ -766,7 +571,7 @@ deploy to production:
     name: production
 ```
 
-#### environment:url
+### `environment:url`
 
 >
 **Notes:**
@@ -793,7 +598,7 @@ deploy to production:
     url: https://prod.example.com
 ```
 
-#### environment:on_stop
+### `environment:on_stop`
 
 >
 **Notes:**
@@ -808,7 +613,7 @@ the environment.
 
 Read the `environment:action` section for an example.
 
-#### environment:action
+### `environment:action`
 
 > [Introduced][ce-6669] in GitLab 8.13.
 
@@ -849,7 +654,7 @@ The `stop_review_app` job is **required** to have the following keywords defined
 - `stage` should be the same as the `review_app` in order for the environment
   to stop automatically when the branch is deleted
 
-#### dynamic environments
+### Dynamic environments
 
 >
 **Notes:**
@@ -859,71 +664,236 @@ The `stop_review_app` job is **required** to have the following keywords defined
   including predefined, secure variables and `.gitlab-ci.yml` [`variables`](#variables).
   You however cannot use variables defined under `script`.
 
-For example:
+For example:
+
+```yaml
+deploy as review app:
+  stage: deploy
+  script: make deploy
+  environment:
+    name: review/$CI_COMMIT_REF_NAME
+    url: https://$CI_ENVIRONMENT_SLUG.example.com/
+```
+
+The `deploy as review app` job will be marked as deployment to dynamically
+create the `review/$CI_COMMIT_REF_NAME` environment, where `$CI_COMMIT_REF_NAME`
+is an [environment variable][variables] set by the Runner. The
+`$CI_ENVIRONMENT_SLUG` variable is based on the environment name, but suitable
+for inclusion in URLs. In this case, if the `deploy as review app` job was run
+in a branch named `pow`, this environment would be accessible with an URL like
+`https://review-pow.example.com/`.
+
+This of course implies that the underlying server which hosts the application
+is properly configured.
+
+The common use case is to create dynamic environments for branches and use them
+as Review Apps. You can see a simple example using Review Apps at
+<https://gitlab.com/gitlab-examples/review-apps-nginx/>.
+
+## `cache`
+
+>
+**Notes:**
+- Introduced in GitLab Runner v0.7.0.
+- `cache` can be set globally and per-job.
+- From GitLab 9.0, caching is enabled and shared between pipelines and jobs
+  by default.
+- From GitLab 9.2, caches are restored before [artifacts](#artifacts).
+
+TIP: **Learn more:**
+Read how caching works and find out some good practices in the
+[caching dependencies documentation](../caching/index.md).
+
+`cache` is used to specify a list of files and directories which should be
+cached between jobs. You can only use paths that are within the project
+workspace.
+
+If `cache` is defined outside the scope of jobs, it means it is set
+globally and all jobs will use that definition.
+
+### `cache:paths`
+
+Use the `paths` directive to choose which files or directories will be cached.
+Wildcards can be used as well.
+
+Cache all files in `binaries` that end in `.apk` and the `.config` file:
+
+```yaml
+rspec:
+  script: test
+  cache:
+    paths:
+    - binaries/*.apk
+    - .config
+```
+
+Locally defined cache overrides globally defined options. The following `rspec`
+job will cache only `binaries/`:
+
+```yaml
+cache:
+  paths:
+  - my/files
+
+rspec:
+  script: test
+  cache:
+    paths:
+    - binaries/
+```
+
+### `cache:key`
+
+> Introduced in GitLab Runner v1.0.0.
+
+Since the cache is shared between jobs, if you're using different
+paths for different jobs, you should also set a different `cache:key`
+otherwise cache content can be overwritten.
+
+The `key` directive allows you to define the affinity of caching between jobs,
+allowing to have a single cache for all jobs, cache per-job, cache per-branch
+or any other way that fits your workflow. This way, you can fine tune caching,
+allowing you to cache data between different jobs or even different branches.
+
+The `cache:key` variable can use any of the
+[predefined variables](../variables/README.md), and the default key, if not set,
+is `$CI_JOB_NAME-$CI_COMMIT_REF_NAME` which translates as "per-job and
+per-branch". It is the default across the project, therefore everything is
+shared between pipelines and jobs running on the same branch by default.
+
+NOTE: **Note:**
+The `cache:key` variable cannot contain the `/` character, or the equivalent
+URI-encoded `%2F`; a value made only of dots (`.`, `%2E`) is also forbidden.
+
+For example, to enable per-branch caching:
+
+```yaml
+cache:
+  key: "$CI_COMMIT_REF_SLUG"
+  paths:
+  - binaries/
+```
+
+If you use **Windows Batch** to run your shell scripts you need to replace
+`$` with `%`:
+
+```yaml
+cache:
+  key: "%CI_JOB_STAGE%-%CI_COMMIT_REF_SLUG%"
+  paths:
+  - binaries/
+```
+
+If you use **Windows PowerShell** to run your shell scripts you need to replace
+`$` with `$env:`:
+
+```yaml
+cache:
+  key: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_SLUG"
+  paths:
+  - binaries/
+```
+
+### `cache:untracked`
+
+Set `untracked: true` to cache all files that are untracked in your Git
+repository:
+
+```yaml
+rspec:
+  script: test
+  cache:
+    untracked: true
+```
+
+Cache all Git untracked files and files in `binaries`:
+
+```yaml
+rspec:
+  script: test
+  cache:
+    untracked: true
+    paths:
+    - binaries/
+```
+
+### `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
 
-```yaml
-deploy as review app:
-  stage: deploy
-  script: make deploy
-  environment:
-    name: review/$CI_COMMIT_REF_NAME
-    url: https://$CI_ENVIRONMENT_SLUG.example.com/
+rspec:
+  stage: test
+  cache:
+    key: gems
+    paths:
+      - vendor/bundle
+    policy: pull
+  script:
+    - bundle exec rspec ...
 ```
 
-The `deploy as review app` job will be marked as deployment to dynamically
-create the `review/$CI_COMMIT_REF_NAME` environment, where `$CI_COMMIT_REF_NAME`
-is an [environment variable][variables] set by the Runner. The
-`$CI_ENVIRONMENT_SLUG` variable is based on the environment name, but suitable
-for inclusion in URLs. In this case, if the `deploy as review app` job was run
-in a branch named `pow`, this environment would be accessible with an URL like
-`https://review-pow.example.com/`.
-
-This of course implies that the underlying server which hosts the application
-is properly configured.
+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.
 
-The common use case is to create dynamic environments for branches and use them
-as Review Apps. You can see a simple example using Review Apps at
-<https://gitlab.com/gitlab-examples/review-apps-nginx/>.
+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.
 
-### artifacts
+## `artifacts`
 
 >
 **Notes:**
 - Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
 - Windows support was added in GitLab Runner v.1.0.0.
-- Prior to GitLab 9.2, caches were restored after artifacts.
 - From GitLab 9.2, caches are restored before artifacts.
-- Currently not all executors are supported.
+- Not all executors are [supported](https://docs.gitlab.com/runner/executors/#compatibility-chart).
 - Job artifacts are only collected for successful jobs by default.
 
 `artifacts` is used to specify a list of files and directories which should be
-attached to the job after success. You can only use paths that are within the
-project workspace. To pass artifacts between different jobs, see [dependencies](#dependencies).
-Below are some examples.
+attached to the job after success.
 
-Send all files in `binaries` and `.config`:
+The artifacts will be sent to GitLab after the job finishes successfully and will
+be available for download in the GitLab UI.
 
-```yaml
-artifacts:
-  paths:
-  - binaries/
-  - .config
-```
+[Read more about artifacts.](../../user/project/pipelines/job_artifacts.md)
 
-Send all Git untracked files:
+### `artifacts:paths`
 
-```yaml
-artifacts:
-  untracked: true
-```
+You can only use paths that are within the project workspace. To pass artifacts
+between different jobs, see [dependencies](#dependencies).
 
-Send all Git untracked files and files in `binaries`:
+Send all files in `binaries` and `.config`:
 
 ```yaml
 artifacts:
-  untracked: true
   paths:
   - binaries/
+  - .config
 ```
 
 To disable artifact passing, define the job with empty [dependencies](#dependencies):
@@ -957,10 +927,7 @@ release-job:
     - tags
 ```
 
-The artifacts will be sent to GitLab after the job finishes successfully and will
-be available for download in the GitLab UI.
-
-#### artifacts:name
+### `artifacts:name`
 
 > Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
 
@@ -970,36 +937,36 @@ useful when you'd like to download the archive from GitLab. The `artifacts:name`
 variable can make use of any of the [predefined variables](../variables/README.md).
 The default name is `artifacts`, which becomes `artifacts.zip` when downloaded.
 
----
-
-**Example configurations**
-
 To create an archive with a name of the current job:
 
 ```yaml
 job:
   artifacts:
     name: "$CI_JOB_NAME"
+    paths:
+    - binaries/
 ```
 
 To create an archive with a name of the current branch or tag including only
-the files that are untracked by Git:
+the binaries directory:
 
 ```yaml
 job:
    artifacts:
      name: "$CI_COMMIT_REF_NAME"
-     untracked: true
+    paths:
+    - binaries/
 ```
 
 To create an archive with a name of the current job and the current branch or
-tag including only the files that are untracked by Git:
+tag including only the binaries directory:
 
 ```yaml
 job:
   artifacts:
-    name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
-    untracked: true
+    name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
+    paths:
+    - binaries/
 ```
 
 To create an archive with a name of the current [stage](#stages) and branch name:
@@ -1007,8 +974,9 @@ To create an archive with a name of the current [stage](#stages) and branch name
 ```yaml
 job:
   artifacts:
-    name: "${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}"
-    untracked: true
+    name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME"
+    paths:
+    - binaries/
 ```
 
 ---
@@ -1019,8 +987,9 @@ If you use **Windows Batch** to run your shell scripts you need to replace
 ```yaml
 job:
   artifacts:
-    name: "%CI_JOB_STAGE%_%CI_COMMIT_REF_NAME%"
-    untracked: true
+    name: "%CI_JOB_STAGE%-%CI_COMMIT_REF_NAME%"
+    paths:
+    - binaries/
 ```
 
 If you use **Windows PowerShell** to run your shell scripts you need to replace
@@ -1029,11 +998,37 @@ If you use **Windows PowerShell** to run your shell scripts you need to replace
 ```yaml
 job:
   artifacts:
-    name: "$env:CI_JOB_STAGE_$env:CI_COMMIT_REF_NAME"
-    untracked: true
+    name: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_NAME"
+    paths:
+    - binaries/
+```
+
+### `artifacts:untracked`
+
+`artifacts:untracked` is used to add all Git untracked files as artifacts (along
+to the paths defined in `artifacts:paths`).
+
+NOTE: **Note:**
+To exclude the folders/files which should not be a part of `untracked` just
+add them to `.gitignore`.
+
+Send all Git untracked files:
+
+```yaml
+artifacts:
+  untracked: true
+```
+
+Send all Git untracked files and files in `binaries`:
+
+```yaml
+artifacts:
+  untracked: true
+  paths:
+  - binaries/
 ```
 
-#### artifacts:when
+### `artifacts:when`
 
 > Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
 
@@ -1046,11 +1041,7 @@ failure.
 1. `on_failure` - upload artifacts only when the job fails.
 1. `always` - upload artifacts regardless of the job status.
 
----
-
-**Example configurations**
-
-To upload artifacts only when job fails.
+To upload artifacts only when job fails:
 
 ```yaml
 job:
@@ -1058,22 +1049,23 @@ job:
     when: on_failure
 ```
 
-#### artifacts:expire_in
+### `artifacts:expire_in`
 
 > Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
 
-`artifacts:expire_in` is used to delete uploaded artifacts after the specified
-time. By default, artifacts are stored on GitLab forever. `expire_in` allows you
-to specify how long artifacts should live before they expire, counting from the
-time they are uploaded and stored on GitLab.
+`expire_in` allows you to specify how long artifacts should live before they
+expire and therefore deleted, counting from the time they are uploaded and
+stored on GitLab. If the expiry time is not defined, it defaults to the
+[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration)
+(30 days by default, forever on GitLab.com).
 
 You can use the **Keep** button on the job page to override expiration and
 keep artifacts forever.
 
-After expiry, artifacts are actually deleted hourly by default (via a cron job),
-but they are not accessible after expiry.
+After their expiry, artifacts are deleted hourly by default (via a cron job),
+and are not accessible anymore.
 
-The value of `expire_in` is an elapsed time. Examples of parseable values:
+The value of `expire_in` is an elapsed time. Examples of parsable values:
 
 - '3 mins 4 sec'
 - '2 hrs 20 min'
@@ -1082,10 +1074,6 @@ The value of `expire_in` is an elapsed time. Examples of parseable values:
 - '47 yrs 6 mos and 4d'
 - '3 weeks and 2 days'
 
----
-
-**Example configurations**
-
 To expire artifacts 1 week after being uploaded:
 
 ```yaml
@@ -1094,7 +1082,7 @@ job:
     expire_in: 1 week
 ```
 
-### dependencies
+## `dependencies`
 
 > Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
@@ -1153,7 +1141,7 @@ deploy:
   script: make deploy
 ```
 
-#### When a dependent job will fail
+### When a dependent job will fail
 
 > Introduced in GitLab 10.3.
 
@@ -1167,27 +1155,9 @@ You can ask your administrator to
 [flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
 and bring back the old behavior.
 
-### before_script and after_script
+## `coverage`
 
-It's possible to overwrite the globally defined `before_script` and `after_script`:
-
-```yaml
-before_script:
-- global before script
-
-job:
-  before_script:
-  - execute this instead of global before script
-  script:
-  - my command
-  after_script:
-  - execute this after my script
-```
-
-### coverage
-
-**Notes:**
-- [Introduced][ce-7447] in GitLab 8.17.
+> [Introduced][ce-7447] in GitLab 8.17.
 
 `coverage` allows you to configure how code coverage will be extracted from the
 job output.
@@ -1205,10 +1175,9 @@ job1:
   coverage: '/Code coverage: \d+\.\d+/'
 ```
 
-### retry
+## `retry`
 
-**Notes:**
-- [Introduced][ce-3442] in GitLab 9.5.
+> [Introduced][ce-12909] in GitLab 9.5.
 
 `retry` allows you to configure how many times a job is going to be retried in
 case of a failure.
@@ -1228,16 +1197,57 @@ test:
   retry: 2
 ```
 
-## Git Strategy
+## `variables`
+
+> Introduced in GitLab Runner v0.5.0.
+
+NOTE: **Note:**
+Integers (as well as strings) are legal both for variable's name and value.
+Floats are not legal and cannot be used.
+
+GitLab CI/CD allows you to define variables inside `.gitlab-ci.yml` that are
+then passed in the job environment. They can be set globally and per-job.
+When the `variables` keyword is used on a job level, it overrides the global
+YAML variables and predefined ones.
+
+They are stored in the Git repository and are meant to store non-sensitive
+project configuration, for example:
+
+```yaml
+variables:
+  DATABASE_URL: "postgres://postgres@postgres/my_database"
+```
+
+These variables can be later used in all executed commands and scripts.
+The YAML-defined variables are also set to all created service containers,
+thus allowing to fine tune them.
+
+To turn off global defined variables in a specific job, define an empty hash:
+
+```yaml
+job_name:
+  variables: {}
+```
+
+Except for the user defined variables, there are also the ones [set up by the
+Runner itself](../variables/README.md#predefined-variables-environment-variables).
+One example would be `CI_COMMIT_REF_NAME` which has the value of
+the branch or tag name for which project is built. Apart from the variables
+you can set in `.gitlab-ci.yml`, there are also the so called
+[secret variables](../variables/README.md#secret-variables)
+which can be set in GitLab's UI.
+
+[Learn more about variables and their priority.][variables]
+
+### Git strategy
 
 > Introduced in GitLab 8.9 as an experimental feature.  May change or be removed
   completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
   v1.7+.
 
 You can set the `GIT_STRATEGY` used for getting recent application code, either
-in the global [`variables`](#variables) section or the [`variables`](#job-variables)
-section for individual jobs. If left unspecified, the default from project
-settings will be used.
+globally or per-job in the [`variables`](#variables) section. If left
+unspecified, the default from project settings will be used.
 
 There are three possible values: `clone`, `fetch`, and `none`.
 
@@ -1269,44 +1279,13 @@ variables:
   GIT_STRATEGY: none
 ```
 
-## Git Checkout
-
-> Introduced in GitLab Runner 9.3
-
-The `GIT_CHECKOUT` variable can be used when the `GIT_STRATEGY` is set to either
-`clone` or `fetch` to specify whether a `git checkout` should be run. If not
-specified, it defaults to true. Like `GIT_STRATEGY`, it can be set in either the
-global [`variables`](#variables) section or the [`variables`](#job-variables)
-section for individual jobs.
-
-If set to `false`, the Runner will:
-
-- when doing `fetch` - update the repository and leave working copy on
-  the current revision,
-- when doing `clone` - clone the repository and leave working copy on the
-  default branch.
-
-Having this setting set to `true` will mean that for both `clone` and `fetch`
-strategies the Runner will checkout the working copy to a revision related
-to the CI pipeline:
-
-```yaml
-variables:
-  GIT_STRATEGY: clone
-  GIT_CHECKOUT: "false"
-script:
-  - git checkout master
-  - git merge $CI_BUILD_REF_NAME
-```
-
-## Git Submodule Strategy
+### Git submodule strategy
 
 > Requires GitLab Runner v1.10+.
 
 The `GIT_SUBMODULE_STRATEGY` variable is used to control if / how Git
-submodules are included when fetching the code before a build. Like
-`GIT_STRATEGY`, it can be set in either the global [`variables`](#variables)
-section or the [`variables`](#job-variables) section for individual jobs.
+submodules are included when fetching the code before a build. You can set them
+globally or per-job in the [`variables`](#variables) section.
 
 There are three possible values: `none`, `normal`, and `recursive`:
 
@@ -1336,8 +1315,36 @@ Note that for this feature to work correctly, the submodules must be configured
 - a relative path to another repository on the same GitLab server. See the
   [Git submodules](../git_submodules.md) documentation.
 
+### Git checkout
+
+> Introduced in GitLab Runner 9.3
+
+The `GIT_CHECKOUT` variable can be used when the `GIT_STRATEGY` is set to either
+`clone` or `fetch` to specify whether a `git checkout` should be run. If not
+specified, it defaults to true. You can set them globally or per-job in the
+[`variables`](#variables) section.
+
+If set to `false`, the Runner will:
+
+- when doing `fetch` - update the repository and leave working copy on
+  the current revision,
+- when doing `clone` - clone the repository and leave working copy on the
+  default branch.
+
+Having this setting set to `true` will mean that for both `clone` and `fetch`
+strategies the Runner will checkout the working copy to a revision related
+to the CI pipeline:
+
+```yaml
+variables:
+  GIT_STRATEGY: clone
+  GIT_CHECKOUT: "false"
+script:
+  - git checkout master
+  - git merge $CI_BUILD_REF_NAME
+```
 
-## Job stages attempts
+### Job stages attempts
 
 > Introduced in GitLab, it requires GitLab Runner v1.9+.
 
@@ -1359,10 +1366,9 @@ variables:
   GET_SOURCES_ATTEMPTS: 3
 ```
 
-You can set them in the global [`variables`](#variables) section or the
-[`variables`](#job-variables) section for individual jobs.
+You can set them globally or per-job in the [`variables`](#variables) section.
 
-## Shallow cloning
+### Shallow cloning
 
 > Introduced in GitLab 8.9 as an experimental feature. May change in future
 releases or be removed completely.
@@ -1393,7 +1399,17 @@ variables:
   GIT_DEPTH: "3"
 ```
 
-## Hidden keys (jobs)
+You can set it globally or per-job in the [`variables`](#variables) section.
+
+## Special YAML features
+
+It's possible to use special YAML features like anchors (`&`), aliases (`*`)
+and map merging (`<<`), which will allow you to greatly reduce the complexity
+of `.gitlab-ci.yml`.
+
+Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/).
+
+### Hidden keys (jobs)
 
 > Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
@@ -1419,14 +1435,6 @@ Use this feature to ignore jobs, or use the
 [special YAML features](#special-yaml-features) and transform the hidden keys
 into templates.
 
-## Special YAML features
-
-It's possible to use special YAML features like anchors (`&`), aliases (`*`)
-and map merging (`<<`), which will allow you to greatly reduce the complexity
-of `.gitlab-ci.yml`.
-
-Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/).
-
 ### Anchors
 
 > Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
@@ -1556,50 +1564,22 @@ with an API call.
 
 [Read more in the triggers documentation.](../triggers/README.md)
 
-### pages
-
-`pages` is a special job that is used to upload static content to GitLab that
-can be used to serve your website. It has a special syntax, so the two
-requirements below must be met:
-
-1. Any static content must be placed under a `public/` directory
-1. `artifacts` with a path to the `public/` directory must be defined
-
-The example below simply moves all files from the root of the project to the
-`public/` directory. The `.public` workaround is so `cp` doesn't also copy
-`public/` to itself in an infinite loop:
-
-```
-pages:
-  stage: deploy
-  script:
-  - mkdir .public
-  - cp -r * .public
-  - mv .public public
-  artifacts:
-    paths:
-    - public
-  only:
-  - master
-```
+## Skipping jobs
 
-Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
+If your commit message contains `[ci skip]` or `[skip ci]`, using any
+capitalization, the commit will be created but the pipeline will be skipped.
 
 ## Validate the .gitlab-ci.yml
 
-Each instance of GitLab CI has an embedded debug tool called Lint.
-You can find the link under `/ci/lint` of your gitlab instance.
+Each instance of GitLab CI has an embedded debug tool called Lint, which validates the
+content of your `.gitlab-ci.yml` files. You can find the Lint under the page `ci/lint` of your 
+project namespace (e.g, `http://gitlab-example.com/gitlab-org/project-123/ci/lint`)
 
 ## Using reserved keywords
 
 If you get validation error when using specific values (e.g., `true` or `false`),
 try to quote them, or change them to a different form (e.g., `/bin/true`).
 
-## Skipping jobs
-
-If your commit message contains `[ci skip]` or `[skip ci]`, using any
-capitalization, the commit will be created but the jobs will be skipped.
-
 ## Examples
 
 Visit the [examples README][examples] to see a list of examples using GitLab
@@ -1613,5 +1593,6 @@ CI with various languages.
 [variables]: ../variables/README.md
 [ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
 [ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
-[ce-3442]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3442
+[ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909
 [schedules]: ../../user/project/pipelines/schedules.md
+[variables-expressions]: ../variables/README.md#variables-expressions
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index cf6314f95211083d9e3c6bfd11744279107e6d9a..a85e5b1b1cc394f9bb0b210f4377faa88a83fd6a 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -103,6 +103,99 @@ Notes:
 - You can use [`git rerere`](https://git-scm.com/blog/2010/03/08/rerere.html)
   to avoid resolving the same conflicts multiple times.
 
+### Cherry-picking from CE to EE
+
+For avoiding merge conflicts, we use a method of creating equivalent branches
+for CE and EE. If the `ee-compat-check` job fails, this process is required.
+
+This method only requires that you have cloned both CE and EE into your computer.
+If you don't have them yet, please go ahead and clone them:
+
+- Clone CE repo: `git clone git@gitlab.com:gitlab-org/gitlab-ce.git`
+- Clone EE repo: `git clone git@gitlab.com:gitlab-org/gitlab-ee.git`
+
+And the only additional setup we need is to add CE as remote of EE and vice-versa:
+
+- Open two terminal windows, one in CE, and another one in EE:
+  - In EE: `git remote add ce git@gitlab.com:gitlab-org/gitlab-ce.git`
+  - In CE: `git remote add ee git@gitlab.com:gitlab-org/gitlab-ee.git`
+
+That's all setup we need, so that we can cherry-pick a commit from CE to EE, and
+from EE to CE.
+
+Now, every time you create an MR for CE and EE:
+
+1. Open two terminal windows, one in CE, and another one in EE
+1. In the CE terminal:
+  1. Create the CE branch, e.g., `branch-example`
+  1. Make your changes and push a commit (commit A)
+  1. Create the CE merge request in GitLab
+1. In the EE terminal:
+  1. Create the EE-equivalent branch ending with `-ee`, e.g.,
+  `git checkout -b branch-example-ee`
+  1. Fetch the CE branch: `git fetch ce branch-example`
+  1. Cherry-pick the commit A: `git cherry-pick commit-A-SHA`
+  1. If Git prompts you to fix the conflicts, do a `git status`
+  to check which files contain conflicts, fix them, save the files
+  1. Add the changes with `git add .` but **DO NOT commit** them
+  1. Continue cherry-picking: `git cherry-pick --continue`
+  1. Push to EE: `git push origin branch-example-ee`
+1. Create the EE-equivalent MR and link to the CE MR from the
+description "Ports [CE-MR-LINK] to EE"
+1. Once all the jobs are passing in both CE and EE, you've addressed the
+feedback from your own team, and got them approved, the merge requests can be merged.
+1. When both MRs are ready, the EE merge request will be merged first, and the
+CE-equivalent will be merged next.
+
+**Important notes:**
+
+- The commit SHA can be easily found from the GitLab UI. From a merge request,
+open the tab **Commits** and click the copy icon to copy the commit SHA.
+- To cherry-pick a **commit range**, such as [A > B > C > D] use:
+
+    ```shell
+    git cherry-pick "oldest-commit-SHA^..newest-commit-SHA"
+    ```
+
+    For example, suppose the commit A is the oldest, and its SHA is `4f5e4018c09ed797fdf446b3752f82e46f5af502`,
+    and the commit D is the newest, and its SHA is `80e1c9e56783bd57bd7129828ec20b252ebc0538`.
+    The cherry-pick command will be:
+
+    ```shell
+    git cherry-pick "4f5e4018c09ed797fdf446b3752f82e46f5af502^..80e1c9e56783bd57bd7129828ec20b252ebc0538"
+    ```
+
+- To cherry-pick a **merge commit**, use the flag `-m 1`. For example, suppose that the
+merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`:
+
+    ```shell
+    git cherry-pick -m 1 138f5e2f20289bb376caffa0303adb0cac859ce1
+    ```
+- To cherry-pick multiple commits, such as B and D in a range [A > B > C > D], use:
+
+    ```shell
+    git cherry-pick commmit-B-SHA commit-D-SHA
+    ```
+
+    For example, suppose commit B SHA = `4f5e4018c09ed797fdf446b3752f82e46f5af502`,
+    and the commit D SHA = `80e1c9e56783bd57bd7129828ec20b252ebc0538`.
+    The cherry-pick command will be:
+
+    ```shell
+    git cherry-pick 4f5e4018c09ed797fdf446b3752f82e46f5af502 80e1c9e56783bd57bd7129828ec20b252ebc0538
+    ```
+
+    This case is particularly useful when you have a merge commit in a sequence of
+    commits and you want to cherry-pick all but the merge commit.
+
+- If you push more commits to the CE branch, you can safely repeat the procedure
+to cherry-pick them to the EE-equivalent branch. You can do that as many times as
+necessary, using the same CE and EE branches.
+- If you submitted the merge request to the CE repo and the `ee-compat-check` job passed,
+you are not required to submit the EE-equivalent MR, but it's still recommended. If the
+job failed, you are required to submit the EE MR so that you can fix the conflicts in EE
+before merging your changes into CE.
+
 ---
 
 [Return to Development documentation](README.md)
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index fc1b202b5eb103606d2baa86fe436a6a8db6f0d2..ce69694ab6ad09345143bb3b56c0e83083b7431e 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -133,11 +133,19 @@ roughly be as follows:
 1. Release B:
   1. Deploy code so that the application starts using the new column and stops
      scheduling jobs for newly created data.
-  1. In a post-deployment migration you'll need to ensure no jobs remain. To do
-     so you can use `Gitlab::BackgroundMigration.steal` to process any remaining
-     jobs before continuing.
+  1. In a post-deployment migration you'll need to ensure no jobs remain.
+     1. Use `Gitlab::BackgroundMigration.steal` to process any remaining
+        jobs in Sidekiq.
+     1. Reschedule the migration to be run directly (i.e. not through Sidekiq)
+        on any rows that weren't migrated by Sidekiq. This can happen if, for
+        instance, Sidekiq received a SIGKILL, or if a particular batch failed
+        enough times to be marked as dead.
   1. Remove the old column.
 
+This may also require a bump to the [import/export version][import-export], if
+importing a project from a prior version of GitLab requires the data to be in
+the new format.
+
 ## Example
 
 To explain all this, let's use the following example: the table `services` has a
@@ -296,3 +304,4 @@ for more details.
 [migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md
 [issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351
 [reliable-sidekiq]: https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
+[import-export]: ../user/project/settings/import_export.md
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 1962392a9ebb9c8a4d5ebd3694a6247dced69563..d5a4acff4816edfe8ded220be674e03cd8923768 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -44,6 +44,7 @@ the `author` field. GitLab team members **should not**.
 - _Any_ contribution from a community member, no matter how small, **may** have
   a changelog entry regardless of these guidelines if the contributor wants one.
   Example: "Fixed a typo on the search results page. (Jane Smith)"
+- Performance improvements **should** have a changelog entry.
 
 ## Writing good changelog entries
 
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index 50eb8005b446d1d88d29c891f4b16fa0a2a48c10..32f392f130374917dff7056564dc897312d6353b 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -53,3 +53,38 @@ bundle exec rails db RAILS_ENV=development
  - `CREATE TABLE board_labels();`: Create a table called `board_labels`
  - `SELECT * FROM schema_migrations WHERE version = '20170926203418';`: Check if a migration was run
  - `DELETE FROM schema_migrations WHERE version = '20170926203418';`: Manually remove a migration
+
+
+## FAQ
+
+### `ActiveRecord::PendingMigrationError` with Spring
+
+When running specs with the [Spring preloader](./rake_tasks.md#speed-up-tests-rake-tasks-and-migrations),
+the test database can get into a corrupted state. Trying to run the migration or
+dropping/resetting the test database has no effect.
+
+```sh
+$ bundle exec spring rspec some_spec.rb
+...
+Failure/Error: ActiveRecord::Migration.maintain_test_schema!
+
+ActiveRecord::PendingMigrationError:
+
+
+  Migrations are pending. To resolve this issue, run:
+
+    bin/rake db:migrate RAILS_ENV=test
+# ~/.rvm/gems/ruby-2.3.3/gems/activerecord-4.2.10/lib/active_record/migration.rb:392:in `check_pending!'
+...
+0 examples, 0 failures, 1 error occurred outside of examples
+```
+
+To resolve, you can kill the spring server and app that lives between spec runs.
+
+```sh
+$ ps aux | grep spring
+eric             87304   1.3  2.9  3080836 482596   ??  Ss   10:12AM   4:08.36 spring app    | gitlab | started 6 hours ago | test mode
+eric             37709   0.0  0.0  2518640   7524 s006  S    Wed11AM   0:00.79 spring server | gitlab | started 29 hours ago
+$ kill 87304
+$ kill 37709
+```
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 6fe5f647d6c6a48038fba135cf20094633d488ec..0550ea527cbd02394f615f7eee863949c0968e6e 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -1,89 +1,28 @@
-# Documentation styleguide
+# Documentation style guidelines
 
-This styleguide recommends best practices to improve documentation and to keep
-it organized and easy to find.
+The documentation style guide defines the markup structure used in
+GitLab documentation. Check the
+[documentation guidelines](writing_documentation.md) for general development instructions.
 
-See also [writing documentation](writing_documentation.md).
-
-## Location and naming of documents
-
->**Note:**
-These guidelines derive from the discussion taken place in issue [#3349][ce-3349].
-
-The documentation hierarchy can be vastly improved by providing a better layout
-and organization of directories.
-
-Having a structured document layout, we will be able to have meaningful URLs
-like `docs.gitlab.com/user/project/merge_requests.html`. With this pattern,
-you can immediately tell that you are navigating a user related documentation
-and is about the project and its merge requests.
-
-Do not create summaries of similar types of content (e.g. an index of all articles, videos, etc.),
-rather organise content by its subject (e.g. everything related to CI goes together)
-and cross-link between any related content.
-
-The table below shows what kind of documentation goes where.
-
-| Directory | What belongs here |
-| --------- | -------------- |
-| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. |
-| `doc/administration/`  | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. |
-| `doc/api/` | API related documentation. |
-| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. |
-| `doc/legal/` | Legal documents about contributing to GitLab. |
-| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
-| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
-| `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) |
-
----
-
-**General rules:**
-
-1. The correct naming and location of a new document, is a combination
-   of the relative URL of the document in question and the GitLab Map design
-   that is used for UX purposes ([source][graffle], [image][gitlab-map]).
-1. When creating a new document and it has more than one word in its name,
-   make sure to use underscores instead of spaces or dashes (`-`). For example,
-   a proper naming would be `import_projects_from_github.md`. The same rule
-   applies to images.
-1. There are four main directories, `user`, `administration`, `api` and `development`.
-1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
-   `profile/`, `dashboard/` and `admin_area/`.
-   1. `doc/user/project/` should contain all project related documentation.
-   1. `doc/user/group/` should contain all group related documentation.
-   1. `doc/user/profile/` should contain all profile related documentation.
-      Every page you would navigate under `/profile` should have its own document,
-      i.e. `account.md`, `applications.md`, `emails.md`, etc.
-   1. `doc/user/dashboard/` should contain all dashboard related documentation.
-   1. `doc/user/admin_area/` should contain all admin related documentation
-      describing what can be achieved by accessing GitLab's admin interface
-      (_not to be confused with `doc/administration` where server access is
-      required_).
-      1. Every category under `/admin/application_settings` should have its
-         own document located at `doc/user/admin_area/settings/`. For example,
-         the **Visibility and Access Controls** category should have a document
-         located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
-1. The `doc/topics/` directory holds topic-related technical content. Create
-   `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary.
-   General user- and admin- related documentation, should be placed accordingly.
-
----
-
-If you are unsure where a document should live, you can ping `@axil` or `@marcia` in your
-merge request.
+Check the GitLab hanbook for the [writing styles guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines).
 
 ## Text
 
-- Split up long lines, this makes it much easier to review and edit. Only
+- Split up long lines (wrap text), this makes it much easier to review and edit. Only
   double line breaks are shown as a full line break in [GitLab markdown][gfm].
   80-100 characters is a good line length
-- Make sure that the documentation is added in the correct directory and that
+- Make sure that the documentation is added in the correct
+  [directory](writing_documentation.md#documentation-directory-structure) and that
   there's a link to it somewhere useful
 - Do not duplicate information
 - Be brief and clear
 - Unless there's a logical reason not to, add documents in alphabetical order
 - Write in US English
 - Use [single spaces][] instead of double spaces
+- Jump a line between different markups (e.g., after every paragraph, hearder, list, etc)
+- Capitalize "G" and "L" in GitLab
+- Capitalize feature, products, and methods names. E.g.: GitLab Runner, Geo,
+Issue Boards, Git, Prometheus, Continuous Integration.
 
 ## Formatting
 
@@ -103,6 +42,8 @@ merge request.
   links shift too, which eventually leads to dead links. If you think it is
   compelling to add numbers in headings, make sure to at least discuss it with
   someone in the Merge Request
+- [Avoid using symbols and special chars](https://gitlab.com/gitlab-com/gitlab-docs/issues/84)
+  in headers. Whenever possible, they should be plain and short text.
 - Avoid adding things that show ephemeral statuses. For example, if a feature is
   considered beta or experimental, put this info in a note, not in the heading.
 - When introducing a new document, be careful for the headings to be
@@ -121,71 +62,18 @@ merge request.
   you can use `[Text][identifier]` and at the bottom of the section or the
   document add: `[identifier]: https://example.com`, in which case, we do
   encourage you to also add an alternative text: `[identifier]: https://example.com "Alternative text"` that appears when hovering your mouse on a link.
-
-### Linking to inline docs
-
-Sometimes it's needed to link to the built-in documentation that GitLab provides
-under `/help`. This is normally done in files inside the `app/views/` directory
-with the help of the `help_page_path` helper method.
-
-In its simplest form, the HAML code to generate a link to the `/help` page is:
-
-```haml
-= link_to 'Help page', help_page_path('user/permissions')
-```
-
-The `help_page_path` contains the path to the document you want to link to with
-the following conventions:
-
-- it is relative to the `doc/` directory in the GitLab repository
-- the `.md` extension must be omitted
-- it must not end with a slash (`/`)
-
-Below are some special cases where should be used depending on the context.
-You can combine one or more of the following:
-
-1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
-   method:
-
-    ```haml
-    = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
-    ```
-
-1. **Opening links in a new tab.** This should be the default behavior:
-
-    ```haml
-    = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
-    ```
-
-1. **Linking to a circle icon.** Usually used in settings where a long
-   description cannot be used, like near checkboxes. You can basically use
-   any font awesome icon, but prefer the `question-circle`:
-
-    ```haml
-    = link_to icon('question-circle'), help_page_path('user/permissions')
-    ```
-
-1. **Using a button link.** Useful in places where text would be out of context
-   with the rest of the page layout:
-
-    ```haml
-    = link_to 'Help page', help_page_path('user/permissions'),  class: 'btn btn-info'
-    ```
-
-1. **Using links inline of some text.**
-
-    ```haml
-    Description to #{link_to 'Help page', help_page_path('user/permissions')}.
-    ```
-
-1. **Adding a period at the end of the sentence.** Useful when you don't want
-   the period to be part of the link:
-
-    ```haml
-    = succeed '.' do
-      Learn more in the
-      = link_to 'Help page', help_page_path('user/permissions')
-    ```
+- To link to internal documentation, use relative links, not full URLs. Use `../` to
+  navigate tp high-level directories, and always add the file name `file.md` at the
+  end of the link with the `.md` extension, not `.html`.
+  Example: instead of `[text](../../merge_requests/)`, use
+  `[text](../../merge_requests/index.md)` or, `[text](../../ci/README.md)`, or,
+  for anchor links, `[text](../../ci/README.md#examples)`.
+  Using the markdown extension is necessary for the [`/help`](writing_documentation.md#gitlab-help)
+  section of GitLab.
+- To link from CE to EE-only documentation, use the EE-only doc full URL.
+- Use [meaningful anchor texts](https://www.futurehosting.com/blog/links-should-have-meaningful-anchor-text-heres-why/).
+  E.g., instead of writing something like `Read more about GitLab Issue Boards [here](LINK)`,
+  write `Read more about [GitLab Issue Boards](LINK)`.
 
 ## Images
 
@@ -222,7 +110,7 @@ Inside the document:
 
 - Notes should be quoted with the word `Note:` being bold. Use this form:
 
-    ```
+    ```md
     >**Note:**
     This is something to note.
     ```
@@ -234,25 +122,25 @@ Inside the document:
 
     If the note spans across multiple lines it's OK to split the line.
 
-## New features
+## Specific sections and terms
 
-New features must be shipped with its accompanying documentation and the doc
-reviewed by a technical writer.
+To mention and/or reference specific terms in GitLab, please follow the styles
+below.
 
-### Mentioning GitLab versions and tiers
+### GitLab versions and tiers
 
 - Every piece of documentation that comes with a new feature should declare the
   GitLab version that feature got introduced. Right below the heading add a
   note:
 
-    ```
+    ```md
     > Introduced in GitLab 8.3.
     ```
 
 - If possible every feature should have a link to the MR, issue, or epic that introduced it.
   The above note would be then transformed to:
 
-    ```
+    ```md
     > [Introduced][ce-1242] in GitLab 8.3.
     ```
 
@@ -263,121 +151,74 @@ reviewed by a technical writer.
   the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
   the feature is available in:
 
-    ```
+    ```md
     > [Introduced][ee-1234] in [GitLab Starter](https://about.gitlab.com/products/) 8.3.
     ```
 
     Otherwise, leave this mention out.
 
-## References
-
-- **GitLab Restart:**
-  There are many cases that a restart/reconfigure of GitLab is required. To
-  avoid duplication, link to the special document that can be found in
-  [`doc/administration/restart_gitlab.md`][doc-restart]. Usually the text will
-  read like:
-
-    ```
-    Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
-    for the changes to take effect.
-    ```
-  If the document you are editing resides in a place other than the GitLab CE/EE
-  `doc/` directory, instead of the relative link, use the full path:
-  `http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
-  Replace `reconfigure` with `restart` where appropriate.
-
-## Installation guide
-
-- **Ruby:**
-  In [step 2 of the installation guide](../install/installation.md#2-ruby),
-  we install Ruby from source. Whenever there is a new version that needs to
-  be updated, remember to change it throughout the codeblock and also replace
-  the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
-  website).
-
-[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
-
-## Changing document location
+### Product badges
 
-Changing a document's location is not to be taken lightly. Remember that the
-documentation is available to all installations under `help/` and not only to
-GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the
-Documentation team beforehand.
+When a feature is available in EE-only tiers, add the corresponding tier according to the
+feature availability:
 
-If you indeed need to change a document's location, do NOT remove the old
-document, but rather replace all of its contents with a new line:
+- For GitLab Starter and GitLab.com Bronze: `**[STARTER]**`
+- For GitLab Premium and GitLab.com Silver: `**[PREMIUM]**`
+- For GitLab Ultimate and GitLab.com Gold: `**[ULTIMATE]**`
+- For GitLab Core and GitLab.com Free: `**[CORE]**`
 
-```
-This document was moved to [another location](path/to/new_doc.md).
-```
+To exclude GitLab.com tiers (when the feature is not available in GitLab.com), add the
+keyword "only":
 
-where `path/to/new_doc.md` is the relative path to the root directory `doc/`.
+- For GitLab Starter: `**[STARTER ONLY]**`
+- For GitLab Premium: `**[PREMIUM ONLY]**`
+- For GitLab Ultimate: `**[ULTIMATE ONLY]**`
+- For GitLab Core: `**[CORE ONLY]**`
 
----
+The tier should be ideally added to headers, so that the full badge will be displayed.
+But it can be also mentioned from paragraphs, list items, and table cells. For these cases,
+the tier mention will be represented by an orange question mark.
+E.g., `**[STARTER]**` renders **[STARTER]**, `**[STARTER ONLY]**` renders **[STARTER ONLY]**.
 
-For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
-`doc/administration/lfs.md`, then the steps would be:
+The absence of tiers' mentions mean that the feature is available in GitLab Core,
+GitLab.com Free, and higher tiers.
 
-1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
-1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
+#### How it works
 
-    ```
-    This document was moved to [another location](../../administration/lfs.md).
-    ```
-
-1. Find and replace any occurrences of the old location with the new one.
-   A quick way to find them is to use `git grep`. First go to the root directory
-   where you cloned the `gitlab-ce` repository and then do:
-
-    ```
-    git grep -n "workflow/lfs/lfs_administration"
-    git grep -n "lfs/lfs_administration"
-    ```
+Introduced by [!244](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/244),
+the special markup `**[STARTER]**` will generate a `span` element to trigger the
+badges and tooltips (`<span class="badge-trigger starter">`). When the keyword
+"only" is added, the corresponding GitLab.com badge will not be displayed.
 
-NOTE: **Note:**
-If the document being moved has any Disqus comments on it, there are extra steps
-to follow documented just [below](#redirections-for-pages-with-disqus-comments).
+### GitLab Restart
 
-Things to note:
+There are many cases that a restart/reconfigure of GitLab is required. To
+avoid duplication, link to the special document that can be found in
+[`doc/administration/restart_gitlab.md`][doc-restart]. Usually the text will
+read like:
 
-- Since we also use inline documentation, except for the documentation itself,
-  the document might also be referenced in the views of GitLab (`app/`) which will
-  render when visiting `/help`, and sometimes in the testing suite (`spec/`).
-- The above `git grep` command will search recursively in the directory you run
-  it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration`
-  and will print the file and the line where this file is mentioned.
-  You may ask why the two greps. Since we use relative paths to link to
-  documentation, sometimes it might be useful to search a path deeper.
-- The `*.md` extension is not used when a document is linked to GitLab's
-  built-in help page, that's why we omit it in `git grep`.
-- Use the checklist on the documentation MR description template.
+  ```
+  Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
+  for the changes to take effect.
+  ```
 
-### Redirections for pages with Disqus comments
+If the document you are editing resides in a place other than the GitLab CE/EE
+`doc/` directory, instead of the relative link, use the full path:
+`http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
+Replace `reconfigure` with `restart` where appropriate.
 
-If the documentation page being relocated already has any Disqus comments,
-we need to preserve the Disqus thread.
+### Installation guide
 
-Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier
-is configured to be the page URL. Therefore, when we change the document location,
-we need to preserve the old URL as the same Disqus identifier.
+**Ruby:**
+In [step 2 of the installation guide](../install/installation.md#2-ruby),
+we install Ruby from source. Whenever there is a new version that needs to
+be updated, remember to change it throughout the codeblock and also replace
+the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
+website).
 
-To do that, add to the frontmatter the variable `redirect_from`,
-using the old URL as value. For example, let's say I moved the document
-available under `https://docs.gitlab.com/my-old-location/README.html` to a new location,
-`https://docs.gitlab.com/my-new-location/index.html`.
-
-Into the **new document** frontmatter add the following:
-
-```yaml
----
-redirect_from: 'https://docs.gitlab.com/my-old-location/README.html'
----
-```
-
-Note: it is necessary to include the file name in the `redirect_from` URL,
-even if it's `index.html` or `README.html`.
+[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
 
-## Configuration documentation for source and Omnibus installations
+### Configuration documentation for source and Omnibus installations
 
 GitLab currently officially supports two installation methods: installations
 from source and Omnibus packages installations.
@@ -394,7 +235,7 @@ When there is a list of steps to perform, usually that entails editing the
 configuration file and reconfiguring/restarting GitLab. In such case, follow
 the style below as a guide:
 
-````
+```md
 **For Omnibus installations**
 
 1. Edit `/etc/gitlab/gitlab.rb`:
@@ -421,7 +262,7 @@ the style below as a guide:
 
 [reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure
 [restart]: path/to/administration/restart_gitlab.md#installations-from-source
-````
+```
 
 In this case:
 
@@ -433,7 +274,7 @@ In this case:
 - different highlighting languages are used for each config in the code block
 - the [references](#references) guide is used for reconfigure/restart
 
-## Fake tokens
+### Fake tokens
 
 There may be times where a token is needed to demonstrate an API call using
 cURL or a secret variable used in CI. It is strongly advised not to use real
@@ -456,7 +297,7 @@ You can use the following fake tokens as examples.
 | Health check token    | `Tu7BgjR9qeZTEyRzGG2P`            |
 | Request profile token | `7VgpS4Ax5utVD2esNstz`            |
 
-## API
+### API
 
 Here is a list of must-have items. Use them in the exact order that appears
 on this document. Further explanation is given below.
@@ -472,10 +313,10 @@ on this document. Further explanation is given below.
 - Every method must have a cURL example.
 - Every method must have a response body (in JSON format).
 
-### Method description
+#### Method description
 
 Use the following table headers to describe the methods. Attributes should
-always be in code blocks using backticks (`).
+always be in code blocks using backticks (``` ` ```).
 
 ```
 | Attribute | Type | Required | Description |
@@ -488,7 +329,7 @@ Rendered example:
 | --------- | ---- | -------- | ----------- |
 | `user`  | string | yes | The GitLab username |
 
-### cURL commands
+#### cURL commands
 
 - Use `https://gitlab.example.com/api/v4/` as an endpoint.
 - Wherever needed use this personal access token: `9koXpg98eAheJpvBs5tK`.
@@ -505,11 +346,11 @@ Rendered example:
 | `-X PUT`    | Use this method when updating existing objects |
 | `-X DELETE` | Use this method when removing existing objects |
 
-### cURL Examples
+#### cURL Examples
 
 Below is a set of [cURL][] examples that you can use in the API documentation.
 
-#### Simple cURL command
+##### Simple cURL command
 
 Get the details of a group:
 
@@ -517,7 +358,7 @@ Get the details of a group:
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/gitlab-org
 ```
 
-#### cURL example with parameters passed in the URL
+##### cURL example with parameters passed in the URL
 
 Create a new project under the authenticated user's namespace:
 
@@ -525,7 +366,7 @@ Create a new project under the authenticated user's namespace:
 curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects?name=foo"
 ```
 
-#### Post data using cURL's --data
+##### Post data using cURL's --data
 
 Instead of using `-X POST` and appending the parameters to the URI, you can use
 cURL's `--data` option. The example below will create a new project `foo` under
@@ -535,7 +376,7 @@ the authenticated user's namespace.
 curl --data "name=foo" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
 ```
 
-#### Post data using JSON content
+##### Post data using JSON content
 
 > **Note:** In this example we create a new group. Watch carefully the single
 and double quotes.
@@ -544,7 +385,7 @@ and double quotes.
 curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v4/groups
 ```
 
-#### Post data using form-data
+##### Post data using form-data
 
 Instead of using JSON or urlencode you can use multipart/form-data which
 properly handles data encoding:
@@ -556,7 +397,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "title
 The above example is run by and administrator and will add an SSH public key
 titled ssh-key to user's account which has an id of 25.
 
-#### Escape special characters
+##### Escape special characters
 
 Spaces or slashes (`/`) may sometimes result to errors, thus it is recommended
 to escape them when possible. In the example below we create a new issue which
@@ -569,7 +410,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl
 
 Use `%2F` for slashes (`/`).
 
-#### Pass arrays to API calls
+##### Pass arrays to API calls
 
 The GitLab API sometimes accepts arrays of strings or integers. For example, to
 restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and
@@ -584,6 +425,3 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain
 [gfm]: http://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation"
 [ce-1242]: https://gitlab.com/gitlab-org/gitlab-ce/issues/1242
 [doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
-[ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure"
-[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
-[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 1eb90c30ebd51240a36acf1fa873581ac6fda579..287143d625558e74cc3e6089fbc4bfdf4b154f55 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -33,6 +33,26 @@ rest of the code should be as close to the CE files as possible.
 
 [single code base]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2952#note_41016454
 
+### EE-specific comments
+
+When complete separation can't be achieved with the `ee/` directory, you can wrap
+code in EE specific comments to designate the difference from CE/EE and add
+some context for someone resolving a conflict.
+
+```rb
+# EE-specific start
+stub_licensed_features(variable_environment_scope: true)
+# EE specific end
+```
+
+```haml
+-# EE-specific start
+= render 'ci/variables/environment_scope', form_field: form_field, variable: variable
+-# EE-specific end
+```
+
+EE-specific comments should not be backported to CE.
+
 ### Detection of EE-only files
 
 For each commit (except on `master`), the `ee-files-location-check` CI job tries
@@ -350,6 +370,255 @@ class beneath the `EE` module just as you would normally.
 For example, if CE has LDAP classes in `lib/gitlab/ldap/` then you would place
 EE-specific LDAP classes in `ee/lib/ee/gitlab/ldap`.
 
+### Code in `lib/api/`
+
+It can be very tricky to extend EE features by a single line of `prepend`,
+and for each different [Grape](https://github.com/ruby-grape/grape) feature,
+we might need different strategies to extend it. To apply different strategies
+easily, we would use `extend ActiveSupport::Concern` in the EE module.
+
+Put the EE module files following
+[EE features based on CE features](#ee-features-based-on-ce-features).
+
+#### EE API routes
+
+For EE API routes, we put them in a `prepended` block:
+
+``` ruby
+module EE
+  module API
+    module MergeRequests
+      extend ActiveSupport::Concern
+
+      prepended do
+        params do
+          requires :id, type: String, desc: 'The ID of a project'
+        end
+        resource :projects, requirements: ::API::API::PROJECT_ENDPOINT_REQUIREMENTS do
+          # ...
+        end
+      end
+    end
+  end
+end
+```
+
+Note that due to namespace differences, we need to use the full qualifier for some
+constants.
+
+#### EE params
+
+We can define `params` and utilize `use` in another `params` definition to
+include params defined in EE. However, we need to define the "interface" first
+in CE in order for EE to override it. We don't have to do this in other places
+due to `prepend`, but Grape is complex internally and we couldn't easily do
+that, so we'll follow regular object-oriented practices that we define the
+interface first here.
+
+For example, suppose we have a few more optional params for EE, given this CE
+API code:
+
+``` ruby
+module API
+  class MergeRequests < Grape::API
+    # EE::API::MergeRequests would override the following helpers
+    helpers do
+      params :optional_params_ee do
+      end
+    end
+
+    prepend EE::API::MergeRequests
+
+    params :optional_params do
+      # CE specific params go here...
+
+      use :optional_params_ee
+    end
+  end
+end
+```
+
+And then we could override it in EE module:
+
+``` ruby
+module EE
+  module API
+    module MergeRequests
+      extend ActiveSupport::Concern
+
+      prepended do
+        helpers do
+          params :optional_params_ee do
+            # EE specific params go here...
+          end
+        end
+      end
+    end
+  end
+end
+```
+
+This way, the only difference between CE and EE for that API file would be
+`prepend EE::API::MergeRequests`.
+
+#### EE helpers
+
+To make it easy for an EE module to override the CE helpers, we need to define
+those helpers we want to extend first. Try to do that immediately after the
+class definition to make it easy and clear:
+
+``` ruby
+module API
+  class JobArtifacts < Grape::API
+    # EE::API::JobArtifacts would override the following helpers
+    helpers do
+      def authorize_download_artifacts!
+        authorize_read_builds!
+      end
+    end
+
+    prepend EE::API::JobArtifacts
+  end
+end
+```
+
+And then we can follow regular object-oriented practices to override it:
+
+``` ruby
+module EE
+  module API
+    module JobArtifacts
+      extend ActiveSupport::Concern
+
+      prepended do
+        helpers do
+          def authorize_download_artifacts!
+            super
+            check_cross_project_pipelines_feature!
+          end
+        end
+      end
+    end
+  end
+end
+```
+
+#### EE-specific behaviour
+
+Sometimes we need EE-specific behaviour in some of the APIs. Normally we could
+use EE methods to override CE methods, however API routes are not methods and
+therefore can't be simply overridden. We need to extract them into a standalone
+method, or introduce some "hooks" where we could inject behavior in the CE
+route. Something like this:
+
+``` ruby
+module API
+  class MergeRequests < Grape::API
+    helpers do
+      # EE::API::MergeRequests would override the following helpers
+      def update_merge_request_ee(merge_request)
+      end
+    end
+
+    prepend EE::API::MergeRequests
+
+    put ':id/merge_requests/:merge_request_iid/merge' do
+      merge_request = find_project_merge_request(params[:merge_request_iid])
+
+      # ...
+
+      update_merge_request_ee(merge_request)
+
+      # ...
+    end
+  end
+end
+```
+
+Note that `update_merge_request_ee` doesn't do anything in CE, but
+then we could override it in EE:
+
+``` ruby
+module EE
+  module API
+    module MergeRequests
+      extend ActiveSupport::Concern
+
+      prepended do
+        helpers do
+          def update_merge_request_ee(merge_request)
+            # ...
+          end
+        end
+      end
+    end
+  end
+end
+```
+
+#### EE `route_setting`
+
+It's very hard to extend this in an EE module, and this is simply storing
+some meta-data for a particular route. Given that, we could simply leave the
+EE `route_setting` in CE as it won't hurt and we are just not going to use
+those meta-data in CE.
+
+We could revisit this policy when we're using `route_setting` more and whether
+or not we really need to extend it from EE. For now we're not using it much.
+
+#### Utilizing class methods for setting up EE-specific data
+
+Sometimes we need to use different arguments for a particular API route, and we
+can't easily extend it with an EE module because Grape has different context in
+different blocks. In order to overcome this, we could use class methods from the
+API class.
+
+For example, in one place we need to pass an extra argument to
+`at_least_one_of` so that the API could consider an EE-only argument as the
+least argument. This is not quite beautiful but it's working:
+
+``` ruby
+module API
+  class MergeRequests < Grape::API
+    def self.update_params_at_least_one_of
+      %i[
+        assignee_id
+        description
+      ]
+    end
+
+    prepend EE::API::MergeRequests
+
+    params do
+      at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of)
+    end
+  end
+end
+```
+
+And then we could easily extend that argument in the EE class method:
+
+``` ruby
+module EE
+  module API
+    module MergeRequests
+      extend ActiveSupport::Concern
+
+      class_methods do
+        def update_params_at_least_one_of
+          super.push(*%i[
+            squash
+          ])
+        end
+      end
+    end
+  end
+end
+```
+
+It could be annoying if we need this for a lot of routes, but it might be the
+simplest solution right now.
+
 ### Code in `spec/`
 
 When you're testing EE-only features, avoid adding examples to the
@@ -360,33 +629,22 @@ Instead place EE specs in the `ee/spec` folder.
 
 ## JavaScript code in `assets/javascripts/`
 
-To separate EE-specific JS-files we can also move the files into an `ee` folder.
+To separate EE-specific JS-files we should also move the files into an `ee` folder.
 
 For example there can be an
 `app/assets/javascripts/protected_branches/protected_branches_bundle.js` and an
 EE counterpart
 `ee/app/assets/javascripts/protected_branches/protected_branches_bundle.js`.
 
-That way we can create a separate webpack bundle in `webpack.config.js`:
-
-```javascript
-    protected_branches:    '~/protected_branches',
-    ee_protected_branches: 'ee/protected_branches/protected_branches_bundle.js',
-```
-
-With the separate bundle in place, we can decide which bundle to load inside the
-view, using the `page_specific_javascript_bundle_tag` helper.
-
-```haml
-- content_for :page_specific_javascripts do
-  = page_specific_javascript_bundle_tag('protected_branches')
-```
+See the frontend guide [performance section](./fe_guide/performance.md) for
+information on managing page-specific javascript within EE.
 
 ## SCSS code in `assets/stylesheets`
 
 To separate EE-specific styles in SCSS files, if a component you're adding styles for
 is limited to only EE, it is better to have a separate SCSS file in appropriate directory
 within `app/assets/stylesheets`.
+See [backporting changes](#backporting-changes) for instructions on how to merge changes safely.
 
 In some cases, this is not entirely possible or creating dedicated SCSS file is an overkill,
 e.g. a text style of some component is different for EE. In such cases,
@@ -417,14 +675,28 @@ to avoid conflicts during CE to EE merge.
   }
 }
 
-/* EE-specific styles */
+// EE-specific start
 .section-body.ee-section-body {
   .section-title {
     background: $gl-header-color-cyan;
   }
 }
+// EE-specific end
 ```
 
+### Backporting changes from EE to CE
+
+When working in EE-specific features, you might have to tweak a few files that are not EE-specific. Here is a workflow to make sure those changes end up backported safely into CE too.
+(This approach does not refer to changes introduced via [csslab](https://gitlab.com/gitlab-org/csslab/).)
+
+1. **Make your changes in the EE branch.** If possible, keep a separated commit (to be squashed) to help backporting and review.
+1. **Open merge request to EE project.**
+1. **Apply the changes you made to CE files in a branch of the CE project.** (Tip: Use `patch` with the diff from your commit in EE branch)
+1. **Open merge request to CE project**, referring it's a backport of EE changes and link to MR open in EE.
+1. Once EE MR is merged, the MR towards CE can be merged. **But not before**.
+
+**Note:** regarding SCSS, make sure the files living outside `/ee/` don't diverge between CE and EE projects.
+
 ## gitlab-svgs
 
 Conflicts in `app/assets/images/icons.json` or `app/assets/images/icons.svg` can
diff --git a/doc/development/emails.md b/doc/development/emails.md
index 677029b12956229c5725f1d99d0cbaa45e930831..73cac82caf09cad4539c3c0c2374acaea6b53e99 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -60,16 +60,10 @@ See the [Rails guides] for more info.
 
     As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
 
-1. Uncomment the `mail_room` line in your `Procfile`:
-
-    ```yaml
-    mail_room: bundle exec mail_room -q -c config/mail_room.yml
-    ```
-
-1. Restart GitLab:
+1. Run this command in the GitLab root directory to launch `mail_room`:
 
     ```sh
-    bundle exec foreman start
+    bundle exec mail_room -q -c config/mail_room.yml
     ```
 
 1. Verify that everything is configured correctly:
@@ -80,6 +74,24 @@ See the [Rails guides] for more info.
 
 1. Reply by email should now be working.
 
+## Email namespace
+
+If you need to implement a new feature which requires a new email handler, follow these rules:
+
+ - You must choose a namespace. The namespace cannot contain `/` or `+`, and cannot match `\h{16}`.
+ - If your feature is related to a project, you will append the namespace **after** the project path, separated by a `+`
+ - If you have different actions in the namespace, you add the actions **after** the namespace separated by a `+`. The action name cannot contain `/` or `+`, , and cannot match `\h{16}`.
+ - You will register your handlers in `lib/gitlab/email/handler.rb`
+
+Therefore, these are the only valid formats for an email handler:
+
+ - `path/to/project+namespace`
+ - `path/to/project+namespace+action`
+ - `namespace`
+ - `namespace+action`
+
+Please note that `path/to/project` is used in GitLab Premium as handler for the Service Desk feature.
+
 ---
 
 [Return to Development documentation](README.md)
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 12dfc10812b2c7b0d8f56093810c4b3fc5f51762..2280cf79f868daa8df1750137ab106548c51200e 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -14,8 +14,8 @@ support through [webpack][webpack].
 We also utilize [webpack][webpack] to handle the bundling, minification, and
 compression of our assets.
 
-Working with our frontend assets requires Node (v4.3 or greater) and Yarn
-(v0.17 or greater).  You can find information on how to install these on our
+Working with our frontend assets requires Node (v6.0 or greater) and Yarn
+(v1.2 or greater).  You can find information on how to install these on our
 [installation guide][install].
 
 [jQuery][jquery] is used throughout the application's JavaScript, with
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 98e43931a02779e2ef7bba5057223b59bce7b464..1320efaf767beb1794353e5a3f55992338c8e4ef 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -23,7 +23,7 @@ controlled by the server.
 1. The backend code will most likely be using etags. You do not and should not check for status
 `304 Not Modified`. The browser will transform it for you.
 
-### Lazy Loading
+### Lazy Loading Images
 
 To improve the time to first render we are using lazy loading for images. This works by setting 
 the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded, 
@@ -47,41 +47,103 @@ properties once, and handle the actual animation with transforms.
 
 ## Reducing Asset Footprint
 
-### Page-specific JavaScript
+### Universal code
 
-Certain pages may require the use of a third party library, such as [d3][d3] for
-the User Activity Calendar and [Chart.js][chartjs] for the Graphs pages. These
-libraries increase the page size significantly, and impact load times due to
-bandwidth bottlenecks and the browser needing to parse more JavaScript.
-
-In cases where libraries are only used on a few specific pages, we use
-"page-specific JavaScript" to prevent the main `main.js` file from
-becoming unnecessarily large.
-
-Steps to split page-specific JavaScript from the main `main.js`:
-
-1. Create a directory for the specific page(s), e.g. `graphs/`.
-1. In that directory, create a `namespace_bundle.js` file, e.g. `graphs_bundle.js`.
-1. Add the new "bundle" file to the list of entry files in `config/webpack.config.js`.
-  - For example: `graphs: './graphs/graphs_bundle.js',`.
-1. Move code reliant on these libraries into the `graphs` directory.
-1. In `graphs_bundle.js` add CommonJS `require('./path_to_some_component.js');` statements to load any other files in this directory. Make sure to use relative urls.
-1. In the relevant views, add the scripts to the page with the following:
-
-```haml
-- content_for :page_specific_javascripts do
-  = webpack_bundle_tag 'lib_chart'
-  = webpack_bundle_tag 'graphs'
-```
+Code that is contained within `main.js` and `commons/index.js` are loaded and
+run on _all_ pages. **DO NOT ADD** anything to these files unless it is truly
+needed _everywhere_. These bundles include ubiquitous libraries like `vue`,
+`axios`, and `jQuery`, as well as code for the main navigation and sidebar.
+Where possible we should aim to remove modules from these bundles to reduce our
+code footprint.
+
+### Page-specific JavaScript
 
-The above loads `chart.js` and `graphs_bundle.js` for this page only. `chart.js`
-is separated from the bundle file so it can be cached separately from the bundle
-and reused for other pages that also rely on the library. For an example, see
-[this Haml file][page-specific-js-example].
+Webpack has been configured to automatically generate entry point bundles based
+on the file structure within `app/assets/javascripts/pages/*`. The directories
+within the `pages` directory correspond to Rails controllers and actions. These
+auto-generated bundles will be automatically included on the corresponding
+pages.
+
+For example, if you were to visit [gitlab.com/gitlab-org/gitlab-ce/issues](https://gitlab.com/gitlab-org/gitlab-ce/issues),
+you would be accessing the `app/controllers/projects/issues_controller.rb`
+controller with the `index` action. If a corresponding file exists at
+`pages/projects/issues/index/index.js`, it will be compiled into a webpack
+bundle and included on the page.
+
+> **Note:** Previously we had encouraged the use of
+> `content_for :page_specific_javascripts` within haml files, along with
+> manually generated webpack bundles. However under this new system you should
+> not ever need to manually add an entry point to the `webpack.config.js` file.
+
+> **Tip:**
+> If you are unsure what controller and action corresponds to a given page, you
+> can find this out by inspecting `document.body.dataset.page` within your
+> browser's developer console while on any page within gitlab.
+
+#### Important Considerations:
+
+- **Keep Entry Points Lite:**
+  Page-specific javascript entry points should be as lite as possible.  These
+  files are exempt from unit tests, and should be used primarily for
+  instantiation and dependency injection of classes and methods that live in
+  modules outside of the entry point script.  Just import, read the DOM,
+  instantiate, and nothing else.
+
+- **Entry Points May Be Asynchronous:**
+  _DO NOT ASSUME_ that the DOM has been fully loaded and available when an
+  entry point script is run.  If you require that some code be run after the
+  DOM has loaded, you should attach an event handler to the `DOMContentLoaded`
+  event with:
+
+    ```javascript
+    import initMyWidget from './my_widget';
+  
+    document.addEventListener('DOMContentLoaded', () => {
+      initMyWidget();
+    });
+    ```
+
+- **Supporting Module Placement:**  
+    - If a class or a module is _specific to a particular route_, try to locate
+      it close to the entry point it will be used. For instance, if
+      `my_widget.js` is only imported within `pages/widget/show/index.js`, you
+      should place the module at `pages/widget/show/my_widget.js` and import it
+      with a relative path (e.g. `import initMyWidget from './my_widget';`).
+      
+    - If a class or module is _used by multiple routes_, place it within a
+      shared directory at the closest common parent directory for the entry
+      points that import it.  For example, if `my_widget.js` is imported within
+      both `pages/widget/show/index.js` and `pages/widget/run/index.js`, then
+      place the module at `pages/widget/shared/my_widget.js` and import it with
+      a relative path if possible (e.g. `../shared/my_widget`).
+
+- **Enterprise Edition Caveats:**
+  For GitLab Enterprise Edition, page-specific entry points will override their
+  Community Edition counterparts with the same name, so if
+  `ee/app/assets/javascripts/pages/foo/bar/index.js` exists, it will take
+  precedence over `app/assets/javascripts/pages/foo/bar/index.js`.  If you want
+  to minimize duplicate code, you can import one entry point from the other.
+  This is not done automatically to allow for flexibility in overriding
+  functionality.
 
 ### Code Splitting
 
-> *TODO* flesh out this section once webpack is ready for code-splitting
+For any code that does not need to be run immediately upon page load, (e.g.
+modals, dropdowns, and other behaviors that can be lazy-loaded), you can split
+your module into asynchronous chunks with dynamic import statements.  These
+imports return a Promise which will be resolved once the script has loaded:
+
+```javascript
+import(/* webpackChunkName: 'emoji' */ '~/emoji')
+  .then(/* do something */)
+  .catch(/* report error */)
+```
+
+Please try to use `webpackChunkName` when generating these dynamic imports as
+it will provide a deterministic filename for the chunk which can then be cached
+the browser across GitLab versions.
+
+More information is available in [webpack's code splitting documentation](https://webpack.js.org/guides/code-splitting/#dynamic-imports).
 
 ### Minimizing page size
 
@@ -95,7 +157,8 @@ General tips:
 - Prefer font formats with better compression, e.g. WOFF2 is better than WOFF, which is better than TTF.
 - Compress and minify assets wherever possible (For CSS/JS, Sprockets and webpack do this for us).
 - If some functionality can reasonably be achieved without adding extra libraries, avoid them.
-- Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages.
+- Use page-specific JavaScript as described above to load libraries that are only needed on certain pages.
+- Use code-splitting dynamic imports wherever possible to lazy-load code that is not needed initially.
 - [High Performance Animations][high-perf-animations]
 
 -------
@@ -112,8 +175,5 @@ General tips:
 [pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/
 [google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en
 [browser-diet]: https://browserdiet.com/
-[d3]: https://d3js.org/
-[chartjs]: http://www.chartjs.org/
-[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8
 [high-perf-animations]: https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/
 [flip]: https://aerotwist.com/blog/flip-your-animations/
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 917d28b48eefdccb31e01622df311f6feb819563..7b5fa6ca42f390b3d0d70f7c0e501fcb05a89272 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -548,6 +548,57 @@ On those a default key should not be provided.
 1. Properties in a Vue Component:
   Check [order of properties in components rule][vue-order].
 
+#### `:key`
+When using `v-for` you need to provide a *unique* `:key` attribute for each item.
+
+1. If the elements of the array being iterated have an unique `id` it is advised to use it:
+    ```html
+      <div
+        v-for="item in items"
+        :key="item.id"
+      >
+        <!-- content -->
+      </div>
+    ```
+
+1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
+    ```html
+      <div
+        v-for="(item, index) in items"
+        :key="index"
+      >
+        <!-- content -->
+      </div>
+    ```
+
+
+1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
+    ```html
+      <template v-for="(item, index) in items">
+        <span :key="`span-${index}`"></span>
+        <button :key="`button-${index}`"></button>
+      </template>
+    ```
+
+1. When dealing with nested `v-for` use the same guidelines as above.
+      ```html
+        <div
+          v-for="item in items"
+          :key="item.id"
+        >
+          <span
+            v-for="element in array"
+            :key="element.id"
+          >
+            <!-- content -->
+          </span>
+        </div>
+      ```
+
+
+Useful links:
+1. [`key`](https://vuejs.org/v2/guide/list.html#key)
+1. [Vue Style Guide: Keyed v-for](https://vuejs.org/v2/style-guide/#Keyed-v-for-essential )
 #### Vue and Bootstrap
 
 1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 093a3ca4407064c03bb2f0dc18e1cb9ede5a69da..9c4b0e86351339bb8a2683ea38e6f8ce3feec9f1 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -53,13 +53,13 @@ you can find a clear separation of concerns:
 ```
 new_feature
 鈹溾攢鈹€ components
-鈹�   鈹斺攢鈹€ component.js.es6
+鈹�   鈹斺攢鈹€ component.vue
 鈹�   鈹斺攢鈹€ ...
-鈹溾攢鈹€ store
-鈹�  鈹斺攢鈹€ new_feature_store.js.es6
-鈹溾攢鈹€ service
-鈹�  鈹斺攢鈹€ new_feature_service.js.es6
-鈹溾攢鈹€ new_feature_bundle.js.es6
+鈹溾攢鈹€ stores
+鈹�  鈹斺攢鈹€ new_feature_store.js
+鈹溾攢鈹€ services
+鈹�  鈹斺攢鈹€ new_feature_service.js
+鈹溾攢鈹€ new_feature_bundle.js
 ```
 _For consistency purposes, we recommend you to follow the same structure._
 
@@ -523,8 +523,8 @@ export default new Vuex.Store({
 ```
 _Note:_ If the state of the application is too complex, an individual file for the state may be better.
 
-#### `actions.js`
-An action commits a mutatation. In this file, we will write the actions that will call the respective mutation:
+##### `actions.js`
+An action commits a mutation. In this file, we will write the actions that will commit the respective mutation:
 
 ```javascript
   import * as types from './mutation_types';
@@ -550,7 +550,7 @@ import { mapActions } from 'vuex';
 };
 ```
 
-#### `getters.js`
+##### `getters.js`
 Sometimes we may need to get derived state based on store state, like filtering for a specific prop. This can be done through the `getters`:
 
 ```javascript
@@ -573,7 +573,7 @@ import { mapGetters } from 'vuex';
 };
 ```
 
-#### `mutations.js`
+##### `mutations.js`
 The only way to actually change state in a Vuex store is by committing a mutation.
 
 ```javascript
@@ -586,7 +586,7 @@ The only way to actually change state in a Vuex store is by committing a mutatio
   };
 ```
 
-#### `mutations_types.js`
+##### `mutations_types.js`
 From [vuex mutations docs][vuex-mutations]:
 > It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
 
@@ -661,7 +661,7 @@ describe('component', () => {
     };
 
     // populate the store
-    store.dipatch('addUser', user);
+    store.dispatch('addUser', user);
 
     vm = new Component({
       store,
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index 26abf967dcf0211cfa40c886b0f0d0348cfb2f4a..4f9ca1920a5f55c042fc464ef9fa5d2a2eaface4 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -7,6 +7,67 @@ be replaced by Gitaly API calls.
 Visit the [Gitaly Migration Board](https://gitlab.com/gitlab-org/gitaly/boards/331341) for current
 status of the migration.
 
+## Developing new Git features
+
+Starting with Gitlab 10.8, all new Git features should be developed in
+Gitaly.
+
+> This is a new process that is not clearly defined yet. If you want
+to contribute a Git feature and you're getting stuck, reach out to the
+Gitaly team or `@jacobvosmaer-gitlab`.
+
+By 'new feature' we mean any method or class in `lib/gitlab/git` that is
+called from outside `lib/gitlab/git`. For new methods that are called
+from inside `lib/gitlab/git`, see 'Modifying existing Git features'
+below.
+
+There should be no new code that touches Git repositories via
+disk access (e.g. Rugged, `git`, `rm -rf`) anywhere outside
+`lib/gitlab/git`.
+
+The process for adding new Gitaly features is:
+
+- exploration / prototyping
+- design and create a new Gitaly RPC [in gitaly-proto](https://gitlab.com/gitlab-org/gitaly-proto)
+- release a new version of gitaly-proto
+- write implementation and tests for the RPC [in Gitaly](https://gitlab.com/gitlab-org/gitaly), in Go or Ruby
+- release a new version of Gitaly
+- write client code in gitlab-ce/ee, gitlab-workhorse or gitlab-shell that calls the new Gitaly RPC
+
+These steps often overlap. It is possible to use an unreleased version
+of Gitaly and gitaly-proto during testing and development.
+
+- See the [Gitaly repo](https://gitlab.com/gitlab-org/gitaly/blob/master/CONTRIBUTING.md#development-and-testing-with-a-custom-gitaly-proto) for instructions on writing server side code with an unreleased protocol.
+- See [below](#running-tests-with-a-locally-modified-version-of-gitaly) for instructions on running gitlab-ce tests with a modified version of Gitaly.
+- In GDK run `gdk install` and restart `gdk run` (or `gdk run app`) to use a locally modified Gitaly version for development
+
+### Gitaly-ruby
+
+It is possible to implement and test RPC's in Gitaly using Ruby code,
+in
+[gitaly-ruby](https://gitlab.com/gitlab-org/gitaly/tree/master/ruby).
+This should make it easier to contribute for developers who are less
+comfortable writing Go code.
+
+There is documentation for this approach in [the Gitaly
+repo](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/ruby_endpoint.md).
+
+## Modifying existing Git features
+
+If you modify existing Git features in `lib/gitlab/git` you need to make
+sure the changes also work in Gitaly. Because we are still in the
+migration process there are a number of subtle pitfalls. Features that
+have been migrated have dual implementations (Gitaly and local). The
+Gitaly implementation may or may not use a vendored (and therefore
+possibly outdated) copy of the local implementation in `lib/gitlab/git`.
+
+To avoid unexpected problems and conflicts, all changes to
+`lib/gitlab/git` need to be approved by a member of the Gitaly team.
+
+For the time being, while the Gitaly migration is still in progress,
+there should be no Enterprise Edition-only Git code in
+`lib/gitlab/git`. Also no mixins.
+
 ## Feature Flags
 
 Gitaly makes heavy use of [feature flags](feature_flags.md).
@@ -99,10 +160,14 @@ end
 
 ## Running tests with a locally modified version of Gitaly
 
-Normally, gitlab-ce/ee tests use a local clone of Gitaly in `tmp/tests/gitaly`
-pinned at the version specified in GITALY_SERVER_VERSION. If you want
-to run tests locally against a modified version of Gitaly you can
-replace `tmp/tests/gitaly` with a symlink.
+Normally, gitlab-ce/ee tests use a local clone of Gitaly in
+`tmp/tests/gitaly` pinned at the version specified in
+`GITALY_SERVER_VERSION`. The `GITALY_SERVER_VERSION` file supports
+`=my-branch` syntax to use a custom branch in gitlab-org/gitaly. If
+you want to run tests locally against a modified version of Gitaly you
+can replace `tmp/tests/gitaly` with a symlink. This is much faster
+because the `=my-branch` syntax forces a Gitaly re-install each time
+you run `rspec`.
 
 ```shell
 rm -rf tmp/tests/gitaly
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index c0ce49eb40b6bf2726c0ef0809cca3c6be5c9fb7..b1bec84a2f38b2d07c2e19d644389d33de0720c1 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -45,7 +45,7 @@ We basically have 4 types of files:
 1. Ruby files: basically Models and Controllers.
 1. HAML files: these are the view files.
 1. ERB files: used for email templates.
-1. JavaScript files: we mostly need to work with VUE JS templates.
+1. JavaScript files: we mostly need to work with Vue templates.
 
 ### Ruby files
 
@@ -131,6 +131,9 @@ There is also and alternative method to [translate messages from validation erro
 
 ### Interpolation
 
+Placeholders in translated text should match the code style of the respective source file.
+For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
+
 - In Ruby/HAML:
 
     ```ruby
@@ -141,11 +144,19 @@ There is also and alternative method to [translate messages from validation erro
 
     ```js
     import { __, sprintf } from '~/locale';
-    sprintf(__('Hello %{username}'), { username: 'Joe' }) => 'Hello Joe'
+
+    sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
     ```
 
-The placeholders should match the code style of the respective source file.
-For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
+    By default, `sprintf` escapes the placeholder values.
+    If you want to take care of that yourself, you can pass `false` as third argument.
+
+    ```js
+    import { __, sprintf } from '~/locale';
+
+    sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is &lt;strong&gt;bold&lt;/strong&gt;'
+    sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>'
+    ```
 
 ### Plurals
 
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 9aa3fb07abf6217ce2ade9c453807a31e4d38f40..5185d843ccb70dcab1eb3d20ed6efab00f1aeb11 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -10,6 +10,7 @@ are very appreciative of the work done by translators and proofreaders!
   - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
 - Chinese Traditional
   - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
+  - Weizhe Ding - [GitLab](https://gitlab.com/d.weizhe), [Crowdin](https://crowdin.com/profile/d.weizhe)
 - Chinese Traditional, Hong Kong
   - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
 - Dutch
@@ -17,11 +18,17 @@ are very appreciative of the work done by translators and proofreaders!
 - French
   - R茅my Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai)
 - German
+- Indonesian
+  - Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm)
 - Italian
   - Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
 - Japanese
+  - Yamana Tokiuji - [GitLab](https://gitlab.com/tokiuji), [Crowdin](https://crowdin.com/profile/yamana)
 - Korean
+  - Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
   - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
+- Polish
+  - Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz)
 - Portuguese, Brazilian
   - Paulo George Gomes Bezerra - [GitLab](https://gitlab.com/paulobezerra), [Crowdin](https://crowdin.com/profile/paulogomes.rep)
 - Russian
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 243ac7f0c985cf7f1a03bc3ed9d2f397c4f4cc14..a211effdfa7d8d7d9c7a584b21ed872d2633254c 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -23,10 +23,6 @@ When downtime is necessary the migration has to be approved by:
 An up-to-date list of people holding these titles can be found at
 <https://about.gitlab.com/team/>.
 
-The document ["What Requires Downtime?"](what_requires_downtime.md) specifies
-various database operations, whether they require downtime and how to
-work around that whenever possible.
-
 When writing your migrations, also consider that databases might have stale data
 or inconsistencies and guard for that. Try to make as few assumptions as
 possible about the state of the database.
@@ -41,6 +37,18 @@ Migrations that make changes to the database schema (e.g. adding a column) can
 only be added in the monthly release, patch releases may only contain data
 migrations _unless_ schema changes are absolutely required to solve a problem.
 
+## What Requires Downtime?
+
+The document ["What Requires Downtime?"](what_requires_downtime.md) specifies
+various database operations, such as 
+
+- [adding, dropping, and renaming columns](what_requires_downtime.md#adding-columns)
+- [changing column constraints and types](what_requires_downtime.md#changing-column-constraints)
+- [adding and dropping indexes, tables, and foreign keys](what_requires_downtime.md#adding-indexes)
+
+and whether they require downtime and how to work around that whenever possible.
+
+
 ## Downtime Tagging
 
 Every migration must specify if it requires downtime or not, and if it should
@@ -136,11 +144,14 @@ class MyMigration < ActiveRecord::Migration
   disable_ddl_transaction!
 
   def up
-    remove_concurrent_index :table_name, :column_name if index_exists?(:table_name, :column_name)
+    remove_concurrent_index :table_name, :column_name
   end
 end
 ```
 
+Note that it is not necessary to check if the index exists prior to
+removing it.
+
 ## Adding indexes
 
 If you need to add a unique index please keep in mind there is the possibility
diff --git a/doc/development/new_fe_guide/dependencies.md b/doc/development/new_fe_guide/dependencies.md
new file mode 100644
index 0000000000000000000000000000000000000000..3417d77a06dd447b8f77f0be1825e890e4b114db
--- /dev/null
+++ b/doc/development/new_fe_guide/dependencies.md
@@ -0,0 +1,3 @@
+# Dependencies
+
+> TODO: Add Dependencies
\ No newline at end of file
diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md
new file mode 100644
index 0000000000000000000000000000000000000000..ed35f08432fdf5c5ceb81d1c7275d6056c87dd13
--- /dev/null
+++ b/doc/development/new_fe_guide/development/accessibility.md
@@ -0,0 +1,3 @@
+# Accessibility
+
+> TODO: Add content
diff --git a/doc/development/new_fe_guide/development/components.md b/doc/development/new_fe_guide/development/components.md
new file mode 100644
index 0000000000000000000000000000000000000000..899efb398cde49d5f948f8519d21c62ba3a2027b
--- /dev/null
+++ b/doc/development/new_fe_guide/development/components.md
@@ -0,0 +1,21 @@
+# Components
+
+## Graphs
+
+We have a lot of graphing libraries in our codebase to render graphs. In an effort to improve maintainability, new graphs should use [D3.js](https://d3js.org/). If a new graph is fairly simple, consider implementing it in SVGs or HTML5 canvas.
+
+We chose D3 as our library going forward because of the following features:
+
+* [Tree shaking webpack capabilities.](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40)
+* [Compatible with vue.js as well as vanilla javascript.](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40)
+
+D3 is very popular across many projects outside of GitLab:
+
+* [The New York Times](https://archive.nytimes.com/www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html)
+* [plot.ly](https://plot.ly/)
+* [Droptask](https://www.droptask.com/)
+
+Within GitLab, D3 has been used for the following notable features
+
+* [Prometheus graphs](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html)
+* Contribution calendars
diff --git a/doc/development/new_fe_guide/development/design_patterns.md b/doc/development/new_fe_guide/development/design_patterns.md
new file mode 100644
index 0000000000000000000000000000000000000000..ee06566ed3045dab614952c9fa311d3e6a32f319
--- /dev/null
+++ b/doc/development/new_fe_guide/development/design_patterns.md
@@ -0,0 +1,3 @@
+# Design patterns
+
+> TODO: Add content
diff --git a/doc/development/new_fe_guide/development/index.md b/doc/development/new_fe_guide/development/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..cee8e43ebadbcc788dec0f6add0dd95f2e9c5513
--- /dev/null
+++ b/doc/development/new_fe_guide/development/index.md
@@ -0,0 +1,29 @@
+# Development
+
+## [Design patterns](design_patterns.md)
+
+Examples of proven design patterns used in our codebase.
+
+## [Components](components.md)
+
+Documentation on existing components and how to best create a new component.
+
+## [Accessibility](accessibility.md)
+
+Learn how to implement an accessible frontend.
+
+## [Network requests](network_requests.md)
+
+Learn how to handle network requests in our codebase.
+
+## [Security](security.md)
+
+Learn how to ensure that our frontend is secure.
+
+## [Performance](performance.md)
+
+Learn how to keep our frontend performant.
+
+## [Testing](testing.md)
+
+Learn how to keep our frontend tested.
diff --git a/doc/development/new_fe_guide/development/network_requests.md b/doc/development/new_fe_guide/development/network_requests.md
new file mode 100644
index 0000000000000000000000000000000000000000..047c00313bc5ea297d434826f0f7b1311f9c7932
--- /dev/null
+++ b/doc/development/new_fe_guide/development/network_requests.md
@@ -0,0 +1,3 @@
+# Network requests
+
+> TODO: Add content
diff --git a/doc/development/new_fe_guide/development/performance.md b/doc/development/new_fe_guide/development/performance.md
new file mode 100644
index 0000000000000000000000000000000000000000..244dfb3756fb2f911b3afe1de78350389b0a3c6c
--- /dev/null
+++ b/doc/development/new_fe_guide/development/performance.md
@@ -0,0 +1,16 @@
+# Performance
+
+## Monitoring
+
+We have a performance dashboard available in one of our [grafana instances](https://performance.gprd.gitlab.com/dashboard/db/sitespeed-page-summary?orgId=1). This dashboard automatically aggregates metric data from [sitespeed.io](https://sitespeed.io) every 6 hours. These changes are displayed after a set number of pages are aggregated.
+
+These pages can be found inside a text file in the gitlab-build-images [repository](https://gitlab.com/gitlab-org/gitlab-build-images) called [gitlab.txt](https://gitlab.com/gitlab-org/gitlab-build-images/blob/master/scripts/gitlab.txt)
+Any frontend engineer can contribute to this dashboard. They can contribute by adding or removing urls of pages from this text file. Please have a [frontend monitoring expert](https://about.gitlab.com/team) review your changes before assigning to a maintainer of the `gitlab-build-images` project. The changes will go live on the next scheduled run after the changes are merged into `master`.
+
+There are 3 recommended high impact metrics to review on each page
+
+* [First visual change](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint)
+* [Speed Index](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
+* [Visual Complete 95%](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
+
+For these metrics, lower numbers are better as it means that the website is more performant.
diff --git a/doc/development/new_fe_guide/development/security.md b/doc/development/new_fe_guide/development/security.md
new file mode 100644
index 0000000000000000000000000000000000000000..5bb38f179885579ac787728d44809bc2947e0321
--- /dev/null
+++ b/doc/development/new_fe_guide/development/security.md
@@ -0,0 +1,14 @@
+# Security
+
+## Avoid inline scripts and styles
+
+Inline scripts and styles should be avoided in almost all cases. In an effort to protect users from [XSS vulnerabilities](https://en.wikipedia.org/wiki/Cross-site_scripting), we will be disabling inline scripts using Content Security Policy.
+
+## Including external resources
+
+External fonts, CSS, and JavaScript should never be used with the exception of Google Analytics and Piwik - and only when the instance has enabled it. Assets should always be hosted and served locally from the GitLab instance. Embedded resources via `iframes` should never be used except in certain circumstances such as with ReCaptcha, which cannot be used without an `iframe`.
+
+## Resources for security testing
+
+- [Mozilla's HTTP Observatory CLI](https://github.com/mozilla/http-observatory-cli)
+- [Qualys SSL Labs Server Test](https://www.ssllabs.com/ssltest/analyze.html)
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
new file mode 100644
index 0000000000000000000000000000000000000000..c359bd83ed189abc3cd3af3ef8138fcf3876002f
--- /dev/null
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -0,0 +1,3 @@
+# Testing
+
+> TODO: Add content
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..78931defa247ead62387cbec675e80b9046db04f
--- /dev/null
+++ b/doc/development/new_fe_guide/index.md
@@ -0,0 +1,28 @@
+# Frontend Development Guidelines
+
+This guide contains all the information to successfully contribute to GitLab's frontend.
+This is a living document, and we welcome contributions, feedback and suggestions.
+
+## [Principles](principles.md)
+
+Ensure that your frontend contribution starts off in the right direction.
+
+## [Initiatives](initiatives.md)
+
+High level overview of where we are going from a frontend perspective.
+
+## [Development](development/index.md)
+
+Guidance on topics related to development.
+
+## [Dependencies](dependencies.md)
+
+Learn about all the dependencies that make up our frontend, including some of our own custom built libraries.
+
+## [Style guides](style/index.md)
+
+Style guides to keep our code consistent.
+
+## [Tips](tips.md)
+
+Tips from our frontend team to develop more efficiently and effectively.
diff --git a/doc/development/new_fe_guide/initiatives.md b/doc/development/new_fe_guide/initiatives.md
new file mode 100644
index 0000000000000000000000000000000000000000..c81ed3579f0bded8a6dea049134c4afcec6c0543
--- /dev/null
+++ b/doc/development/new_fe_guide/initiatives.md
@@ -0,0 +1,3 @@
+# Initiatives
+
+> TODO: Add Initiatives
diff --git a/doc/development/new_fe_guide/principles.md b/doc/development/new_fe_guide/principles.md
new file mode 100644
index 0000000000000000000000000000000000000000..0af5f506a914b0b12f8b6aefd50e069aade9d7dc
--- /dev/null
+++ b/doc/development/new_fe_guide/principles.md
@@ -0,0 +1,35 @@
+# Principles
+
+These principles will ensure that your frontend contribution starts off in the right direction.
+
+## Discuss architecture before implementation
+
+Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
+
+## Be consistent
+
+There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it more easier us to maintain our code across GitLab.
+
+## Enhance progressively
+
+Whenever you see with existing code that does not follow our current style guide, update it proactively. Refrain from changing everything but each merge request should progressively enhance our codebase and reduce technical debt.
+
+## When to use Vue
+
+- Use Vue for feature that make use of heavy DOM manipulation
+- Use Vue for reusable components
+
+## When to use jQuery
+
+- Use jQuery to interact with Bootstrap JavaScript components
+- Avoid jQuery when a better alternative exists. We are slowly moving away from it [#43559][jquery-future]
+
+## Mixing Vue and jQuery
+
+- Mixing Vue and jQuery is not recommended.
+- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][select2].
+- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
+- It is not recommended to add new jQuery events for Vue to interact with jQuery.
+
+[jquery-future]: https://gitlab.com/gitlab-org/gitlab-ce/issues/43559
+[select2]: https://vuejs.org/v2/examples/select2.html
diff --git a/doc/development/new_fe_guide/style/html.md b/doc/development/new_fe_guide/style/html.md
new file mode 100644
index 0000000000000000000000000000000000000000..2d5b7d048ab070fd791c64a6fe2f0cd2b30d44f9
--- /dev/null
+++ b/doc/development/new_fe_guide/style/html.md
@@ -0,0 +1,53 @@
+# HTML style guide
+
+## Buttons
+
+<a name="button-type"></a><a name="1.1"></a>
+- [1.1](#button-type) **Use button type** Button tags requires a `type` attribute according to the [W3C HTML specification][button-type-spec].
+
+```
+// bad
+<button></button>
+
+// good
+<button type="button"></button>
+```
+
+<a name="button-role"></a><a name="1.2"></a>
+- [1.2](#button-role) **Use button role for non buttons** If an HTML element has an onClick handler but is not a button, it should have `role="button"`. This is more [accessible][button-role-accessible].
+
+```
+// bad
+<div onClick="doSomething"></div>
+
+// good
+<div role="button" onClick="doSomething"></div>
+```
+
+## Links
+
+<a name="blank-links"></a><a name="2.1"></a>
+- [2.1](#blank-links) **Use rel for target blank** Use `rel="noopener noreferrer"` whenever your links open in a new window i.e. `target="_blank"`. This prevents [the following][jitbit-target-blank] security vulnerability documented by JitBit
+
+```
+// bad
+<a href="url" target="_blank"></a>
+
+// good
+<a href="url" target="_blank" rel="noopener noreferrer"></a>
+```
+
+<a name="fake-links"></a><a name="2.2"></a>
+- [2.2](#fake-links) **Do not use fake links** Use a button tag if a link only invokes JavaScript click event handlers. This is more semantic.
+
+```
+// bad
+<a class="js-do-something" href="#"></a>
+
+// good
+<button class="js-do-something" type="button"></button>
+```
+
+[button-type-spec]: https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#dom-button-type
+[button-role-accessible]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role
+[jitbit-target-blank]: https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
diff --git a/doc/development/new_fe_guide/style/index.md b/doc/development/new_fe_guide/style/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..335d9e66240695ee86a447cc10b770778a51eae3
--- /dev/null
+++ b/doc/development/new_fe_guide/style/index.md
@@ -0,0 +1,15 @@
+# Style guides
+
+## [HTML style guide](html.md)
+
+## [SCSS style guide](scss.md)
+
+## [JavaScript style guide](javascript.md)
+
+## [Vue style guide](vue.md)
+
+# Tooling
+
+## [Prettier](prettier.md)
+
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our guidelines.
diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md
new file mode 100644
index 0000000000000000000000000000000000000000..57efd9353bc8f75e7585dc6c12cb493121d30387
--- /dev/null
+++ b/doc/development/new_fe_guide/style/javascript.md
@@ -0,0 +1,195 @@
+# JavaScript style guide
+
+We use [Airbnb's JavaScript Style Guide][airbnb-style-guide] and it's accompanying linter to manage most of our JavaScript style guidelines.
+
+In addition to the style guidelines set by Airbnb, we also have a few specific rules listed below.
+
+> **Tip:**
+You can run eslint locally by running `yarn eslint`
+
+## Arrays
+
+<a name="avoid-foreach"></a><a name="1.1"></a>
+- [1.1](#avoid-foreach) **Avoid ForEach when mutating data** Use `map`, `reduce` or `filter` instead of `forEach` when mutating data. This will minimize mutations in functions ([which is aligned with Airbnb's style guide][airbnb-minimize-mutations])
+
+```
+// bad
+users.forEach((user, index) => {
+  user.id = index;
+});
+
+// good
+const usersWithId = users.map((user, index) => {
+  return Object.assign({}, user, { id: index });
+});
+```
+
+## Functions
+
+<a name="limit-params"></a><a name="2.1"></a>
+- [2.1](#limit-params) **Limit number of parameters** If your function or method has more than 3 parameters, use an object as a parameter instead.
+
+```
+// bad
+function a(p1, p2, p3) {
+  // ...
+};
+
+// good
+function a(p) {
+  // ...
+};
+```
+
+## Classes & constructors
+
+<a name="avoid-constructor-side-effects"></a><a name="3.1"></a>
+- [3.1](#avoid-constructor-side-effects) **Avoid side effects in constructors** Avoid making some operations in the `constructor`, such as asynchronous calls, API requests and DOM manipulations. Prefer moving them into separate functions. This will make tests easier to write and code easier to maintain.
+
+  ```javascript
+  // bad
+  class myClass {
+    constructor(config) {
+      this.config = config;
+      axios.get(this.config.endpoint)
+    }
+  }
+
+  // good
+  class myClass {
+    constructor(config) {
+      this.config = config;
+    }
+
+    makeRequest() {
+      axios.get(this.config.endpoint)
+    }
+  }
+  const instance = new myClass();
+  instance.makeRequest();
+
+  ```
+
+<a name="avoid-classes-to-handle-dom-events"></a><a name="3.2"></a>
+- [3.2](#avoid-classes-to-handle-dom-events) **Avoid classes to handle DOM events** If the only purpose of the class is to bind a DOM event and handle the callback, prefer using a function.
+
+```
+// bad
+class myClass {
+  constructor(config) {
+    this.config = config;
+  }
+
+  init() {
+    document.addEventListener('click', () => {});
+  }
+}
+
+// good
+
+const myFunction = () => {
+  document.addEventListener('click', () => {
+    // handle callback here
+  });
+}
+```
+
+<a name="element-container"></a><a name="3.3"></a>
+- [3.3](#element-container) **Pass element container to constructor** When your class manipulates the DOM, receive the element container as a parameter.
+This is more maintainable and performant.
+
+```
+// bad
+class a {
+  constructor() {
+    document.querySelector('.b');
+  }
+}
+
+// good
+class a {
+  constructor(options) {
+    options.container.querySelector('.b');
+  }
+}
+```
+
+## Type Casting & Coercion
+
+<a name="use-parseint"></a><a name="4.1"></a>
+- [4.1](#use-parseint) **Use ParseInt** Use `ParseInt` when converting a numeric string into a number.
+
+```
+// bad
+Number('10')
+
+
+// good
+parseInt('10', 10);
+```
+
+## CSS Selectors
+
+<a name="use-js-prefix"></a><a name="5.1"></a>
+- [5.1](#use-js-prefix) **Use js prefix** If a CSS class is only being used in JavaScript as a reference to the element, prefix the class name with `js-`
+
+```
+// bad
+<button class="add-user"></button>
+
+// good
+<button class="js-add-user"></button>
+```
+
+## Modules
+
+<a name="use-absolute-paths"></a><a name="6.1"></a>
+- [6.1](#use-absolute-paths) **Use absolute paths for nearby modules** Use absolute paths if the module you are importing is less than two levels up.
+
+```
+// bad
+import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
+
+// good
+import GitLabStyleGuide from '../GitLabStyleGuide';
+```
+
+<a name="use-relative-paths"></a><a name="6.2"></a>
+- [6.2](#use-relative-paths) **Use relative paths for distant modules** If the module you are importing is two or more levels up, use a relative path instead of an absolute path.
+
+```
+// bad
+import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
+
+// good
+import GitLabStyleGuide from '~/GitLabStyleGuide';
+```
+
+<a name="global-namespace"></a><a name="6.3"></a>
+- [6.3](#global-namespace) **Do not add to global namespace**
+
+<a name="domcontentloaded"></a><a name="6.4"></a>
+- [6.4](#domcontentloaded) **Do not use DOMContentLoaded in non-page modules** Imported modules should act the same each time they are loaded. `DOMContentLoaded` events are only allowed on modules loaded in the `/pages/*` directory because those are loaded dynamically with webpack.
+
+## Security
+
+<a name="avoid-xss"></a><a name="7.1"></a>
+- [7.1](#avoid-xss) **Avoid XSS** Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many vulnerabilities.
+
+## ESLint
+
+<a name="disable-eslint-file"></a><a name="8.1"></a>
+- [8.1](#disable-eslint-file) **Disabling ESLint in new files** Do not disable ESLint when creating new files. Existing files may have existing rules disabled due to legacy compatibility reasons but they are in the process of being refactored.
+
+<a name="disable-eslint-rule"></a><a name="8.2"></a>
+- [8.2](#disable-eslint-rule) **Disabling ESLint rule** Do not disable specific ESLint rules. Due to technical debt, you may disable the following rules only if you are invoking/instantiating existing code modules
+
+  - [no-new][no-new]
+  - [class-method-use-this][class-method-use-this]
+
+> Note: Disable these rules on a per line basis. This makes it easier to refactor in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`
+
+[airbnb-style-guide]: https://github.com/airbnb/javascript
+[airbnb-minimize-mutations]: https://github.com/airbnb/javascript#testing--for-real
+[no-new]: http://eslint.org/docs/rules/no-new
+[class-method-use-this]: http://eslint.org/docs/rules/class-methods-use-this
diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/new_fe_guide/style/prettier.md
new file mode 100644
index 0000000000000000000000000000000000000000..eb18189282bcadfaa49a8eaa5598ba96af6b4f29
--- /dev/null
+++ b/doc/development/new_fe_guide/style/prettier.md
@@ -0,0 +1,45 @@
+# Formatting with Prettier
+
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting .js, .vue, and .scss files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
+
+## Editor
+
+The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor. 
+
+Please take care that you only let Prettier format the same file types as the global Yarn script does (.js, .vue, and .scss). In VSCode by example you can easily exclude file formats in your settings file:
+
+```
+  "prettier.disableLanguages": [
+      "json",
+      "markdown"
+  ],
+```
+
+## Yarn Script
+
+The following yarn scripts are available to do global formatting:
+
+```
+yarn prettier-staged-save
+```
+
+Updates all currently staged files (based on `git diff`) with Prettier and saves the needed changes.
+
+```
+yarn prettier-staged
+```
+Checks all currently staged files (based on `git diff`) with Prettier and log which files would need manual updating to the console.
+
+```
+yarn prettier-all
+```
+
+Checks all files with Prettier and logs which files need manual updating to the console.
+
+```
+yarn prettier-all-save
+```
+
+Formats all files in the repository with Prettier. (This should only be used to test global rule updates otherwise you would end up with huge MR's).
+
+The source of these Yarn scripts can be found in `/scripts/frontend/prettier.js`.
diff --git a/doc/development/new_fe_guide/style/scss.md b/doc/development/new_fe_guide/style/scss.md
new file mode 100644
index 0000000000000000000000000000000000000000..6f5e818d7db37229e39e7a0a0f97b4e6c396eb23
--- /dev/null
+++ b/doc/development/new_fe_guide/style/scss.md
@@ -0,0 +1,3 @@
+# SCSS style guide
+
+> TODO: Add content
diff --git a/doc/development/new_fe_guide/style/vue.md b/doc/development/new_fe_guide/style/vue.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd9353e0d3f1031a5c3eed56b6f4f5ff3d0d03d3
--- /dev/null
+++ b/doc/development/new_fe_guide/style/vue.md
@@ -0,0 +1,3 @@
+# Vue style guide
+
+> TODO: Add content
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
new file mode 100644
index 0000000000000000000000000000000000000000..f0cdf52d61830645e2fb7894ac16fa5af660246d
--- /dev/null
+++ b/doc/development/new_fe_guide/tips.md
@@ -0,0 +1,3 @@
+# Tips
+
+> TODO: Add tips
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index dc88ce1522c79c32bee9873423128ac5bc349e5e..fdfa1f1040292e35c6d1d9b2b89c80ed78c224c5 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -102,6 +102,12 @@ variable to `1`:
 export ENABLE_SPRING=1
 ```
 
+Alternatively you can use the following on each spec run,
+
+```
+bundle exec spring rspec some_spec.rb
+```
+
 ## Compile Frontend Assets
 
 You shouldn't ever need to compile frontend assets manually in development, but
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md
index 5b4f6511f042d65ce168a2ff7570cb4bc1ccbe45..d10a797a142e88a4c7c803eab91c6cb5145ffbc8 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end_tests.md
@@ -22,7 +22,7 @@ You can find these nightly pipelines at [GitLab QA pipelines page][gitlab-qa-pip
 
 It is possible to run end-to-end tests (eventually being run within a
 [GitLab QA pipeline][gitlab-qa-pipelines]) for a merge request by triggering
-the `package-qa` manual action, that should be present in a merge request
+the `package-and-qa` manual action, that should be present in a merge request
 widget.
 
 Manual action that starts end-to-end tests is also available in merge requests
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 0c63f51cb451cc2760aa0290dbe977282a33f2f8..0a6f402d5d2583456c823ddb6a04e48d1b09c80d 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -152,19 +152,33 @@ is sufficient (and saves you some time).
 ### Live testing and focused testing
 
 While developing locally, it may be helpful to keep karma running so that you
-can get instant feedback on as you write tests and modify code.  To do this
-you can start karma with `npm run karma-start`.  It will compile the javascript
+can get instant feedback on as you write tests and modify code. To do this
+you can start karma with `yarn run karma-start`. It will compile the javascript
 assets and run a server at `http://localhost:9876/` where it will automatically
-run the tests on any browser which connects to it.  You can enter that url on
+run the tests on any browser which connects to it. You can enter that url on
 multiple browsers at once to have it run the tests on each in parallel.
 
 While karma is running, any changes you make will instantly trigger a recompile
 and retest of the entire test suite, so you can see instantly if you've broken
-a test with your changes.  You can use [jasmine focused][jasmine-focus] or
+a test with your changes. You can use [jasmine focused][jasmine-focus] or
 excluded tests (with `fdescribe` or `xdescribe`) to get karma to run only the
 tests you want while you're working on a specific feature, but make sure to
 remove these directives when you commit your code.
 
+It is also possible to only run karma on specific folders or files by filtering
+the run tests via the argument `--filter-spec` or short `-f`:
+
+```bash
+# Run all files
+yarn karma-start
+# Run specific spec files
+yarn karma-start --filter-spec profile/account/components/update_username_spec.js
+# Run specific spec folder
+yarn karma-start --filter-spec profile/account/components/
+# Run all specs which path contain vue_shared or vie
+yarn karma-start -f vue_shared -f vue_mr_widget
+```
+
 ## RSpec feature integration tests
 
 Information on setting up and running RSpec integration tests with
@@ -176,7 +190,7 @@ Information on setting up and running RSpec integration tests with
 
 Similar errors will be thrown if you're using JavaScript features not yet
 supported by the PhantomJS test runner which is used for both Karma and RSpec
-tests.  We polyfill some JavaScript objects for older browsers, but some
+tests. We polyfill some JavaScript objects for older browsers, but some
 features are still unavailable:
 
 - Array.from
@@ -188,7 +202,7 @@ features are still unavailable:
 - Symbol/Symbol.iterator
 - Spread
 
-Until these are polyfilled appropriately, they should not be used.  Please
+Until these are polyfilled appropriately, they should not be used. Please
 update this list with additional unsupported features.
 
 ### RSpec errors due to JavaScript
@@ -223,7 +237,7 @@ end
 ### Spinach errors due to missing JavaScript
 
 NOTE: **Note:** Since we are discouraging the use of Spinach when writing new
-feature tests, you shouldn't ever need to use this.  This information is kept
+feature tests, you shouldn't ever need to use this. This information is kept
 available for legacy purposes only.
 
 In Spinach, the JavaScript driver is enabled differently. In the `*.feature`
diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md
index 403c9d08752f77d1fef19e0d2bce0618bc03244b..d6a13e7483a4f44421e126e3a269c25524c87e36 100644
--- a/doc/development/writing_documentation.md
+++ b/doc/development/writing_documentation.md
@@ -1,13 +1,9 @@
-# Writing documentation
+# GitLab Documentation guidelines
 
   - **General Documentation**: written by the [developers responsible by creating features](#contributing-to-docs). Should be submitted in the same merge request containing code. Feature proposals (by GitLab contributors) should also be accompanied by its respective documentation. They can be later improved by PMs and Technical Writers.
   - **[Technical Articles](#technical-articles)**: written by any [GitLab Team](https://about.gitlab.com/team/) member, GitLab contributors, or [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/).
   - **Indexes per topic**: initially prepared by the Technical Writing Team, and kept up-to-date by developers and PMs in the same merge request containing code. They gather all resources for that topic in a single page (user and admin documentation, articles, and third-party docs).
 
-## Documentation style guidelines
-
-All the docs follow the same [styleguide](doc_styleguide.md).
-
 ## Contributing to docs
 
 Whenever a feature is changed, updated, introduced, or deprecated, the merge
@@ -19,7 +15,7 @@ The one responsible for writing the first piece of documentation is the develope
 wrote the code. It's the job of the Product Manager to ensure all features are
 shipped with its docs, whether is a small or big change. At the pace GitLab evolves,
 this is the only way to keep the docs up-to-date. If you have any questions about it,
-please ask a Technical Writer. Otherwise, when your content is ready, assign one of
+ask a Technical Writer. Otherwise, when your content is ready, assign one of
 them to review it for you.
 
 We use the [monthly release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/#monthly-releases) as a changelog checklist to ensure everything
@@ -27,23 +23,18 @@ is documented.
 
 Whenever you submit a merge request for the documentation, use the documentation MR description template.
 
-### Documentation directory structure
+Please check the [documentation workflow](https://about.gitlab.com/handbook/product/technical-writing/workflow/) before getting started.
 
-The documentation is structured based on the GitLab UI structure itself,
-separated by [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user),
-[`administrator`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`contributor`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development).
+## Documentation structure
 
-To learn where to place a new document, check the [documentation style guide](doc_styleguide.md#location-and-naming-of-documents).
+- Overview and use cases: what it is, why it is necessary, why one would use it
+- Requirements: what do we need to get started
+- Tutorial: how to set it up, how to use it
 
-In order to have a [solid site structure](https://searchengineland.com/seo-benefits-developing-solid-site-structure-277456) for our documentation,
-all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent.
+Always link a new document from its topic-related index, otherwise, it will
+not be included it in the documentation site search.
 
-The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have
-been deprecated and the majority their docs have been moved to their correct location
-in small iterations. Please don't create new docs in these folders.
-
-To move a document from its location to another directory, read the section
-[changing document location](doc_styleguide.md#changing-document-location) of the doc style guide.
+_Note: to be extended._
 
 ### Feature overview and use cases
 
@@ -73,16 +64,169 @@ overview there.
 > **Overview** and **use cases** are required to **every** Enterprise Edition feature,
 and for every **major** feature present in Community Edition.
 
-### Markdown
+## Markdown and styles
 
 Currently GitLab docs use Redcarpet as [markdown](../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future.
 
-### Previewing locally
+All the docs follow the [documentation style guidelines](doc_styleguide.md).
 
-To preview your changes to documentation locally, please follow
-this [development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
+## Documentation directory structure
+
+The documentation is structured based on the GitLab UI structure itself,
+separated by [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user),
+[`administrator`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`contributor`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development). 
+
+In order to have a [solid site structure](https://searchengineland.com/seo-benefits-developing-solid-site-structure-277456) for our documentation,
+all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent.
+
+The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have
+been deprecated and the majority their docs have been moved to their correct location
+in small iterations. Please don't create new docs in these folders.
+
+### Location and naming documents
+
+The documentation hierarchy can be vastly improved by providing a better layout
+and organization of directories.
+
+Having a structured document layout, we will be able to have meaningful URLs
+like `docs.gitlab.com/user/project/merge_requests/index.html`. With this pattern,
+you can immediately tell that you are navigating a user related documentation
+and is about the project and its merge requests.
+
+Do not create summaries of similar types of content (e.g. an index of all articles, videos, etc.),
+rather organize content by its subject (e.g. everything related to CI goes together)
+and cross-link between any related content.
+
+The table below shows what kind of documentation goes where.
+
+| Directory | What belongs here |
+| --------- | -------------- |
+| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. |
+| `doc/administration/`  | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. |
+| `doc/api/` | API related documentation. |
+| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. |
+| `doc/legal/` | Legal documents about contributing to GitLab. |
+| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
+| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
+| `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) |
+
+---
+
+**General rules:**
+
+1. The correct naming and location of a new document, is a combination
+   of the relative URL of the document in question and the GitLab Map design
+   that is used for UX purposes ([source][graffle], [image][gitlab-map]).
+1. When creating a new document and it has more than one word in its name,
+   make sure to use underscores instead of spaces or dashes (`-`). For example,
+   a proper naming would be `import_projects_from_github.md`. The same rule
+   applies to images.
+1. Start a new directory with an `index.md` file.
+1. There are four main directories, `user`, `administration`, `api` and `development`.
+1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
+   `profile/`, `dashboard/` and `admin_area/`.
+   1. `doc/user/project/` should contain all project related documentation.
+   1. `doc/user/group/` should contain all group related documentation.
+   1. `doc/user/profile/` should contain all profile related documentation.
+      Every page you would navigate under `/profile` should have its own document,
+      i.e. `account.md`, `applications.md`, `emails.md`, etc.
+   1. `doc/user/dashboard/` should contain all dashboard related documentation.
+   1. `doc/user/admin_area/` should contain all admin related documentation
+      describing what can be achieved by accessing GitLab's admin interface
+      (_not to be confused with `doc/administration` where server access is
+      required_).
+      1. Every category under `/admin/application_settings` should have its
+         own document located at `doc/user/admin_area/settings/`. For example,
+         the **Visibility and Access Controls** category should have a document
+         located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
+1. The `doc/topics/` directory holds topic-related technical content. Create
+   `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary.
+   General user- and admin- related documentation, should be placed accordingly.
+
+If you are unsure where a document should live, you can ping `@axil` or `@marcia` in your
+merge request.
+
+### Changing document location
+
+Changing a document's location is not to be taken lightly. Remember that the
+documentation is available to all installations under `help/` and not only to
+GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the
+Documentation team beforehand.
+
+If you indeed need to change a document's location, do NOT remove the old
+document, but rather replace all of its contents with a new line:
+
+```
+This document was moved to [another location](path/to/new_doc.md).
+```
+
+where `path/to/new_doc.md` is the relative path to the root directory `doc/`.
+
+---
+
+For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
+`doc/administration/lfs.md`, then the steps would be:
+
+1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
+1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
+
+    ```
+    This document was moved to [another location](../../administration/lfs.md).
+    ```
+
+1. Find and replace any occurrences of the old location with the new one.
+   A quick way to find them is to use `git grep`. First go to the root directory
+   where you cloned the `gitlab-ce` repository and then do:
+
+    ```
+    git grep -n "workflow/lfs/lfs_administration"
+    git grep -n "lfs/lfs_administration"
+    ```
+
+NOTE: **Note:**
+If the document being moved has any Disqus comments on it, there are extra steps
+to follow documented just [below](#redirections-for-pages-with-disqus-comments).
+
+Things to note:
+
+- Since we also use inline documentation, except for the documentation itself,
+  the document might also be referenced in the views of GitLab (`app/`) which will
+  render when visiting `/help`, and sometimes in the testing suite (`spec/`).
+- The above `git grep` command will search recursively in the directory you run
+  it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration`
+  and will print the file and the line where this file is mentioned.
+  You may ask why the two greps. Since we use relative paths to link to
+  documentation, sometimes it might be useful to search a path deeper.
+- The `*.md` extension is not used when a document is linked to GitLab's
+  built-in help page, that's why we omit it in `git grep`.
+- Use the checklist on the documentation MR description template.
+
+### Redirections for pages with Disqus comments
+
+If the documentation page being relocated already has any Disqus comments,
+we need to preserve the Disqus thread.
+
+Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier
+is configured to be the page URL. Therefore, when we change the document location,
+we need to preserve the old URL as the same Disqus identifier.
+
+To do that, add to the frontmatter the variable `redirect_from`,
+using the old URL as value. For example, let's say I moved the document
+available under `https://docs.gitlab.com/my-old-location/README.html` to a new location,
+`https://docs.gitlab.com/my-new-location/index.html`.
+
+Into the **new document** frontmatter add the following:
+
+```yaml
+---
+redirect_from: 'https://docs.gitlab.com/my-old-location/README.html'
+---
+```
+
+Note: it is necessary to include the file name in the `redirect_from` URL,
+even if it's `index.html` or `README.html`.
 
-### Testing
+## Testing
 
 We treat documentation as code, thus have implemented some testing.
 Currently, the following tests are in place:
@@ -101,7 +245,7 @@ Currently, the following tests are in place:
     Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
     essential to avoid them.
 
-### Branch naming
+## Branch naming
 
 If your contribution contains **only** documentation changes, you can speed up
 the CI process by following some branch naming conventions. You have three
@@ -116,7 +260,53 @@ choices:
 If your branch name matches any of the above, it will run only the docs
 tests. If it doesn't, the whole test suite will run (including docs).
 
-### Previewing the changes live
+## Merge requests for GitLab documentation
+
+Before getting started, make sure you read the introductory section
+"[contributing to docs](#contributing-to-docs)" above and the
+[tech writing workflow](https://about.gitlab.com/handbook/product/technical-writing/workflow/)
+for GitLab Team members.
+
+- Use the current [merge request description template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Documentation.md)
+- Use the correct [branch name](#branch-naming)
+- Label the MR `Documentation`
+- Assign the correct milestone (see note below)
+
+
+NOTE: **Note:**
+If the release version you want to add the documentation to has already been
+frozen or released, use the label `Pick into X.Y` to get it merged into
+the correct release. Avoid picking into a past release as much as you can, as
+it increases the work of the release managers.
+
+### Cherry-picking from CE to EE
+
+As we have the `master` branch of CE merged into EE once a day, it's common to
+run into merge conflicts. To avoid them, we [test for merge conflicts against EE](#testing)
+with the `ee-compat-check` job, and use the following method of creating equivalent
+branches for CE and EE.
+
+Follow this [method for cherry-picking from CE to EE](automatic_ce_ee_merge.md#cherry-picking-from-ce-to-ee), with a few adjustments:
+
+- Create the [CE branch](#branch-naming) starting with `docs-`,
+  e.g.: `git checkout -b docs-example`
+- Create the EE-equivalent branch ending with `-ee`, e.g.,
+  `git checkout -b docs-example-ee`
+- Once all the jobs are passing in CE and EE, and you've addressed the
+feedback from your own team, assign the CE MR to a technical writer for review
+- When both MRs are ready, the EE merge request will be merged first, and the
+CE-equivalent will be merged next.
+- Note that the review will occur only in the CE MR, as the EE MR
+contains the same commits as the CE MR.
+- If you have a few more changes that apply to the EE-version only, you can submit
+a couple more commits to the EE branch, but ask the reviewer to review the EE merge request
+additionally to the CE MR. If there are many EE-only changes though, start a new MR
+to EE only.
+
+## Previewing the changes live
+
+To preview your changes to documentation locally, please follow
+this [development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
 
 If you want to preview the doc changes of your merge request live, you can use
 the manual `review-docs-deploy` job in your merge request. You will need at
@@ -176,7 +366,7 @@ working on. If you don't, the remote docs branch won't be removed either,
 and the server where the Review Apps are hosted will eventually be out of
 disk space.
 
-#### Technical aspects
+### Technical aspects
 
 If you want to know the hot details, here's what's really happening:
 
@@ -211,6 +401,74 @@ The following GitLab features are used among others:
 - [Artifacts](../ci/yaml/README.md#artifacts)
 - [Specific Runner](../ci/runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
 
+## GitLab `/help`
+
+Every GitLab instance includes the documentation, which is available from `/help`
+(`http://my-instance.com/help`), e.g., <https://gitlab.com/help>.
+
+When you're building a new feature, you may need to link the documentation
+from GitLab, the application. This is normally done in files inside the
+`app/views/` directory with the help of the `help_page_path` helper method.
+
+In its simplest form, the HAML code to generate a link to the `/help` page is:
+
+```haml
+= link_to 'Help page', help_page_path('user/permissions')
+```
+
+The `help_page_path` contains the path to the document you want to link to with
+the following conventions:
+
+- it is relative to the `doc/` directory in the GitLab repository
+- the `.md` extension must be omitted
+- it must not end with a slash (`/`)
+
+Below are some special cases where should be used depending on the context.
+You can combine one or more of the following:
+
+1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
+   method:
+
+    ```haml
+    = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
+    ```
+
+1. **Opening links in a new tab.** This should be the default behavior:
+
+    ```haml
+    = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
+    ```
+
+1. **Linking to a circle icon.** Usually used in settings where a long
+   description cannot be used, like near checkboxes. You can basically use
+   any font awesome icon, but prefer the `question-circle`:
+
+    ```haml
+    = link_to icon('question-circle'), help_page_path('user/permissions')
+    ```
+
+1. **Using a button link.** Useful in places where text would be out of context
+   with the rest of the page layout:
+
+    ```haml
+    = link_to 'Help page', help_page_path('user/permissions'),  class: 'btn btn-info'
+    ```
+
+1. **Using links inline of some text.**
+
+    ```haml
+    Description to #{link_to 'Help page', help_page_path('user/permissions')}.
+    ```
+
+1. **Adding a period at the end of the sentence.** Useful when you don't want
+   the period to be part of the link:
+
+    ```haml
+    = succeed '.' do
+      Learn more in the
+      = link_to 'Help page', help_page_path('user/permissions')
+    ```
+
 ## General Documentation vs Technical Articles
 
 ### General documentation
@@ -225,7 +483,7 @@ They are topic-related documentation, written with an user-friendly approach and
 
 A technical article guides users and/or admins to achieve certain objectives (within guides and tutorials), or provide an overview of that particular topic or feature (within technical overviews). It can also describe the use, implementation, or integration of third-party tools with GitLab.
 
-They should be placed in a new directory named `/article-title/index.md` under a topic-related folder, and their images should be placed in `/article-title/img/`. For example, a new article on GitLab Pages should be placed in `doc/user/project/pages/article-title/` and a new article on GitLab CI/CD should be placed in `doc/ci/article-title/`.
+They should be placed in a new directory named `/article-title/index.md` under a topic-related folder, and their images should be placed in `/article-title/img/`. For example, a new article on GitLab Pages should be placed in `doc/user/project/pages/article-title/` and a new article on GitLab CI/CD should be placed in `doc/ci/examples/article-title/`.
 
 #### Types of Technical Articles
 
@@ -279,3 +537,5 @@ date: 2017-02-01
 
 Use the [writing method](https://about.gitlab.com/handbook/product/technical-writing/#writing-method) defined by the Technical Writing team.
 
+[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png
+[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md
index 75bae3245851f03b78072393c5d835db2b8953ee..f656057e3dafb26df51781c722a6bc503c0918af 100644
--- a/doc/downgrade_ee_to_ce/README.md
+++ b/doc/downgrade_ee_to_ce/README.md
@@ -70,7 +70,7 @@ To downgrade an Omnibus installation, it is sufficient to install the Community
 Edition package on top of the currently installed one. You can do this manually,
 by directly [downloading the package](https://packages.gitlab.com/gitlab/gitlab-ce)
 you need, or by adding our CE package repository and following the
-[CE installation instructions](https://about.gitlab.com/downloads/).
+[CE installation instructions](https://about.gitlab.com/installation/?version=ce).
 
 **Source Installation**
 
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index 2a531193adf03e6992771335dffca7069c2946dd..c9766040234dc6aa26e68316be71359f7c67d832 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -71,7 +71,7 @@ rm NAME-OF-FILE
 ### Remove a directory and all of its contents
 
 ```
-rm -rf NAME-OF-DIRECTORY
+rm -r NAME-OF-DIRECTORY
 ```
 
 ### View history in the command line
diff --git a/doc/img/devops_lifecycle.png b/doc/img/devops_lifecycle.png
new file mode 100644
index 0000000000000000000000000000000000000000..0616be46df8d2bb32b8823ee23d6a5f1cdf332cf
Binary files /dev/null and b/doc/img/devops_lifecycle.png differ
diff --git a/doc/install/README.md b/doc/install/README.md
index 87f6969b415c9dc937d6cded22dbd47f8a7884ea..5dadf57ea9a15ab5f93d535aaac4af68e202d526 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -31,12 +31,12 @@ the hardware requirements.
 - [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/) via [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/)
 - [Install GitLab on Azure](azure/index.md)
 - [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md)
-- [Install GitLab on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on
-the full process of installing GitLab on Google Container Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production.
+- [Install GitLab on Google Kubernetes Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on
+the full process of installing GitLab on Google Kubernetes Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production.
 - [Install on AWS](https://about.gitlab.com/aws/)
 - _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) -
   Quickly test any version of GitLab on DigitalOcean using Docker Machine.
-- [Getting started with GitLab and DigitalOcean](ttps://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): requirements, installation process, updates.
+- [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): requirements, installation process, updates.
 - [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/): video demonstration on how to install GitLab on Kubernetes, build a project, create Review Apps, store Docker images in Container Registry, deploy to production on Kubernetes, and monitor with Prometheus.
 
 ## Database
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 170d92faa0953339180f22f11b1bb199e0862b96..fa5bcfa6f07db51b211034e8dd8447b8cc7fa68c 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source.
 
     # Download and compile from source
     cd /tmp
-    curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.2.tar.gz
-    echo '9acc4339b7a2ab484eea69d705923271682b7058015219cf5a7e6ed8dee5b5fb  git-2.16.2.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.2.tar.gz
-    cd git-2.16.2/
+    curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.3.tar.gz
+    echo 'dda229e9c73f4fbb7d4324e0d993e11311673df03f73b194c554c2e9451e17cd  git-2.16.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.3.tar.gz
+    cd git-2.16.3/
     ./configure
     make prefix=/usr/local all
 
@@ -133,9 +133,10 @@ Remove the old Ruby 1.8 if present:
 Download Ruby and compile it:
 
     mkdir /tmp/ruby && cd /tmp/ruby
-    curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.gz
-    echo '4e6a0f828819e15d274ae58485585fc8b7caace0  ruby-2.3.6.tar.gz' | shasum -c - && tar xzf ruby-2.3.6.tar.gz
-    cd ruby-2.3.6
+    curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.7.tar.gz
+    echo '540996fec64984ab6099e34d2f5820b14904f15a  ruby-2.3.7.tar.gz' | shasum -c - && tar xzf ruby-2.3.7.tar.gz
+    cd ruby-2.3.7
+
     ./configure --disable-install-rdoc
     make
     sudo make install
@@ -162,13 +163,14 @@ page](https://golang.org/dl).
 
 ## 4. Node
 
-Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile
-javascript assets, and yarn >= v0.17.0 to manage javascript dependencies.
-In many distros the versions provided by the official package  repositories
-are out of date, so we'll need to install through the following commands:
+Since GitLab 8.17, GitLab requires the use of Node to compile javascript
+assets, and Yarn to manage javascript dependencies. The current minimum
+requirements for these are node >= v6.0.0 and yarn >= v1.2.0. In many distros
+the versions provided by the official package repositories are out of date, so
+we'll need to install through the following commands:
 
-    # install node v7.x
-    curl --location https://deb.nodesource.com/setup_7.x | sudo bash -
+    # install node v8.x
+    curl --location https://deb.nodesource.com/setup_8.x | sudo bash -
     sudo apt-get install -y nodejs
 
     curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
@@ -299,9 +301,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 10-5-stable gitlab
+    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-7-stable gitlab
 
-**Note:** You can change `10-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `10-6-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/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index 1f53e12d5f876a0e6a2197777c3d0e3f467f5b77..a03c49cbd89b748981b7629ec0f4773f59646b34 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -72,6 +72,18 @@ concurrent: 10
 ##
 checkInterval: 30
 
+## For RBAC support:
+rbac:
+  create: false
+
+  ## Run the gitlab-bastion container with the ability to deploy/manage containers of jobs
+  ## cluster-wide or only within namespace
+  clusterWideAccess: false
+
+  ## Use the following Kubernetes Service Account name if RBAC is disabled in this Helm chart (see rbac.create)
+  ##
+  # serviceAccountName: default
+
 ## Configuration for the Pods that that the runner launches for each new job
 ##
 runners:
@@ -80,7 +92,7 @@ runners:
   image: ubuntu:16.04
 
   ## Run all containers with the privileged flag enabled
-  ## This will allow the docker:dind image to run if you need to run Docker
+  ## This will allow the docker:stable-dind image to run if you need to run Docker
   ## commands. Please read the docs before turning this on:
   ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
   ##
@@ -116,6 +128,12 @@ runners:
 
 ```
 
+### Enabling RBAC support
+
+If your cluster has RBAC enabled, you can choose to either have the chart create its own sevice account or provide one.
+
+To have the chart create the service account for you, set `rbac.create` to true. 
+
 ### Controlling maximum Runner concurrency
 
 A single GitLab Runner deployed on Kubernetes is able to execute multiple jobs in parallel by automatically starting additional Runner pods. The [`concurrent` setting](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section) controls the maximum number of pods allowed at a single time, and defaults to `10`.
@@ -147,7 +165,7 @@ enable privileged mode in `values.yaml`:
 ```yaml
 runners:
   ## Run all containers with the privileged flag enabled
-  ## This will allow the docker:dind image to run if you need to run Docker
+  ## This will allow the docker:stable-dind image to run if you need to run Docker
   ## commands. Please read the docs before turning this on:
   ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
   ##
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index cd889e744874cac8264b8a728b1aa364aeaf148c..aa9b8777359b3a741afc91b03ce3d9e7208f1fa1 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -10,7 +10,7 @@ should be deployed, upgraded, and configured.
 ## Chart Overview
 
 * **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart).
-* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components.
+* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in alpha. Will support large deployments with horizontal scaling of individual GitLab components.
 * Other Charts
   * [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner.
   * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box.
@@ -35,9 +35,9 @@ By offering individual containers and charts, we will be able to provide a numbe
 * Potential for rolling updates and canaries within a service,
 * and plenty more.
 
-This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We are planning to launch this chart in beta by the end of 2017.
+Presently this chart is available in alpha for testing, and not recommended for production use. 
 
-Learn more about the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md).
+Learn more about the [cloud native GitLab chart here ](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) and [here [Video]](https://youtu.be/Z6jWR8Z8dv8).
 
 ## Other Charts
 
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 1f46ee4c1ea8b5b03f69bf72f3f80c90ba7423d1..e6ccfccd33fd0e637179e1878228d35ebe665ef8 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -464,7 +464,9 @@ bother us. In any case, it is something to keep in mind when deploying GitLab
 on a production cluster.
 
 In order to deploy GitLab on a production cluster, you will need to assign the
-GitLab service account  to the `anyuid` Security Context.
+GitLab service account to the `anyuid` [Security Context Constraints][scc].
+
+For OpenShift v3.0, you will need to do this manually:
 
 1. Edit the Security Context:
    ```sh
@@ -477,6 +479,12 @@ GitLab service account  to the `anyuid` Security Context.
 
 1. Save and exit the editor
 
+For OpenShift v3.1 and above, you can do:
+
+```sh
+oc adm policy add-scc-to-user anyuid system:serviceaccount:gitlab:gitlab-ce-user
+```
+
 ## Conclusion
 
 By now, you should have an understanding of the basic OpenShift Origin concepts
@@ -513,3 +521,4 @@ PaaS and managing your applications with the ease of containers.
 [autoscaling]: https://docs.openshift.org/latest/dev_guide/pod_autoscaling.html "Documentation - Autoscale"
 [basic-cli]: https://docs.openshift.org/latest/cli_reference/basic_cli_operations.html "Documentation - Basic CLI operations"
 [openshift-docs]: https://docs.openshift.org "OpenShift documentation"
+[scc]: https://docs.openshift.org/latest/admin_guide/manage_scc.html "Documentation - Managing Security Context Constraints"
\ No newline at end of file
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index b2c9177e6ebedec4a6d240d02eb9fc4a132fec19..1f2b4d9d3d9484b7ed8b638f100f40cc69f6e592 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -137,6 +137,14 @@ 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.
 
+#### Additional requirements for GitLab Geo
+
+If you are using [GitLab Geo](https://docs.gitlab.com/ee/development/geo.html), the [tracking database](https://docs.gitlab.com/ee/development/geo.html#geo-tracking-database) also requires the `postgres_fdw` extension.
+
+```
+CREATE EXTENSION postgres_fdw;
+```
+
 ## 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.
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index c39d7ab57c66e9844df6cf9b079d1cff5c0ef8e1..a75836a915a54f4cafb74ef6abeeab0ad40bd04e 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -56,7 +56,8 @@ for initial settings.
           "name" => "auth0",
           "args" => { client_id: 'YOUR_AUTH0_CLIENT_ID',
                       client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
-                      namespace: 'YOUR_AUTH0_DOMAIN'
+                      domain: 'YOUR_AUTH0_DOMAIN',
+                      scope: 'openid profile email'
                     }
         }
       ]
@@ -69,8 +70,8 @@ for initial settings.
           args: {
             client_id: 'YOUR_AUTH0_CLIENT_ID',
             client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
-            namespace: 'YOUR_AUTH0_DOMAIN'
-            }
+            domain: 'YOUR_AUTH0_DOMAIN',
+            scope: 'openid profile email' }
         }
     ```
 
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 07a700f7b64ea758865293bc0d92e42207401619..ae1d848f4394a75702dce6eade36a19314fb8b1a 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -35,7 +35,7 @@ In Google's side:
 
 1. You should now be able to see a Client ID and Client secret. Note them down
    or keep this page open as you will need them later.
-1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Compute > Google Container Engine API > Enable**
+1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Compute > Google Kubernetes Engine API > Enable**
 
 On your GitLab server:
 
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 20087a981f9afdeb9bbe599fb3e3e3a0b4b04edd..3edde3de83de5a9c126229d38f738f389518da29 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -32,6 +32,7 @@ contains some settings that are common for all providers.
 - [Auth0](auth0.md)
 - [Authentiq](../administration/auth/authentiq.md)
 - [OAuth2Generic](oauth2_generic.md)
+- [JWT](../administration/auth/jwt.md)
 
 ## Initial OmniAuth Configuration
 
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index f8a7dd6b1dc95a1d3d7fc6aedacfa26fef0739f0..3f49432ce9355da6b7066598859b5286dca23535 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -102,9 +102,10 @@ in your SAML IdP:
     installation to generate the correct value).
 
 1.  Change the values of `idp_cert_fingerprint`, `idp_sso_target_url`,
-    `name_identifier_format` to match your IdP. Check
+    `name_identifier_format` to match your IdP. If a fingerprint is used it must
+    be a SHA1 fingerprint; check
     [the omniauth-saml documentation](https://github.com/omniauth/omniauth-saml)
-    for details on these options.
+    for more details on these options.
 
 1.  Change the value of `issuer` to a unique name, which will identify the application
     to the IdP.
@@ -311,6 +312,7 @@ need to be validated using a fingerprint, a certificate or a validator.
 
 For this you need take the following into account:
 
+- If a fingerprint is used, it must be the SHA1 fingerprint
 - If no certificate is provided in the settings, a fingerprint or fingerprint
   validator needs to be provided and the response from the server must contain
   a certificate (`<ds:KeyInfo><ds:X509Data><ds:X509Certificate>`)
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index 36a8844e95348d73762b0ba75dabb5afd24c3bef..7d73026a6c67c074f39f6fdb1805b8c930de7845 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -15,9 +15,10 @@ Taking the trigger term as `project-name`, the commands are:
 | `/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 issue move <id> to <project>` | Moves issue ID `<id>` to `<project>` |
 | `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
 
-Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for 
+Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for
 your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage).
 
 ## Issue commands
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index 8d0afa9e69234fe0338e2d8547967e4e62faff43..7f0285654129786e33eae291fb61e71ddabf25d5 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -44,7 +44,7 @@ This decision is made on a case-by-case basis.
 
 ## Upgrade recommendations
 
-We encourage everyone to run the latest stable release to ensure that you can
+We encourage everyone to run the [latest stable release](https://about.gitlab.com/blog/categories/release/) to ensure that you can
 easily upgrade to the most secure and feature-rich GitLab experience. In order
 to make sure you can easily run the most recent stable release, we are working
 hard to keep the update process simple and reliable.
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 2f916f5dea7410ac27559503f27cf901b2715346..90187617c419f4e02b177340145c9ad1cd90ae50 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -14,3 +14,4 @@ comments: false
 - [Webhooks](web_hooks.md)
 - [Import](import.md) of git repositories in bulk
 - [Rebuild authorized_keys file](http://docs.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
+- [Migrate Uploads](../administration/raketasks/uploads/migrate.md)
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 33a2d7a88a7579ccd87d6dc6ed37acceb48571d3..aa14a39e4c9bd1b85e5932c0bbe824a846f7ca75 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -35,8 +35,8 @@ to clipboard step.
 If you don't see the string or would like to generate a SSH key pair with a
 custom name continue onto the next step.
 
->
-**Note:** Public SSH key may also be named as follows:
+Note that Public SSH key may also be named as follows:
+
 - `id_dsa.pub`
 - `id_ecdsa.pub`
 - `id_ed25519.pub`
@@ -73,7 +73,7 @@ custom name continue onto the next step.
    key pair, but it is not required and you can skip creating a password by
    pressing enter.
 
-     >**Note:**
+     NOTE: **Note:**
      If you want to change the password of your SSH key pair, you can use
      `ssh-keygen -p <keyname>`.
 
@@ -162,11 +162,13 @@ That's why it needs to uniquely map to a single user.
 
 ## Deploy keys
 
+### Per-repository deploy keys
+
 Deploy keys allow read-only or read-write (if enabled) access to one or
 multiple projects with a single SSH key pair.
 
 This is really useful for cloning repositories to your Continuous
-Integration (CI) server. By using deploy keys, you don't have to setup a
+Integration (CI) server. By using deploy keys, you don't have to set up a
 dummy user account.
 
 If you are a project master or owner, you can add a deploy key in the
@@ -185,6 +187,47 @@ a group.
 Deploy keys can be shared between projects, you just need to add them to each
 project.
 
+### Global shared deploy keys
+
+Global Shared Deploy keys allow read-only or read-write (if enabled) access to 
+be configured on any repository in the entire GitLab installation.
+
+This is really useful for integrating repositories to secured, shared Continuous
+Integration (CI) services or other shared services. 
+GitLab administrators can set up the Global Shared Deploy key in GitLab and 
+add the private key to any shared systems.  Individual repositories opt into
+exposing their repsitory using these keys when a project masters (or higher)
+authorizes a Global Shared Deploy key to be used with their project. 
+
+Global Shared Keys can provide greater security compared to Per-Project Deploy
+Keys since an administrator of the target integrated system is the only one
+who needs to know and configure the private key.
+
+GitLab administrators set up Global Deploy keys in the Admin area under the
+section **Deploy Keys**. Ensure keys have a meaningful title as that will be
+the primary way for project masters and owners to identify the correct Global
+Deploy key to add.  For instance, if the key gives access to a SaaS CI instance,
+use the name of that service in the key name if that is all it is used for.
+When creating Global Shared Deploy keys, give some thought to the granularity
+of keys - they could be of very narrow usage such as just a specific service or 
+of broader usage for something like "Anywhere you need to give read access to 
+your repository".
+
+Once a GitLab administrator adds the Global Deployment key, project masters 
+and owners can add it in project's **Settings > Repository** section by expanding the 
+**Deploy Key** section and clicking **Enable** next to the appropriate key listed 
+under **Public deploy keys available to any project**.
+
+NOTE: **Note:**
+The heading **Public deploy keys available to any project** only appears
+if there is at least one Global Deploy Key configured.
+
+CAUTION: **Warning:**
+Defining Global Deploy Keys does not expose any given repository via
+the key until that respository adds the Global Deploy Key to their project.
+In this way the Global Deploy Keys enable access by other systems, but do
+not implicitly give any access just by setting them up.
+
 ## Applications
 
 ### Eclipse
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 5f5ba2b69bcd808205da2a0117ee0ec52bcc8625..fb2ce27bf490035dd831e5d6b875863706b38bf1 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -20,7 +20,8 @@ project in an easy and automatic way:
 1. [Auto Test](#auto-test)
 1. [Auto Code Quality](#auto-code-quality)
 1. [Auto SAST (Static Application Security Testing)](#auto-sast)
-1. [Auto SAST for Docker images](#auto-sast-for-docker-images)
+1. [Auto Dependency Scanning](#auto-dependency-scanning)
+1. [Auto Container Scanning](#auto-container-scanning)
 1. [Auto Review Apps](#auto-review-apps)
 1. [Auto DAST (Dynamic Application Security Testing)](#auto-dast)
 1. [Auto Deploy](#auto-deploy)
@@ -95,7 +96,7 @@ Auto Deploy, and Auto Monitoring will be silently skipped.
 
 The Auto DevOps base domain is required if you want to make use of [Auto
 Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It is defined
-either under the project's CI/CD settings while 
+either under the project's CI/CD settings while
 [enabling Auto DevOps](#enabling-auto-devops) or in instance-wide settings in
 the CI/CD section.
 It can also be set at the project or group level as a variable, `AUTO_DEVOPS_DOMAIN`.
@@ -209,7 +210,7 @@ target branches are also
 > Introduced in [GitLab Ultimate][ee] 10.3.
 
 Static Application Security Testing (SAST) uses the
-[gl-sast Docker image](https://gitlab.com/gitlab-org/gl-sast) to run static
+[SAST Docker image](https://gitlab.com/gitlab-org/security-products/sast) to run static
 analysis on the current code and checks for potential security issues. Once the
 report is created, it's uploaded as an artifact which you can later download and
 check out.
@@ -217,7 +218,20 @@ check out.
 In GitLab Ultimate, any security warnings are also
 [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
 
-### Auto SAST for Docker images
+### Auto Dependency Scanning
+
+> Introduced in [GitLab Ultimate][ee] 10.7.
+
+Dependency Scanning uses the
+[Dependency Scanning Docker image](https://gitlab.com/gitlab-org/security-products/dependency-scanning)
+to run analysis on the project dependencies and checks for potential security issues. Once the
+report is created, it's uploaded as an artifact which you can later download and
+check out.
+
+In GitLab Ultimate, any security warnings are also
+[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/dependency_scanning.html).
+
+### Auto Container Scanning
 
 > Introduced in GitLab 10.4.
 
@@ -228,7 +242,7 @@ created, it's uploaded as an artifact which you can later download and
 check out.
 
 In GitLab Ultimate, any security warnings are also
-[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html).
+[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
 
 ### Auto Review Apps
 
@@ -309,6 +323,18 @@ enable them.
 You can make use of [environment variables](#helm-chart-variables) to automatically
 scale your pod replicas.
 
+It's important to note that when a project is deployed to a Kubernetes cluster,
+it relies on a Docker image that has been pushed to the
+[GitLab Container Registry](../../user/project/container_registry.md). Kubernetes
+fetches this image and uses it to run the application. If the project is public,
+the image can be accessed by Kubernetes without any authentication, allowing us
+to have deployments more usable. If the project is private/internal, the
+Registry requires credentials to pull the image. Currently, this is addressed
+by providing `CI_JOB_TOKEN` as the password that can be used, but this token will
+no longer be valid as soon as the deployment job finishes. This means that
+Kubernetes can run the application, but in case it should be restarted or
+executed somewhere else, it cannot be accessed again.
+
 ### Auto Monitoring
 
 NOTE: **Note:**
@@ -357,12 +383,12 @@ into your project to enable staging and canary deployments, and more.
 ### Custom buildpacks
 
 If the automatic buildpack detection fails for your project, or if you want to
-use a custom buildpack, you can override the buildpack using a project variable
-or a `.buildpack` file in your project:
+use a custom buildpack, you can override the buildpack(s) using a project variable
+or a `.buildpacks` file in your project:
 
 - **Project variable** - Create a project variable `BUILDPACK_URL` with the URL
   of the buildpack to use.
-- **`.buildpack` file** - Add a file in your project's repo called  `.buildpack`
+- **`.buildpacks` file** - Add a file in your project's repo called  `.buildpacks`
   and add the URL of the buildpack to use on a line in the file. If you want to
   use multiple buildpacks, you can enter them in, one on each line.
 
@@ -429,17 +455,19 @@ The following variables can be used for setting up the Auto DevOps domain,
 providing a custom Helm chart, or scaling your application. PostgreSQL can be
 also be customized, and you can easily use a [custom buildpack](#custom-buildpacks).
 
-| **Variable** | **Description** |
-| ------------ | --------------- |
-| `AUTO_DEVOPS_DOMAIN`        | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
-| `AUTO_DEVOPS_CHART`         | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). |
-| `PRODUCTION_REPLICAS`       | The number of replicas to deploy in the production environment; defaults to 1. |
-| `CANARY_PRODUCTION_REPLICAS`| The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. |
-| `POSTGRES_ENABLED`  | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
-| `POSTGRES_USER`     | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
-| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
-| `POSTGRES_DB`       | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. |
-| `BUILDPACK_URL`  | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142`|
+| **Variable**                 | **Description**                                                                                                                                                                                                               |
+| ------------                 | ---------------                                                                                                                                                                                                               |
+| `AUTO_DEVOPS_DOMAIN`         | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops).                                                                                              |
+| `AUTO_DEVOPS_CHART`          | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app).                                                             |
+| `REPLICAS`                   | The number of replicas to deploy; defaults to 1.                                                                                                                                                                              |
+| `PRODUCTION_REPLICAS`        | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1.                                                                                                         |
+| `CANARY_REPLICAS`            | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1                                                                              |
+| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1  |
+| `POSTGRES_ENABLED`           | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL.                                                                                                        |
+| `POSTGRES_USER`              | The PostgreSQL user; defaults to `user`. Set it to use a custom username.                                                                                                                                                     |
+| `POSTGRES_PASSWORD`          | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password.                                                                                                                                     |
+| `POSTGRES_DB`                | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name.                               |
+| `BUILDPACK_URL`              | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
 
 TIP: **Tip:**
 Set up the replica variables using a
@@ -470,8 +498,9 @@ The general rule is: `TRACK_ENV_REPLICAS`. Where:
 That way, you can define your own `TRACK_ENV_REPLICAS` variables with which
 you will be able to scale the pod's replicas easily.
 
-In the example below, the environment's name is `qa` which would result in
-looking for the `QA_REPLICAS` environment variable:
+In the example below, the environment's name is `qa` and it deploys the track
+`foo` which would result in looking for the `FOO_QA_REPLICAS` environment
+variable:
 
 ```yaml
 QA testing:
@@ -479,11 +508,11 @@ QA testing:
   environment:
     name: qa
   script:
-  - deploy qa
+  - deploy foo
 ```
 
-If, in addition, there was also a `track: foo` defined in the application's Helm
-chart, like:
+The track `foo` being referenced would also need to be defined in the
+application's Helm chart, like:
 
 ```yaml
 replicaCount: 1
@@ -505,8 +534,6 @@ service:
   internalPort: 5000
 ```
 
-then the environment variable would be `FOO_QA_REPLICAS`.
-
 ## Currently supported languages
 
 NOTE: **Note:**
diff --git a/doc/update/10.5-to-10.6.md b/doc/update/10.5-to-10.6.md
new file mode 100644
index 0000000000000000000000000000000000000000..2f90fb62c4a2aff89f9c81f551370daddc2cc264
--- /dev/null
+++ b/doc/update/10.5-to-10.6.md
@@ -0,0 +1,361 @@
+---
+comments: false
+---
+
+# From 10.5 to 10.6
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be
+sure to upgrade your interpreter if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.gz
+echo '4e6a0f828819e15d274ae58485585fc8b7caace0  ruby-2.3.6.tar.gz' | shasum -c - && tar xzf ruby-2.3.6.tar.gz
+cd ruby-2.3.6
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Update Node
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v6.0.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v6.0.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 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
+
+sudo -u git -H git fetch --all --prune
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-6-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-6-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+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.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### New Gitaly configuration options required
+
+In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`.
+
+```shell
+echo '
+[gitaly-ruby]
+dir = "/home/git/gitaly/ruby"
+
+[gitlab-shell]
+dir = "/home/git/gitlab-shell"
+' | sudo -u git tee -a /home/git/gitaly/config.toml
+```
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 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';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 11. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-5-stable:config/gitlab.yml.example origin/10-6-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/10-5-stable:lib/support/nginx/gitlab-ssl origin/10-6-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/10-5-stable:lib/support/nginx/gitlab origin/10-6-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-5-stable:lib/support/init.d/gitlab.default.example origin/10-6-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 12. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+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).
+
+### 13. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 14. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (10.5)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 10.4 to 10.5](10.4-to-10.5.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/update/10.6-to-10.7.md b/doc/update/10.6-to-10.7.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a76ae14d2e952b208cc25de2356ff57e8ecf8e3
--- /dev/null
+++ b/doc/update/10.6-to-10.7.md
@@ -0,0 +1,361 @@
+---
+comments: false
+---
+
+# From 10.6 to 10.7
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be
+sure to upgrade your interpreter if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.gz
+echo '4e6a0f828819e15d274ae58485585fc8b7caace0  ruby-2.3.6.tar.gz' | shasum -c - && tar xzf ruby-2.3.6.tar.gz
+cd ruby-2.3.6
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Update Node
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v6.0.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v6.0.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 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
+
+sudo -u git -H git fetch --all --prune
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-7-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+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.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### New Gitaly configuration options required
+
+In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`.
+
+```shell
+echo '
+[gitaly-ruby]
+dir = "/home/git/gitaly/ruby"
+
+[gitlab-shell]
+dir = "/home/git/gitlab-shell"
+' | sudo -u git tee -a /home/git/gitaly/config.toml
+```
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 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';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 11. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-6-stable:config/gitlab.yml.example origin/10-7-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/10-6-stable:lib/support/nginx/gitlab-ssl origin/10-7-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/10-6-stable:lib/support/nginx/gitlab origin/10-7-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-6-stable:lib/support/init.d/gitlab.default.example origin/10-7-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 12. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+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).
+
+### 13. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 14. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (10.5)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 10.5 to 10.6](10.5-to-10.6.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
new file mode 100644
index 0000000000000000000000000000000000000000..7c9e5bf882ee5ea88e1294ea288fe591c143f0d8
--- /dev/null
+++ b/doc/user/admin_area/settings/email.md
@@ -0,0 +1,5 @@
+# Email
+
+## Custom logo
+
+The logo in the header of some emails can be customized, see the [logo customization section](../../../customization/branded_page_and_email_header.md).
diff --git a/doc/user/admin_area/settings/img/update-available.png b/doc/user/admin_area/settings/img/update-available.png
new file mode 100644
index 0000000000000000000000000000000000000000..0dafdad618e248bee5d18a841e239f414c9024bf
Binary files /dev/null and b/doc/user/admin_area/settings/img/update-available.png differ
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index d874688cc29ab1142b786456517fff5fad266a4b..381efdf5d674e9438f37df495c2142fb226b05ab 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -8,20 +8,26 @@ under **Admin area > Settings > Usage statistics**.
 
 ## Version check
 
-GitLab can inform you when an update is available and the importance of it.
+If enabled, version check will inform you if a new version is available and the
+importance of it through a status. This is shown on the help page (i.e. `/help`)
+for all signed in users, and on the admin pages. The statuses are:
 
-No information other than the GitLab version and the instance's hostname (through the HTTP referer)
-are collected.
+* Green: You are running the latest version of GitLab.
+* Orange: An updated version of GitLab is available.
+* Red: The version of GitLab you are running is vulnerable. You should install
+  the latest version with security fixes as soon as possible.
 
-In the **Overview** tab you can see if your GitLab version is up to date. There
-are three cases: 1) you are up to date (green), 2) there is an update available
-(yellow) and 3) your version is vulnerable and a security fix is released (red).
+![Orange version check example](img/update-available.png)
 
-In any case, you will see a message informing you of the state and the
-importance of the update.
+GitLab Inc. collects your instance's version and hostname (through the HTTP
+referer) as part of the version check. No other information is collected.
 
-If enabled, the version status will also be shown in the help page (`/help`)
-for all signed in users.
+This information is used, among other things, to identify to which versions
+patches will need to be back ported, making sure active GitLab instances remain
+secure.
+
+If you disable version check, this information will not be collected.  Enable or
+disable the version check at **Admin area > Settings > Usage statistics**.
 
 ## Usage ping
 
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index 633f16a617ced19552e04c1fe4099c7ff49e9abd..3d38588a9edbf62356a07f4f84250606127cf961 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -32,9 +32,15 @@ When you choose to allow only one of the protocols, a couple of things will happ
 On top of these UI restrictions, GitLab will deny all Git actions on the protocol
 not selected.
 
+CAUTION: **Important:**
+Starting with [GitLab 10.7][ce-18021], HTTP(s) protocol will be allowed for
+git clone/fetch requests done by GitLab Runner from CI/CD Jobs, even if
+_Only SSH_ was selected.
+
 > **Note:** Please keep in mind that disabling an access protocol does not actually
-  block access to the server itself. The ports used for the protocol, be it SSH or
-  HTTP, will still be accessible. What GitLab does is restrict access on the
-  application level.
+block access to the server itself. The ports used for the protocol, be it SSH or
+HTTP, will still be accessible. What GitLab does is restrict access on the
+application level.
 
 [ce-4696]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4696
+[ce-18021]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18021
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index eacfe2baa27b2f2ee5329efe4f197331c02661a5..159109e8954abaa67dc664ab959d9d1aacb95f8a 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -14,6 +14,10 @@ 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.
 
+You could also reply to the notification email in order to reply to a comment,
+provided that [Reply by email] is configured by your GitLab admin. This also
+supports [Markdown] and [quick actions] as if replied from the web.
+
 Apart from the standard comments, you also have the option to create a comment
 in the form of a resolvable or threaded discussion.
 
@@ -283,3 +287,4 @@ edit existing comments. Non-team members are restricted from adding or editing c
 [markdown]: ../markdown.md
 [quick actions]: ../project/quick_actions.md
 [permissions]: ../permissions.md
+[Reply by email]: ../../administration/reply_by_email.md
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..7baccb796c6668251cbf9f9ae8f6a37b05248444
--- /dev/null
+++ b/doc/user/gitlab_com/index.md
@@ -0,0 +1,342 @@
+# GitLab.com settings
+
+In this page you will find information about the settings that are used on
+[GitLab.com](https://about.gitlab.com/pricing).
+
+## SSH host keys fingerprints
+
+Below are the fingerprints for GitLab.com's SSH host keys.
+
+| Algorithm | MD5 | SHA256  |
+| --------- | --- | ------- |
+|  DSA      | `7a:47:81:3a:ee:89:89:64:33:ca:44:52:3d:30:d4:87` | `p8vZBUOR0XQz6sYiaWSMLmh0t9i8srqYKool/Xfdfqw` |
+|  ECDSA    | `f1:d0:fb:46:73:7a:70:92:5a:ab:5d:ef:43:e2:1c:35` | `HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw` |
+|  ED25519  | `2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16` | `eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8` |
+|  RSA      | `b6:03:0e:39:97:9e:d0:e7:24:ce:a3:77:3e:01:42:09` | `ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ` |
+
+## Mail configuration
+
+GitLab.com sends emails from the `mg.gitlab.com` domain via [Mailgun] and has
+its own dedicated IP address (`198.61.254.240`).
+
+## Alternative SSH port
+
+GitLab.com can be reached via a [different SSH port][altssh] for `git+ssh`.
+
+| Setting     | Value               |
+| ---------   | ------------------- |
+| `Hostname`  | `altssh.gitlab.com` |
+| `Port`      | `443`               |
+
+An example `~/.ssh/config` is the following:
+
+```
+Host gitlab.com
+  Hostname altssh.gitlab.com
+  User git
+  Port 443
+  PreferredAuthentications publickey
+  IdentityFile ~/.ssh/gitlab
+```
+
+## GitLab Pages
+
+Below are the settings for [GitLab Pages].
+
+| Setting                 | GitLab.com        | Default       |
+| ----------------------- | ----------------  | ------------- |
+| Domain name             | `gitlab.io`       | -             |
+| IP address              | `52.167.214.135`  | -             |
+| Custom domains support  | yes               | no            |
+| TLS certificates support| yes               | no            |
+
+The maximum size of your Pages site is regulated by the artifacts maximum size
+which is part of [GitLab CI/CD](#gitlab-ci-cd).
+
+## GitLab CI/CD
+
+Below are the current settings regarding [GitLab CI/CD](../../ci/README.md).
+
+| Setting                 | GitLab.com        | Default       |
+| -----------             | ----------------- | ------------- |
+| Artifacts maximum size  | 1G                | 100M          |
+
+## Repository size limit
+
+The maximum size your Git repository is allowed to be including LFS.
+
+| Setting                 | GitLab.com        | Default       |
+| -----------             | ----------------- | ------------- |
+| Repository size including LFS | 10G         | Unlimited     |
+
+## Shared Runners
+
+Shared Runners on GitLab.com run in [autoscale mode] and powered by
+Google Cloud Platform and DigitalOcean. Autoscaling means reduced
+waiting times to spin up CI/CD jobs, and isolated VMs for each project,
+thus maximizing security.
+
+They're free to use for public open source projects and limited to 2000 CI
+minutes per month per group for private projects. Read about all
+[GitLab.com plans](https://about.gitlab.com/pricing/).
+
+In case of DigitalOcean based Runners, all your CI/CD jobs run on ephemeral
+instances with 2GB of RAM, CoreOS and the latest Docker Engine installed.
+Instances provide 2 vCPUs and 60GB of SSD disk space. The default region of the
+VMs is NYC1.
+
+In case of Google Cloud Platform based Runners, all your CI/CD jobs run on
+ephemeral instances with 3.75GB of RAM, CoreOS and the latest Docker Engine
+installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default
+region of the VMs is US East1.
+
+Below are the shared Runners settings.
+
+| Setting                               | GitLab.com                                        | Default    |
+| -----------                           | -----------------                                 | ---------- |
+| [GitLab Runner]                       | [Runner versions dashboard][ci_version_dashboard] | -          |
+| Executor                              | `docker+machine`                                  | -          |
+| Default Docker image                  | `ruby:2.5`                                        | -          |
+| `privileged` (run [Docker in Docker]) | `true`                                            | `false`    |
+
+[ci_version_dashboard]: https://monitor.gitlab.net/dashboard/db/ci?from=now-1h&to=now&refresh=5m&orgId=1&panelId=12&fullscreen&theme=light
+
+### `config.toml`
+
+The full contents of our `config.toml` are:
+
+**DigitalOcean**
+
+```toml
+concurrent = X
+check_interval = 1
+metrics_server = "X"
+sentry_dsn = "X"
+
+[[runners]]
+  name = "docker-auto-scale"
+  request_concurrency = X
+  url = "https://gitlab.com/"
+  token = "SHARED_RUNNER_TOKEN"
+  executor = "docker+machine"
+  environment = [
+    "DOCKER_DRIVER=overlay2"
+  ]
+  limit = X
+  [runners.docker]
+    image = "ruby:2.5"
+    privileged = true
+  [runners.machine]
+    IdleCount = 20
+    IdleTime = 1800
+    OffPeakPeriods = ["* * * * * sat,sun *"]
+    OffPeakTimezone = "UTC"
+    OffPeakIdleCount = 5
+    OffPeakIdleTime = 1800
+    MaxBuilds = 1
+    MachineName = "srm-%s"
+    MachineDriver = "digitalocean"
+    MachineOptions = [
+      "digitalocean-image=X",
+      "digitalocean-ssh-user=core",
+      "digitalocean-region=nyc1",
+      "digitalocean-size=s-2vcpu-2gb",
+      "digitalocean-private-networking",
+      "digitalocean-tags=shared_runners,gitlab_com",
+      "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR",
+      "digitalocean-access-token=DIGITAL_OCEAN_ACCESS_TOKEN",
+    ]
+  [runners.cache]
+    Type = "s3"
+    BucketName = "runner"
+    Insecure = true
+    Shared = true
+    ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
+    AccessKey = "ACCESS_KEY"
+    SecretKey = "ACCESS_SECRET_KEY"
+```
+
+**Google Cloud Platform**
+
+```toml
+concurrent = X
+check_interval = 1
+metrics_server = "X"
+sentry_dsn = "X"
+
+[[runners]]
+  name = "docker-auto-scale"
+  request_concurrency = X
+  url = "https://gitlab.com/"
+  token = "SHARED_RUNNER_TOKEN"
+  executor = "docker+machine"
+  environment = [
+    "DOCKER_DRIVER=overlay2"
+  ]
+  limit = X
+  [runners.docker]
+    image = "ruby:2.5"
+    privileged = true
+  [runners.machine]
+    IdleCount = 20
+    IdleTime = 1800
+    OffPeakPeriods = ["* * * * * sat,sun *"]
+    OffPeakTimezone = "UTC"
+    OffPeakIdleCount = 5
+    OffPeakIdleTime = 1800
+    MaxBuilds = 1
+    MachineName = "srm-%s"
+    MachineDriver = "google"
+    MachineOptions = [
+      "google-project=PROJECT",
+      "google-disk-size=25",
+      "google-machine-type=n1-standard-1",
+      "google-username=core",
+      "google-tags=gitlab-com,srm",
+      "google-use-internal-ip",
+      "google-zone=us-east1-d",
+      "google-machine-image=PROJECT/global/images/IMAGE",
+      "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR"
+    ]
+  [runners.cache]
+    Type = "s3"
+    BucketName = "runner"
+    Insecure = true
+    Shared = true
+    ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
+    AccessKey = "ACCESS_KEY"
+    SecretKey = "ACCESS_SECRET_KEY"
+```
+
+## Sidekiq
+
+GitLab.com runs [Sidekiq][sidekiq] with arguments `--timeout=4 --concurrency=4`
+and the following environment variables:
+
+| Setting                                 | GitLab.com | Default   |
+|--------                                 |----------- |--------   |
+| `SIDEKIQ_MEMORY_KILLER_MAX_RSS`         | `1000000`  | `1000000` |
+| `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL` | `SIGKILL`  | -         |
+| `SIDEKIQ_LOG_ARGUMENTS`                 | `1`        | -         |
+
+## Cron jobs
+
+Periodically executed jobs by Sidekiq, to self-heal Gitlab, do external
+synchronizations, run scheduled pipelines, etc.:
+
+| Setting                     | GitLab.com   | Default      |
+|--------                     |------------- |------------- |
+| `pipeline_schedule_worker`  | `19 * * * *` | `19 * * * *` |
+
+## PostgreSQL
+
+GitLab.com being a fairly large installation of GitLab means we have changed
+various PostgreSQL settings to better suit our needs. For example, we use
+streaming replication and servers in hot-standby mode to balance queries across
+different database servers.
+
+The list of GitLab.com specific settings (and their defaults) is as follows:
+
+| Setting                             | GitLab.com                                                          | Default                               |
+|:------------------------------------|:--------------------------------------------------------------------|:--------------------------------------|
+| archive_command                     | `/usr/bin/envdir /etc/wal-e.d/env /opt/wal-e/bin/wal-e wal-push %p` | empty                                 |
+| archive_mode                        | on                                                                  | off                                   |
+| autovacuum_analyze_scale_factor     | 0.01                                                                | 0.01                                  |
+| autovacuum_max_workers              | 6                                                                   | 3                                     |
+| autovacuum_vacuum_cost_limit        | 1000                                                                | -1                                    |
+| autovacuum_vacuum_scale_factor      | 0.01                                                                | 0.02                                  |
+| checkpoint_completion_target        | 0.7                                                                 | 0.9                                   |
+| checkpoint_segments                 | 32                                                                  | 10                                    |
+| effective_cache_size                | 338688MB                                                            | Based on how much memory is available |
+| hot_standby                         | on                                                                  | off                                   |
+| hot_standby_feedback                | on                                                                  | off                                   |
+| log_autovacuum_min_duration         | 0                                                                   | -1                                    |
+| log_checkpoints                     | on                                                                  | off                                   |
+| log_line_prefix                     | `%t [%p]: [%l-1] `                                                  | empty                                 |
+| log_min_duration_statement          | 1000                                                                | -1                                    |
+| log_temp_files                      | 0                                                                   | -1                                    |
+| maintenance_work_mem                | 2048MB                                                              | 16 MB                                 |
+| max_replication_slots               | 5                                                                   | 0                                     |
+| max_wal_senders                     | 32                                                                  | 0                                     |
+| max_wal_size                        | 5GB                                                                 | 1GB                                   |
+| shared_buffers                      | 112896MB                                                            | Based on how much memory is available |
+| shared_preload_libraries            | pg_stat_statements                                                  | empty                                 |
+| shmall                              | 30146560                                                            | Based on the server's capabilities    |
+| shmmax                              | 123480309760                                                        | Based on the server's capabilities    |
+| wal_buffers                         | 16MB                                                                | -1                                    |
+| wal_keep_segments                   | 512                                                                 | 10                                    |
+| wal_level                           | replica                                                             | minimal                               |
+| statement_timeout                   | 15s                                                                 | 60s                                   |
+| idle_in_transaction_session_timeout | 60s                                                                 | 60s                                   |
+
+Some of these settings are in the process being adjusted. For example, the value
+for `shared_buffers` is quite high and as such we are looking into adjusting it.
+More information on this particular change can be found at
+<https://gitlab.com/gitlab-com/infrastructure/issues/1555>. An up to date list
+of proposed changes can be found at
+<https://gitlab.com/gitlab-com/infrastructure/issues?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=database&label_name[]=change>.
+
+## Unicorn
+
+GitLab.com adjusts the memory limits for the [unicorn-worker-killer][unicorn-worker-killer] gem.
+
+Base default:
+* `memory_limit_min` = 750MiB
+* `memory_limit_max` = 1024MiB
+
+Web front-ends:
+* `memory_limit_min` = 1024MiB
+* `memory_limit_max` = 1280MiB
+
+## GitLab.com at scale
+
+In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
+the following applications and settings to achieve scale. All settings are
+located publicly available [chef cookbooks](https://gitlab.com/gitlab-cookbooks).
+
+### ELK
+
+We use Elasticsearch, logstash, and Kibana for part of our monitoring solution:
+
+- [gitlab-cookbooks / gitlab-elk 路 GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-elk)
+- [gitlab-cookbooks / gitlab_elasticsearch 路 GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_elasticsearch)
+
+### Prometheus
+
+Prometheus complete our monitoring stack:
+
+- [gitlab-cookbooks / gitlab-prometheus 路 GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-prometheus)
+
+### Grafana
+
+For the visualization of monitoring data:
+
+- [gitlab-cookbooks / gitlab-grafana 路 GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-grafana)
+
+### Sentry
+
+Open source error tracking:
+
+- [gitlab-cookbooks / gitlab-sentry 路 GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-sentry)
+
+### Consul
+
+Service discovery:
+
+- [gitlab-cookbooks / gitlab_consul 路 GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_consul)
+
+### Haproxy
+
+High Performance TCP/HTTP Load Balancer:
+
+- [gitlab-cookbooks / gitlab-haproxy 路 GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-haproxy)
+
+[autoscale mode]: https://docs.gitlab.com/runner/configuration/autoscale.html "How Autoscale works"
+[runners-post]: https://about.gitlab.com/2016/04/05/shared-runners/ "Shared Runners on GitLab.com"
+[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-runner
+[altssh]: https://about.gitlab.com/2016/02/18/gitlab-dot-com-now-supports-an-alternate-git-plus-ssh-port/ "GitLab.com now supports an alternate git+ssh port"
+[GitLab Pages]: https://about.gitlab.com/features/pages "GitLab Pages"
+[docker in docker]: https://hub.docker.com/_/docker/ "Docker in Docker at DockerHub"
+[mailgun]: https://www.mailgun.com/ "Mailgun website"
+[sidekiq]: http://sidekiq.org/ "Sidekiq website"
+[unicorn-worker-killer]: https://rubygems.org/gems/unicorn-worker-killer "unicorn-worker-killer"
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 88efddbfba87e20ab7a3d45fc0988256c306c676..88f4bb2ee04fe2320088eee8ad19e27fab43fd89 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -245,10 +245,7 @@ To enable this feature, navigate to the group settings page. Select
 
 ![Checkbox for share with group lock](img/share_with_group_lock.png)
 
-#### Member Lock
-
-> Available in [GitLab Starter](https://about.gitlab.com/products/) and
-[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
+#### Member Lock **[STARTER]**
 
 With **Member Lock** it is possible to lock membership in project to the
 level of members in group.
@@ -259,8 +256,8 @@ Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#
 
 - **Projects**: view all projects within that group, add members to each project,
 access each project's settings, and remove any project from the same screen.
-- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md)
-and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Starter](https://about.gitlab.com/products/).)
+- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) to your group.
+- **Push rules**: configure [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group. **[STARTER]**
 - **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
-for the group (GitLab admins only, available in [GitLab Starter][ee]).
+for the group. **[STARTER ONLY]**
 - **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 914a80bcd6a541fed3bd526d81b694b56e575125..a9ba2a51242d53702e9825625682d816a2e41568 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -15,6 +15,10 @@ GitLab [administrators](../README.md#administrator-documentation) receive all pe
 To add or import a user, you can follow the
 [project members documentation](../user/project/members/index.md).
 
+## Principles behind permissions
+
+See our [product handbook on permissions](https://about.gitlab.com/handbook/product#permissions-in-gitlab)
+
 ## Project members permissions
 
 The following table depicts the various user permission levels in a project.
@@ -25,7 +29,8 @@ The following table depicts the various user permission levels in a project.
 | Create confidential issue             | 鉁� [^1]  | 鉁�          | 鉁�           | 鉁�        | 鉁�      |
 | View confidential issues              | (鉁�) [^2] | 鉁�         | 鉁�           | 鉁�        | 鉁�      |
 | Leave comments                        | 鉁� [^1]  | 鉁�          | 鉁�           | 鉁�        | 鉁�      |
-| Lock discussions (issues and merge requests) |  |            |             | 鉁�        | 鉁�      |
+| Lock issue discussions                |         | 鉁�          | 鉁�           | 鉁�        | 鉁�      |
+| Lock merge request discussions        |         |            | 鉁�           | 鉁�        | 鉁�      |
 | See a list of jobs                    | 鉁� [^3]  | 鉁�          | 鉁�           | 鉁�        | 鉁�      |
 | See a job log                         | 鉁� [^3]  | 鉁�          | 鉁�           | 鉁�        | 鉁�      |
 | Download and browse job artifacts     | 鉁� [^3]  | 鉁�          | 鉁�           | 鉁�        | 鉁�      |
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index 022d6317555e8a8fce38a8f61381dd11a7663cab..930e506802ac40ca4b23b377856733ca317a29e9 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -41,7 +41,7 @@ select few, the amount of activity on the default Dashboard page can be
 overwhelming. Changing this setting allows you to redefine what your default
 dashboard will be.
 
-You have 6 options here that you can use for your default dashboard view:
+You have 8 options here that you can use for your default dashboard view:
 
 - Your projects (default)
 - Starred projects
@@ -49,6 +49,8 @@ You have 6 options here that you can use for your default dashboard view:
 - Starred projects' activity
 - Your groups
 - Your [Todos]
+- Assigned Issues
+- Assigned Merge Requests
 
 ### Project home page content
 
diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md
new file mode 100644
index 0000000000000000000000000000000000000000..c4e59444ef7c4bef3df47c55af517db4cce8d192
--- /dev/null
+++ b/doc/user/project/badges.md
@@ -0,0 +1,73 @@
+# Badges
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41174)
+in GitLab 10.7.
+
+Badges are a unified way to present condensed pieces of information about your
+projects. They consist of a small image and additionally a URL that the image
+points to. Examples for badges can be the [pipeline status], [test coverage],
+or ways to contact the project maintainers.
+
+![Badges on Project overview page](img/project_overview_badges.png)
+
+## Project badges
+
+Badges can be added to a project and will then be visible on the project's overview page.
+If you find that you have to add the same badges to several projects, you may want to add them at the [group level](#group-badges).
+
+To add a new badge to a project:
+
+1.  Navigate to your project's **Settings > Badges**.
+1.  Under "Link", enter the URL that the badges should point to and under
+    "Badge image URL" the URL of the image that should be displayed.
+1.  Submit the badge by clicking the **Add badge** button.
+
+After adding a badge to a project, you can see it in the list below the form.
+You can edit it by clicking on the pen icon next to it or to delete it by
+clicking on the trash icon.
+
+Badges associated with a group can only be edited or deleted on the
+[group level](#group-badges).
+
+## Group badges
+
+Badges can be added to a group and will then be visible on every project's
+overview page that's under that group. In this case, they cannot be edited or
+deleted on the project level. If you need to have individual badges for each
+project, consider adding them on the [project level](#project-badges) or use
+[placeholders](#placeholders).
+
+To add a new badge to a group:
+
+1.  Navigate to your group's **Settings > Project Badges**.
+1.  Under "Link", enter the URL that the badges should point to and under
+    "Badge image URL" the URL of the image that should be displayed.
+1.  Submit the badge by clicking the **Add badge** button.
+
+After adding a badge to a group, you can see it in the list below the form.
+You can edit the badge by clicking on the pen icon next to it or to delete it
+by clicking on the trash icon.
+
+Badges directly associated with a project can be configured on the
+[project level](#project-badges).
+
+## Placeholders
+
+The URL a badge points to, as well as the image URL, can contain placeholders
+which will be evaluated when displaying the badge. The following placeholders
+are available:
+
+- `%{project_path}`: Path of a project including the parent groups
+- `%{project_id}`: Database ID associated with a project
+- `%{default_branch}`: Default branch name configured for a project's repository
+- `%{commit_sha}`: ID of the most recent commit to the default branch of a
+  project's repository
+
+## API
+
+You can also configure badges via the GitLab API. As in the settings, there is
+a distinction between endpoints for badges on the
+[project level](../../api/project_badges.md) and [group level](../../api/group_badges.md).
+
+[pipeline status]: pipelines/settings.md#pipeline-status-badge
+[test coverage]: pipelines/settings.md#test-coverage-report-badge
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 4ac54f96aa21e8a3adfbcf7c83eae2b84987372f..716787532fc87877d9792848f057558d0350a3ce 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -71,7 +71,7 @@ You need Master [permissions] and above to access the Kubernetes page.
 To add an existing Kubernetes cluster to your project:
 
 1. Navigate to your project's **CI/CD > Kubernetes** page.
-1. Click on **Add Kuberntes cluster**.
+1. Click on **Add Kubernetes cluster**.
 1. Click on **Add an existing Kubernetes cluster** and fill in the details:
     - **Kubernetes cluster name** (required) - The name you wish to give the cluster.
     - **Environment scope** (required)- The
@@ -101,7 +101,7 @@ To add an existing Kubernetes cluster to your project:
       - If you or someone created a secret specifically for the project, usually
         with limited permissions, the secret's namespace and project namespace may
         be the same.
-1. Finally, click the **Create Kuberntes cluster** button.
+1. Finally, click the **Create Kubernetes cluster** button.
 
 After a few moments, your cluster should be created. If something goes wrong,
 you will be notified.
@@ -109,6 +109,41 @@ you will be notified.
 You can now proceed to install some pre-defined applications and then
 enable the Kubernetes cluster integration.
 
+## Security implications
+
+CAUTION: **Important:**
+The whole cluster security is based on a model where [developers](../../permissions.md)
+are trusted, so **only trusted users should be allowed to control your clusters**.
+
+The default cluster configuration grants access to a wide set of
+functionalities needed to successfully build and deploy a containerized
+application. Bare in mind that the same credentials are used for all the
+applications running on the cluster.
+
+When GitLab creates the cluster, it enables and uses the legacy
+[Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/).
+The newer [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/)
+authorization will be supported in a
+[future release](https://gitlab.com/gitlab-org/gitlab-ce/issues/29398).
+
+### Security of GitLab Runners
+
+GitLab Runners have the [privileged mode](https://docs.gitlab.com/runner/executors/docker.html#the-privileged-mode)
+enabled by default, which allows them to execute special commands and running
+Docker in Docker. This functionality is needed to run some of the [Auto DevOps]
+jobs. This implies the containers are running in privileged mode and you should,
+therefore, be aware of some important details.
+
+The privileged flag gives all capabilities to the running container, which in
+turn can do almost everything that the host can do. Be aware of the
+inherent security risk associated with performing `docker run` operations on
+arbitrary images as they effectively have root access.
+
+If you don't want to use GitLab Runner in privileged mode, first make sure that
+you don't have it installed via the applications, and then use the
+[Runner's Helm chart](../../../install/kubernetes/gitlab_runner_chart.md) to
+install it manually.
+
 ## Installing applications
 
 GitLab provides a one-click install for various applications which will be
@@ -118,20 +153,31 @@ added directly to your configured cluster. Those applications are needed for
 | Application | GitLab version | Description |
 | ----------- | :------------: | ----------- |
 | [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It will be automatically installed as a dependency when you try to install a different app. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. |
-| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. |
+| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. |
 | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications |
-| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. |
+| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. |
 
 ## Getting the external IP address
 
 NOTE: **Note:**
 You need a load balancer installed in your cluster in order to obtain the
 external IP address with the following procedure. It can be deployed using the
-[**Ingress** application](#installing-appplications).
+[**Ingress** application](#installing-applications).
 
 In order to publish your web application, you first need to find the external IP
 address associated to your load balancer.
 
+### Let GitLab fetch the IP address
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
+
+If you installed the Ingress [via the **Applications**](#installing-applications),
+you should see the Ingress IP address on this same page within a few minutes.
+If you don't see this, GitLab might not be able to determine the IP address of
+your ingress application in which case you should manually determine it.
+
+### Manually determining the IP address
+
 If the cluster is on GKE, click on the **Google Kubernetes Engine** link in the
 **Advanced settings**, or go directly to the
 [Google Kubernetes Engine dashboard](https://console.cloud.google.com/kubernetes/)
@@ -158,6 +204,24 @@ The output is the external IP address of your cluster. This information can then
 be used to set up DNS entries and forwarding rules that allow external access to
 your deployed applications.
 
+### Using a static IP
+
+By default, an ephemeral external IP address is associated to the cluster's load
+balancer. If you associate the ephemeral IP with your DNS and the IP changes,
+your apps will not be able to be reached, and you'd have to change the DNS
+record again. In order to avoid that, you should change it into a static
+reserved IP.
+
+[Read how to promote an ephemeral external IP address in GKE.](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#promote_ephemeral_ip)
+
+### Pointing your DNS at the cluster IP
+
+Once you've set up the static IP, you should associate it to a [wildcard DNS
+record](https://en.wikipedia.org/wiki/Wildcard_DNS_record), in order to be able
+to reach your apps. This heavily depends on your domain provider, but in case
+you aren't sure, just create an A record with a wildcard host like
+`*.example.com.`.
+
 ## Setting the environment scope
 
 NOTE: **Note:**
@@ -329,3 +393,4 @@ the deployment variables above, ensuring any pods you create are labelled with
 
 [permissions]: ../../permissions.md
 [ee]: https://about.gitlab.com/products/
+[Auto DevOps]: ../../../topics/autodevops/index.md
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 394aa9209e4f0c404fc86a8f1afb196646fe709f..9c5e3509046ddafaef32a8fce24a0530d026ca2a 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -115,15 +115,16 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do
 
 ## Using with private projects
 
-> [Introduced][ce-11845] in GitLab 9.3.
+> Personal Access tokens were [introduced][ce-11845] in GitLab 9.3.
+> Project Deploy Tokens were [introduced][ce-17894] in GitLab 10.7
 
 If a project is private, credentials will need to be provided for authorization.
-The preferred way to do this, is by using [personal access tokens][pat].
-The minimal scope needed is `read_registry`.
+The preferred way to do this, is either by using a [personal access tokens][pat] or a [project deploy token][pdt].
+The minimal scope needed for both of them is `read_registry`.
 
 Example of using a personal access token:
 ```
-docker login registry.example.com -u <your_username> -p <your_personal_access_token>
+docker login registry.example.com -u <your_username> -p <your_access_token>
 ```
 
 ## Troubleshooting the GitLab Container Registry
@@ -270,5 +271,7 @@ Once the right permissions were set, the error will go away.
 
 [ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
 [ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
+[ce-17894]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894
 [docker-docs]: https://docs.docker.com/engine/userguide/intro/
 [pat]: ../profile/personal_access_tokens.md
+[pdt]: ../project/deploy_tokens/index.md
diff --git a/doc/user/project/deploy_tokens/img/deploy_tokens.png b/doc/user/project/deploy_tokens/img/deploy_tokens.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e2d67a3120e4163dd79aab3c001aaad5c9f8315
Binary files /dev/null and b/doc/user/project/deploy_tokens/img/deploy_tokens.png differ
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..86fc58020e88d28dd991aef7a39bc33ecdffcf34
--- /dev/null
+++ b/doc/user/project/deploy_tokens/index.md
@@ -0,0 +1,76 @@
+# Deploy Tokens
+
+> [Introduced][ce-17894] in GitLab 10.7.
+
+Deploy tokens allow to download (through `git clone`), or read the container registry images of a project without the need of having a user and a password.
+
+Please note, that the expiration of deploy tokens happens on the date you define,
+at midnight UTC and that they can be only managed by [masters](https://docs.gitlab.com/ee/user/permissions.html).
+
+## Creating a Deploy Token
+
+You can create as many deploy tokens as you like from the settings of your project: 
+
+1. Log in to your GitLab account.
+1. Go to the project you want to create Deploy Tokens for.
+1. Go to **Settings** > **Repository**
+1. Click on "Expand" on **Deploy Tokens** section
+1. Choose a name and optionally an expiry date for the token.
+1. Choose the [desired scopes](#limiting-scopes-of-a-deploy-token).
+1. Click on **Create deploy token**.
+1. Save the deploy token somewhere safe. Once you leave or refresh
+   the page, **you won't be able to access it again**.
+
+![Personal access tokens page](img/deploy_tokens.png)
+
+## Revoking a personal access token
+
+At any time, you can revoke any deploy token by just clicking the
+respective **Revoke** button under the 'Active deploy tokens' area.
+
+## Limiting scopes of a deploy token
+
+Deploy tokens can be created with two different scopes that allow various
+actions that a given token can perform. The available scopes are depicted in
+the following table.
+
+| Scope | Description |
+| ----- | ----------- |
+| `read_repository` | Allows read-access to the repository through `git clone` |
+| `read_registry` | Allows read-access to [container registry] images if a project is private and authorization is required. |
+
+## Usage
+
+### Git clone a repository
+
+To download a repository using a Deploy Token, you just need to:
+
+1. Create a Deploy Token with `read_repository` as a scope.
+2. Take note of your `username` and `token`
+3. `git clone` the project using the Deploy Token:
+
+
+```bash
+git clone http://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
+```
+
+Just replace `<username>` and `<deploy_token>` with the proper values
+
+### Read container registry images
+
+To read the container registry images, you'll need to:
+
+1. Create a Deploy Token with `read_registry` as a scope.
+2. Take note of your `username` and `token`
+3. Log in to GitLab鈥檚 Container Registry using the deploy token:
+
+```
+docker login registry.example.com -u <username> -p <deploy_token>
+```
+
+Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply 
+pull images from your Container Registry.
+
+[ce-17894]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894
+[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
+[container registry]: ../container_registry.md
diff --git a/doc/user/project/img/project_overview_badges.png b/doc/user/project/img/project_overview_badges.png
new file mode 100644
index 0000000000000000000000000000000000000000..3067a7dfa131daaab3640149cb9d1a1da740bdcd
Binary files /dev/null and b/doc/user/project/img/project_overview_badges.png differ
diff --git a/doc/user/project/import/img/import_projects_from_repo_url.png b/doc/user/project/import/img/import_projects_from_repo_url.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec867da1087f7aedaa0529528b4c71c7a2160d61
Binary files /dev/null and b/doc/user/project/import/img/import_projects_from_repo_url.png differ
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index e2b285678c340577823a631a3c6abb159e58aac4..72cc58546b74f81d63004d6d21f688104097ab65 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -10,6 +10,7 @@
 1. [From Perforce](perforce.md)
 1. [From SVN](svn.md)
 1. [From TFS](tfs.md)
+1. [From repo by URL](repo_by_url.md)
 
 In addition to the specific migration documentation above, you can import any
 Git repository via HTTP from the New Project page. Be aware that if the
diff --git a/doc/user/project/import/perforce.md b/doc/user/project/import/perforce.md
index aa7508e1e8ee47dcfb3c8cafd3bca2d58bb901b6..a1ea716b6061da25887dd293cb0a09f1cf50aba1 100644
--- a/doc/user/project/import/perforce.md
+++ b/doc/user/project/import/perforce.md
@@ -48,3 +48,9 @@ Here's a few links to get you started:
 - [git-p4 manual page](https://www.kernel.org/pub/software/scm/git/docs/git-p4.html)
 - [git-p4 example usage](https://git.wiki.kernel.org/index.php/Git-p4_Usage)
 - [Git book migration guide](https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git#_perforce_import)
+
+Note that `git p4` and `git filter-branch` are not very good at
+creating small and efficient Git pack files. So it might be a good
+idea to spend time and CPU to properly repack your repository before
+sending it for the first time to your GitLab server. See
+[this StackOverflow question](https://stackoverflow.com/questions/28720151/git-gc-aggressive-vs-git-repack/).
diff --git a/doc/user/project/import/repo_by_url.md b/doc/user/project/import/repo_by_url.md
new file mode 100644
index 0000000000000000000000000000000000000000..f43e384de88a3487ff290ce941c8d9e3cf86703e
--- /dev/null
+++ b/doc/user/project/import/repo_by_url.md
@@ -0,0 +1,12 @@
+# Import project from repo by URL
+
+You can import your existing repositories by providing the Git URL:
+
+1. From your GitLab dashboard click **New project**
+1. Switch to the **Import project** tab
+1. Click on the **Repo by URL** button
+1. Fill in the "Git repository URL" and the remaining project fields
+1. Click **Create project** to being the import process
+1. Once complete, you will be redirected to your newly created project
+
+![Import project by repo URL](img/import_projects_from_repo_url.png)
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 175a8975ae1e83d73f20d2dc0a5bf6a35856b2a1..557375a1da977fa4093d130c6de8642b6aed75ce 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -17,7 +17,7 @@ When you create a project in GitLab, you'll have access to a large number of
 
 - [Issue tracker](issues/index.md): Discuss implementations with your team within issues
   - [Issue Boards](issue_board.md): Organize and prioritize your workflow
-  - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) (**Starter/Premium**): Allow your teams to create their own workflows (Issue Boards) for the same project
+  - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
 - [Repositories](repository/index.md): Host your code in a fully
 integrated platform
   - [Branches](repository/branches/index.md): use Git branching strategies to
@@ -27,10 +27,11 @@ integrated platform
   - [Protected tags](protected_tags.md): Control over who has
   permission to create tags, and prevent accidental update or deletion
   - [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits
+  - [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry.
 - [Merge Requests](merge_requests/index.md): Apply your branching
 strategy and get reviewed by your team
-  - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) (**Starter/Premium**): Ask for approval before
-  implementing a change
+  - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before
+  implementing a change **[STARTER]**
   - [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
   Your Git diff tool right from GitLab's UI
   - [Review Apps](../../ci/review_apps/index.md): Live preview the results
@@ -44,6 +45,7 @@ and time spent on
 templates for issue and merge request description fields for your project
 - [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for
 common actions on issues or merge requests
+- [Web IDE](web_ide/index.md)
 
 **GitLab CI/CD:**
 
@@ -73,6 +75,7 @@ website with GitLab Pages
 - [Cycle Analytics](cycle_analytics.md): Review your development lifecycle
 - [Syntax highlighting](highlighting.md): An alternative to customize
 your code blocks, overriding GitLab's default choice of language
+- [Badges](badges.md): Badges for the project overview
 
 ### Project's integrations
 
@@ -128,11 +131,9 @@ and Git push/pull redirects.
 
 Depending on the situation, different things apply.
 
-When [renaming a user](../profile/index.md#changing-your-username) or
-[changing a group path](../group/index.md#changing-a-group-s-path):
+When [renaming a user](../profile/index.md#changing-your-username),
+[changing a group path](../group/index.md#changing-a-group-s-path) or [renaming a repository](settings/index.md#renaming-a-repository):
 
-- **The redirect to the new URL is permanent**, which means that the original
-  namespace can't be claimed again by any group or user.
 - Existing web URLs for the namespace and anything under it (e.g., projects) will
   redirect to the new URLs.
 - Starting with GitLab 10.3, existing Git remote URLs for projects under the
@@ -141,9 +142,5 @@ When [renaming a user](../profile/index.md#changing-your-username) or
   your remote will be displayed instead of rejecting your action.
   This means that any automation scripts, or Git clients will continue to
   work after a rename, making any transition a lot smoother.
-  To avoid pulling from or pushing to an entirely incorrect repository, the old
-  path will be reserved.
-
-When [renaming-a-repository](settings/index.md#renaming-a-repository), the same
-things apply, except for the Git push/pull actions which will be rejected with a
-warning message to change to the new remote URL.
+- The redirects will be available as long as the original path is not claimed by
+  another group, user or project.
diff --git a/doc/user/project/integrations/custom_issue_tracker.md b/doc/user/project/integrations/custom_issue_tracker.md
index 731291ebe844721312a9c4ec9490426cb7d1ff8c..6fc083170b69b2eca1841ac31216093503307578 100644
--- a/doc/user/project/integrations/custom_issue_tracker.md
+++ b/doc/user/project/integrations/custom_issue_tracker.md
@@ -15,8 +15,8 @@ in the table below.
 
 Once you have configured and enabled Custom Issue Tracker Service you'll see a link on the GitLab project pages that takes you to that custom issue tracker.
 
-
 ## Referencing issues
 
-Issues are referenced with `#<ID>`, where `<ID>` is a number (example `#143`). 
-So with the example above, `#143` would refer to `https://customissuetracker.com/project-name/143`.
\ No newline at end of file
+- Issues are referenced with `ANYTHING-<ID>`, where `ANYTHING` can be any string and `<ID>` is a number used in the target project of the custom integration (example `PROJECT-143`). 
+- `ANYTHING` is a placeholder to differentiate against GitLab issues, which are referenced with `#<ID>`. You can use a project name or project key to replace it for example.
+- So with the example above, `PROJECT-143` would refer to `https://customissuetracker.com/project-name/143`.
\ No newline at end of file
diff --git a/doc/user/project/integrations/img/jira_workflow_screenshot.png b/doc/user/project/integrations/img/jira_workflow_screenshot.png
deleted file mode 100644
index e62fb202613febb8cc0a681944aad7e028f12db6..0000000000000000000000000000000000000000
Binary files a/doc/user/project/integrations/img/jira_workflow_screenshot.png and /dev/null differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index fc527663db052ee033bb6b9bbbc2721c00e35aaa..5933bcedc8b612394055503827a847d5dc1d108f 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -113,7 +113,20 @@ in the table below.
 | `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
 | `Username` | The user name created in [configuring JIRA step](#configuring-jira). |
 | `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
-| `Transition ID` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
+| `Transition ID` | This is the ID of a transition that moves issues to the desired state.  **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
+
+### Getting a transition ID
+
+In the most recent JIRA UI, you can no longer see transition IDs in the workflow
+administration UI. You can get the ID you need in either of the following ways:
+
+1. By using the API, with a request like `https://yourcompany.atlassian.net/rest/api/2/issue/ISSUE-123/transitions`
+   using an issue that is in the appropriate "open" state
+1. By mousing over the link for the transition you want and looking for the
+   "action" parameter in the URL
+
+Note that the transition ID may vary between workflows (e.g., bug vs. story),
+even if the status you are changing to is the same.
 
 After saving the configuration, your GitLab project will be able to interact
 with all JIRA projects in your JIRA instance and you'll see the JIRA link on the GitLab project pages that takes you to the appropriate JIRA project.
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 249463fb86e1d54c4c9079c84d9c6dd5e3de4946..fa7e504c4aa0de656b34f38c732bc30f8f04fa8b 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -2,7 +2,7 @@
 
 > [Introduced][ce-8935] in GitLab 9.0.
 
-GitLab offers powerful integration with [Prometheus] for monitoring key metrics your apps, directly within GitLab.
+GitLab offers powerful integration with [Prometheus] for monitoring key metrics of your apps, directly within GitLab.
 Metrics for each environment are retrieved from Prometheus, and then displayed
 within the GitLab interface.
 
@@ -12,17 +12,21 @@ There are two ways to setup Prometheus integration, depending on where your apps
 * For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes)
 * For other deployment targets, simply [specify the Prometheus server](#manual-configuration-of-prometheus).
 
-## Managed Prometheus on Kubernetes
+Once enabled, GitLab will automatically detect metrics from known services in the [metric library](#monitoring-ci-cd-environments).
+
+## Enabling Prometheus Integration
+
+### Managed Prometheus on Kubernetes
 > **Note**: [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28916) in GitLab 10.5
 
 GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cluster](../clusters/index.md), making monitoring of your apps easy.
 
-### Requirements
+#### Requirements
 
 * A [connected Kubernetes cluster](../clusters/index.md)
 * Helm Tiller [installed by GitLab](../clusters/index.md#installing-applications)
 
-### Getting started
+#### Getting started
 
 Once you have a connected Kubernetes cluster with Helm installed, deploying a managed Prometheus is as easy as a single click.
 
@@ -32,7 +36,7 @@ Once you have a connected Kubernetes cluster with Helm installed, deploying a ma
 
 ![Managed Prometheus Deploy](img/prometheus_deploy.png)
 
-### About managed Prometheus deployments
+#### About managed Prometheus deployments
 
 Prometheus is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/prometheus). Prometheus is only accessible within the cluster, with GitLab communicating through the [Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/).
 
@@ -45,9 +49,9 @@ CPU and Memory consumption is monitored, but requires [naming conventions](prome
 
 The [NGINX Ingress](../clusters/index.md#installing-applications) that is deployed by GitLab to clusters, is automatically annotated for monitoring providing key response metrics: latency, throughput, and error rates.
 
-## Manual configuration of Prometheus
+### Manual configuration of Prometheus
 
-### Requirements
+#### Requirements
 
 Integration with Prometheus requires the following:
 
@@ -56,7 +60,7 @@ Integration with Prometheus requires the following:
 1. Each metric must be have a label to indicate the environment
 1. GitLab must have network connectivity to the Prometheus server
 
-### Getting started
+#### Getting started
 
 Installing and configuring Prometheus to monitor applications is fairly straight forward.
 
@@ -64,7 +68,7 @@ Installing and configuring Prometheus to monitor applications is fairly straight
 1. Set up one of the [supported monitoring targets](prometheus_library/metrics.md)
 1. Configure the Prometheus server to [collect their metrics](https://prometheus.io/docs/operating/configuration/#scrape_config)
 
-### Configuration in GitLab
+#### Configuration in GitLab
 
 The actual configuration of Prometheus integration within GitLab is very simple.
 All you will need is the DNS or IP address of the Prometheus server you'd like
@@ -83,9 +87,9 @@ to integrate with.
 Once configured, GitLab will attempt to retrieve performance metrics for any
 environment which has had a successful deployment.
 
-GitLab will automatically scan the Prometheus server for known metrics and attempt to identify the metrics for a particular environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html).
+GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html). 
 
-[Learn more about monitoring environments.](../../../ci/environments.md#monitoring-environments)
+You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments.md#monitoring-environments).
 
 ## Determining the performance impact of a merge
 
@@ -93,7 +97,7 @@ GitLab will automatically scan the Prometheus server for known metrics and attem
 > GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
 > Requires [Kubernetes](prometheus_library/kubernetes.md) metrics
 
-Developers can view theperformance impact of their changes within the merge
+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 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 comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after
@@ -109,7 +113,7 @@ Prometheus server.
 
 ## Troubleshooting
 
-If the "Attempting to load performance data" screen continues to appear, it could be due to:
+If the "No data found" screen continues to appear, it could be due to:
 
 - No successful deployments have occurred to this environment.
 - Prometheus does not have performance data for this environment, or the metrics
diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md
index 34a0b97a171c4162cd798dc06d17633d9c90deae..bf6c0dc0e7e43f0dfecb9a7e1ce9665c71a4e707 100644
--- a/doc/user/project/integrations/prometheus_library/cloudwatch.md
+++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md
@@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring AWS resources, sta
 
 ## Requirements
 
-The [Prometheus service](../prometheus/index.md) must be enabled.
+The [Prometheus service](../prometheus.md) must be enabled.
 
 ## Metrics supported
 
diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md
index 518018e58395c6260c9ad53562207319c7bb4cb2..cd398f7c0fd076101e44f7cf29176ebca08cb7ab 100644
--- a/doc/user/project/integrations/prometheus_library/haproxy.md
+++ b/doc/user/project/integrations/prometheus_library/haproxy.md
@@ -5,7 +5,7 @@ GitLab has support for automatically detecting and monitoring HAProxy. This is p
 
 ## Requirements
 
-The [Prometheus service](../prometheus/index.md) must be enabled.
+The [Prometheus service](../prometheus.md) must be enabled.
 
 ## Metrics supported
 
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 02adc5620282da01e56feeb56739d5b03bb6a062..6b190deaa6ccee9ac502eb77b4263fd7724586d3 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -11,23 +11,27 @@ integration services must be enabled.
 
 ## Metrics supported
 
-| Name | Query |
-| ---- | ----- |
-| Average Memory Usage (MB) | (sum(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job))) / count(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job)) /1024/1024 |
-| Average CPU Utilization (%) | sum(avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="%{ci_environment_slug}"}[2m])) without (job)) * 100 |
+- Average Memory Usage (MB):
 
-## Configuring Prometheus to monitor for Kubernetes node metrics
+    ```
+    avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024
+    ```
 
-In order for Prometheus to collect Kubernetes metrics, you first must have a
-Prometheus server up and running. You have two options here:
+- Average CPU Utilization (%):
 
-- If you have an Omnibus based GitLab installation within your Kubernetes cluster, you can leverage the bundled Prometheus server to [monitor Kubernetes](../../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes).
-- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/) or [our guide](../../../../administration/monitoring/prometheus/index.md#configuring-your-own-prometheus-server-within-kubernetes).
+    ```
+    avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))
+    ```
+
+## Configuring Prometheus to monitor for Kubernetes metrics
+
+Prometheus needs to be deployed into the cluster and configured properly in order to gather Kubernetes metrics. GitLab supports two methods for doing so:
+
+- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics.
+- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/).
 
 ## Specifying the Environment
 
 In order to isolate and only display relevant CPU and Memory metrics for a given environment, GitLab needs a method to detect which containers it is running. Because these metrics are tracked at the container level, traditional Kubernetes labels are not available.
 
 Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name should begin with [CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables). It can be followed by a `-` and additional content if desired. For example, a deployment name of `review-homepage-5620p5` would match the `review/homepage` environment.
-
-If you are using [GitLab Auto-Deploy](../../../../ci/autodeploy/index.md) and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added.
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
index f09ecf9ff2d070e1eca64f57563f1c29137e82fb..96a22316265fa4f3e3558f88c90c0259dcd1fdec 100644
--- a/doc/user/project/integrations/prometheus_library/metrics.md
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -1,4 +1,5 @@
 # Prometheus Metrics library
+
 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0
 
 GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are:
@@ -15,7 +16,7 @@ We have tried to surface the most important metrics for each exporter, and will
 GitLab retrieves performance data from the configured Prometheus server, and attempts to identifying the presence of known metrics. Once identified, GitLab then needs to be able to map the data to a particular environment.
 
 In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that,
-GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library).
+GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library).
 
 ## Adding to the library
 
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
index 7fb8369d3c1e92e58ba240c96708dcad865f06f4..fea3231006b18ee039038623d58c6351cdfe42c1 100644
--- a/doc/user/project/integrations/prometheus_library/nginx.md
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro
 
 ## Requirements
 
-The [Prometheus service](../prometheus/index.md) must be enabled.
+The [Prometheus service](../prometheus.md) must be enabled.
 
 ## Metrics supported
 
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index 49b34c82ae6ab733deb6b6e788c0e9ca45bb413f..590b1c4275a5eb26a5975b79cbf5786351e0e3b9 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
 
 ## Requirements
 
-[Prometheus integration](../prometheus/index.md) must be active.
+[Prometheus integration](../prometheus.md) must be active.
 
 ## Metrics supported
 
@@ -27,7 +27,7 @@ For other deployments, there is [some configuration](#manually-setting-up-nginx-
 
 ### About managed NGINX Ingress deployments
 
-NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](https://docs.gitlab.com/ce/user/project/clusters/index.html#getting-the-external-ip-address).
+NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
 
 NGINX is configured for Prometheus monitoring, by setting:
 * `enable-vts-status: "true"`, to export Prometheus metrics
@@ -51,4 +51,4 @@ Managing these settings depends on how NGINX ingress has been deployed. If you h
 
 In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
 
-If you have used [Auto Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
+If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index bc6306927e14fca60c328c890fdeb92d18a41fef..7eab825fa32a453487c31217619096c5eeacbbcc 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -235,6 +235,26 @@ to another list the label changes and a system not is recorded.
 [Developers and up](../permissions.md) can use all the functionality of the
 Issue Board, that is create/delete lists and drag issues around.
 
+##  Group Issue Board
+
+>Introduced in GitLab 10.6
+
+Group issue board is analogous to project-level issue board and it is accessible at the group
+navigation level. A group-level issue board allows you to view all issues from all projects in that group or descendant subgroups. Similarly, you can only filter by group labels for these
+boards. When updating milestones and labels for an issue through the sidebar update mechanism, again only
+group-level objects are available.
+
+## Features per tier
+
+Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table:
+
+| Tier | Number of project issue boards | Board with configuration in project issue boards | Number of group issue boards | Board with configuration in group issue boards |
+| --- | --- | --- | --- | --- |
+| Core    | 1        | No  | 1        | No  |
+| Starter  | Multiple | Yes | 1        | No  |
+| Premium  | Multiple | Yes | Multiple | Yes |
+| Ultimate | Multiple | Yes | Multiple | Yes |
+
 ## Tips
 
 A few things to remember:
diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md
index e0c405353ce1214414ae4cf41f2b4ac05456fd4c..1bf8b776c2e200b006a10834132fab164d0bbf46 100644
--- a/doc/user/project/issues/due_dates.md
+++ b/doc/user/project/issues/due_dates.md
@@ -35,5 +35,9 @@ Due dates also appear in your [todos list](../../../workflow/todos.md).
 
 ![Issues with due dates in the todos](img/due_dates_todos.png)
 
+The day before an open issue is due, an email will be sent to all participants
+of the issue. Both the due date and the day before are calculated using the
+server's timezone.
+
 [ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
 [permissions]: ../../permissions.md#project
diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md
index f2ca6a6822e04cefbc1968485208fea34a6ec0ad..cf5cf1794eef3060574f25022a581b6505c9343c 100644
--- a/doc/user/project/issues/issues_functionalities.md
+++ b/doc/user/project/issues/issues_functionalities.md
@@ -28,7 +28,7 @@ Comments and system notes also appear automatically in response to various actio
 #### 2. Todos
 
 - Add todo: add that issue to your [GitLab Todo](../../../workflow/todos.html) list
-- Mark done: mark that issue as done (reflects on the Todo list)
+- Mark todo as done: mark that issue as done (reflects on the Todo list)
 
 #### 3. Assignee
 
@@ -41,10 +41,7 @@ it's reassigned to someone else to take it from there.
 if a user is not member of that project, it can only be
 assigned to them if they created the issue themselves.
 
-##### 3.1. Multiple Assignees
-
-> Available in [GitLab Starter](https://about.gitlab.com/products/) and
-[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
+##### 3.1. Multiple Assignees **[STARTER]**
 
 Often multiple people likely work on the same issue together,
 which can especially be difficult to track in large teams
@@ -89,10 +86,7 @@ but they are immediately available to all projects in the group.
 > **Tip:**
 if the label doesn't exist yet, when you click **Edit**, it opens a dropdown menu from which you can select **Create new label**.
 
-#### 8. Weight
-
-> Available in [GitLab Starter](https://about.gitlab.com/products/) and
-[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
+#### 8. Weight **[STARTER]**
 
 - Attribute a weight (in a 0 to 9 range) to that issue. Easy to complete
 should weight 1 and very hard to complete should weight 9.
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index dabffaec5fa9ce22c19607527de05c7a4ec84f9f..914898ea2eaab4dc1c6eafba8a30fa3160a1f8b6 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -9,8 +9,7 @@ Labels allow you to categorize issues or merge requests using descriptive titles
 In GitLab, you can create project and group labels:
 
 - **Project labels** can be assigned to issues or merge requests in that project only. 
-- **Group labels** can be assigned to any issue or merge request of any project in that group. 
-- In the [future](https://gitlab.com/gitlab-org/gitlab-ce/issues/40915), you will be able to assign group labels to issues and merge reqeusts of projects in [subgroups](../group/subgroups/index.md).
+- **Group labels** can be assigned to any issue or merge request of any project in that group or any subgroups of the group.
 
 ## Creating labels
 
@@ -74,9 +73,9 @@ Every issue and merge request can be assigned any number of labels. The labels a
 
 ### Filtering in list pages
 
-From the project issue list page and the project merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group labels and project labels.
+From the project issue list page and the project merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group (including subgroup ancestors) labels and project labels.
 
-From the group issue list page and the group merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group labels and project labels.
+From the group issue list page and the group merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group labels (including subgroup ancestors and subgroup descendants) and project labels.
 
 ![Labels group issues](img/labels_group_issues.png)
 
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
index f5c748a03b304f94c6183a82f7fbcdfe6a7c4066..5d819998dd92404e17526143a1c1a709a7b7865e 100644
--- a/doc/user/project/members/share_project_with_groups.md
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -16,19 +16,29 @@ say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'P
 Acme'.  But what if 'Project Acme' already belongs to another group, say 'Open Source'?
 This is where the group sharing feature can be of use.
 
-To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the **Settings > Members** section.
+To share 'Project Acme' with the 'Engineering' group:
 
-![share project with groups](img/share_project_with_groups.png)
+1. For 'Project Acme' use the left navigation menu to go to **Settings > Members**
 
-Then select the 'Share with group' tab by clicking it.
+    ![share project with groups](img/share_project_with_groups.png)
 
-Now you can add the 'Engineering' group with the maximum access level of your choice. Click 'Share' to share it.
+1. Select the 'Share with group' tab
+1. Add the 'Engineering' group with the maximum access level of your choice
+1. Click **Share** to share it
 
-![share project with groups tab](img/share_project_with_groups_tab.png)
+    ![share project with groups tab](img/share_project_with_groups_tab.png)
 
-After sharing 'Project Acme' with 'Engineering', the project will be listed on the group dashboard.
+1. After sharing 'Project Acme' with 'Engineering', the project will be listed
+   on the group dashboard
 
-!['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
+    !['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
+
+Note that you can only share a project with:
+
+- groups for which you have an explicitly defined membership
+- groups that contain a nested subgroup or project for which you have an explicitly defined role
+
+Admins are able to share projects with any group in the system.
 
 ## Maximum access level
 
diff --git a/doc/user/project/merge_requests/img/allow_maintainer_push.png b/doc/user/project/merge_requests/img/allow_maintainer_push.png
new file mode 100644
index 0000000000000000000000000000000000000000..91cc399f4ff62770e23253d053a593f35716f041
Binary files /dev/null and b/doc/user/project/merge_requests/img/allow_maintainer_push.png differ
diff --git a/doc/user/project/merge_requests/img/remove_source_branch_status.png b/doc/user/project/merge_requests/img/remove_source_branch_status.png
new file mode 100644
index 0000000000000000000000000000000000000000..1377fab54ecfec702744dc13a41cf4d581c1b521
Binary files /dev/null and b/doc/user/project/merge_requests/img/remove_source_branch_status.png differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index d3220598933e1c7130f8433f79ea435df7e7394e..a6c0fd49c45f5b882b6e6e13a6b75825d7d790e4 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -28,13 +28,14 @@ With GitLab merge requests, you can:
 - Enable [fast-forward merge requests](#fast-forward-merge-requests)
 - Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch
 - [Create new merge requests by email](#create-new-merge-requests-by-email)
+- Allow maintainers of the target project to push directly to the fork by [allowing edits from maintainers](maintainer_access.md)
 
 With **[GitLab Enterprise Edition][ee]**, you can also:
 
-- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Premium)
-- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Starter)
-- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Starter)
-- Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter)
+- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) **[PREMIUM]**
+- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers **[STARTER]**
+- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history **[STARTER]**
+- Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) **[STARTER]**
 
 ## Use cases
 
@@ -42,7 +43,7 @@ A. Consider you are a software developer working in a team:
 
 1. You checkout a new branch, and submit your changes through a merge request
 1. You gather feedback from your team
-1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter)
+1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) **[STARTER]**
 1. You build and test your changes with GitLab CI/CD
 1. You request the approval from your manager
 1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
@@ -55,7 +56,7 @@ B. Consider you're a web developer writing a webpage for your company's:
 1. You gather feedback from your reviewers
 1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md)
 1. You request your web designers for their implementation
-1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Starter)
+1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager **[STARTER]**
 1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Starter)
 1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production
 
@@ -76,6 +77,22 @@ You can [search and filter the results](../../search/index.md#issues-and-merge-r
 
 ![Group Issues list view](img/group_merge_requests_list_view.png)
 
+## Removing the source branch
+
+When creating a merge request, select the "Remove source branch when merge
+request accepted" option and the source branch will be removed when the merge
+request is merged.
+
+This option is also visible in an existing merge request next to the merge
+request button and can be selected/deselected before merging. It's only visible
+to users with [Master permissions](../../permissions.md) in the source project.
+
+If the user viewing the merge request does not have the correct permissions to
+remove the source branch and the source branch is set for removal, the merge
+request widget will show the "Removes source branch" text.
+
+![Remove source branch status](img/remove_source_branch_status.png)
+
 ## Authorization for merge requests
 
 There are two main ways to have a merge request flow with GitLab:
diff --git a/doc/user/project/merge_requests/maintainer_access.md b/doc/user/project/merge_requests/maintainer_access.md
new file mode 100644
index 0000000000000000000000000000000000000000..c9763a3fe02df8b55a25624dce2c1915ec7c692a
--- /dev/null
+++ b/doc/user/project/merge_requests/maintainer_access.md
@@ -0,0 +1,18 @@
+# Allow maintainer pushes for merge requests across forks
+
+> [Introduced][ce-17395] in GitLab 10.6.
+
+This feature is available for merge requests across forked projects that are
+publicly accessible. It makes it easier for maintainers of projects to
+collaborate on merge requests across forks.
+
+When enabled for a merge request, members with merge access to the target
+branch of the project will be granted write permissions to the source branch
+of the merge request.
+
+The feature can only be enabled by users who already have push access to the
+source project, and only lasts while the merge request is open.
+
+Enable this functionality while creating a merge request:
+
+![Enable maintainer edits](./img/allow_maintainer_push.png)
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 34809a2826f1e13914645acb06db660568852cc3..a13b1b4561c0c3031dad09a1e8bb069b16f67bea 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -12,7 +12,7 @@ month on the 22nd for a certain branch.
 
 In order to schedule a pipeline:
 
-1. Navigate to your project's **Pipelines 鉃� Schedules** and click the
+1. Navigate to your project's **CI / CD 鉃� Schedules** and click the
    **New Schedule** button.
 1. Fill in the form
 1. Hit **Save pipeline schedule** for the changes to take effect.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 43451844f2de8630d7478d097260994b3a4448e6..14f2e522f0175b68d3ef9c88c3fd79b4aff9d4d1 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -27,6 +27,13 @@ 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.
 
+### Timeout overriding on Runner level
+
+> - [Introduced][ce-17221] in GitLab 10.7.
+
+Project defined timeout (either specific timeout set by user or the default
+60 minutes timeout) may be [overridden on Runner level][timeout overriding].
+
 ## Custom CI config path
 
 >  - [Introduced][ce-12509] in GitLab 9.4.
@@ -99,7 +106,7 @@ 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
+## Pipeline Badges
 
 In the pipelines settings page you can find pipeline status and test coverage
 badges for your project. The latest successful pipeline will be used to read
@@ -152,5 +159,7 @@ into your `README.md`:
 
 [var]: ../../../ci/yaml/README.md#git-strategy
 [coverage report]: #test-coverage-parsing
+[timeout overriding]: ../../../ci/runners/README.html#setting-maximum-job-timeout-for-a-runner
 [ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362
 [ce-12509]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509
+[ce-17221]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17221
diff --git a/doc/user/project/repository/img/jupyter_notebook.png b/doc/user/project/repository/img/jupyter_notebook.png
new file mode 100644
index 0000000000000000000000000000000000000000..52c5c5aea32b82b23cafb2bd3b5ce3c38d3c8c0c
Binary files /dev/null and b/doc/user/project/repository/img/jupyter_notebook.png differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index e6aede7f46e0e344f324dbd17738e1f76df95921..376f4e3cbe4ae2aff142f9ce2cbf7d5e47126caa 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -53,6 +53,22 @@ To get started with the command line, please read through the
 
 Use GitLab's [file finder](../../../workflow/file_finder.md) to search for files in a repository.
 
+### Jupyter Notebook files
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2508) in GitLab 9.1
+
+[Jupyter][jupyter] Notebook (previously IPython Notebook) files are used for
+interactive computing in many fields and contain a complete record of the
+user's sessions and include code, narrative text, equations and rich output.
+
+When added to a repository, Jupyter Notebooks with a `.ipynb` extension will be
+rendered to HTML when viewed.
+
+![Jupyter Notebook Rich Output](img/jupyter_notebook.png)
+
+Interactive features, including JavaScript plots, will not work when viewed in
+GitLab.
+
 ## Branches
 
 When you submit changes in a new [branch](branches/index.md), you create a new version
@@ -116,8 +132,9 @@ Use GPG to [sign your commits](gpg_signed_commits/index.md).
 
 ## Repository size
 
-In GitLab.com, your repository size limit it 10GB. For other instances,
-the repository size is limited by your system administrators.
+On GitLab.com, your [repository size limit is 10GB](../../gitlab_com/index.md#repository-size-limit)
+(including LFS). For other instances, the repository size is limited by your
+system administrators.
 
 You can also [reduce a repository size using Git](reducing_the_repo_size_using_git.md).
 
@@ -158,3 +175,5 @@ Lock your files to prevent any conflicting changes.
 ## Repository's API
 
 You can access your repos via [repository API](../../../api/repositories.md).
+
+[jupyter]: https://jupyter.org
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index dedf102fc3777ff0e97ca9b35a51151925a2e4fc..eb0ac221e30c12a563227494cdb763e4c3d7685d 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -57,11 +57,11 @@ The following items will be exported:
 - Project configuration including web hooks and services
 - Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
   and other project entities
+- LFS objects
 
 The following items will NOT be exported:
 
 - Build traces and artifacts
-- LFS objects
 - Container registry images
 - CI variables
 - Any encrypted tokens
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 888dd0e143ac6ddce641d3e51aaf8b9d8dd0ea5b..c9d2f8dc32dbb2a325e571e176602cb07d5b5519 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -34,7 +34,7 @@ Set up your project's merge request settings:
 
 - Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)).
 - Merge request [description templates](../description_templates.md#description-templates).
-- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals), _available in [GitLab Starter](https://about.gitlab.com/products/)_.
+- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals). **[STARTER]**
 - Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
 - Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved).
 
@@ -57,15 +57,20 @@ Here you can run housekeeping, archive, rename, transfer, or remove a project.
 NOTE: **Note:**
 Only project Owners and Admin users have the [permissions] to archive a project.
 
-An archived project will be hidden by default in the project listings.
+Archiving a project makes it read-only for all users and indicates that it is
+no longer actively maintained. Projects that have been archived can also be
+unarchived.
+
+When a project is archived, the repository, issues, merge requests and all
+other features are read-only. Archived projects are also hidden
+in project listings.
+
+To archive a project:
 
 1. Navigate to your project's **Settings > General > Advanced settings**.
-1. Under "Archive project", hit the **Archive project** button.
+1. In the Archive project section, click the **Archive project** button.
 1. Confirm the action when asked to.
 
-An archived project can be fully restored and will therefore retain its
-repository and all associated resources whilst in an archived state.
-
 #### Renaming a repository
 
 NOTE: **Note:**
diff --git a/doc/user/project/web_ide/img/commit_changes.png b/doc/user/project/web_ide/img/commit_changes.png
new file mode 100644
index 0000000000000000000000000000000000000000..b6fcbf699aa01cce57994f5fa8a82ec0718f4641
Binary files /dev/null and b/doc/user/project/web_ide/img/commit_changes.png differ
diff --git a/doc/user/project/web_ide/img/enable_web_ide.png b/doc/user/project/web_ide/img/enable_web_ide.png
new file mode 100644
index 0000000000000000000000000000000000000000..196baa82ad27c3549fae7fed925d6e8e7dea4f14
Binary files /dev/null and b/doc/user/project/web_ide/img/enable_web_ide.png differ
diff --git a/doc/user/project/web_ide/img/open_web_ide.png b/doc/user/project/web_ide/img/open_web_ide.png
new file mode 100644
index 0000000000000000000000000000000000000000..d1192daf506908d2fe26ea23fa06f65e66843900
Binary files /dev/null and b/doc/user/project/web_ide/img/open_web_ide.png differ
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..b7064b83c4e232d3a5f674ef4bc13795d6f31fd7
--- /dev/null
+++ b/doc/user/project/web_ide/index.md
@@ -0,0 +1,33 @@
+# Web IDE
+
+> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) [GitLab Ultimate][ee] 10.4.
+> [Brought to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7.
+
+The Web IDE makes it faster and easier to contribute changes to your projects
+by providing an advanced editor with commit staging.
+
+## Open the Web IDE
+
+The Web IDE can be opened when viewing a file, from the repository file list,
+and from merge requests.
+
+![Open Web IDE](img/open_web_ide.png)
+
+## Commit changes
+
+Changed files are shown on the right in the commit panel. All changes are
+automatically staged. To commit your changes, add a commit message and click
+the 'Commit Button'.
+
+![Commit changes](img/commit_changes.png)
+
+## Comparing changes
+
+Before you commit your changes, you can compare them with the previous commit
+by switching to the review mode or selecting the file from the staged files
+list.
+
+An additional review mode is available when you open a merge request, which
+shows you a preview of the merge request diff if you commit your changes.
+
+[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index d768b73286de9a748c2d30d35256bcae27a03341..f824756c10ca96f375f5e0ad73953aec4390d266 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -5,6 +5,7 @@ Documentation on how to use Git LFS are under [Managing large binary files with
 ## Requirements
 
 * Git LFS is supported in GitLab starting with version 8.2.
+* Support for object storage, such as AWS S3, was introduced in 10.0.
 * Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up.
 
 ## Configuration
@@ -12,16 +13,18 @@ Documentation on how to use Git LFS are under [Managing large binary files with
 Git LFS objects can be large in size. By default, they are stored on the server
 GitLab is installed on.
 
-There are two configuration options to help GitLab server administrators:
+There are various configuration options to help GitLab server administrators:
 
 * Enabling/disabling Git LFS support
 * Changing the location of LFS object storage
+* Setting up AWS S3 compatible object storage
 
-### Omnibus packages
+### Configuration for Omnibus installations
 
 In `/etc/gitlab/gitlab.rb`:
 
 ```ruby
+# Change to true to enable lfs
 gitlab_rails['lfs_enabled'] = false
 
 # Optionally, change the storage path location. Defaults to
@@ -30,16 +33,123 @@ gitlab_rails['lfs_enabled'] = false
 gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects"
 ```
 
-### Installations from source
+### Configuration for installations from source
 
 In `config/gitlab.yml`:
 
 ```yaml
+# Change to true to enable lfs
   lfs:
     enabled: false
     storage_path: /mnt/storage/lfs-objects
 ```
 
+## Storing the LFS objects in an S3-compatible object storage
+
+> [Introduced][ee-2760] in [GitLab Premium][eep] 10.0. Brought to GitLab Core
+in 10.7.
+
+It is possible to store LFS objects on a remote object storage which allows you
+to offload storage to an external AWS S3 compatible service, freeing up disk
+space locally. You can also host your own S3 compatible storage decoupled from
+GitLab, with with a service such as [Minio](https://www.minio.io/).
+
+Object storage currently transfers files first to GitLab, and then on the
+object storage in a second stage. This can be done either by using a rake task
+to transfer existing objects, or in a background job after each file is received.
+
+The following general settings are supported.
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `enabled` | Enable/disable object storage | `false` |
+| `remote_directory` | The bucket name where LFS objects will be stored| |
+| `direct_upload` | Set to true to enable direct upload of LFS without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` |
+| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` |
+| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
+| `connection` | Various connection options described below | |
+
+The `connection` settings match those provided by [Fog](https://github.com/fog).
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `provider` | Always `AWS` for compatible hosts | AWS |
+| `aws_access_key_id` | AWS credentials, or compatible | |
+| `aws_secret_access_key` | AWS credentials, or compatible | |
+| `region` | AWS region | us-east-1 |
+| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
+| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
+| `path_style` | Set to true to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as false for AWS S3 | false |
+
+### S3 for Omnibus installations
+
+On Omnibus installations, the settings are prefixed by `lfs_object_store_`:
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
+   the values you want:
+
+	```ruby
+	gitlab_rails['lfs_object_store_enabled'] = true
+	gitlab_rails['lfs_object_store_remote_directory'] = "lfs-objects"
+	gitlab_rails['lfs_object_store_connection'] = {
+	  'provider' => 'AWS',
+	  'region' => 'eu-central-1',
+	  'aws_access_key_id' => '1ABCD2EFGHI34JKLM567N',
+	  'aws_secret_access_key' => 'abcdefhijklmnopQRSTUVwxyz0123456789ABCDE',
+	  # The below options configure an S3 compatible host instead of AWS
+	  'host' => 'localhost',
+	  'endpoint' => 'http://127.0.0.1:9000',
+	  'path_style' => true
+	}
+	```
+
+1. Save the file and [reconfigure GitLab]s for the changes to take effect.
+1. Migrate any existing local LFS objects to the object storage:
+
+    ```bash
+    gitlab-rake gitlab:lfs:migrate
+    ```
+
+    This will migrate existing LFS objects to object storage. New LFS objects
+    will be forwarded to object storage unless
+    `gitlab_rails['lfs_object_store_background_upload']` is set to false.
+
+### S3 for installations from source
+
+For source installations the settings are nested under `lfs:` and then
+`object_store:`:
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
+   lines:
+
+	```yaml
+	lfs:
+	enabled: true
+	object_store:
+	  enabled: false
+	  remote_directory: lfs-objects # Bucket name
+	  connection:
+	    provider: AWS
+	    aws_access_key_id: 1ABCD2EFGHI34JKLM567N
+	    aws_secret_access_key: abcdefhijklmnopQRSTUVwxyz0123456789ABCDE
+	    region: eu-central-1
+	    # Use the following options to configure an AWS compatible host such as Minio
+	    host: 'localhost'
+	    endpoint: 'http://127.0.0.1:9000'
+	    path_style: true
+	```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+1. Migrate any existing local LFS objects to the object storage:
+
+    ```bash
+    sudo -u git -H bundle exec rake gitlab:lfs:migrate RAILS_ENV=production
+    ```
+
+    This will migrate existing LFS objects to object storage. New LFS objects
+    will be forwarded to object storage unless `background_upload` is set to
+    false.
+
 ## Storage statistics
 
 You can see the total storage used for LFS objects on groups and projects
@@ -48,10 +158,13 @@ and [projects APIs](../../api/projects.md).
 
 ## Known limitations
 
-* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
-  is not supported
 * Support for removing unreferenced LFS objects was added in 8.14 onwards.
 * LFS authentications via SSH was added with GitLab 8.12
 * Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
 * The storage statistics currently count each LFS object multiple times for
   every project linking to it
+
+[reconfigure gitlab]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
+[restart gitlab]: ../../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
+[eep]: https://about.gitlab.com/products/ "GitLab Premium"
+[ee-2760]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2760
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 37265a5b771cacf4a783b12f87b95ab3339cdeb7..f1501c81b2738ffca8ee03c1f612dc36ee12c2a7 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -67,7 +67,7 @@ Below is the table of events users can be notified of:
 
 ### Issue / Merge request events
 
-In all of the below cases, the notification will be sent to:
+In most of the below cases, the notification will be sent to:
 - Participants:
   - the author and assignee of the issue/merge request
   - authors of comments on the issue/merge request
@@ -86,7 +86,9 @@ In all of the below cases, the notification will be sent to:
 | Close issue            | |
 | Reassign issue         | The above, plus the old assignee |
 | Reopen issue           | |
+| Due issue              | Participants and Custom notification level with this event selected |
 | New merge request      | |
+| Push to merge request  | Participants and Custom notification level with this event selected |
 | Reassign merge request | The above, plus the old assignee |
 | Close merge request    | |
 | Reopen merge request   | |
@@ -95,15 +97,14 @@ In all of the below cases, the notification will be sent to:
 | Failed pipeline        | The author of the pipeline |
 | Successful pipeline    | The author of the pipeline, if they have the custom notification setting for successful pipelines set |
 
-
 In addition, if the title or description of an Issue or Merge Request is
 changed, notifications will be sent to any **new** mentions by `@username` as
 if they had been mentioned in the original text.
 
-You won't receive notifications for Issues, Merge Requests or Milestones
-created by yourself. You will only receive automatic notifications when
-somebody else comments or adds changes to the ones that you've created or
-mentions you.
+You won't receive notifications for Issues, Merge Requests or Milestones created
+by yourself (except when an issue is due). You will only receive automatic
+notifications when somebody else comments or adds changes to the ones that
+you've created or mentions you.
 
 ### Email Headers
 
@@ -121,7 +122,7 @@ Notification emails include headers that provide extra content about the notific
 | X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc             |
 
 #### X-GitLab-NotificationReason
-This header holds the reason for the notification to have been sent out, 
+This header holds the reason for the notification to have been sent out,
 where reason can be `mentioned`, `assigned`, `own_activity`, etc.
 Only one reason is sent out according to its priority:
 - `own_activity`
@@ -129,7 +130,7 @@ Only one reason is sent out according to its priority:
 - `mentioned`
 
 The reason in this header will also be shown in the footer of the notification email.  For example an email with the
-reason `assigned` will have this sentence in the footer: 
+reason `assigned` will have this sentence in the footer:
 `"You are receiving this email because you have been assigned an item on {configured GitLab hostname}"`
 
 **Note: Only reasons listed above have been implemented so far**
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 3d8d3ce8f1322842cde1688fb7f4dc5819918d2e..f13d29884d41854d5f1af9ba0618316f85a35f56 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -28,11 +28,10 @@ A Todo appears in your Todos dashboard when:
 - an issue or merge request is assigned to you,
 - you are `@mentioned` in an issue or merge request, be it the description of
   the issue/merge request or in a comment,
+- you are `@mentioned` in a comment on a commit,
 - a job in the CI pipeline running for your merge request failed, but this
   job is not allowed to fail.
 
->**Note:** Commenting on a commit will _not_ trigger a Todo.
-
 ### Directly addressed Todos
 
 > [Introduced][ce-7926] in GitLab 9.0.
@@ -93,9 +92,9 @@ corresponding **Done** button, and it will disappear from your Todo list.
 ![A Todo in the Todos dashboard](img/todo_list_item.png)
 
 A Todo can also be marked as done from the issue or merge request sidebar using
-the "Mark done" button.
+the "Mark todo as done" button.
 
-![Mark Done from the issuable sidebar](img/todos_mark_done_sidebar.png)
+![Mark todo as done from the issuable sidebar](img/todos_mark_done_sidebar.png)
 
 You can mark all your Todos as done at once by clicking on the **Mark all as
 done** button.
diff --git a/features/groups.feature b/features/groups.feature
deleted file mode 100644
index 4044bd9be79c93a142b414283b1b465c4234cdbb..0000000000000000000000000000000000000000
--- a/features/groups.feature
+++ /dev/null
@@ -1,73 +0,0 @@
-Feature: Groups
-  Background:
-    Given I sign in as "John Doe"
-    And "John Doe" is owner of group "Owned"
-
-  Scenario: I should not see a group if it does not exist
-    When I visit group "NonExistentGroup" page
-    Then page status code should be 404
-
-  @javascript
-  Scenario: I should see group "Owned" dashboard list
-    When I visit group "Owned" page
-    Then I should see group "Owned" projects list
-
-  @javascript
-  Scenario: I should see group "Owned" activity feed
-    When I visit group "Owned" activity page
-    And I should see projects activity feed
-
-  Scenario: I should see group "Owned" issues list
-    Given project from group "Owned" has issues assigned to me
-    When I visit group "Owned" issues page
-    Then I should see issues from group "Owned" assigned to me
-
-  Scenario: I should not see issues from archived project in "Owned" group issues list
-    Given Group "Owned" has archived project
-    And the archived project have some issues
-    When I visit group "Owned" issues page
-    Then I should not see issues from the archived project
-
-  Scenario: I should see group "Owned" merge requests list
-    Given project from group "Owned" has merge requests assigned to me
-    When I visit group "Owned" merge requests page
-    Then I should see merge requests from group "Owned" assigned to me
-
-  Scenario: I should not see merge requests from archived project in "Owned" group merge requests list
-    Given Group "Owned" has archived project
-    And the archived project have some merge_requests
-    When I visit group "Owned" merge requests page
-    Then I should not see merge requests from the archived project
-
-  Scenario: I edit group "Owned" avatar
-    When I visit group "Owned" settings page
-    And I change group "Owned" avatar
-    And I visit group "Owned" settings page
-    Then I should see new group "Owned" avatar
-    And I should see the "Remove avatar" button
-
-  Scenario: I remove group "Owned" avatar
-    When I visit group "Owned" settings page
-    And I have group "Owned" avatar
-    And I visit group "Owned" settings page
-    And I remove group "Owned" avatar
-    Then I should not see group "Owned" avatar
-    And I should not see the "Remove avatar" button
-
-  # Group projects in settings
-  Scenario: I should see all projects in the project list in settings
-    Given Group "Owned" has archived project
-    When I visit group "Owned" projects page
-    Then I should see group "Owned" projects list
-    And I should see "archived" label
-
-  # Public group
-  @javascript
-  Scenario: Signed out user should see group
-    Given "Mary Jane" is owner of group "Owned"
-    And I am a signed out user
-    And Group "Owned" has a public project "Public-project"
-    When I visit group "Owned" page
-    Then I should see group "Owned"
-    Then I should see project "Public-project"
-
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
deleted file mode 100644
index 3ea0aab5a67a63e7d2819a5e655884e92533e627..0000000000000000000000000000000000000000
--- a/features/project/active_tab.feature
+++ /dev/null
@@ -1,138 +0,0 @@
-Feature: Project Active Tab
-  Background:
-    Given I sign in as a user
-    And I own a project
-
-  # Main Tabs
-
-  Scenario: On Project Home
-    Given I visit my project's home page
-    Then the active sub tab should be Home
-    And no other sub tabs should be active
-    And the active main tab should be Project
-
-  Scenario: On Project Repository
-    Given I visit my project's files page
-    Then the active main tab should be Repository
-    And no other main tabs should be active
-
-  Scenario: On Project Issues
-    Given I visit my project's issues page
-    Then the active main tab should be Issues
-    And no other main tabs should be active
-
-  Scenario: On Project Merge Requests
-    Given I visit my project's merge requests page
-    Then the active main tab should be Merge Requests
-    And no other main tabs should be active
-
-  Scenario: On Project Wiki
-    Given I visit my project's wiki page
-    Then the active main tab should be Wiki
-    And no other main tabs should be active
-
-  Scenario: On Project Members
-    Given I visit my project's members page
-    Then the active main tab should be Members
-    And no other main tabs should be active
-
-  # Sub Tabs: Home
-
-  Scenario: On Project Home/Show
-    Given I visit my project's home page
-    Then the active sub tab should be Home
-    And no other sub tabs should be active
-    And the active main tab should be Project
-    And no other main tabs should be active
-
-  Scenario: On Project Home/Activity
-    Given I visit my project's home page
-    And I click the "Activity" tab
-    Then the active sub tab should be Activity
-    And no other sub tabs should be active
-    And the active main tab should be Project
-
-  # Sub Tabs: Settings
-
-  Scenario: On Project Settings/Integrations
-    Given I visit my project's settings page
-    And I click the "Integrations" tab
-    Then the active sub tab should be Integrations
-    And no other sub tabs should be active
-    And the active main tab should be Settings
-
-  Scenario: On Project Settings/Repository
-    Given I visit my project's settings page
-    And I click the "Repository" tab
-    Then the active sub tab should be Repository
-    And no other sub tabs should be active
-    And the active main tab should be Settings
-
-  # Sub Tabs: Repository
-
-  Scenario: On Project Repository/Files
-    Given I visit my project's files page
-    Then the active sub tab should be Files
-    And no other sub tabs should be active
-    And the active main tab should be Repository
-
-  Scenario: On Project Repository/Commits
-    Given I visit my project's commits page
-    Then the active sub tab should be Commits
-    And no other sub tabs should be active
-    And the active main tab should be Repository
-
-  Scenario: On Project Repository/Graph
-    Given I visit my project's graph page
-    Then the active sub tab should be Graph
-    And no other sub tabs should be active
-    And the active main tab should be Repository
-
-  Scenario: On Project Repository/Compare
-    Given I visit my project's commits page
-    And I click the "Compare" tab
-    Then the active sub tab should be Compare
-    And no other sub tabs should be active
-    And the active main tab should be Repository
-
-  Scenario: On Project Repository/Charts
-    Given I visit my project's commits page
-    And I click the "Charts" tab
-    Then the active sub tab should be Charts
-    And no other sub tabs should be active
-    And the active main tab should be Repository
-
-  Scenario: On Project Repository/Branches
-    Given I visit my project's commits page
-    And I click the "Branches" tab
-    Then the active sub tab should be Branches
-    And no other sub tabs should be active
-    And the active main tab should be Repository
-
-  Scenario: On Project Repository/Tags
-    Given I visit my project's commits page
-    And I click the "Tags" tab
-    Then the active sub tab should be Tags
-    And no other sub tabs should be active
-    And the active main tab should be Repository
-
-  Scenario: On Project Issues/Browse
-    Given I visit my project's issues page
-    Then the active main tab should be Issues
-    And no other main tabs should be active
-
-  Scenario: On Project Issues/Milestones
-    Given I visit my project's issues page
-    And I click the "Milestones" sub tab
-    Then the active main tab should be Issues
-    Then the active sub tab should be Milestones
-    And no other main tabs should be active
-    And no other sub tabs should be active
-
-  Scenario: On Project Issues/Labels
-    Given I visit my project's issues page
-    And I click the "Labels" sub tab
-    Then the active main tab should be Issues
-    Then the active sub tab should be Labels
-    And no other main tabs should be active
-    And no other sub tabs should be active
diff --git a/features/project/builds/permissions.feature b/features/project/builds/permissions.feature
deleted file mode 100644
index db15968db064b9211302bdeac1020b9cfb5ae2ab..0000000000000000000000000000000000000000
--- a/features/project/builds/permissions.feature
+++ /dev/null
@@ -1,54 +0,0 @@
-Feature: Project Builds Permissions
-  Background:
-    Given I sign in as a user
-    And project exists in some group namespace
-    And project has CI enabled
-    And project has a recent build
-
-  Scenario: I try to visit build details as guest
-    Given I am member of a project with a guest role
-    When I visit recent build details page
-    Then page status code should be 404
-
-  Scenario: I try to visit project builds page as guest
-    Given I am member of a project with a guest role
-    When I visit project builds page
-    Then page status code should be 404
-
-  Scenario: I try to visit build details of internal project without access to builds
-    Given The project is internal
-    And public access for builds is disabled
-    When I visit recent build details page
-    Then page status code should be 404
-
-  Scenario: I try to visit internal project builds page without access to builds
-    Given The project is internal
-    And public access for builds is disabled
-    When I visit project builds page
-    Then page status code should be 404
-
-  @javascript
-  Scenario: I try to visit build details of internal project with access to builds
-    Given The project is internal
-    And public access for builds is enabled
-    When I visit recent build details page
-    Then I see details of a build
-    And I see build trace
-
-  Scenario: I try to visit internal project builds page with access to builds
-    Given The project is internal
-    And public access for builds is enabled
-    When I visit project builds page
-    Then I see the build
-
-  Scenario: I try to download build artifacts as guest
-    Given I am member of a project with a guest role
-    And recent build has artifacts available
-    When I access artifacts download page
-    Then page status code should be 404
-
-  Scenario: I try to download build artifacts as reporter
-    Given I am member of a project with a reporter role
-    And recent build has artifacts available
-    When I access artifacts download page
-    Then download of build artifacts archive starts
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
deleted file mode 100644
index c57376aecffb8515a3f4dc7f0642e0e3dbff5096..0000000000000000000000000000000000000000
--- a/features/project/commits/branches.feature
+++ /dev/null
@@ -1,42 +0,0 @@
-@project_commits
-Feature: Project Commits Branches
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    And project "Shop" has protected branches
-
-  Scenario: I can see project all git branches
-    Given I visit project branches page
-    Then I should see "Shop" all branches list
-
-  Scenario: I can see project protected git branches
-    Given I visit project protected branches page
-    Then I should see "Shop" protected branches list
-
-  @javascript
-  Scenario: I create a branch
-    Given I visit project branches page
-    And I click new branch link
-    And I submit new branch form
-    Then I should see new branch created
-
-  @javascript
-  Scenario: I delete a branch
-    Given I visit project branches page
-    And I filter for branch improve/awesome
-    And I click branch 'improve/awesome' delete link
-    Then I should not see branch 'improve/awesome'
-
-  @javascript
-  Scenario: I create a branch with invalid name
-    Given I visit project branches page
-    And I click new branch link
-    And I submit new branch form with invalid name
-    Then I should see new an error that branch is invalid
-
-  @javascript
-  Scenario: I create a branch that already exists
-    Given I visit project branches page
-    And I click new branch link
-    And I submit new branch form with branch that already exists
-    Then I should see new an error that branch already exists
diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature
deleted file mode 100644
index fafb54b183a219090db2b6816261ead641b9db7b..0000000000000000000000000000000000000000
--- a/features/project/commits/comments.feature
+++ /dev/null
@@ -1,51 +0,0 @@
-@project_commits
-Feature: Project Commits Comments
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    And I visit project commit page
-
-  @javascript
-  Scenario: I can comment on a commit
-    Given I leave a comment like "XML attached"
-    Then I should see a comment saying "XML attached"
-
-  @javascript
-  Scenario: I can't cancel the main form
-    Then I should not see the cancel comment button
-
-  @javascript
-  Scenario: I can preview with text
-    Given I write a comment like ":+1: Nice"
-    Then The comment preview tab should be display rendered Markdown
-
-  @javascript
-  Scenario: I preview a comment
-    Given I preview a comment text like "Bug fixed :smile:"
-    Then I should see the comment preview
-    And I should not see the comment text field
-
-  @javascript
-  Scenario: I can edit after preview
-    Given I preview a comment text like "Bug fixed :smile:"
-    Then I should see the comment write tab
-
-  @javascript
-  Scenario: I have a reset form after posting from preview
-    Given I preview a comment text like "Bug fixed :smile:"
-    And I submit the comment
-    Then I should see an empty comment text field
-    And I should not see the comment preview
-
-  @javascript
-  Scenario: I can delete a comment
-    Given I leave a comment like "XML attached"
-    Then I should see a comment saying "XML attached"
-    And I delete a comment
-    Then I should not see a comment saying "XML attached"
-
-  @javascript
-  Scenario: I can edit a comment with +1
-    Given I leave a comment like "XML attached"
-    And I edit the last comment with a +1
-    Then I should see +1 in the description
diff --git a/features/project/fork.feature b/features/project/fork.feature
deleted file mode 100644
index ca3f2771aa50a85bf63d5fa948e22eb0050f2cda..0000000000000000000000000000000000000000
--- a/features/project/fork.feature
+++ /dev/null
@@ -1,49 +0,0 @@
-Feature: Project Fork
-  Background:
-    Given I sign in as a user
-    And I am a member of project "Shop"
-    When I visit project "Shop" page
-
-  Scenario: User fork a project
-    Given I click link "Fork"
-    When I fork to my namespace
-    Then I should see the forked project page
-
-  Scenario: User already has forked the project
-    Given I already have a project named "Shop" in my namespace
-    And I click link "Fork"
-    When I fork to my namespace
-    Then I should see a "Name has already been taken" warning
-
-  Scenario: Merge request on canonical repo goes to fork merge request page
-    Given I click link "Fork"
-    And I fork to my namespace
-    Then I should see the forked project page
-    When I visit project "Shop" page
-    Then I should see "New merge request"
-    And I goto the Merge Requests page
-    Then I should see "New merge request"
-    And I click link "New merge request"
-    Then I should see the new merge request page for my namespace
-
-  Scenario: Viewing forks of a Project
-    Given I click link "Fork"
-    When I fork to my namespace
-    And I visit the forks page of the "Shop" project
-    Then I should see my fork on the list
-
-  Scenario: Viewing forks of a Project that has no repo
-    Given I click link "Fork"
-    When I fork to my namespace
-    And I make forked repo invalid
-    And I visit the forks page of the "Shop" project
-    Then I should see my fork on the list
-
-  Scenario: Viewing private forks of a Project
-    Given There is an existent fork of the "Shop" project
-    And I click link "Fork"
-    When I fork to my namespace
-    And I visit the forks page of the "Shop" project
-    Then I should see my fork on the list
-    And I should not see the other fork listed
-    And I should see a private fork notice
diff --git a/features/project/graph.feature b/features/project/graph.feature
deleted file mode 100644
index b25c73ad87084046420f141e8daa543a905271cf..0000000000000000000000000000000000000000
--- a/features/project/graph.feature
+++ /dev/null
@@ -1,33 +0,0 @@
-Feature: Project Graph
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-
-  @javascript
-  Scenario: I should see project graphs
-    When I visit project "Shop" graph page
-    Then page should have graphs
-
-  @javascript
-  Scenario: I should see project languages & commits graphs on commits graph url
-    When I visit project "Shop" commits graph page
-    Then page should have commits graphs
-    Then page should have languages graphs
-
-  @javascript
-  Scenario: I should see project ci graphs
-    Given project "Shop" has CI enabled
-    When I visit project "Shop" CI graph page
-    Then page should have CI graphs
-
-  @javascript
-  Scenario: I should see project languages & commits graphs on language graph url
-    When I visit project "Shop" languages graph page
-    Then page should have languages graphs
-    Then page should have commits graphs
-
-  @javascript
-  Scenario: I should see project languages & commits graphs on charts url
-    When I visit project "Shop" chart page
-    Then page should have languages graphs
-    Then page should have commits graphs
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
deleted file mode 100644
index 819354bb7801e2377f54475bd2d9dcc663151ca8..0000000000000000000000000000000000000000
--- a/features/project/issues/issues.feature
+++ /dev/null
@@ -1,180 +0,0 @@
-@project_issues
-Feature: Project Issues
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    And project "Shop" have "Release 0.4" open issue
-    And project "Shop" have "Tweet control" open issue
-    And project "Shop" have "Release 0.3" closed issue
-    And I visit project "Shop" issues page
-
-  Scenario: I should see open issues
-    Given I should see "Release 0.4" in issues
-    And I should not see "Release 0.3" in issues
-
-  @javascript
-  Scenario: I should see closed issues
-    Given I click link "Closed"
-    Then I should see "Release 0.3" in issues
-    And I should not see "Release 0.4" in issues
-
-  @javascript
-  Scenario: I should see all issues
-    Given I click link "All"
-    Then I should see "Release 0.3" in issues
-    And I should see "Release 0.4" in issues
-
-  Scenario: I visit issue page
-    Given I click link "Release 0.4"
-    Then I should see issue "Release 0.4"
-
-  Scenario: I submit new unassigned issue
-    Given I click link "New Issue"
-    And I submit new issue "500 error on profile"
-    Then I should see issue "500 error on profile"
-
-  @javascript
-  Scenario: I submit new unassigned issue with labels
-    Given project "Shop" has labels: "bug", "feature", "enhancement"
-    And I click link "New Issue"
-    And I submit new issue "500 error on profile" with label 'bug'
-    Then I should see issue "500 error on profile"
-    And I should see label 'bug' with issue
-
-  @javascript
-  Scenario: I comment issue
-    Given I visit issue page "Release 0.4"
-    And I leave a comment like "XML attached"
-    Then I should see comment "XML attached"
-    And I should see an error alert section within the comment form
-
-  @javascript
-  Scenario: Visiting Issues after being sorted the list
-    Given I visit project "Shop" issues page
-    And I sort the list by "Last updated"
-    And I visit my project's home page
-    And I visit project "Shop" issues page
-    Then The list should be sorted by "Last updated"
-
-  @javascript
-  Scenario: Visiting Merge Requests after being sorted the list
-    Given project "Shop" has a "Bugfix MR" merge request open
-    And I visit project "Shop" issues page
-    And I sort the list by "Last updated"
-    And I visit project "Shop" merge requests page
-    Then The list should be sorted by "Last updated"
-
-  @javascript
-  Scenario: Visiting Merge Requests from a differente Project after sorting
-    Given project "Shop" has a "Bugfix MR" merge request open
-    And I visit project "Shop" merge requests page
-    And I sort the list by "Last updated"
-    And I visit dashboard merge requests page
-    Then The list should be sorted by "Last updated"
-
-  @javascript
-  Scenario: Sort issues by upvotes/downvotes
-    Given project "Shop" have "Bugfix" open issue
-    And issue "Release 0.4" have 2 upvotes and 1 downvote
-    And issue "Tweet control" have 1 upvote and 2 downvotes
-    And I sort the list by "Popularity"
-    Then The list should be sorted by "Popularity"
-
-  # Markdown
-
-  @javascript
-  Scenario: Headers inside the description should have ids generated for them.
-    Given I visit issue page "Release 0.4"
-    Then Header "Description header" should have correct id and link
-
-  @javascript
-  Scenario: Headers inside comments should not have ids generated for them.
-    Given I visit issue page "Release 0.4"
-    And I leave a comment with a header containing "Comment with a header"
-    Then The comment with the header should not have an ID
-
-  @javascript
-  Scenario: Blocks inside comments should not build relative links
-    Given I visit issue page "Release 0.4"
-    And I leave a comment with code block
-    Then The code block should be unchanged
-
-  Scenario: Issues on empty project
-    Given empty project "Empty Project"
-    And I have an ssh key
-    When I visit empty project page
-    And I see empty project details with ssh clone info
-    When I visit empty project's issues page
-    Given I click link "New Issue"
-    And I submit new issue "500 error on profile"
-    Then I should see issue "500 error on profile"
-
-  Scenario: Clickable labels
-    Given issue 'Release 0.4' has label 'bug'
-    And I visit project "Shop" issues page
-    When I click label 'bug'
-    And I should see "Release 0.4" in issues
-    And I should not see "Tweet control" in issues
-
-  @javascript
-  Scenario: Issue notes should be editable with +1
-    Given project "Shop" have "Release 0.4" open issue
-    When I visit issue page "Release 0.4"
-    And I leave a comment with a header containing "Comment with a header"
-    Then The comment with the header should not have an ID
-    And I edit the last comment with a +1
-    Then I should see +1 in the description
-
-  # Issue description preview
-
-  @javascript
-  Scenario: I can't preview without text
-    Given I click link "New Issue"
-    And I haven't written any description text
-    Then The Markdown preview tab should say there is nothing to do
-
-  @javascript
-  Scenario: I can preview with text
-    Given I click link "New Issue"
-    And I write a description like ":+1: Nice"
-    Then The Markdown preview tab should display rendered Markdown
-
-  @javascript
-  Scenario: I preview an issue description
-    Given I click link "New Issue"
-    And I preview a description text like "Bug fixed :smile:"
-    Then I should see the Markdown preview
-    And I should not see the Markdown text field
-
-  @javascript
-  Scenario: I can edit after preview
-    Given I click link "New Issue"
-    And I preview a description text like "Bug fixed :smile:"
-    Then I should see the Markdown write tab
-
-  @javascript
-  Scenario: I can preview when editing an existing issue
-    Given I click link "Release 0.4"
-    And I click link "Edit" for the issue
-    And I preview a description text like "Bug fixed :smile:"
-    Then I should see the Markdown write tab
-
-  @javascript
-  Scenario: I can unsubscribe from issue
-    Given project "Shop" have "Release 0.4" open issue
-    When I visit issue page "Release 0.4"
-    Then I should see that I am subscribed
-    When I click the subscription toggle
-    Then I should see that I am unsubscribed
-
-  @javascript
-  Scenario: I submit new unassigned issue as guest
-    Given public project "Community"
-    When I visit project "Community" page
-    And I visit project "Community" issues page
-    And I click link "New Issue"
-    And I should not see assignee field
-    And I should not see milestone field
-    And I should not see labels field
-    And I submit new issue "500 error on profile"
-    Then I should see issue "500 error on profile"
diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature
deleted file mode 100644
index 45de57f18e328e8a7880744b0c5c70f9cb62424c..0000000000000000000000000000000000000000
--- a/features/project/issues/labels.feature
+++ /dev/null
@@ -1,48 +0,0 @@
-@project_issues
-Feature: Project Issues Labels
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    And project "Shop" has labels: "bug", "feature", "enhancement"
-    Given I visit project "Shop" labels page
-
-  Scenario: I should see labels list
-    Then I should see label 'bug'
-    And I should see label 'feature'
-
-  Scenario: I create new label
-    Given I visit project "Shop" new label page
-    When I submit new label 'support'
-    Then I should see label 'support'
-
-  Scenario: I edit label
-    Given I visit 'bug' label edit page
-    When I change label 'bug' to 'fix'
-    Then I should not see label 'bug'
-    Then I should see label 'fix'
-
-  Scenario: I remove label
-    When I remove label 'bug'
-    Then I should not see label 'bug'
-
-  @javascript
-  Scenario: I remove all labels
-    When I delete all labels
-    Then I should see labels help message
-
-  Scenario: I create a label with invalid color
-    Given I visit project "Shop" new label page
-    When I submit new label with invalid color
-    Then I should see label color error message
-
-  Scenario: I create a label that already exists
-    Given I visit project "Shop" new label page
-    When I submit new label 'bug'
-    Then I should see label label exist error message
-
-  Scenario: I create the same label on another project
-    Given I own project "Forum"
-    And I visit project "Forum" labels page
-    And I visit project "Forum" new label page
-    When I submit new label 'bug'
-    Then I should see label 'bug'
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
deleted file mode 100644
index d121222308d1bbf79d5f63a21dccf7d68293ef02..0000000000000000000000000000000000000000
--- a/features/project/issues/milestones.feature
+++ /dev/null
@@ -1,42 +0,0 @@
-@project_issues
-Feature: Project Issues Milestones
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    And project "Shop" has milestone "v2.2"
-    Given I visit project "Shop" milestones page
-
-  Scenario: I should see active milestones
-    Then I should see milestone "v2.2"
-
-  Scenario: I should see milestone
-    Given I click link "v2.2"
-    Then I should see milestone "v2.2"
-
-  @javascript
-  Scenario: I create and delete new milestone
-    Given I click link "New Milestone"
-    And I submit new milestone "v2.3"
-    Then I should see milestone "v2.3"
-    Given I click button to remove milestone
-    And I confirm in modal
-    When I visit project "Shop" activity page
-    Then I should see deleted milestone activity
-
-  @javascript
-  Scenario: I delete new milestone
-    Given I click button to remove milestone
-    And I confirm in modal
-    And I should see no milestones
-
-  @javascript
-  Scenario: Listing closed issues
-    Given the milestone has open and closed issues
-    And I click link "v2.2"
-    Then I should see 3 issues
-
-  # Markdown
-
-  Scenario: Headers inside the description should have ids generated for them.
-    Given I click link "v2.2"
-    Then Header "Description header" should have correct id and link
diff --git a/features/project/project.feature b/features/project/project.feature
deleted file mode 100644
index 23817ef3ac92817b58f89a285927663194995306..0000000000000000000000000000000000000000
--- a/features/project/project.feature
+++ /dev/null
@@ -1,86 +0,0 @@
-Feature: Project
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    And project "Shop" has push event
-    And I visit project "Shop" page
-
-  Scenario: I edit the project avatar
-    Given I visit edit project "Shop" page
-    When I change the project avatar
-    And I should see new project avatar
-    And I should see the "Remove avatar" button
-
-  Scenario: I remove the project avatar
-    Given I visit edit project "Shop" page
-    And I have an project avatar
-    When I remove my project avatar
-    Then I should see the default project avatar
-    And I should not see the "Remove avatar" button
-
-  @javascript
-  Scenario: I should have readme on page
-    And I visit project "Shop" page
-    Then I should see project "Shop" README
-
-  Scenario: I should see last commit with CI
-    Given project "Shop" has CI enabled
-    Given project "Shop" has CI build
-    And I visit project "Shop" page
-    And I should see last commit with CI status
-
-  @javascript
-  Scenario: I should see project activity
-    When I visit project "Shop" activity page
-    Then I should see project "Shop" activity feed
-
-  Scenario: I visit edit project
-    When I visit edit project "Shop" page
-    Then I should see project settings
-
-  Scenario: I edit project
-    When I visit edit project "Shop" page
-    And change project settings
-    And I save project
-    Then I should see project with new settings
-
-  Scenario: I change project path
-    When I visit edit project "Shop" page
-    And change project path settings
-    Then I should see project with new path settings
-
-  Scenario: I should change project default branch
-    When I visit edit project "Shop" page
-    And change project default branch
-    And I save project
-    Then I should see project default branch changed
-
-  Scenario: I tag a project
-    When I visit edit project "Shop" page
-    Then I should see project settings
-    And I add project tags
-    And I save project
-    Then I should see project tags
-
-  Scenario: I should not see "New Issue" or "New Merge Request" buttons
-    Given I disable issues and merge requests in project
-    When I visit project "Shop" page
-    Then I should not see "New Issue" button
-    And I should not see "New Merge Request" button
-
-  Scenario: I should not see Project snippets
-    Given I disable snippets in project
-    When I visit project "Shop" page
-    Then I should not see "Snippets" button
-
-  @javascript
-  Scenario: I edit Project Notifications
-    Given I click notifications drop down button
-    When I choose Mention setting
-    Then I should see Notification saved message
-
-  Scenario: I should see command line instructions
-    Given I own an empty project
-    And I visit my empty project page
-    And I create bare repo
-    Then I should see command line instructions
diff --git a/features/project/redirects.feature b/features/project/redirects.feature
deleted file mode 100644
index a2e77e7bf3091e934bcc09730c41df61c9e0811a..0000000000000000000000000000000000000000
--- a/features/project/redirects.feature
+++ /dev/null
@@ -1,38 +0,0 @@
-Feature: Project Redirects
-  Background:
-    Given public project "Community"
-    And private project "Enterprise"
-
-  Scenario: I visit public project page
-    When I visit project "Community" page
-    Then I should see project "Community" home page
-
-  Scenario: I visit private project page
-    When I visit project "Enterprise" page
-    Then I should be redirected to sign in page
-
-  Scenario: I visit a non-existent project page
-    When I visit project "CommunityDoesNotExist" page
-    Then I should be redirected to sign in page
-
-  Scenario: I visit a non-existent project page as user
-    Given I sign in as a user
-    When I visit project "CommunityDoesNotExist" page
-    Then page status code should be 404
-
-  Scenario: I visit unauthorized project page as user
-    Given I sign in as a user
-    When I visit project "Enterprise" page
-    Then page status code should be 404
-
-  Scenario: I visit a public project without signing in
-    When I visit project "Community" page
-    And I should see project "Community" home page
-    And I click on "Sign In"
-    And Authenticate
-    Then I should be redirected to "Community" page
-
-  Scenario: I visit private project page without signing in
-    When I visit project "Enterprise" page
-    And I get redirected to signin page where I sign in
-    Then I should be redirected to "Enterprise" page
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
deleted file mode 100644
index 753694a5392232ed8c5fba2fa4d2ef1126870f9a..0000000000000000000000000000000000000000
--- a/features/steps/groups.rb
+++ /dev/null
@@ -1,147 +0,0 @@
-class Spinach::Features::Groups < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedGroup
-  include SharedUser
-
-  step 'I should see group "Owned"' do
-    expect(page).to have_content 'Owned'
-  end
-
-  step 'I am a signed out user' do
-    logout
-  end
-
-  step 'Group "Owned" has a public project "Public-project"' do
-    group = owned_group
-
-    @project = create :project, :public,
-                 group: group,
-                 name: "Public-project"
-  end
-
-  step 'I should see project "Public-project"' do
-    expect(page).to have_content 'Public-project'
-  end
-
-  step 'I should see group "Owned" projects list' do
-    owned_group.projects.each do |project|
-      expect(page).to have_link project.name
-    end
-  end
-
-  step 'I should see projects activity feed' do
-    expect(page).to have_content 'joined project'
-  end
-
-  step 'I should see issues from group "Owned" assigned to me' do
-    assigned_to_me(:issues).each do |issue|
-      expect(page).to have_content issue.title
-    end
-  end
-
-  step 'I should not see issues from the archived project' do
-    @archived_project.issues.each do |issue|
-      expect(page).not_to have_content issue.title
-    end
-  end
-
-  step 'I should not see merge requests from the archived project' do
-    @archived_project.merge_requests.each do |mr|
-      expect(page).not_to have_content mr.title
-    end
-  end
-
-  step 'I should see merge requests from group "Owned" assigned to me' do
-    assigned_to_me(:merge_requests).each do |issue|
-      expect(page).to have_content issue.title[0..80]
-    end
-  end
-
-  step 'project from group "Owned" has issues assigned to me' do
-    create :issue,
-      project: project,
-      assignees: [current_user],
-      author: current_user
-  end
-
-  step 'project from group "Owned" has merge requests assigned to me' do
-    create :merge_request,
-      source_project: project,
-      target_project: project,
-      assignee: current_user,
-      author: current_user
-  end
-
-  step 'I change group "Owned" avatar' do
-    attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
-    click_button "Save group"
-    owned_group.reload
-  end
-
-  step 'I should see new group "Owned" avatar' do
-    expect(owned_group.avatar).to be_instance_of AvatarUploader
-    expect(owned_group.avatar.url).to eq "/uploads/-/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif"
-  end
-
-  step 'I should see the "Remove avatar" button' do
-    expect(page).to have_link("Remove avatar")
-  end
-
-  step 'I have group "Owned" avatar' do
-    attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
-    click_button "Save group"
-    owned_group.reload
-  end
-
-  step 'I remove group "Owned" avatar' do
-    click_link "Remove avatar"
-    owned_group.reload
-  end
-
-  step 'I should not see group "Owned" avatar' do
-    expect(owned_group.avatar?).to eq false
-  end
-
-  step 'I should not see the "Remove avatar" button' do
-    expect(page).not_to have_link("Remove avatar")
-  end
-
-  step 'Group "Owned" has archived project' do
-    group = Group.find_by(name: 'Owned')
-    @archived_project = create(:project, :archived, namespace: group, path: "archived-project")
-  end
-
-  step 'I should see "archived" label' do
-    expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
-  end
-
-  step 'I visit group "NonExistentGroup" page' do
-    visit group_path("NonExistentGroup")
-  end
-
-  step 'the archived project have some issues' do
-    create :issue,
-      project: @archived_project,
-      assignees: [current_user],
-      author: current_user
-  end
-
-  step 'the archived project have some merge requests' do
-    create :merge_request,
-      source_project: @archived_project,
-      target_project: @archived_project,
-      assignee: current_user,
-      author: current_user
-  end
-
-  private
-
-  def assigned_to_me(key)
-    project.send(key).assigned_to(current_user)
-  end
-
-  def project
-    owned_group.projects.first
-  end
-end
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
deleted file mode 100644
index 1a18f1d7065cb8baa164268726421eea81c1ca26..0000000000000000000000000000000000000000
--- a/features/steps/project/active_tab.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-  include SharedActiveTab
-  include SharedProjectTab
-
-  # Sub Tabs: Home
-
-  step 'I click the "Team" tab' do
-    click_link('Members')
-  end
-
-  step 'I click the "Attachments" tab' do
-    click_link('Attachments')
-  end
-
-  step 'I click the "Snippets" tab' do
-    page.within('.layout-nav') do
-      click_link('Snippets')
-    end
-  end
-
-  step 'I click the "Edit Project"' do
-    page.within '.nav-sidebar' do
-      click_link('Edit Project')
-    end
-  end
-
-  step 'I click the "Integrations" tab' do
-    page.within '.nav-sidebar' do
-      click_link('Integrations')
-    end
-  end
-
-  step 'I click the "Repository" tab' do
-    page.within '.sidebar-top-level-items > .active' do
-      click_link('Repository')
-    end
-  end
-
-  step 'I click the "Activity" tab' do
-    page.within '.sidebar-top-level-items > .active' do
-      click_link('Activity')
-    end
-  end
-
-  step 'the active sub tab should be Members' do
-    ensure_active_sub_tab('Members')
-  end
-
-  step 'the active sub tab should be Integrations' do
-    ensure_active_sub_tab('Integrations')
-  end
-
-  step 'the active sub tab should be Repository' do
-    ensure_active_sub_tab('Repository')
-  end
-
-  step 'the active sub tab should be Pages' do
-    ensure_active_sub_tab('Pages')
-  end
-
-  step 'the active sub tab should be Activity' do
-    ensure_active_sub_tab('Activity')
-  end
-
-  # Sub Tabs: Commits
-
-  step 'I click the "Compare" tab' do
-    click_link('Compare')
-  end
-
-  step 'I click the "Branches" tab' do
-    page.within '.nav-sidebar' do
-      click_link('Branches')
-    end
-  end
-
-  step 'I click the "Tags" tab' do
-    click_link('Tags')
-  end
-
-  step 'I click the "Charts" tab' do
-    page.within('.sidebar-top-level-items > .active') do
-      click_link('Charts')
-    end
-  end
-
-  step 'the active sub tab should be Compare' do
-    ensure_active_sub_tab('Compare')
-  end
-
-  step 'the active sub tab should be Branches' do
-    ensure_active_sub_tab('Branches')
-  end
-
-  step 'the active sub tab should be Tags' do
-    ensure_active_sub_tab('Tags')
-  end
-
-  # Sub Tabs: Issues
-
-  step 'I click the "Milestones" sub tab' do
-    page.within('.nav-sidebar') do
-      click_link('Milestones')
-    end
-  end
-
-  step 'I click the "Labels" sub tab' do
-    page.within('.nav-sidebar') do
-      click_link('Labels')
-    end
-  end
-
-  step 'the active sub tab should be Issues' do
-    ensure_active_sub_tab('Issues')
-  end
-
-  step 'the active sub tab should be Milestones' do
-    ensure_active_sub_tab('Milestones')
-  end
-
-  step 'the active sub tab should be Labels' do
-    ensure_active_sub_tab('Labels')
-  end
-end
diff --git a/features/steps/project/builds/permissions.rb b/features/steps/project/builds/permissions.rb
deleted file mode 100644
index 6e9d6504fd55847b21623b8befea89ac707d5446..0000000000000000000000000000000000000000
--- a/features/steps/project/builds/permissions.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Spinach::Features::ProjectBuildsPermissions < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedProject
-  include SharedBuilds
-  include SharedPaths
-  include RepoHelpers
-end
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index c3ae33d2aa920d5a8058a759d334823f10c6f2f9..3ecd4c8b67205812bacc78ee7b7f710a56a5358f 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -7,37 +7,14 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
     click_link "All"
   end
 
-  step 'I should see "Shop" all branches list' do
-    expect(page).to have_content "Branches"
-    expect(page).to have_content "master"
-  end
-
   step 'I click link "Protected"' do
     click_link "Protected"
   end
 
-  step 'I should see "Shop" protected branches list' do
-    page.within ".protected-branches-list" do
-      expect(page).to have_content "stable"
-      expect(page).not_to have_content "master"
-    end
-  end
-
-  step 'project "Shop" has protected branches' do
-    project = Project.find_by(name: "Shop")
-    create(:protected_branch, project: project, name: "stable")
-  end
-
   step 'I click new branch link' do
     click_link "New branch"
   end
 
-  step 'I submit new branch form' do
-    fill_in 'branch_name', with: 'deploy_keys'
-    select_branch('master')
-    click_button 'Create branch'
-  end
-
   step 'I submit new branch form with invalid name' do
     fill_in 'branch_name', with: '1.0 stable'
     page.find("body").click # defocus the branch_name input
@@ -45,40 +22,6 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
     click_button 'Create branch'
   end
 
-  step 'I submit new branch form with branch that already exists' do
-    fill_in 'branch_name', with: 'master'
-    select_branch('master')
-    click_button 'Create branch'
-  end
-
-  step 'I should see new branch created' do
-    expect(page).to have_content 'deploy_keys'
-  end
-
-  step 'I should see new an error that branch is invalid' do
-    expect(page).to have_content 'Branch name is invalid'
-    expect(page).to have_content "can't contain spaces"
-  end
-
-  step 'I should see new an error that branch already exists' do
-    expect(page).to have_content 'Branch already exists'
-  end
-
-  step 'I filter for branch improve/awesome' do
-    fill_in 'branch-search', with: 'improve/awesome'
-    find('#branch-search').native.send_keys(:enter)
-  end
-
-  step "I click branch 'improve/awesome' delete link" do
-    page.within '.js-branch-improve\/awesome' do
-      accept_alert { find('.btn-remove').click }
-    end
-  end
-
-  step "I should not see branch 'improve/awesome'" do
-    expect(page).to have_css('.js-branch-improve\\/awesome', visible: :hidden)
-  end
-
   def select_branch(branch_name)
     find('.git-revision-dropdown-toggle').click
 
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
deleted file mode 100644
index 0350e1c2aef91ae7fce6bac6a8caf2c6094e93fc..0000000000000000000000000000000000000000
--- a/features/steps/project/fork.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-class Spinach::Features::ProjectFork < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-
-  step 'I click link "Fork"' do
-    expect(page).to have_content "Shop"
-    click_link "Fork"
-  end
-
-  step 'I am a member of project "Shop"' do
-    @project = create(:project, :repository, name: "Shop")
-    @project.add_reporter(@user)
-  end
-
-  step 'I should see the forked project page' do
-    expect(page).to have_content "Forked from"
-  end
-
-  step 'I already have a project named "Shop" in my namespace' do
-    @my_project = create(:project, :repository, name: "Shop", namespace: current_user.namespace)
-  end
-
-  step 'I should see a "Name has already been taken" warning' do
-    expect(page).to have_content "Name has already been taken"
-  end
-
-  step 'I fork to my namespace' do
-    page.within '.fork-thumbnail-container' do
-      click_link current_user.name
-    end
-  end
-
-  step 'I should see "New merge request"' do
-    expect(page).to have_content(/new merge request/i)
-  end
-
-  step 'I goto the Merge Requests page' do
-    page.within '.nav-sidebar' do
-      first(:link, "Merge Requests").click
-    end
-  end
-
-  step 'I click link "New merge request"' do
-    page.within '#content-body' do
-      page.has_link?('New Merge Request') ? click_link("New Merge Request") : click_link('New merge request')
-    end
-  end
-
-  step 'I should see the new merge request page for my namespace' do
-    current_path.should have_content(/#{current_user.namespace.name}/i)
-  end
-
-  step 'I visit the forks page of the "Shop" project' do
-    @project = Project.where(name: 'Shop').first
-    visit project_forks_path(@project)
-  end
-
-  step 'I should see my fork on the list' do
-    page.within('.js-projects-list-holder') do
-      project = @user.fork_of(@project.reload)
-      expect(page).to have_content("#{project.namespace.human_name} / #{project.name}")
-    end
-  end
-
-  step 'I make forked repo invalid' do
-    project = @user.fork_of(@project.reload)
-    project.path = 'test-crappy-path'
-    project.save!
-  end
-
-  step 'There is an existent fork of the "Shop" project' do
-    user = create(:user, name: 'Mike')
-    @project.add_reporter(user)
-    @forked_project = Projects::ForkService.new(@project, user).execute
-  end
-
-  step 'I should not see the other fork listed' do
-    expect(page).not_to have_content("#{@forked_project.namespace.human_name} / #{@forked_project.name}")
-  end
-
-  step 'I should see a private fork notice' do
-    expect(page).to have_content("1 private fork")
-  end
-end
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
deleted file mode 100644
index b9cddf4041d66b78051c1f99703cecfbc67cb640..0000000000000000000000000000000000000000
--- a/features/steps/project/graph.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedProject
-
-  step 'page should have graphs' do
-    expect(page).to have_selector ".stat-graph"
-  end
-
-  When 'I visit project "Shop" graph page' do
-    visit project_graph_path(project, "master")
-  end
-
-  step 'I visit project "Shop" commits graph page' do
-    visit commits_project_graph_path(project, "master")
-  end
-
-  step 'I visit project "Shop" languages graph page' do
-    visit languages_project_graph_path(project, "master")
-  end
-
-  step 'I visit project "Shop" chart page' do
-    visit charts_project_graph_path(project, "master")
-  end
-
-  step 'page should have languages graphs' do
-    expect(page).to have_content /Ruby 66.* %/
-    expect(page).to have_content /JavaScript 22.* %/
-  end
-
-  step 'page should have commits graphs' do
-    expect(page).to have_content "Commit statistics for master"
-    expect(page).to have_content "Commits per day of month"
-  end
-
-  step 'I visit project "Shop" CI graph page' do
-    visit ci_project_graph_path(project, 'master')
-  end
-
-  step 'page should have CI graphs' do
-    expect(page).to have_content 'Overall'
-    expect(page).to have_content 'Pipelines for last week'
-    expect(page).to have_content 'Pipelines for last month'
-    expect(page).to have_content 'Pipelines for last year'
-    expect(page).to have_content 'Commit duration in minutes for last 30 commits'
-  end
-
-  def project
-    @project ||= Project.find_by(name: "Shop")
-  end
-end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 3cd26bb429bc4aa55602126c2a73faf31776856c..baa78c2320377d7a42a85b055da7c48f139213f6 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -7,36 +7,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
   include SharedMarkdown
   include SharedUser
 
-  step 'I should see "Release 0.4" in issues' do
-    expect(page).to have_content "Release 0.4"
-  end
-
   step 'I should not see "Release 0.3" in issues' do
     expect(page).not_to have_content "Release 0.3"
   end
 
-  step 'I should not see "Tweet control" in issues' do
-    expect(page).not_to have_content "Tweet control"
-  end
-
-  step 'I should see that I am subscribed' do
-    wait_for_requests
-    expect(find('.js-issuable-subscribe-button')).to have_css 'button.is-checked'
-  end
-
-  step 'I should see that I am unsubscribed' do
-    wait_for_requests
-    expect(find('.js-issuable-subscribe-button')).to have_css 'button:not(.is-checked)'
-  end
-
   step 'I click link "Closed"' do
     find('.issues-state-filters [data-state="closed"] span', text: 'Closed').click
   end
 
-  step 'I click the subscription toggle' do
-    find('.js-issuable-subscribe-button button').click
-  end
-
   step 'I should see "Release 0.3" in issues' do
     expect(page).to have_content "Release 0.3"
   end
@@ -51,24 +29,10 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     expect(find('.issues-state-filters > .active')).to have_content 'All'
   end
 
-  step 'I click link "Release 0.4"' do
-    click_link "Release 0.4"
-  end
-
-  step 'I should see issue "Release 0.4"' do
-    expect(page).to have_content "Release 0.4"
-  end
-
   step 'I should see issue "Tweet control"' do
     expect(page).to have_content "Tweet control"
   end
 
-  step 'I click link "New issue"' do
-    page.within '#content-body' do
-      page.has_link?('New Issue') ? click_link('New Issue') : click_link('New issue')
-    end
-  end
-
   step 'I click "author" dropdown' do
     page.find('.js-author-search').click
     sleep 1
@@ -81,18 +45,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     expect(users[1].text).to eq "#{current_user.name} #{current_user.to_reference}"
   end
 
-  step 'I submit new issue "500 error on profile"' do
-    fill_in "issue_title", with: "500 error on profile"
-    click_button "Submit issue"
-  end
-
-  step 'I submit new issue "500 error on profile" with label \'bug\'' do
-    fill_in "issue_title", with: "500 error on profile"
-    click_button "Label"
-    click_link "bug"
-    click_button "Submit issue"
-  end
-
   step 'I click link "500 error on profile"' do
     click_link "500 error on profile"
   end
@@ -103,13 +55,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     end
   end
 
-  step 'I should see issue "500 error on profile"' do
-    issue = Issue.find_by(title: "500 error on profile")
-    expect(page).to have_content issue.title
-    expect(page).to have_content issue.author_name
-    expect(page).to have_content issue.project.name
-  end
-
   step 'I fill in issue search with "Re"' do
     filter_issue "Re"
   end
@@ -163,49 +108,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     expect(find(issues_assignee_selector)).to have_content(assignee_name)
   end
 
-  step 'project "Shop" have "Release 0.4" open issue' do
-    create(:issue,
-           title: "Release 0.4",
-           project: project,
-           author: project.users.first,
-           description: "# Description header"
-          )
-    wait_for_requests
-  end
-
-  step 'project "Shop" have "Tweet control" open issue' do
-    create(:issue,
-           title: "Tweet control",
-           project: project,
-           author: project.users.first)
-  end
-
-  step 'project "Shop" have "Bugfix" open issue' do
-    create(:issue,
-           title: "Bugfix",
-           project: project,
-           author: project.users.first)
-  end
-
-  step 'project "Shop" have "Release 0.3" closed issue' do
-    create(:closed_issue,
-           title: "Release 0.3",
-           project: project,
-           author: project.users.first)
-  end
-
-  step 'issue "Release 0.4" have 2 upvotes and 1 downvote' do
-    awardable = Issue.find_by(title: 'Release 0.4')
-    create_list(:award_emoji, 2, awardable: awardable)
-    create(:award_emoji, :downvote, awardable: awardable)
-  end
-
-  step 'issue "Tweet control" have 1 upvote and 2 downvotes' do
-    awardable = Issue.find_by(title: 'Tweet control')
-    create(:award_emoji, :upvote, awardable: awardable)
-    create_list(:award_emoji, 2, awardable: awardable, name: 'thumbsdown')
-  end
-
   step 'The list should be sorted by "Least popular"' do
     page.within '.issues-list' do
       page.within 'li.issue:nth-child(1)' do
@@ -225,69 +127,16 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     end
   end
 
-  step 'The list should be sorted by "Popularity"' do
-    page.within '.issues-list' do
-      page.within 'li.issue:nth-child(1)' do
-        expect(page).to have_content 'Release 0.4'
-        expect(page).to have_content '2 1'
-      end
-
-      page.within 'li.issue:nth-child(2)' do
-        expect(page).to have_content 'Tweet control'
-        expect(page).to have_content '1 2'
-      end
-
-      page.within 'li.issue:nth-child(3)' do
-        expect(page).to have_content 'Bugfix'
-        expect(page).not_to have_content '0 0'
-      end
-    end
-  end
-
-  step 'empty project "Empty Project"' do
-    create :project_empty_repo, name: 'Empty Project', namespace: @user.namespace
-  end
-
   When 'I visit empty project page' do
     project = Project.find_by(name: 'Empty Project')
     visit project_path(project)
   end
 
-  step 'I see empty project details with ssh clone info' do
-    project = Project.find_by(name: 'Empty Project')
-    page.all(:css, '.git-empty .clone').each do |element|
-      expect(element.text).to include(project.url_to_repo)
-    end
-  end
-
   When "I visit project \"Community\" issues page" do
     project = Project.find_by(name: 'Community')
     visit project_issues_path(project)
   end
 
-  When "I visit empty project's issues page" do
-    project = Project.find_by(name: 'Empty Project')
-    visit project_issues_path(project)
-  end
-
-  step 'I leave a comment with code block' do
-    page.within(".js-main-target-form") do
-      fill_in "note[note]", with: "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```"
-      click_button "Comment"
-      sleep 0.05
-    end
-  end
-
-  step 'I should see an error alert section within the comment form' do
-    page.within(".js-main-target-form") do
-      find(".error-alert")
-    end
-  end
-
-  step 'The code block should be unchanged' do
-    expect(page).to have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```")
-  end
-
   step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do
     create(:issue, title: 'Bugfix1', description: 'Description for issue1', project: project)
   end
@@ -320,36 +169,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     expect(page).not_to have_content 'Bugfix1'
   end
 
-  step 'issue \'Release 0.4\' has label \'bug\'' do
-    label = project.labels.create!(name: 'bug', color: '#990000')
-    issue = Issue.find_by!(title: 'Release 0.4')
-    issue.labels << label
-  end
-
-  step 'I click label \'bug\'' do
-    page.within ".issues-list" do
-      click_link 'bug'
-    end
-  end
-
-  step 'I should not see labels field' do
-    page.within '.issue-form' do
-      expect(page).not_to have_content("Labels")
-    end
-  end
-
-  step 'I should not see milestone field' do
-    page.within '.issue-form' do
-      expect(page).not_to have_content("Milestone")
-    end
-  end
-
-  step 'I should not see assignee field' do
-    page.within '.issue-form' do
-      expect(page).not_to have_content("Assign to")
-    end
-  end
-
   def filter_issue(text)
     fill_in 'issuable_search', with: text
   end
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
deleted file mode 100644
index 4df96e081f98aacadb9f46f7f44b75d14e406347..0000000000000000000000000000000000000000
--- a/features/steps/project/issues/labels.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedProject
-  include SharedPaths
-
-  step 'I visit \'bug\' label edit page' do
-    visit edit_project_label_path(project, bug_label)
-  end
-
-  step 'I remove label \'bug\'' do
-    page.within "#project_label_#{bug_label.id}" do
-      first(:link, 'Delete').click
-    end
-  end
-
-  step 'I delete all labels' do
-    page.within '.labels' do
-      page.all('.label-list-item').each do
-        first('.remove-row').click
-        first(:link, 'Delete label').click
-      end
-    end
-  end
-
-  step 'I should see labels help message' do
-    page.within '.labels' do
-      expect(page).to have_content 'Generate a default set of labels'
-      expect(page).to have_content 'New label'
-    end
-  end
-
-  step 'I submit new label \'support\'' do
-    fill_in 'Title', with: 'support'
-    fill_in 'Background color', with: '#F95610'
-    click_button 'Create label'
-  end
-
-  step 'I submit new label \'bug\'' do
-    fill_in 'Title', with: 'bug'
-    fill_in 'Background color', with: '#F95610'
-    click_button 'Create label'
-  end
-
-  step 'I submit new label with invalid color' do
-    fill_in 'Title', with: 'support'
-    fill_in 'Background color', with: '#12'
-    click_button 'Create label'
-  end
-
-  step 'I should see label label exist error message' do
-    page.within '.label-form' do
-      expect(page).to have_content 'Title has already been taken'
-    end
-  end
-
-  step 'I should see label color error message' do
-    page.within '.label-form' do
-      expect(page).to have_content 'Color must be a valid color code'
-    end
-  end
-
-  step 'I should see label \'feature\'' do
-    page.within '.other-labels .manage-labels-list' do
-      expect(page).to have_content 'feature'
-    end
-  end
-
-  step 'I should see label \'bug\'' do
-    page.within '.other-labels .manage-labels-list' do
-      expect(page).to have_content 'bug'
-    end
-  end
-
-  step 'I should not see label \'bug\'' do
-    page.within '.other-labels .manage-labels-list' do
-      expect(page).not_to have_content 'bug'
-    end
-  end
-
-  step 'I should see label \'support\'' do
-    page.within '.other-labels .manage-labels-list' do
-      expect(page).to have_content 'support'
-    end
-  end
-
-  step 'I change label \'bug\' to \'fix\'' do
-    fill_in 'Title', with: 'fix'
-    fill_in 'Background color', with: '#F15610'
-    click_button 'Save changes'
-  end
-
-  step 'I should see label \'fix\'' do
-    page.within '.other-labels .manage-labels-list' do
-      expect(page).to have_content 'fix'
-    end
-  end
-
-  def bug_label
-    project.labels.find_or_create_by(title: 'bug')
-  end
-end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 4ce67aa651c38ecedcdce7260434ddef654e9d62..30927306a4f36421929adb738768bafc1cae3d19 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -4,35 +4,6 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
   include SharedPaths
   include SharedMarkdown
 
-  step 'I should see milestone "v2.2"' do
-    milestone = @project.milestones.find_by(title: "v2.2")
-    expect(page).to have_content(milestone.title[0..10])
-    expect(page).to have_content(milestone.expires_at)
-    expect(page).to have_content("Issues")
-  end
-
-  step 'I click link "v2.2"' do
-    click_link "v2.2"
-  end
-
-  step 'I click link "New Milestone"' do
-    page.within('.nav-controls') do
-      click_link "New milestone"
-    end
-  end
-
-  step 'I submit new milestone "v2.3"' do
-    fill_in "milestone_title", with: "v2.3"
-    click_button "Create milestone"
-  end
-
-  step 'I should see milestone "v2.3"' do
-    milestone = @project.milestones.find_by(title: "v2.3")
-    expect(page).to have_content(milestone.title[0..10])
-    expect(page).to have_content(milestone.expires_at)
-    expect(page).to have_content("Issues")
-  end
-
   step 'project "Shop" has milestone "v2.2"' do
     project = Project.find_by(name: "Shop")
     milestone = create(:milestone,
@@ -43,36 +14,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
     3.times { create(:issue, project: project, milestone: milestone) }
   end
 
-  step 'the milestone has open and closed issues' do
-    project = Project.find_by(name: "Shop")
-    milestone = project.milestones.find_by(title: 'v2.2')
-
-    # 3 Open issues created above; create one closed issue
-    create(:closed_issue, project: project, milestone: milestone)
-  end
-
-  step 'I should see deleted milestone activity' do
-    expect(page).to have_content('opened milestone in')
-    expect(page).to have_content('destroyed milestone in')
-  end
-
   When 'I click link "All Issues"' do
     click_link 'All Issues'
   end
-
-  step 'I should see 3 issues' do
-    expect(page).to have_selector('#tab-issues li.issuable-row', count: 4)
-  end
-
-  step 'I click button to remove milestone' do
-    click_button 'Delete'
-  end
-
-  step 'I confirm in modal' do
-    click_button 'Delete milestone'
-  end
-
-  step 'I should see no milestones' do
-    expect(page).to have_content('No milestones to show')
-  end
 end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
deleted file mode 100644
index 3a762be8f1f5df290fd3b505ba64cbe24dbb10ba..0000000000000000000000000000000000000000
--- a/features/steps/project/project.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-class Spinach::Features::Project < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedProject
-  include SharedPaths
-  include WaitForRequests
-
-  step 'change project settings' do
-    fill_in 'project_name_edit', with: 'NewName'
-  end
-
-  step 'I save project' do
-    page.within '.general-settings' do
-      click_button 'Save changes'
-    end
-  end
-
-  step 'I should see project with new settings' do
-    expect(find_field('project_name').value).to eq 'NewName'
-  end
-
-  step 'change project path settings' do
-    fill_in 'project_path', with: 'new-path'
-    click_button 'Rename'
-  end
-
-  step 'I should see project with new path settings' do
-    expect(project.path).to eq 'new-path'
-  end
-
-  step 'I change the project avatar' do
-    attach_file(
-      :project_avatar,
-      File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
-    )
-    page.within '.general-settings' do
-      click_button 'Save changes'
-    end
-    @project.reload
-  end
-
-  step 'I should see new project avatar' do
-    expect(@project.avatar).to be_instance_of AvatarUploader
-    url = @project.avatar.url
-    expect(url).to eq "/uploads/-/system/project/avatar/#{@project.id}/banana_sample.gif"
-  end
-
-  step 'I should see the "Remove avatar" button' do
-    expect(page).to have_link('Remove avatar')
-  end
-
-  step 'I have an project avatar' do
-    attach_file(
-      :project_avatar,
-      File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
-    )
-    page.within '.general-settings' do
-      click_button 'Save changes'
-    end
-    @project.reload
-  end
-
-  step 'I remove my project avatar' do
-    click_link 'Remove avatar'
-    @project.reload
-  end
-
-  step 'I should see the default project avatar' do
-    expect(@project.avatar?).to eq false
-  end
-
-  step 'I should not see the "Remove avatar" button' do
-    expect(page).not_to have_link('Remove avatar')
-  end
-
-  step 'change project default branch' do
-    select 'fix', from: 'project_default_branch'
-    page.within '.general-settings' do
-      click_button 'Save changes'
-    end
-  end
-
-  step 'I should see project default branch changed' do
-    expect(find(:css, 'select#project_default_branch').value).to eq 'fix'
-  end
-
-  step 'I select project "Forum" README tab' do
-    click_link 'Readme'
-  end
-
-  step 'I should see project "Forum" README' do
-    page.within('.readme-holder') do
-      expect(page).to have_content 'Sample repo for testing gitlab features'
-    end
-  end
-
-  step 'I should see project "Shop" README' do
-    wait_for_requests
-    page.within('.readme-holder') do
-      expect(page).to have_content 'testme'
-    end
-  end
-
-  step 'I add project tags' do
-    fill_in 'Tags', with: 'tag1, tag2'
-  end
-
-  step 'I should see project tags' do
-    expect(find_field('Tags').value).to eq 'tag1, tag2'
-  end
-
-  step 'I should not see "New Issue" button' do
-    expect(page).not_to have_link 'New Issue'
-  end
-
-  step 'I should not see "New Merge Request" button' do
-    expect(page).not_to have_link 'New Merge Request'
-  end
-
-  step 'I should not see "Snippets" button' do
-    page.within '.content' do
-      expect(page).not_to have_link 'Snippets'
-    end
-  end
-
-  step 'project "Shop" belongs to group' do
-    group = create(:group)
-    @project.namespace = group
-    @project.save!
-  end
-
-  step 'I click notifications drop down button' do
-    first('.notifications-btn').click
-  end
-
-  step 'I choose Mention setting' do
-    click_link 'On mention'
-  end
-
-  step 'I should see Notification saved message' do
-    page.within '#notifications-button' do
-      expect(page).to have_content 'On mention'
-    end
-  end
-
-  step 'I create bare repo' do
-    click_link 'Create empty bare repository'
-  end
-
-  step 'I should see command line instructions' do
-    page.within ".empty_wrapper" do
-      expect(page).to have_content("Command line instructions")
-    end
-  end
-end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
deleted file mode 100644
index 9ce86ca45d0dfa1b7214346e5ae02226691bb106..0000000000000000000000000000000000000000
--- a/features/steps/project/redirects.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-
-  step 'public project "Community"' do
-    create(:project, :public, name: 'Community')
-  end
-
-  step 'private project "Enterprise"' do
-    create(:project, :private, name: 'Enterprise')
-  end
-
-  step 'I visit project "Community" page' do
-    project = Project.find_by(name: 'Community')
-    visit project_path(project)
-  end
-
-  step 'I should see project "Community" home page' do
-    Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com")
-    page.within '.breadcrumbs .breadcrumb-item-text' do
-      expect(page).to have_content 'Community'
-    end
-  end
-
-  step 'I visit project "Enterprise" page' do
-    project = Project.find_by(name: 'Enterprise')
-    visit project_path(project)
-  end
-
-  step 'I visit project "CommunityDoesNotExist" page' do
-    project = Project.find_by(name: 'Community')
-    visit project_path(project) + 'DoesNotExist'
-  end
-
-  step 'I click on "Sign In"' do
-    first(:link, "Sign in").click
-  end
-
-  step 'Authenticate' do
-    admin = create(:admin)
-    fill_in "user_login", with: admin.email
-    fill_in "user_password", with: admin.password
-    click_button "Sign in"
-    Thread.current[:current_user] = admin
-  end
-
-  step 'I should be redirected to "Community" page' do
-    project = Project.find_by(name: 'Community')
-    expect(current_path).to eq "/#{project.full_path}"
-    expect(status_code).to eq 200
-  end
-
-  step 'I get redirected to signin page where I sign in' do
-    admin = create(:admin)
-    fill_in "user_login", with: admin.email
-    fill_in "user_password", with: admin.password
-    click_button "Sign in"
-    Thread.current[:current_user] = admin
-  end
-
-  step 'I should be redirected to "Enterprise" page' do
-    project = Project.find_by(name: 'Enterprise')
-    expect(current_path).to eq "/#{project.full_path}"
-    expect(status_code).to eq 200
-  end
-end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index a3e4459f169c0e80c481a4b595d22f117dc5f2b1..c2197584d8d669c29118a6dd4e2193fcec631d6a 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -11,7 +11,7 @@ module SharedBuilds
 
   step 'project has a recent build' do
     @pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
-    @build = create(:ci_build, :running, :coverage, pipeline: @pipeline)
+    @build = create(:ci_build, :running, :coverage, :trace_artifact, pipeline: @pipeline)
   end
 
   step 'recent build is successful' do
@@ -30,10 +30,6 @@ module SharedBuilds
     visit project_job_path(@project, @build)
   end
 
-  step 'I visit project builds page' do
-    visit project_jobs_path(@project)
-  end
-
   step 'recent build has artifacts available' do
     artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
     archive = fixture_file_upload(artifacts, 'application/zip')
@@ -54,25 +50,4 @@ module SharedBuilds
     expect(page.response_headers['Content-Type']).to eq 'application/zip'
     expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
   end
-
-  step 'I access artifacts download page' do
-    visit download_project_job_artifacts_path(@project, @build)
-  end
-
-  step 'I see details of a build' do
-    expect(page).to have_content "Job ##{@build.id}"
-  end
-
-  step 'I see build trace' do
-    expect(page).to have_css '#build-trace'
-  end
-
-  step 'I see the build' do
-    page.within('.build') do
-      expect(page).to have_content "##{@build.id}"
-      expect(page).to have_content @build.sha[0..7]
-      expect(page).to have_content @build.ref
-      expect(page).to have_content @build.name
-    end
-  end
 end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index f90247c3fe841d0da208f9dd614f8a2d5368007a..a9174efd334e2fbbc059fa08ee9964bf37e76c6b 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -105,17 +105,6 @@ module SharedIssuable
     edit_issuable
   end
 
-  step 'I click link "Edit" for the issue' do
-    edit_issuable
-  end
-
-  step 'I sort the list by "Last updated"' do
-    find('button.dropdown-toggle').click
-    page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
-      click_link "Last updated"
-    end
-  end
-
   step 'I sort the list by "Least popular"' do
     find('button.dropdown-toggle').click
 
@@ -124,18 +113,6 @@ module SharedIssuable
     end
   end
 
-  step 'I sort the list by "Popularity"' do
-    find('button.dropdown-toggle').click
-
-    page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
-      click_link 'Popularity'
-    end
-  end
-
-  step 'The list should be sorted by "Last updated"' do
-    expect(find('.issues-filters')).to have_content('Last updated')
-  end
-
   step 'I click link "Next" in the sidebar' do
     page.within '.issuable-sidebar' do
       click_link 'Next'
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index c2bec2a6320ca7db2247e1f5486d661e3b6edda3..9d522936fb6d94b754c344b64d2569fe0d6f357f 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -10,51 +10,10 @@ module SharedMarkdown
     expect(find(:xpath, "#{node.path}/..").text).to eq text
   end
 
-  step 'Header "Description header" should have correct id and link' do
-    header_should_have_correct_id_and_link(1, 'Description header', 'description-header')
-  end
-
   step 'I should not see the Markdown preview' do
     expect(find('.gfm-form .js-md-preview')).not_to be_visible
   end
 
-  step 'The Markdown preview tab should say there is nothing to do' do
-    page.within('.gfm-form') do
-      find('.js-md-preview-button').click
-      expect(find('.js-md-preview')).to have_content('Nothing to preview.')
-    end
-  end
-
-  step 'I should not see the Markdown text field' do
-    expect(find('.gfm-form textarea')).not_to be_visible
-  end
-
-  step 'I should see the Markdown write tab' do
-    expect(first('.gfm-form')).to have_link('Write', visible: true)
-  end
-
-  step 'I should see the Markdown preview' do
-    expect(find('.gfm-form')).to have_css('.js-md-preview', visible: true)
-  end
-
-  step 'The Markdown preview tab should display rendered Markdown' do
-    page.within('.gfm-form') do
-      find('.js-md-preview-button').click
-      expect(find('.js-md-preview')).to have_css('gl-emoji', visible: true)
-    end
-  end
-
-  step 'I write a description like ":+1: Nice"' do
-    find('.gfm-form').fill_in 'Description', with: ':+1: Nice'
-  end
-
-  step 'I preview a description text like "Bug fixed :smile:"' do
-    page.within(first('.gfm-form')) do
-      fill_in 'Description', with: 'Bug fixed :smile:'
-      click_link 'Preview'
-    end
-  end
-
   step 'I haven\'t written any description text' do
     find('.gfm-form').fill_in 'Description', with: ''
   end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 95f0cd2156eb73a9c19b0456a509b41a0a997058..bf1b88c60d78fe55ae94e5c25175c99c82981eb2 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -6,70 +6,12 @@ module SharedNote
     wait_for_requests if javascript_test?
   end
 
-  step 'I delete a comment' do
-    page.within('.main-notes-list') do
-      note = find('.note')
-      note.hover
-
-      find('.more-actions').click
-      find('.more-actions .dropdown-menu li', match: :first)
-
-      accept_confirm { find(".js-note-delete").click }
-    end
-  end
-
   step 'I haven\'t written any comment text' do
     page.within(".js-main-target-form") do
       fill_in "note[note]", with: ""
     end
   end
 
-  step 'I leave a comment like "XML attached"' do
-    page.within(".js-main-target-form") do
-      fill_in "note[note]", with: "XML attached"
-      click_button "Comment"
-    end
-
-    wait_for_requests
-  end
-
-  step 'I preview a comment text like "Bug fixed :smile:"' do
-    page.within(".js-main-target-form") do
-      fill_in "note[note]", with: "Bug fixed :smile:"
-      find('.js-md-preview-button').click
-    end
-  end
-
-  step 'I submit the comment' do
-    page.within(".js-main-target-form") do
-      click_button "Comment"
-    end
-
-    wait_for_requests
-  end
-
-  step 'I write a comment like ":+1: Nice"' do
-    page.within(".js-main-target-form") do
-      fill_in 'note[note]', with: ':+1: Nice'
-    end
-  end
-
-  step 'I should not see a comment saying "XML attached"' do
-    expect(page).not_to have_css(".note")
-  end
-
-  step 'I should not see the cancel comment button' do
-    page.within(".js-main-target-form") do
-      should_not have_link("Cancel")
-    end
-  end
-
-  step 'I should not see the comment preview' do
-    page.within(".js-main-target-form") do
-      expect(find('.js-md-preview')).not_to be_visible
-    end
-  end
-
   step 'The comment preview tab should say there is nothing to do' do
     page.within(".js-main-target-form") do
       find('.js-md-preview-button').click
@@ -77,93 +19,7 @@ module SharedNote
     end
   end
 
-  step 'I should not see the comment text field' do
-    page.within(".js-main-target-form") do
-      expect(find('.js-note-text')).not_to be_visible
-    end
-  end
-
-  step 'I should see a comment saying "XML attached"' do
-    page.within(".note") do
-      expect(page).to have_content("XML attached")
-    end
-  end
-
-  step 'I should see an empty comment text field' do
-    page.within(".js-main-target-form") do
-      expect(page).to have_field("note[note]", with: "")
-    end
-  end
-
-  step 'I should see the comment write tab' do
-    page.within(".js-main-target-form") do
-      expect(page).to have_css('.js-md-write-button', visible: true)
-    end
-  end
-
-  step 'The comment preview tab should be display rendered Markdown' do
-    page.within(".js-main-target-form") do
-      find('.js-md-preview-button').click
-      expect(find('.js-md-preview')).to have_css('gl-emoji', visible: true)
-    end
-  end
-
-  step 'I should see the comment preview' do
-    page.within(".js-main-target-form") do
-      expect(page).to have_css('.js-md-preview', visible: true)
-    end
-  end
-
-  step 'I should see comment "XML attached"' do
-    page.within(".note") do
-      expect(page).to have_content("XML attached")
-    end
-  end
-
   step 'I should see no notes at all' do
     expect(page).not_to have_css('.note')
   end
-
-  # Markdown
-
-  step 'I leave a comment with a header containing "Comment with a header"' do
-    page.within(".js-main-target-form") do
-      fill_in "note[note]", with: "# Comment with a header"
-      click_button "Comment"
-    end
-
-    wait_for_requests
-  end
-
-  step 'The comment with the header should not have an ID' do
-    page.within(".note-body > .note-text") do
-      expect(page).to have_content("Comment with a header")
-      expect(page).not_to have_css("#comment-with-a-header")
-    end
-  end
-
-  step 'I edit the last comment with a +1' do
-    page.within(".main-notes-list") do
-      note = find('.note')
-      note.hover
-
-      note.find('.js-note-edit').click
-    end
-
-    page.find('.current-note-edit-form textarea')
-
-    page.within(".current-note-edit-form") do
-      fill_in 'note[note]', with: '+1 Awesome!'
-      click_button 'Save comment'
-    end
-    wait_for_requests
-  end
-
-  step 'I should see +1 in the description' do
-    page.within(".note") do
-      expect(page).to have_content("+1 Awesome!")
-    end
-
-    wait_for_requests
-  end
 end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index bff0d58aaf4da0bdc52d0b2850f2e6a410baff52..d16c127f6e61d07b8b6812e5591fd865536ac07a 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -96,10 +96,6 @@ module SharedPaths
     visit assigned_issues_dashboard_path
   end
 
-  step 'I visit dashboard merge requests page' do
-    visit assigned_mrs_dashboard_path
-  end
-
   step 'I visit dashboard search page' do
     visit search_path
   end
@@ -200,10 +196,6 @@ module SharedPaths
   # Generic Project
   # ----------------------------------------
 
-  step "I visit my project's home page" do
-    visit project_path(@project)
-  end
-
   step "I visit my project's settings page" do
     visit edit_project_path(@project)
   end
@@ -272,10 +264,6 @@ module SharedPaths
     visit project_path(project)
   end
 
-  step 'I visit project "Shop" activity page' do
-    visit activity_project_path(project)
-  end
-
   step 'I visit project "Forked Shop" merge requests page' do
     visit project_merge_requests_path(@forked_project)
   end
@@ -284,14 +272,6 @@ module SharedPaths
     visit edit_project_path(project)
   end
 
-  step 'I visit project branches page' do
-    visit project_branches_path(@project)
-  end
-
-  step 'I visit project protected branches page' do
-    visit project_protected_branches_path(@project)
-  end
-
   step 'I visit compare refs page' do
     visit project_compare_index_path(@project)
   end
@@ -339,20 +319,11 @@ module SharedPaths
     visit project_commit_path(@project, sample_commit.id)
   end
 
-  step 'I visit project "Shop" issues page' do
-    visit project_issues_path(project)
-  end
-
   step 'I visit issue page "Release 0.4"' do
     issue = Issue.find_by(title: "Release 0.4")
     visit project_issue_path(issue.project, issue)
   end
 
-  step 'I visit project "Shop" labels page' do
-    project = Project.find_by(name: 'Shop')
-    visit project_labels_path(project)
-  end
-
   step 'I visit project "Forum" labels page' do
     project = Project.find_by(name: 'Forum')
     visit project_labels_path(project)
@@ -394,18 +365,10 @@ module SharedPaths
     wait_for_requests
   end
 
-  step 'I visit project "Shop" merge requests page' do
-    visit project_merge_requests_path(project)
-  end
-
   step 'I visit forked project "Shop" merge requests page' do
     visit project_merge_requests_path(project)
   end
 
-  step 'I visit project "Shop" milestones page' do
-    visit project_milestones_path(project)
-  end
-
   step 'I visit project "Shop" team page' do
     visit project_project_members_path(project)
   end
@@ -418,11 +381,6 @@ module SharedPaths
   # Visibility Projects
   # ----------------------------------------
 
-  step 'I visit project "Community" page' do
-    project = Project.find_by(name: "Community")
-    visit project_path(project)
-  end
-
   step 'I visit project "Community" source page' do
     project = Project.find_by(name: 'Community')
     visit project_tree_path(project, root_ref)
@@ -442,11 +400,6 @@ module SharedPaths
   # Empty Projects
   # ----------------------------------------
 
-  step "I visit empty project page" do
-    project = Project.find_by(name: "Empty Public Project")
-    visit project_path(project)
-  end
-
   step "I should not see command line instructions" do
     expect(page).not_to have_css('.empty_wrapper')
   end
@@ -482,12 +435,4 @@ module SharedPaths
     mr = MergeRequest.find_by(title: title)
     project_merge_request_path(mr.target_project, mr)
   end
-
-  # ----------------------------------------
-  # Errors
-  # ----------------------------------------
-
-  step 'page status code should be 404' do
-    expect(status_code).to eq 404
-  end
 end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index affbccccdf9390b050418a8d1051d7d2a74c85ee..a1945cf5f3dbdf2862dc41a39e8802c0d2962a77 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -13,11 +13,6 @@ module SharedProject
     @project.add_master(@user)
   end
 
-  step "project exists in some group namespace" do
-    @group = create(:group, name: 'some group')
-    @project = create(:project, :repository, namespace: @group, public_builds: false)
-  end
-
   # Create a specific project called "Shop"
   step 'I own project "Shop"' do
     @project = Project.find_by(name: "Shop")
@@ -25,100 +20,22 @@ module SharedProject
     @project.add_master(@user)
   end
 
-  step 'I disable snippets in project' do
-    @project.snippets_enabled = false
-    @project.save
-  end
-
-  step 'I disable issues and merge requests in project' do
-    @project.issues_enabled = false
-    @project.merge_requests_enabled = false
-    @project.save
-  end
-
-  # Add another user to project "Shop"
-  step 'I add a user to project "Shop"' do
-    @project = Project.find_by(name: "Shop")
-    other_user = create(:user, name: 'Alpha')
-    @project.add_master(other_user)
-  end
-
-  # Create another specific project called "Forum"
-  step 'I own project "Forum"' do
-    @project = Project.find_by(name: "Forum")
-    @project ||= create(:project, :repository, name: "Forum", namespace: @user.namespace, path: 'forum_project')
-    @project.build_project_feature
-    @project.project_feature.save
-    @project.add_master(@user)
-  end
-
-  # Create an empty project without caring about the name
-  step 'I own an empty project' do
-    @project = create(:project, name: 'Empty Project', namespace: @user.namespace)
-    @project.add_master(@user)
-  end
-
-  step 'I visit my empty project page' do
-    project = Project.find_by(name: 'Empty Project')
-    visit project_path(project)
-  end
-
-  step 'I visit project "Shop" activity page' do
-    project = Project.find_by(name: 'Shop')
-    visit project_path(project)
-  end
-
-  step 'project "Shop" has push event' do
-    @project = Project.find_by(name: "Shop")
-    @event = create(:push_event, project: @project, author: @user)
-
-    create(:push_event_payload,
-           event: @event,
-           action: :created,
-           commit_to: '6d394385cf567f80a8fd85055db1ab4c5295806f',
-           ref: 'fix',
-           commit_count: 1)
-  end
-
-  step 'I should see project "Shop" activity feed' do
-    project = Project.find_by(name: "Shop")
-    expect(page).to have_content "#{@user.name} pushed new branch fix at #{project.name_with_namespace}"
-  end
-
-  step 'I should see project settings' do
-    expect(current_path).to eq edit_project_path(@project)
-    expect(page).to have_content("Project name")
-    expect(page).to have_content("Permissions")
-  end
-
   def current_project
     @project ||= Project.first
   end
 
-  # ----------------------------------------
-  # Project permissions
-  # ----------------------------------------
-
-  step 'I am member of a project with a guest role' do
-    @project.add_guest(@user)
-  end
-
-  step 'I am member of a project with a reporter role' do
-    @project.add_reporter(@user)
-  end
-
   # ----------------------------------------
   # Visibility of archived project
   # ----------------------------------------
 
   step 'I should not see project "Archive"' do
     project = Project.find_by(name: "Archive")
-    expect(page).not_to have_content project.name_with_namespace
+    expect(page).not_to have_content project.full_name
   end
 
   step 'I should see project "Archive"' do
     project = Project.find_by(name: "Archive")
-    expect(page).to have_content project.name_with_namespace
+    expect(page).to have_content project.full_name
   end
 
   # ----------------------------------------
@@ -206,40 +123,6 @@ module SharedProject
     create(:label, project: project, title: 'enhancement')
   end
 
-  step 'project "Shop" has CI enabled' do
-    project = Project.find_by(name: "Shop")
-    project.enable_ci
-  end
-
-  step 'project "Shop" has CI build' do
-    project = Project.find_by(name: "Shop")
-    pipeline = create :ci_pipeline, project: project, sha: project.commit.sha, ref: 'master'
-    pipeline.skip
-  end
-
-  step 'I should see last commit with CI status' do
-    page.within ".blob-commit-info" do
-      expect(page).to have_content(project.commit.sha[0..6])
-      expect(page).to have_link("Commit: skipped")
-    end
-  end
-
-  step 'The project is internal' do
-    @project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
-  end
-
-  step 'public access for builds is enabled' do
-    @project.update(public_builds: true)
-  end
-
-  step 'public access for builds is disabled' do
-    @project.update(public_builds: false)
-  end
-
-  step 'project "Shop" has a "Bugfix MR" merge request open' do
-    create(:merge_request, title: "Bugfix MR", target_project: project, source_project: project, author: project.users.first)
-  end
-
   def user_owns_project(user_name:, project_name:, visibility: :private)
     user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore)
     project = Project.find_by(name: project_name)
diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb
index 9856c510aa028c48aab2c2b34c7d89bc8f207e60..9cadc91769d19877499c7a6690962b065c99ef35 100644
--- a/features/steps/shared/user.rb
+++ b/features/steps/shared/user.rb
@@ -19,10 +19,6 @@ module SharedUser
     User.find_by(name: name) || create(:user, { name: name, admin: false }.merge(options))
   end
 
-  step 'I have an ssh key' do
-    create(:personal_key, user: @user)
-  end
-
   step 'I have no ssh keys' do
     @user.keys.delete_all
   end
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 4e2b3c67af5b6d11441a4b9d9b349c7826013761..8879c9ab650f29bf60ee33eb6625b52ca733b804 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -21,13 +21,7 @@ Capybara.register_driver :chrome do |app|
   options.add_argument("no-sandbox")
 
   # Run headless by default unless CHROME_HEADLESS specified
-  unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
-    options.add_argument("headless")
-
-    # Chrome documentation says this flag is needed for now
-    # https://developers.google.com/web/updates/2017/04/headless-chrome#cli
-    options.add_argument("disable-gpu")
-  end
+  options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
 
   # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
   options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 754549f72f0deec0ff307d66ea15f055f3a3e2be..073471b4c4dff7e010de2c660dd48d66b056e17f 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -78,6 +78,14 @@ module API
       rack_response({ 'message' => '404 Not found' }.to_json, 404)
     end
 
+    rescue_from UploadedFile::InvalidPathError do |e|
+      rack_response({ 'message' => e.message }.to_json, 400)
+    end
+
+    rescue_from ObjectStorage::RemoteStoreError do |e|
+      rack_response({ 'message' => e.message }.to_json, 500)
+    end
+
     # Retain 405 error rather than a 500 error for Grape 0.15.0+.
     # https://github.com/ruby-grape/grape/blob/a3a28f5b5dfbb2797442e006dbffd750b27f2a76/UPGRADING.md#changes-to-method-not-allowed-routes
     rescue_from Grape::Exceptions::MethodNotAllowed do |e|
@@ -108,6 +116,7 @@ module API
     mount ::API::AccessRequests
     mount ::API::Applications
     mount ::API::AwardEmoji
+    mount ::API::Badges
     mount ::API::Boards
     mount ::API::Branches
     mount ::API::BroadcastMessages
@@ -120,6 +129,7 @@ module API
     mount ::API::Events
     mount ::API::Features
     mount ::API::Files
+    mount ::API::GroupBoards
     mount ::API::Groups
     mount ::API::GroupMilestones
     mount ::API::Internal
@@ -134,10 +144,12 @@ module API
     mount ::API::MergeRequests
     mount ::API::Namespaces
     mount ::API::Notes
+    mount ::API::Discussions
     mount ::API::NotificationSettings
     mount ::API::PagesDomains
     mount ::API::Pipelines
     mount ::API::PipelineSchedules
+    mount ::API::ProjectExport
     mount ::API::ProjectImport
     mount ::API::ProjectHooks
     mount ::API::Projects
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8ceffe9c5ef518951e7821ae94cb75bfb8eaa7af
--- /dev/null
+++ b/lib/api/badges.rb
@@ -0,0 +1,135 @@
+module API
+  class Badges < Grape::API
+    include PaginationParams
+
+    before { authenticate_non_get! }
+
+    helpers ::API::Helpers::BadgesHelpers
+
+    helpers do
+      def find_source_if_admin(source_type)
+        source = find_source(source_type, params[:id])
+
+        authorize_admin_source!(source_type, source)
+
+        source
+      end
+    end
+
+    %w[group project].each do |source_type|
+      params do
+        requires :id, type: String, desc: "The ID of a #{source_type}"
+      end
+      resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+        desc "Gets a list of #{source_type} badges viewable by the authenticated user." do
+          detail 'This feature was introduced in GitLab 10.6.'
+          success Entities::Badge
+        end
+        params do
+          use :pagination
+        end
+        get ":id/badges" do
+          source = find_source(source_type, params[:id])
+
+          present_badges(source, paginate(source.badges))
+        end
+
+        desc "Preview a badge from a #{source_type}." do
+          detail 'This feature was introduced in GitLab 10.6.'
+          success Entities::BasicBadgeDetails
+        end
+        params do
+          requires :link_url, type: String, desc: 'URL of the badge link'
+          requires :image_url, type: String, desc: 'URL of the badge image'
+        end
+        get ":id/badges/render" do
+          authenticate!
+
+          source = find_source_if_admin(source_type)
+
+          badge = ::Badges::BuildService.new(declared_params(include_missing: false))
+                                        .execute(source)
+
+          if badge.valid?
+            present_badges(source, badge, with: Entities::BasicBadgeDetails)
+          else
+            render_validation_error!(badge)
+          end
+        end
+
+        desc "Gets a badge of a #{source_type}." do
+          detail 'This feature was introduced in GitLab 10.6.'
+          success Entities::Badge
+        end
+        params do
+          requires :badge_id, type: Integer, desc: 'The badge ID'
+        end
+        get ":id/badges/:badge_id" do
+          source = find_source(source_type, params[:id])
+          badge = find_badge(source)
+
+          present_badges(source, badge)
+        end
+
+        desc "Adds a badge to a #{source_type}." do
+          detail 'This feature was introduced in GitLab 10.6.'
+          success Entities::Badge
+        end
+        params do
+          requires :link_url, type: String, desc: 'URL of the badge link'
+          requires :image_url, type: String, desc: 'URL of the badge image'
+        end
+        post ":id/badges" do
+          source = find_source_if_admin(source_type)
+
+          badge = ::Badges::CreateService.new(declared_params(include_missing: false)).execute(source)
+
+          if badge.persisted?
+            present_badges(source, badge)
+          else
+            render_validation_error!(badge)
+          end
+        end
+
+        desc "Updates a badge of a #{source_type}." do
+          detail 'This feature was introduced in GitLab 10.6.'
+          success Entities::Badge
+        end
+        params do
+          optional :link_url, type: String, desc: 'URL of the badge link'
+          optional :image_url, type: String, desc: 'URL of the badge image'
+        end
+        put ":id/badges/:badge_id" do
+          source = find_source_if_admin(source_type)
+
+          badge = ::Badges::UpdateService.new(declared_params(include_missing: false))
+                                         .execute(find_badge(source))
+
+          if badge.valid?
+            present_badges(source, badge)
+          else
+            render_validation_error!(badge)
+          end
+        end
+
+        desc 'Removes a badge from a project or group.' do
+          detail 'This feature was introduced in GitLab 10.6.'
+        end
+        params do
+          requires :badge_id, type: Integer, desc: 'The badge ID'
+        end
+        delete ":id/badges/:badge_id" do
+          source = find_source_if_admin(source_type)
+          badge = find_badge(source)
+
+          if badge.is_a?(GroupBadge) && source.is_a?(Project)
+            error!('To delete a Group badge please use the Group endpoint', 403)
+          end
+
+          destroy_conditionally!(badge)
+          body false
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 1794207e29bc2325d1ed292d4bbfea534af1a69d..13cfba728fa55b157f48c0d23b3ac9e97b87b35c 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -16,6 +16,10 @@ module API
           render_api_error!('The branch refname is invalid', 400)
         end
       end
+
+      params :filter_params do
+        optional :search, type: String, desc: 'Return list of branches matching the search criteria'
+      end
     end
 
     params do
@@ -27,15 +31,23 @@ module API
       end
       params do
         use :pagination
+        use :filter_params
       end
       get ':id/repository/branches' do
         Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
 
         repository = user_project.repository
-        branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name))
+
+        branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
+
         merged_branch_names = repository.merged_branch_names(branches.map(&:name))
 
-        present paginate(branches), with: Entities::Branch, project: user_project, merged_branch_names: merged_branch_names
+        present(
+          paginate(::Kaminari.paginate_array(branches)),
+          with: Entities::Branch,
+          project: user_project,
+          merged_branch_names: merged_branch_names
+        )
       end
 
       resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 982f45425a3402454e98394b5fced30e1749899a..684955a1b248814fb1395a4072e7027eea21018e 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -231,6 +231,20 @@ module API
           render_api_error!("Failed to save note #{note.errors.messages}", 400)
         end
       end
+
+      desc 'Get Merge Requests associated with a commit' do
+        success Entities::MergeRequestBasic
+      end
+      params do
+        requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to find Merge Requests'
+        use :pagination
+      end
+      get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
+        commit = user_project.commit(params[:sha])
+        not_found! 'Commit' unless commit
+
+        present paginate(commit.merge_requests), with: Entities::MergeRequestBasic
+      end
     end
   end
 end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index b0b7b50998f5cde4146bbcd17ad85eac7502406b..70d43ac1d79c777e92e9415595777ceef5d0ec65 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -54,7 +54,7 @@ module API
         present key, with: Entities::DeployKeysProject
       end
 
-      desc 'Add new deploy key to currently authenticated user' do
+      desc 'Add new deploy key to a project' do
         success Entities::DeployKeysProject
       end
       params do
@@ -66,33 +66,32 @@ module API
         params[:key].strip!
 
         # Check for an existing key joined to this project
-        key = user_project.deploy_keys_projects
+        deploy_key_project = user_project.deploy_keys_projects
                           .joins(:deploy_key)
                           .find_by(keys: { key: params[:key] })
 
-        if key
-          present key, with: Entities::DeployKeysProject
+        if deploy_key_project
+          present deploy_key_project, with: Entities::DeployKeysProject
           break
         end
 
         # Check for available deploy keys in other projects
         key = current_user.accessible_deploy_keys.find_by(key: params[:key])
         if key
-          added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
+          deploy_key_project = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
 
-          present added_key, with: Entities::DeployKeysProject
+          present deploy_key_project, with: Entities::DeployKeysProject
           break
         end
 
         # Create a new deploy key
-        key_attributes = { can_push: !!params[:can_push],
-                           deploy_key_attributes: declared_params.except(:can_push) }
-        key = add_deploy_keys_project(user_project, key_attributes)
+        deploy_key_attributes = declared_params.except(:can_push).merge(user: current_user)
+        deploy_key_project = add_deploy_keys_project(user_project, deploy_key_attributes: deploy_key_attributes, can_push: !!params[:can_push])
 
-        if key.valid?
-          present key, with: Entities::DeployKeysProject
+        if deploy_key_project.valid?
+          present deploy_key_project, with: Entities::DeployKeysProject
         else
-          render_validation_error!(key)
+          render_validation_error!(deploy_key_project)
         end
       end
 
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7975f35ab1ee666889beca1e989c807f1bfd6393
--- /dev/null
+++ b/lib/api/discussions.rb
@@ -0,0 +1,195 @@
+module API
+  class Discussions < Grape::API
+    include PaginationParams
+    helpers ::API::Helpers::NotesHelpers
+
+    before { authenticate! }
+
+    NOTEABLE_TYPES = [Issue, Snippet].freeze
+
+    NOTEABLE_TYPES.each do |noteable_type|
+      parent_type = noteable_type.parent_class.to_s.underscore
+      noteables_str = noteable_type.to_s.underscore.pluralize
+
+      params do
+        requires :id, type: String, desc: "The ID of a #{parent_type}"
+      end
+      resource parent_type.pluralize.to_sym, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+        desc "Get a list of #{noteable_type.to_s.downcase} discussions" do
+          success Entities::Discussion
+        end
+        params do
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+          use :pagination
+        end
+        get ":id/#{noteables_str}/:noteable_id/discussions" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+
+          break not_found!("Discussions") unless can?(current_user, noteable_read_ability_name(noteable), noteable)
+
+          notes = noteable.notes
+            .inc_relations_for_view
+            .includes(:noteable)
+            .fresh
+
+          notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+          discussions = Kaminari.paginate_array(Discussion.build_collection(notes, noteable))
+
+          present paginate(discussions), with: Entities::Discussion
+        end
+
+        desc "Get a single #{noteable_type.to_s.downcase} discussion" do
+          success Entities::Discussion
+        end
+        params do
+          requires :discussion_id, type: String, desc: 'The ID of a discussion'
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+        end
+        get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+          notes = readable_discussion_notes(noteable, params[:discussion_id])
+
+          if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable)
+            break not_found!("Discussion")
+          end
+
+          discussion = Discussion.build(notes, noteable)
+
+          present discussion, with: Entities::Discussion
+        end
+
+        desc "Create a new #{noteable_type.to_s.downcase} discussion" do
+          success Entities::Discussion
+        end
+        params do
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+          requires :body, type: String, desc: 'The content of a note'
+          optional :created_at, type: String, desc: 'The creation date of the note'
+        end
+        post ":id/#{noteables_str}/:noteable_id/discussions" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+
+          opts = {
+            note: params[:body],
+            created_at: params[:created_at],
+            type: 'DiscussionNote',
+            noteable_type: noteables_str.classify,
+            noteable_id: noteable.id
+          }
+
+          note = create_note(noteable, opts)
+
+          if note.valid?
+            present note.discussion, with: Entities::Discussion
+          else
+            bad_request!("Note #{note.errors.messages}")
+          end
+        end
+
+        desc "Get comments in a single #{noteable_type.to_s.downcase} discussion" do
+          success Entities::Discussion
+        end
+        params do
+          requires :discussion_id, type: String, desc: 'The ID of a discussion'
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+        end
+        get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+          notes = readable_discussion_notes(noteable, params[:discussion_id])
+
+          if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable)
+            break not_found!("Notes")
+          end
+
+          present notes, with: Entities::Note
+        end
+
+        desc "Add a comment to a #{noteable_type.to_s.downcase} discussion" do
+          success Entities::Note
+        end
+        params do
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+          requires :discussion_id, type: String, desc: 'The ID of a discussion'
+          requires :body, type: String, desc: 'The content of a note'
+          optional :created_at, type: String, desc: 'The creation date of the note'
+        end
+        post ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+          notes = readable_discussion_notes(noteable, params[:discussion_id])
+
+          break not_found!("Discussion") if notes.empty?
+          break bad_request!("Discussion is an individual note.") unless notes.first.part_of_discussion?
+
+          opts = {
+            note: params[:body],
+            type: 'DiscussionNote',
+            in_reply_to_discussion_id: params[:discussion_id],
+            created_at: params[:created_at]
+          }
+          note = create_note(noteable, opts)
+
+          if note.valid?
+            present note, with: Entities::Note
+          else
+            bad_request!("Note #{note.errors.messages}")
+          end
+        end
+
+        desc "Get a comment in a #{noteable_type.to_s.downcase} discussion" do
+          success Entities::Note
+        end
+        params do
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+          requires :discussion_id, type: String, desc: 'The ID of a discussion'
+          requires :note_id, type: Integer, desc: 'The ID of a note'
+        end
+        get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+
+          get_note(noteable, params[:note_id])
+        end
+
+        desc "Edit a comment in a #{noteable_type.to_s.downcase} discussion" do
+          success Entities::Note
+        end
+        params do
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+          requires :discussion_id, type: String, desc: 'The ID of a discussion'
+          requires :note_id, type: Integer, desc: 'The ID of a note'
+          requires :body, type: String, desc: 'The content of a note'
+        end
+        put ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+
+          update_note(noteable, params[:note_id])
+        end
+
+        desc "Delete a comment in a #{noteable_type.to_s.downcase} discussion" do
+          success Entities::Note
+        end
+        params do
+          requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+          requires :discussion_id, type: String, desc: 'The ID of a discussion'
+          requires :note_id, type: Integer, desc: 'The ID of a note'
+        end
+        delete ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+
+          delete_note(noteable, params[:note_id])
+        end
+      end
+    end
+
+    helpers do
+      def readable_discussion_notes(noteable, discussion_id)
+        notes = noteable.notes
+          .where(discussion_id: discussion_id)
+          .inc_relations_for_view
+          .includes(:noteable)
+          .fresh
+
+        notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+      end
+    end
+  end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 0c8ec7dd5f58c55027e8f1cd3e165c189dc0d757..8aad320e376c4960d5a78393c6c2ead7bde80483 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -72,7 +72,7 @@ module API
 
     class ProjectHook < Hook
       expose :project_id, :issues_events, :confidential_issues_events
-      expose :note_events, :pipeline_events, :wiki_page_events
+      expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events
       expose :job_events
     end
 
@@ -91,6 +91,21 @@ module API
       expose :created_at
     end
 
+    class ProjectExportStatus < ProjectIdentity
+      include ::API::Helpers::RelatedResourcesHelpers
+
+      expose :export_status
+      expose :_links, if: lambda { |project, _options| project.export_status == :finished } do
+        expose :api_url do |project|
+          expose_url(api_v4_projects_export_download_path(id: project.id))
+        end
+
+        expose :web_url do |project|
+          Gitlab::Routing.url_helpers.download_export_project_url(project)
+        end
+      end
+    end
+
     class ProjectImportStatus < ProjectIdentity
       expose :import_status
 
@@ -191,6 +206,7 @@ module API
       expose :request_access_enabled
       expose :only_allow_merge_if_all_discussions_are_resolved
       expose :printing_merge_request_link_enabled
+      expose :merge_method
 
       expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
 
@@ -390,6 +406,7 @@ module API
 
     class IssueBasic < ProjectEntity
       expose :closed_at
+      expose :closed_by, using: Entities::UserBasic
       expose :labels do |issue, options|
         # Avoids an N+1 query since labels are preloaded
         issue.labels.map(&:title).sort
@@ -532,6 +549,7 @@ module API
       expose :discussion_locked
       expose :should_remove_source_branch?, as: :should_remove_source_branch
       expose :force_remove_source_branch?, as: :force_remove_source_branch
+      expose :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
 
       expose :web_url do |merge_request, options|
         Gitlab::UrlBuilder.build(merge_request)
@@ -629,6 +647,7 @@ module API
       NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze
 
       expose :id
+      expose :type
       expose :note, as: :body
       expose :attachment_identifier, as: :attachment
       expose :author, using: Entities::UserBasic
@@ -640,6 +659,12 @@ module API
       expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) }
     end
 
+    class Discussion < Grape::Entity
+      expose :id
+      expose :individual_note?, as: :individual_note
+      expose :notes, using: Entities::Note
+    end
+
     class AwardEmoji < Grape::Entity
       expose :id
       expose :name
@@ -769,7 +794,7 @@ module API
       expose :id, :title, :created_at, :updated_at, :active
       expose :push_events, :issues_events, :confidential_issues_events
       expose :merge_requests_events, :tag_push_events, :note_events
-      expose :pipeline_events, :wiki_page_events
+      expose :confidential_note_events, :pipeline_events, :wiki_page_events
       expose :job_events
       # Expose serialized properties
       expose :properties do |service, options|
@@ -903,7 +928,7 @@ module API
     end
 
     class Tag < Grape::Entity
-      expose :name, :message
+      expose :name, :message, :target
 
       expose :commit, using: Entities::Commit do |repo_tag, options|
         options[:project].repository.commit(repo_tag.dereferenced_target)
@@ -928,6 +953,7 @@ module API
       expose :tag_list
       expose :run_untagged
       expose :locked
+      expose :maximum_timeout
       expose :access_level
       expose :version, :revision, :platform, :architecture
       expose :contacted_at
@@ -1096,7 +1122,7 @@ module API
       end
 
       class RunnerInfo < Grape::Entity
-        expose :timeout
+        expose :metadata_timeout, as: :timeout
       end
 
       class Step < Grape::Entity
@@ -1235,5 +1261,23 @@ module API
       expose :startline
       expose :project_id
     end
+
+    class BasicBadgeDetails < Grape::Entity
+      expose :link_url
+      expose :image_url
+      expose :rendered_link_url do |badge, options|
+        badge.rendered_link_url(options.fetch(:project, nil))
+      end
+      expose :rendered_image_url do |badge, options|
+        badge.rendered_image_url(options.fetch(:project, nil))
+      end
+    end
+
+    class Badge < BasicBadgeDetails
+      expose :id
+      expose :kind do |badge|
+        badge.type == 'ProjectBadge' ? 'project' : 'group'
+      end
+    end
   end
 end
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 9385c6ca174243cc9a9d8b649df6dc450b7b8fd0..11d848584d9eb443c351acfae617c800217fc930 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -65,6 +65,13 @@ module API
 
         present feature, with: Entities::Feature, current_user: current_user
       end
+
+      desc 'Remove the gate value for the given feature'
+      delete ':name' do
+        Feature.get(params[:name]).remove
+
+        status 204
+      end
     end
   end
 end
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aa9fff25fc8cb66719a3361ac3db8419ec673925
--- /dev/null
+++ b/lib/api/group_boards.rb
@@ -0,0 +1,117 @@
+module API
+  class GroupBoards < Grape::API
+    include BoardsResponses
+    include PaginationParams
+
+    before do
+      authenticate!
+    end
+
+    helpers do
+      def board_parent
+        user_group
+      end
+    end
+
+    params do
+      requires :id, type: String, desc: 'The ID of a group'
+    end
+
+    resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+      segment ':id/boards' do
+        desc 'Find a group board' do
+          detail 'This feature was introduced in 10.6'
+          success ::API::Entities::Board
+        end
+        get '/:board_id' do
+          present board, with: ::API::Entities::Board
+        end
+
+        desc 'Get all group boards' do
+          detail 'This feature was introduced in 10.6'
+          success Entities::Board
+        end
+        params do
+          use :pagination
+        end
+        get '/' do
+          present paginate(board_parent.boards), with: Entities::Board
+        end
+      end
+
+      params do
+        requires :board_id, type: Integer, desc: 'The ID of a board'
+      end
+      segment ':id/boards/:board_id' do
+        desc 'Get the lists of a group board' do
+          detail 'Does not include backlog and closed lists. This feature was introduced in 10.6'
+          success Entities::List
+        end
+        params do
+          use :pagination
+        end
+        get '/lists' do
+          present paginate(board_lists), with: Entities::List
+        end
+
+        desc 'Get a list of a group board' do
+          detail 'This feature was introduced in 10.6'
+          success Entities::List
+        end
+        params do
+          requires :list_id, type: Integer, desc: 'The ID of a list'
+        end
+        get '/lists/:list_id' do
+          present board_lists.find(params[:list_id]), with: Entities::List
+        end
+
+        desc 'Create a new board list' do
+          detail 'This feature was introduced in 10.6'
+          success Entities::List
+        end
+        params do
+          requires :label_id, type: Integer, desc: 'The ID of an existing label'
+        end
+        post '/lists' do
+          unless available_labels_for(board_parent).exists?(params[:label_id])
+            render_api_error!({ error: 'Label not found!' }, 400)
+          end
+
+          authorize!(:admin_list, user_group)
+
+          create_list
+        end
+
+        desc 'Moves a board list to a new position' do
+          detail 'This feature was introduced in 10.6'
+          success Entities::List
+        end
+        params do
+          requires :list_id,  type: Integer, desc: 'The ID of a list'
+          requires :position, type: Integer, desc: 'The position of the list'
+        end
+        put '/lists/:list_id' do
+          list = board_lists.find(params[:list_id])
+
+          authorize!(:admin_list, user_group)
+
+          move_list(list)
+        end
+
+        desc 'Delete a board list' do
+          detail 'This feature was introduced in 10.6'
+          success Entities::List
+        end
+        params do
+          requires :list_id, type: Integer, desc: 'The ID of a board list'
+        end
+        delete "/lists/:list_id" do
+          authorize!(:admin_list, user_group)
+          list = board_lists.find(params[:list_id])
+
+          destroy_list(list)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index 92800ce6450433e83b0726d4aaeae03453517b87..55d5c7f16063b04245ad62fb257d4736839dcc71 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -31,7 +31,7 @@ module API
         key = params[:key]
         variable = user_group.variables.find_by(key: key)
 
-        return not_found!('GroupVariable') unless variable
+        break not_found!('GroupVariable') unless variable
 
         present variable, with: Entities::Variable
       end
@@ -67,7 +67,7 @@ module API
       put ':id/variables/:key' do
         variable = user_group.variables.find_by(key: params[:key])
 
-        return not_found!('GroupVariable') unless variable
+        break not_found!('GroupVariable') unless variable
 
         variable_params = declared_params(include_missing: false).except(:key)
 
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index e4fca77ab5dd300073b0b4bf0a67f49ecce5179c..b8657cd7ee47cff2b47aa928a60821a9bf93a63f 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -83,12 +83,13 @@ module API
     end
 
     def available_labels_for(label_parent)
-      search_params =
-        if label_parent.is_a?(Project)
-          { project_id: label_parent.id }
-        else
-          { group_id: label_parent.id, only_group_labels: true }
-        end
+      search_params = { include_ancestor_groups: true }
+
+      if label_parent.is_a?(Project)
+        search_params[:project_id] = label_parent.id
+      else
+        search_params.merge!(group_id: label_parent.id, only_group_labels: true)
+      end
 
       LabelsFinder.new(current_user, search_params).execute
     end
@@ -102,9 +103,9 @@ module API
     end
 
     def find_project(id)
-      if id =~ /^\d+$/
+      if id.is_a?(Integer) || id =~ /^\d+$/
         Project.find_by(id: id)
-      else
+      elsif id.include?("/")
         Project.find_by_full_path(id)
       end
     end
@@ -388,29 +389,7 @@ module API
 
     # file helpers
 
-    def uploaded_file(field, uploads_path)
-      if params[field]
-        bad_request!("#{field} is not a file") unless params[field][:filename]
-        return params[field]
-      end
-
-      return nil unless params["#{field}.path"] && params["#{field}.name"]
-
-      # sanitize file paths
-      # this requires all paths to exist
-      required_attributes! %W(#{field}.path)
-      uploads_path = File.realpath(uploads_path)
-      file_path = File.realpath(params["#{field}.path"])
-      bad_request!('Bad file path') unless file_path.start_with?(uploads_path)
-
-      UploadedFile.new(
-        file_path,
-        params["#{field}.name"],
-        params["#{field}.type"] || 'application/octet-stream'
-      )
-    end
-
-    def present_file!(path, filename, content_type = 'application/octet-stream')
+    def present_disk_file!(path, filename, content_type = 'application/octet-stream')
       filename ||= File.basename(path)
       header['Content-Disposition'] = "attachment; filename=#{filename}"
       header['Content-Transfer-Encoding'] = 'binary'
@@ -426,13 +405,17 @@ module API
       end
     end
 
-    def present_artifacts!(artifacts_file)
-      return not_found! unless artifacts_file.exists?
+    def present_carrierwave_file!(file, supports_direct_download: true)
+      return not_found! unless file.exists?
 
-      if artifacts_file.file_storage?
-        present_file!(artifacts_file.path, artifacts_file.filename)
+      if file.file_storage?
+        present_disk_file!(file.path, file.filename)
+      elsif supports_direct_download && file.class.direct_download_enabled?
+        redirect(file.url)
       else
-        redirect_to(artifacts_file.url)
+        header(*Gitlab::Workhorse.send_url(file.url))
+        status :ok
+        body
       end
     end
 
@@ -485,8 +468,8 @@ module API
       header(*Gitlab::Workhorse.send_git_blob(repository, blob))
     end
 
-    def send_git_archive(repository, ref:, format:)
-      header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
+    def send_git_archive(repository, **kwargs)
+      header(*Gitlab::Workhorse.send_git_archive(repository, **kwargs))
     end
 
     def send_artifacts_entry(build, entry)
diff --git a/lib/api/helpers/badges_helpers.rb b/lib/api/helpers/badges_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1f8afbf3c9058d3140b6f9c3e6e3f42929f2553f
--- /dev/null
+++ b/lib/api/helpers/badges_helpers.rb
@@ -0,0 +1,28 @@
+module API
+  module Helpers
+    module BadgesHelpers
+      include ::API::Helpers::MembersHelpers
+
+      def find_badge(source)
+        source.badges.find(params[:badge_id])
+      end
+
+      def present_badges(source, records, options = {})
+        entity_type = options[:with] || Entities::Badge
+        badge_params = badge_source_params(source).merge(with: entity_type)
+
+        present records, badge_params
+      end
+
+      def badge_source_params(source)
+        project = if source.is_a?(Project)
+                    source
+                  else
+                    GroupProjectsFinder.new(group: source, current_user: current_user).execute.first
+                  end
+
+        { project: project }
+      end
+    end
+  end
+end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index cd59da6fc7050efeb79dd106a8ba137fdb62ca4b..abe3d3539843f6c511e9ef51816be6920b0887d4 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -29,18 +29,6 @@ module API
         {}
       end
 
-      def fix_git_env_repository_paths(env, repository_path)
-        if obj_dir_relative = env['GIT_OBJECT_DIRECTORY_RELATIVE'].presence
-          env['GIT_OBJECT_DIRECTORY'] = File.join(repository_path, obj_dir_relative)
-        end
-
-        if alt_obj_dirs_relative = env['GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE'].presence
-          env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = alt_obj_dirs_relative.map { |dir| File.join(repository_path, dir) }
-        end
-
-        env
-      end
-
       def log_user_activity(actor)
         commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS
 
@@ -109,14 +97,7 @@ module API
 
       # Return the Gitaly Address if it is enabled
       def gitaly_payload(action)
-        return unless %w[git-receive-pack git-upload-pack].include?(action)
-
-        if action == 'git-receive-pack'
-          return unless Gitlab::GitalyClient.feature_enabled?(
-            :ssh_receive_pack,
-            status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
-          )
-        end
+        return unless %w[git-receive-pack git-upload-pack git-upload-archive].include?(action)
 
         {
           repository: repository.gitaly_repository,
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cd91df1ecd8bd1ae392b1efe8d6a42f3d6aec79b
--- /dev/null
+++ b/lib/api/helpers/notes_helpers.rb
@@ -0,0 +1,76 @@
+module API
+  module Helpers
+    module NotesHelpers
+      def update_note(noteable, note_id)
+        note = noteable.notes.find(params[:note_id])
+
+        authorize! :admin_note, note
+
+        opts = {
+          note: params[:body]
+        }
+        parent = noteable_parent(noteable)
+        project = parent if parent.is_a?(Project)
+
+        note = ::Notes::UpdateService.new(project, current_user, opts).execute(note)
+
+        if note.valid?
+          present note, with: Entities::Note
+        else
+          bad_request!("Failed to save note #{note.errors.messages}")
+        end
+      end
+
+      def delete_note(noteable, note_id)
+        note = noteable.notes.find(note_id)
+
+        authorize! :admin_note, note
+
+        parent = noteable_parent(noteable)
+        project = parent if parent.is_a?(Project)
+        destroy_conditionally!(note) do |note|
+          ::Notes::DestroyService.new(project, current_user).execute(note)
+        end
+      end
+
+      def get_note(noteable, note_id)
+        note = noteable.notes.with_metadata.find(params[:note_id])
+        can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
+
+        if can_read_note
+          present note, with: Entities::Note
+        else
+          not_found!("Note")
+        end
+      end
+
+      def noteable_read_ability_name(noteable)
+        "read_#{noteable.class.to_s.underscore}".to_sym
+      end
+
+      def find_noteable(parent, noteables_str, noteable_id)
+        public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
+      end
+
+      def noteable_parent(noteable)
+        public_send("user_#{noteable.class.parent_class.to_s.underscore}") # rubocop:disable GitlabSecurity/PublicSend
+      end
+
+      def create_note(noteable, opts)
+        noteables_str = noteable.model_name.to_s.underscore.pluralize
+
+        return not_found!(noteables_str) unless can?(current_user, noteable_read_ability_name(noteable), noteable)
+
+        authorize! :create_note, noteable
+
+        parent = noteable_parent(noteable)
+        if opts[:created_at]
+          opts.delete(:created_at) unless current_user.admin? || parent.owner == current_user
+        end
+
+        project = parent if parent.is_a?(Project)
+        ::Notes::CreateService.new(project, current_user, opts).execute
+      end
+    end
+  end
+end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..381d5e8968c446c140c5fbbdef3cc27c2ae13d8a
--- /dev/null
+++ b/lib/api/helpers/projects_helpers.rb
@@ -0,0 +1,38 @@
+module API
+  module Helpers
+    module ProjectsHelpers
+      extend ActiveSupport::Concern
+
+      included do
+        helpers do
+          params :optional_project_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'
+            optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
+            optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
+            optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+            optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
+            optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
+            optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
+            optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
+            optional :public_builds, type: Boolean, desc: 'Perform public builds'
+            optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+            optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
+            optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
+            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'
+            optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
+          end
+
+          params :optional_project_params do
+            use :optional_project_params_ce
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb
index 1f677529b07482f69d1625dfdac8291d4211c528..7f4d6e58b3495e5df868480fb6907b2adf67a084 100644
--- a/lib/api/helpers/related_resources_helpers.rb
+++ b/lib/api/helpers/related_resources_helpers.rb
@@ -15,7 +15,7 @@ module API
         url_options = Gitlab::Application.routes.default_url_options
         protocol, host, port = url_options.slice(:protocol, :host, :port).values
 
-        URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s
+        URI::Generic.build(scheme: protocol, host: host, port: port, path: path).to_s
       end
 
       private
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index b3660e4a1d0bbc6bc241b895d38fa1bb452e4f51..6b72caea8fd719c41b45b618e7096f66c1531dd7 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -21,8 +21,7 @@ module API
 
         # Stores some Git-specific env thread-safely
         env = parse_env
-        env = fix_git_env_repository_paths(env, repository_path) if project
-        Gitlab::Git::Env.set(env)
+        Gitlab::Git::HookEnv.set(gl_repository, env) if project
 
         actor =
           if params[:key_id]
@@ -51,7 +50,7 @@ module API
           access_checker.check(params[:action], params[:changes])
           @project ||= access_checker.project
         rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e
-          return { status: false, message: e.message }
+          break { status: false, message: e.message }
         end
 
         log_user_activity(actor)
@@ -143,21 +142,21 @@ module API
         if key
           key.update_last_used_at
         else
-          return { 'success' => false, 'message' => 'Could not find the given key' }
+          break { 'success' => false, 'message' => 'Could not find the given key' }
         end
 
         if key.is_a?(DeployKey)
-          return { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
+          break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
         end
 
         user = key.user
 
         unless user
-          return { success: false, message: 'Could not find a user for the given key' }
+          break { success: false, message: 'Could not find a user for the given key' }
         end
 
         unless user.two_factor_enabled?
-          return { success: false, message: 'Two-factor authentication is not enabled for this user' }
+          break { success: false, message: 'Two-factor authentication is not enabled for this user' }
         end
 
         codes = nil
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index b6c278c89d049421b71072580ef6d348fca27dcd..12ff2a1398b0c42c61e252daba52f0de4660e5fc 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -32,6 +32,8 @@ module API
         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'
+        optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time'
+        optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time'
         optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
         optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
         optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
@@ -95,7 +97,7 @@ module API
       get ":id/issues" do
         group = find_group!(params[:id])
 
-        issues = paginate(find_issues(group_id: group.id))
+        issues = paginate(find_issues(group_id: group.id, include_subgroups: true))
 
         options = {
           with: Entities::IssueBasic,
@@ -308,7 +310,7 @@ module API
 
         issue = find_project_issue(params[:issue_iid])
 
-        return not_found!('UserAgentDetail') unless issue.user_agent_detail
+        break not_found!('UserAgentDetail') unless issue.user_agent_detail
 
         present issue.user_agent_detail, with: Entities::UserAgentDetail
       end
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index 2a8fa7659bf86ba13cab198a40361d6d55b04214..32379d7c8ab6c6ae23a89fd035f6b7cd57da2b0c 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -2,39 +2,48 @@ module API
   class JobArtifacts < Grape::API
     before { authenticate_non_get! }
 
+    # EE::API::JobArtifacts would override the following helpers
+    helpers do
+      def authorize_download_artifacts!
+        authorize_read_builds!
+      end
+    end
+
     params do
       requires :id, type: String, desc: 'The ID of a project'
     end
     resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
-      desc 'Download the artifacts file from a job' do
+      desc 'Download the artifacts archive from a job' do
         detail 'This feature was introduced in GitLab 8.10'
       end
       params do
         requires :ref_name, type: String, desc: 'The ref from repository'
         requires :job,      type: String, desc: 'The name for the job'
       end
+      route_setting :authentication, job_token_allowed: true
       get ':id/jobs/artifacts/:ref_name/download',
         requirements: { ref_name: /.+/ } do
-        authorize_read_builds!
+        authorize_download_artifacts!
 
         builds = user_project.latest_successful_builds_for(params[:ref_name])
         latest_build = builds.find_by!(name: params[:job])
 
-        present_artifacts!(latest_build.artifacts_file)
+        present_carrierwave_file!(latest_build.artifacts_file)
       end
 
-      desc 'Download the artifacts file from a job' do
+      desc 'Download the artifacts archive from a job' do
         detail 'This feature was introduced in GitLab 8.5'
       end
       params do
         requires :job_id, type: Integer, desc: 'The ID of a job'
       end
+      route_setting :authentication, job_token_allowed: true
       get ':id/jobs/:job_id/artifacts' do
-        authorize_read_builds!
+        authorize_download_artifacts!
 
         build = find_build!(params[:job_id])
 
-        present_artifacts!(build.artifacts_file)
+        present_carrierwave_file!(build.artifacts_file)
       end
 
       desc 'Download a specific file from artifacts archive' do
@@ -68,7 +77,7 @@ module API
 
         build = find_build!(params[:job_id])
         authorize!(:update_build, build)
-        return not_found!(build) unless build.artifacts?
+        break not_found!(build) unless build.artifacts?
 
         build.keep_artifacts!
 
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 9c205514b3ab374828f409e128a0883f22a831f2..54d1acbd4127eb0b4d932e88da462b3e26602dc6 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -72,7 +72,7 @@ module API
         present build, with: Entities::Job
       end
 
-      # TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
+      # TODO: We should use `present_disk_file!` and leave this implementation for backward compatibility (when build trace
       #       is saved in the DB instead of file). But before that, we need to consider how to replace the value of
       #       `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
       desc 'Get a trace of a specific job of a project'
@@ -120,7 +120,7 @@ module API
 
         build = find_build!(params[:job_id])
         authorize!(:update_build, build)
-        return forbidden!('Job is not retryable') unless build.retryable?
+        break forbidden!('Job is not retryable') unless build.retryable?
 
         build = Ci::Build.retry(build, current_user)
 
@@ -138,7 +138,7 @@ module API
 
         build = find_build!(params[:job_id])
         authorize!(:erase_build, build)
-        return forbidden!('Job is not erasable!') unless build.erasable?
+        break forbidden!('Job is not erasable!') unless build.erasable?
 
         build.erase(erased_by: current_user)
         present build, with: Entities::Job
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 16d0f005f215ade6710c57bb812f8d1b7ee1cb98..d4cc18f622bc4853db40211c8bc14cef5f8f32aa 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -6,6 +6,32 @@ module API
 
     helpers ::Gitlab::IssuableMetadata
 
+    # EE::API::MergeRequests would override the following helpers
+    helpers do
+      params :optional_params_ee do
+      end
+
+      params :merge_params_ee do
+      end
+
+      def update_merge_request_ee(merge_request)
+      end
+    end
+
+    def self.update_params_at_least_one_of
+      %i[
+        assignee_id
+        description
+        labels
+        milestone_id
+        remove_source_branch
+        state_event
+        target_branch
+        title
+        discussion_locked
+      ]
+    end
+
     helpers do
       def find_merge_requests(args = {})
         args = declared_params.merge(args)
@@ -31,6 +57,12 @@ module API
         mr.all_pipelines
       end
 
+      def check_sha_param!(params, merge_request)
+        if params[:sha] && merge_request.diff_head_sha != params[:sha]
+          render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
+        end
+      end
+
       params :merge_requests_params do
         optional :state, type: String, values: %w[opened closed merged all], default: 'all',
                          desc: 'Return opened, closed, merged, or all merge requests'
@@ -42,6 +74,8 @@ module API
         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'
+        optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
+        optional :updated_before, type: DateTime, desc: 'Return merge requests updated before the specified time'
         optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
         optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
         optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
@@ -104,16 +138,15 @@ module API
           render_api_error!(errors, 400)
         end
 
-        params :optional_params_ce do
+        params :optional_params do
           optional :description, type: String, desc: 'The description of the merge request'
           optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
           optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
           optional :labels, type: String, desc: 'Comma-separated list of label names'
           optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
-        end
+          optional :allow_maintainer_to_push, type: Boolean, desc: 'Whether a maintainer of the target project can push to the source project'
 
-        params :optional_params do
-          use :optional_params_ce
+          use :optional_params_ee
         end
       end
 
@@ -156,7 +189,7 @@ module API
       post ":id/merge_requests" do
         Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42316')
 
-        authorize! :create_merge_request, user_project
+        authorize! :create_merge_request_from, user_project
 
         mr_params = declared_params(include_missing: false)
         mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)
@@ -238,18 +271,6 @@ module API
         success Entities::MergeRequest
       end
       params do
-        # CE
-        at_least_one_of_ce = [
-          :assignee_id,
-          :description,
-          :labels,
-          :milestone_id,
-          :remove_source_branch,
-          :state_event,
-          :target_branch,
-          :title,
-          :discussion_locked
-        ]
         optional :title, type: String, allow_blank: false, desc: 'The title of the merge request'
         optional :target_branch, type: String, allow_blank: false, desc: 'The target branch'
         optional :state_event, type: String, values: %w[close reopen],
@@ -257,7 +278,7 @@ module API
         optional :discussion_locked, type: Boolean, desc: 'Whether the MR discussion is locked'
 
         use :optional_params
-        at_least_one_of(*at_least_one_of_ce)
+        at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of)
       end
       put ':id/merge_requests/:merge_request_iid' do
         Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42318')
@@ -280,13 +301,14 @@ module API
         success Entities::MergeRequest
       end
       params do
-        # CE
         optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
         optional :should_remove_source_branch, type: Boolean,
                                                desc: 'When true, the source branch will be deleted if possible'
         optional :merge_when_pipeline_succeeds, type: Boolean,
                                                 desc: 'When true, this merge request will be merged when the pipeline succeeds'
         optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
+
+        use :merge_params_ee
       end
       put ':id/merge_requests/:merge_request_iid/merge' do
         Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42317')
@@ -302,9 +324,9 @@ module API
 
         render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds)
 
-        if params[:sha] && merge_request.diff_head_sha != params[:sha]
-          render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
-        end
+        check_sha_param!(params, merge_request)
+
+        update_merge_request_ee(merge_request)
 
         merge_params = {
           commit_message: params[:merge_commit_message],
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 3588dc85c9e70afda263734330d01dcfc5905483..69f1df6b3418df4e41a995f4d74c65370b2a66c7 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -1,19 +1,23 @@
 module API
   class Notes < Grape::API
     include PaginationParams
+    helpers ::API::Helpers::NotesHelpers
 
     before { authenticate! }
 
     NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze
 
-    params do
-      requires :id, type: String, desc: 'The ID of a project'
-    end
-    resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
-      NOTEABLE_TYPES.each do |noteable_type|
+    NOTEABLE_TYPES.each do |noteable_type|
+      parent_type = noteable_type.parent_class.to_s.underscore
+      noteables_str = noteable_type.to_s.underscore.pluralize
+
+      params do
+        requires :id, type: String, desc: "The ID of a #{parent_type}"
+      end
+      resource parent_type.pluralize.to_sym, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
         noteables_str = noteable_type.to_s.underscore.pluralize
 
-        desc 'Get a list of project +noteable+ notes' do
+        desc "Get a list of #{noteable_type.to_s.downcase} notes" do
           success Entities::Note
         end
         params do
@@ -25,7 +29,7 @@ module API
           use :pagination
         end
         get ":id/#{noteables_str}/:noteable_id/notes" do
-          noteable = find_project_noteable(noteables_str, params[:noteable_id])
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
 
           if can?(current_user, noteable_read_ability_name(noteable), noteable)
             # We exclude notes that are cross-references and that cannot be viewed
@@ -46,7 +50,7 @@ module API
           end
         end
 
-        desc 'Get a single +noteable+ note' do
+        desc "Get a single #{noteable_type.to_s.downcase} note" do
           success Entities::Note
         end
         params do
@@ -54,18 +58,11 @@ module API
           requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
         end
         get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
-          noteable = find_project_noteable(noteables_str, params[:noteable_id])
-          note = noteable.notes.with_metadata.find(params[:note_id])
-          can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
-
-          if can_read_note
-            present note, with: Entities::Note
-          else
-            not_found!("Note")
-          end
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+          get_note(noteable, params[:note_id])
         end
 
-        desc 'Create a new +noteable+ note' do
+        desc "Create a new #{noteable_type.to_s.downcase} note" do
           success Entities::Note
         end
         params do
@@ -74,34 +71,25 @@ module API
           optional :created_at, type: String, desc: 'The creation date of the note'
         end
         post ":id/#{noteables_str}/:noteable_id/notes" do
-          noteable = find_project_noteable(noteables_str, params[:noteable_id])
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
 
           opts = {
             note: params[:body],
             noteable_type: noteables_str.classify,
-            noteable_id: noteable.id
+            noteable_id: noteable.id,
+            created_at: params[:created_at]
           }
 
-          if can?(current_user, noteable_read_ability_name(noteable), noteable)
-            authorize! :create_note, noteable
+          note = create_note(noteable, opts)
 
-            if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
-              opts[:created_at] = params[:created_at]
-            end
-
-            note = ::Notes::CreateService.new(user_project, current_user, opts).execute
-
-            if note.valid?
-              present note, with: Entities.const_get(note.class.name)
-            else
-              not_found!("Note #{note.errors.messages}")
-            end
+          if note.valid?
+            present note, with: Entities.const_get(note.class.name)
           else
-            not_found!("Note")
+            bad_request!("Note #{note.errors.messages}")
           end
         end
 
-        desc 'Update an existing +noteable+ note' do
+        desc "Update an existing #{noteable_type.to_s.downcase} note" do
           success Entities::Note
         end
         params do
@@ -110,24 +98,12 @@ module API
           requires :body, type: String, desc: 'The content of a note'
         end
         put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
-          note = user_project.notes.find(params[:note_id])
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
 
-          authorize! :admin_note, note
-
-          opts = {
-            note: params[:body]
-          }
-
-          note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note)
-
-          if note.valid?
-            present note, with: Entities::Note
-          else
-            render_api_error!("Failed to save note #{note.errors.messages}", 400)
-          end
+          update_note(noteable, params[:note_id])
         end
 
-        desc 'Delete a +noteable+ note' do
+        desc "Delete a #{noteable_type.to_s.downcase} note" do
           success Entities::Note
         end
         params do
@@ -135,25 +111,11 @@ module API
           requires :note_id, type: Integer, desc: 'The ID of a note'
         end
         delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
-          note = user_project.notes.find(params[:note_id])
-
-          authorize! :admin_note, note
+          noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
 
-          destroy_conditionally!(note) do |note|
-            ::Notes::DestroyService.new(user_project, current_user).execute(note)
-          end
+          delete_note(noteable, params[:note_id])
         end
       end
     end
-
-    helpers do
-      def find_project_noteable(noteables_str, noteable_id)
-        public_send("find_project_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
-      end
-
-      def noteable_read_ability_name(noteable)
-        "read_#{noteable.class.to_s.underscore}".to_sym
-      end
-    end
   end
 end
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5ef4e9d530c201e0b72517b8aa7ec280fe95ff77
--- /dev/null
+++ b/lib/api/project_export.rb
@@ -0,0 +1,63 @@
+module API
+  class ProjectExport < Grape::API
+    before do
+      not_found! unless Gitlab::CurrentSettings.project_export_enabled?
+      authorize_admin_project
+    end
+
+    params do
+      requires :id, type: String, desc: 'The ID of a project'
+    end
+    resource :projects, requirements: { id: %r{[^/]+} } do
+      desc 'Get export status' do
+        detail 'This feature was introduced in GitLab 10.6.'
+        success Entities::ProjectExportStatus
+      end
+      get ':id/export' do
+        present user_project, with: Entities::ProjectExportStatus
+      end
+
+      desc 'Download export' do
+        detail 'This feature was introduced in GitLab 10.6.'
+      end
+      get ':id/export/download' do
+        path = user_project.export_project_path
+
+        render_api_error!('404 Not found or has expired', 404) unless path
+
+        present_disk_file!(path, File.basename(path), 'application/gzip')
+      end
+
+      desc 'Start export' do
+        detail 'This feature was introduced in GitLab 10.6.'
+      end
+      params do
+        optional :description, type: String, desc: 'Override the project description'
+        optional :upload, type: Hash do
+          optional :url, type: String, desc: 'The URL to upload the project'
+          optional :http_method, type: String, default: 'PUT', desc: 'HTTP method to upload the exported project'
+        end
+      end
+      post ':id/export' do
+        project_export_params = declared_params(include_missing: false)
+        after_export_params = project_export_params.delete(:upload) || {}
+
+        export_strategy = if after_export_params[:url].present?
+                            params = after_export_params.slice(:url, :http_method).symbolize_keys
+
+                            Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy.new(params)
+                          end
+
+        if export_strategy&.invalid?
+          render_validation_error!(export_strategy)
+        else
+          user_project.add_export_job(current_user: current_user,
+                                      after_export_strategy: export_strategy,
+                                      params: project_export_params)
+        end
+
+        accepted!
+      end
+    end
+  end
+end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index f82241058e5e0dcfa6c80d6ba87f826f160e3f33..68921ae439bde4c881b9da9abcb854cdf66c97c0 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -14,6 +14,7 @@ module API
         optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
         optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
         optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
+        optional :confidential_note_events, type: Boolean, desc: "Trigger hook on confidential note(comment) events"
         optional :job_events, type: Boolean, desc: "Trigger hook on job events"
         optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
         optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index a509c1f32c1912aa34f5e72a7085ca2116cb265e..bc5152e539fbb2565b287445441c83b998482aa1 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -1,6 +1,7 @@
 module API
   class ProjectImport < Grape::API
     include PaginationParams
+    include Helpers::ProjectsHelpers
 
     helpers do
       def import_params
@@ -25,6 +26,12 @@ module API
         requires :path, type: String, desc: 'The new project path and name'
         requires :file, type: File, desc: 'The project export file to be imported'
         optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace."
+        optional :overwrite, type: Boolean, default: false, desc: 'If there is a project in the same namespace and with the same name overwrite it'
+        optional :override_params,
+                 type: Hash,
+                 desc: 'New project params to override values in the export' do
+          use :optional_project_params
+        end
       end
       desc 'Create a new project import' do
         detail 'This feature was introduced in GitLab 10.6.'
@@ -44,10 +51,15 @@ module API
         project_params = {
             path: import_params[:path],
             namespace_id: namespace.id,
-            file: import_params[:file]['tempfile']
+            file: import_params[:file]['tempfile'],
+            overwrite: import_params[:overwrite]
         }
 
-        project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
+        override_params = import_params.delete(:override_params)
+
+        project = ::Projects::GitlabProjectsImportService.new(
+          current_user, project_params, override_params
+        ).execute
 
         render_api_error!(project.errors.full_messages&.first, 400) unless project.saved?
 
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 39c03c40bab9ec620f9bb21ea95ecc74b16dc662..1de5551fee91d857bc418d6cefd6986e0c27c581 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -145,7 +145,7 @@ module API
 
         snippet = Snippet.find_by!(id: params[:snippet_id], project_id: params[:id])
 
-        return not_found!('UserAgentDetail') unless snippet.user_agent_detail
+        break not_found!('UserAgentDetail') unless snippet.user_agent_detail
 
         present snippet.user_agent_detail, with: Entities::UserAgentDetail
       end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index b552b0e0c5d03f663c57054264bc403294037d32..51b3b0459f37c49156194337ec4d473593e37b54 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -4,36 +4,11 @@ module API
   class Projects < Grape::API
     include PaginationParams
     include Helpers::CustomAttributes
+    include Helpers::ProjectsHelpers
 
     before { authenticate_non_get! }
 
     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'
-        optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
-        optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
-        optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
-        optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
-        optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
-        optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
-        optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
-        optional :public_builds, type: Boolean, desc: 'Perform public builds'
-        optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
-        optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
-        optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
-        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
-        use :optional_params_ce
-      end
-
       params :statistics_params do
         optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
       end
@@ -143,7 +118,7 @@ module API
         optional :name, type: String, desc: 'The name of the project'
         optional :path, type: String, desc: 'The path of the repository'
         at_least_one_of :name, :path
-        use :optional_params
+        use :optional_project_params
         use :create_params
       end
       post do
@@ -171,7 +146,7 @@ module API
         requires :user_id, type: Integer, desc: 'The ID of a user'
         optional :path, type: String, desc: 'The path of the repository'
         optional :default_branch, type: String, desc: 'The default branch of the project'
-        use :optional_params
+        use :optional_project_params
         use :create_params
       end
       post "user/:user_id" do
@@ -228,11 +203,7 @@ module API
         namespace_id = fork_params[:namespace]
 
         if namespace_id.present?
-          fork_params[:namespace] = if namespace_id =~ /^\d+$/
-                                      Namespace.find_by(id: namespace_id)
-                                    else
-                                      Namespace.find_by_path_or_name(namespace_id)
-                                    end
+          fork_params[:namespace] = find_namespace(namespace_id)
 
           unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
             not_found!('Target Namespace')
@@ -278,6 +249,7 @@ module API
             :issues_enabled,
             :lfs_enabled,
             :merge_requests_enabled,
+            :merge_method,
             :name,
             :only_allow_merge_if_all_discussions_are_resolved,
             :only_allow_merge_if_pipeline_succeeds,
@@ -295,7 +267,7 @@ module API
         optional :default_branch, type: String, desc: 'The default branch of the project'
         optional :path, type: String, desc: 'The path of the repository'
 
-        use :optional_params
+        use :optional_project_params
         at_least_one_of(*at_least_one_of_ce)
       end
       put ':id' do
@@ -366,6 +338,11 @@ module API
         end
       end
 
+      desc 'Get languages in project repository'
+      get ':id/languages' do
+        user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
+      end
+
       desc 'Remove a project'
       delete ":id" do
         authorize! :remove_project, user_project
@@ -425,7 +402,7 @@ module API
         end
 
         unless user_project.allowed_to_share_with_group?
-          return render_api_error!("The project sharing with group is disabled", 400)
+          break render_api_error!("The project sharing with group is disabled", 400)
         end
 
         link = user_project.project_group_links.new(declared_params(include_missing: false))
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index c15c487deb4958d1022d51228cd56c595ab6d773..aa7cab4a741f14e5333ee201dc8d3f90085988f9 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -52,11 +52,7 @@ module API
           conflict!("Protected branch '#{params[:name]}' already exists")
         end
 
-        # Replace with `declared(params)` after updating to grape v1.0.2
-        # See https://github.com/ruby-grape/grape/pull/1710
-        # and https://gitlab.com/gitlab-org/gitlab-ce/issues/40843
-        declared_params = params.slice("name", "push_access_level", "merge_access_level", "allowed_to_push", "allowed_to_merge")
-
+        declared_params = declared_params(include_missing: false)
         api_service = ::ProtectedBranches::ApiService.new(user_project, current_user, declared_params)
         protected_branch = api_service.create
 
@@ -74,7 +70,10 @@ module API
       delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
         protected_branch = user_project.protected_branches.find_by!(name: params[:name])
 
-        destroy_conditionally!(protected_branch)
+        destroy_conditionally!(protected_branch) do
+          destroy_service = ::ProtectedBranches::DestroyService.new(user_project, current_user)
+          destroy_service.execute(protected_branch)
+        end
       end
     end
   end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 9638c53a1dfa51bf2a6b0ad19507058fbdd54971..bb3fa99af38e7e009739e1ee5c82490c69b3592e 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -88,7 +88,7 @@ module API
       end
       get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
         begin
-          send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
+          send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
         rescue
           not_found!('File')
         end
@@ -111,8 +111,8 @@ module API
       end
       params do
         use :pagination
-        optional :order_by, type: String, values: %w[email name commits], default: nil, desc: 'Return contributors ordered by `name` or `email` or `commits`'
-        optional :sort, type: String, values: %w[asc desc], default: nil, desc: 'Sort by asc (ascending) or desc (descending)'
+        optional :order_by, type: String, values: %w[email name commits], default: 'commits', desc: 'Return contributors ordered by `name` or `email` or `commits`'
+        optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
       end
       get ':id/repository/contributors' do
         begin
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 91cdc564002d70bdab98a520f9dce6be42d6b549..4d4fbe50f9f956b4c8fb2ad80d939f7a1b0ad3d4 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -14,9 +14,10 @@ module API
         optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
         optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
         optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
+        optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
       end
       post '/' do
-        attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list])
+        attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :maximum_timeout])
           .merge(get_runner_details_from_request)
 
         runner =
@@ -28,7 +29,7 @@ module API
             project.runners.create(attributes)
           end
 
-        return forbidden! unless runner
+        break forbidden! unless runner
 
         if runner.id
           present runner, with: Entities::RunnerRegistrationDetails
@@ -82,7 +83,7 @@ module API
         if current_runner.runner_queue_value_latest?(params[:last_update])
           header 'X-GitLab-Last-Update', params[:last_update]
           Gitlab::Metrics.add_event(:build_not_found_cached)
-          return no_content!
+          break no_content!
         end
 
         new_update = current_runner.ensure_runner_queue_value
@@ -151,7 +152,7 @@ module API
 
         stream_size = job.trace.append(request.body.read, content_range[0].to_i)
         if stream_size < 0
-          return error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{-stream_size}" })
+          break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{-stream_size}" })
         end
 
         status 202
@@ -185,7 +186,7 @@ module API
 
         status 200
         content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
-        Gitlab::Workhorse.artifact_upload_ok
+        JobArtifactUploader.workhorse_authorize
       end
 
       desc 'Upload artifacts for job' do
@@ -200,12 +201,15 @@ module API
         requires :id, type: Integer, desc: %q(Job's ID)
         optional :token, type: String, desc: %q(Job's authentication token)
         optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
-        optional :file, type: File, desc: %q(Artifact's file)
         optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
         optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse))
         optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse))
+        optional 'file.size', type: Integer, desc: %q(real size of file (generated by Workhorse))
+        optional 'file.sha256', type: String, desc: %q(sha256 checksum of the file (generated by Workhorse))
         optional 'metadata.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
         optional 'metadata.name', type: String, desc: %q(filename (generated by Workhorse))
+        optional 'metadata.size', type: Integer, desc: %q(real size of metadata (generated by Workhorse))
+        optional 'metadata.sha256', type: String, desc: %q(sha256 checksum of metadata (generated by Workhorse))
       end
       post '/:id/artifacts' do
         not_allowed! unless Gitlab.config.artifacts.enabled
@@ -214,21 +218,34 @@ module API
         job = authenticate_job!
         forbidden!('Job is not running!') unless job.running?
 
-        workhorse_upload_path = JobArtifactUploader.workhorse_upload_path
-        artifacts = uploaded_file(:file, workhorse_upload_path)
-        metadata = uploaded_file(:metadata, workhorse_upload_path)
+        artifacts = UploadedFile.from_params(params, :file, JobArtifactUploader.workhorse_local_upload_path)
+        metadata = UploadedFile.from_params(params, :metadata, JobArtifactUploader.workhorse_local_upload_path)
 
         bad_request!('Missing artifacts file!') unless artifacts
         file_to_large! unless artifacts.size < max_artifacts_size
 
+        bad_request!("Already uploaded") if job.job_artifacts_archive
+
         expire_in = params['expire_in'] ||
           Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
 
-        job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, expire_in: expire_in)
-        job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, expire_in: expire_in) if metadata
-        job.artifacts_expire_in = expire_in
+        job.build_job_artifacts_archive(
+          project: job.project,
+          file: artifacts,
+          file_type: :archive,
+          file_sha256: artifacts.sha256,
+          expire_in: expire_in)
+
+        if metadata
+          job.build_job_artifacts_metadata(
+            project: job.project,
+            file: metadata,
+            file_type: :metadata,
+            file_sha256: metadata.sha256,
+            expire_in: expire_in)
+        end
 
-        if job.save
+        if job.update(artifacts_expire_in: expire_in)
           present job, with: Entities::JobRequest::Response
         else
           render_validation_error!(job)
@@ -243,11 +260,12 @@ module API
       params do
         requires :id, type: Integer, desc: %q(Job's ID)
         optional :token, type: String, desc: %q(Job's authentication token)
+        optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
       end
       get '/:id/artifacts' do
         job = authenticate_job!
 
-        present_artifacts!(job.artifacts_file)
+        present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
       end
     end
   end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 996457c5dfecc1f5c4e9f560f40429b5f0ba0fa6..5f2a95676051e4d725ee5eaa8ec03e9187641d68 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -57,6 +57,7 @@ module API
         optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
         optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
                                 desc: 'The access_level of the runner'
+        optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
         at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level
       end
       put ':id' do
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 3556ad98c52e84e9f16965232c7eb5425954a703..5d9ec617cb7800ef8233eda5331e0daf4d38e192 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -84,7 +84,7 @@ module API
           values: %w(projects issues merge_requests milestones)
         use :pagination
       end
-      get ':id/-/search' do
+      get ':id/(-/)search' do
         present search(group_id: user_group.id), with: entity
       end
     end
@@ -103,7 +103,7 @@ module API
           values: %w(issues merge_requests milestones notes wiki_blobs commits blobs)
         use :pagination
       end
-      get ':id/-/search' do
+      get ':id/(-/)search' do
         present search(project_id: user_project.id), with: entity
       end
     end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 6c97659166dee12756a79f8bf712f63cf2635a2d..794fdab8f2b74652b68d61448ab36a71e09dd07e 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -735,7 +735,7 @@ module API
           required: false,
           name: event_name.to_sym,
           type: String,
-          desc: ServicesHelper.service_event_description(event_name)
+          desc: service.event_description(event_name)
         }
       end
     end
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index c736cc3202107e0ccca1a579cbe122ca9dbd000a..b30305b4bc99d938bac5253ae772b5385be07525 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -94,7 +94,7 @@ module API
       end
       put ':id' do
         snippet = snippets_for_current_user.find_by(id: params.delete(:id))
-        return not_found!('Snippet') unless snippet
+        break not_found!('Snippet') unless snippet
 
         authorize! :update_personal_snippet, snippet
 
@@ -120,7 +120,7 @@ module API
       end
       delete ':id' do
         snippet = snippets_for_current_user.find_by(id: params.delete(:id))
-        return not_found!('Snippet') unless snippet
+        break not_found!('Snippet') unless snippet
 
         authorize! :destroy_personal_snippet, snippet
 
@@ -135,7 +135,7 @@ module API
       end
       get ":id/raw" do
         snippet = snippets_for_current_user.find_by(id: params.delete(:id))
-        return not_found!('Snippet') unless snippet
+        break not_found!('Snippet') unless snippet
 
         env['api.format'] = :txt
         content_type 'text/plain'
@@ -153,7 +153,7 @@ module API
 
         snippet = Snippet.find_by!(id: params[:id])
 
-        return not_found!('UserAgentDetail') unless snippet.user_agent_detail
+        break not_found!('UserAgentDetail') unless snippet.user_agent_detail
 
         present snippet.user_agent_detail, with: Entities::UserAgentDetail
       end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index b3709455bc3394e06279b55c124fa47903fd4378..b29e660c6e0f45bfb5f9c11168f033ba52949390 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -62,7 +62,7 @@ module API
         authorize! :admin_build, user_project
 
         trigger = user_project.triggers.find(params.delete(:trigger_id))
-        return not_found!('Trigger') unless trigger
+        break not_found!('Trigger') unless trigger
 
         present trigger, with: Entities::Trigger
       end
@@ -99,7 +99,7 @@ module API
         authorize! :admin_build, user_project
 
         trigger = user_project.triggers.find(params.delete(:trigger_id))
-        return not_found!('Trigger') unless trigger
+        break not_found!('Trigger') unless trigger
 
         if trigger.update(declared_params(include_missing: false))
           present trigger, with: Entities::Trigger
@@ -119,7 +119,7 @@ module API
         authorize! :admin_build, user_project
 
         trigger = user_project.triggers.find(params.delete(:trigger_id))
-        return not_found!('Trigger') unless trigger
+        break not_found!('Trigger') unless trigger
 
         if trigger.update(owner: current_user)
           status :ok
@@ -140,7 +140,7 @@ module API
         authorize! :admin_build, user_project
 
         trigger = user_project.triggers.find(params.delete(:trigger_id))
-        return not_found!('Trigger') unless trigger
+        break not_found!('Trigger') unless trigger
 
         destroy_conditionally!(trigger)
       end
diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb
index ac76fece931cedfd7ba263b15814e6f3fa832796..b49448e1e6799275e27a5d39e6b5a519d699f57f 100644
--- a/lib/api/v3/builds.rb
+++ b/lib/api/v3/builds.rb
@@ -51,7 +51,7 @@ module API
         get ':id/repository/commits/:sha/builds' do
           authorize_read_builds!
 
-          return not_found! unless user_project.commit(params[:sha])
+          break not_found! unless user_project.commit(params[:sha])
 
           pipelines = user_project.pipelines.where(sha: params[:sha])
           builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
@@ -85,7 +85,7 @@ module API
 
           build = get_build!(params[:build_id])
 
-          present_artifacts!(build.artifacts_file)
+          present_carrierwave_file!(build.artifacts_file)
         end
 
         desc 'Download the artifacts file from build' do
@@ -102,10 +102,10 @@ module API
           builds = user_project.latest_successful_builds_for(params[:ref_name])
           latest_build = builds.find_by!(name: params[:job])
 
-          present_artifacts!(latest_build.artifacts_file)
+          present_carrierwave_file!(latest_build.artifacts_file)
         end
 
-        # TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
+        # TODO: We should use `present_disk_file!` and leave this implementation for backward compatibility (when build trace
         #       is saved in the DB instead of file). But before that, we need to consider how to replace the value of
         #       `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
         desc 'Get a trace of a specific build of a project'
@@ -153,7 +153,7 @@ module API
 
           build = get_build!(params[:build_id])
           authorize!(:update_build, build)
-          return forbidden!('Build is not retryable') unless build.retryable?
+          break forbidden!('Build is not retryable') unless build.retryable?
 
           build = Ci::Build.retry(build, current_user)
 
@@ -171,7 +171,7 @@ module API
 
           build = get_build!(params[:build_id])
           authorize!(:erase_build, build)
-          return forbidden!('Build is not erasable!') unless build.erasable?
+          break forbidden!('Build is not erasable!') unless build.erasable?
 
           build.erase(erased_by: current_user)
           present build, with: ::API::V3::Entities::Build
@@ -188,7 +188,7 @@ module API
 
           build = get_build!(params[:build_id])
           authorize!(:update_build, build)
-          return not_found!(build) unless build.artifacts?
+          break not_found!(build) unless build.artifacts?
 
           build.keep_artifacts!
 
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index ce216497996bcd8985b991f7bff6a211bc5b335a..9b0f70e2bfec71d0004feb1d9762c68f3cee364f 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -93,7 +93,7 @@ module API
         post ":id/merge_requests" do
           Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42126')
 
-          authorize! :create_merge_request, user_project
+          authorize! :create_merge_request_from, 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?
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 7d8b1f369fea1ef638dcedaa79d797e782e156a9..eb3dd1135240644cb49306478ccac74dc6e2f262 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -268,11 +268,7 @@ module API
           namespace_id = fork_params[:namespace]
 
           if namespace_id.present?
-            fork_params[:namespace] = if namespace_id =~ /^\d+$/
-                                        Namespace.find_by(id: namespace_id)
-                                      else
-                                        Namespace.find_by_path_or_name(namespace_id)
-                                      end
+            fork_params[:namespace] = find_namespace(namespace_id)
 
             unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
               not_found!('Target Namespace')
@@ -427,7 +423,7 @@ module API
           end
 
           unless user_project.allowed_to_share_with_group?
-            return render_api_error!("The project sharing with group is disabled", 400)
+            break render_api_error!("The project sharing with group is disabled", 400)
           end
 
           link = user_project.project_group_links.new(declared_params(include_missing: false))
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
index 5b54734bb4503d9008835b59f33c069a759c3f93..f701d64e886bd73606c38cbf9887215f03bcc31a 100644
--- a/lib/api/v3/repositories.rb
+++ b/lib/api/v3/repositories.rb
@@ -75,7 +75,7 @@ module API
         end
         get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
           begin
-            send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
+            send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
           rescue
             not_found!('File')
           end
diff --git a/lib/api/v3/snippets.rb b/lib/api/v3/snippets.rb
index 85613c8ed84fdff70432904577534a6a001020c5..1df8a20e74a8830afed18020f1f6c2e0468d3ba6 100644
--- a/lib/api/v3/snippets.rb
+++ b/lib/api/v3/snippets.rb
@@ -90,7 +90,7 @@ module API
         end
         put ':id' do
           snippet = snippets_for_current_user.find_by(id: params.delete(:id))
-          return not_found!('Snippet') unless snippet
+          break not_found!('Snippet') unless snippet
 
           authorize! :update_personal_snippet, snippet
 
@@ -114,7 +114,7 @@ module API
         end
         delete ':id' do
           snippet = snippets_for_current_user.find_by(id: params.delete(:id))
-          return not_found!('Snippet') unless snippet
+          break not_found!('Snippet') unless snippet
 
           authorize! :destroy_personal_snippet, snippet
           snippet.destroy
@@ -129,7 +129,7 @@ module API
         end
         get ":id/raw" do
           snippet = snippets_for_current_user.find_by(id: params.delete(:id))
-          return not_found!('Snippet') unless snippet
+          break not_found!('Snippet') unless snippet
 
           env['api.format'] = :txt
           content_type 'text/plain'
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index 34f07dfb486297a4e6078cf996397b9d05efaee0..969bb2a05de810a97501e50084c87c4dbc9041a3 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -72,7 +72,7 @@ module API
           authorize! :admin_build, user_project
 
           trigger = user_project.triggers.find_by(token: params[:token].to_s)
-          return not_found!('Trigger') unless trigger
+          break not_found!('Trigger') unless trigger
 
           present trigger, with: ::API::V3::Entities::Trigger
         end
@@ -100,7 +100,7 @@ module API
           authorize! :admin_build, user_project
 
           trigger = user_project.triggers.find_by(token: params[:token].to_s)
-          return not_found!('Trigger') unless trigger
+          break not_found!('Trigger') unless trigger
 
           trigger.destroy
 
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index d08876ae1b981293a78066bd72c2dc99553beb23..a34de9410e80620673b2563d213425a89f2ef15c 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -31,7 +31,7 @@ module API
         key = params[:key]
         variable = user_project.variables.find_by(key: key)
 
-        return not_found!('Variable') unless variable
+        break not_found!('Variable') unless variable
 
         present variable, with: Entities::Variable
       end
@@ -67,7 +67,7 @@ module API
       put ':id/variables/:key' do
         variable = user_project.variables.find_by(key: params[:key])
 
-        return not_found!('Variable') unless variable
+        break not_found!('Variable') unless variable
 
         variable_params = declared_params(include_missing: false).except(:key)
 
diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb
index 4383124d150b67dc80713bb67197b6c4c4d407e3..6a5a223a614117fd08cb3a17e47be6e7d5cd250b 100644
--- a/lib/backup/artifacts.rb
+++ b/lib/backup/artifacts.rb
@@ -5,9 +5,5 @@ module Backup
     def initialize
       super('artifacts', JobArtifactUploader.root)
     end
-
-    def create_files_dir
-      Dir.mkdir(app_files_dir, 0700)
-    end
   end
 end
diff --git a/lib/backup/builds.rb b/lib/backup/builds.rb
index 635967f4bd465949847382d6e0b030b07c9144e5..f869916e199b5e26983f1bf94fc7dcce3d6f7f8e 100644
--- a/lib/backup/builds.rb
+++ b/lib/backup/builds.rb
@@ -5,9 +5,5 @@ module Backup
     def initialize
       super('builds', Settings.gitlab_ci.builds_path)
     end
-
-    def create_files_dir
-      Dir.mkdir(app_files_dir, 0700)
-    end
   end
 end
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index 287d591e88d27d48bb0076774d57f8303ecde17b..88cb7e7b5a43ef3224c8b6381a5cef75841fc802 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -1,7 +1,10 @@
 require 'open3'
+require_relative 'helper'
 
 module Backup
   class Files
+    include Backup::Helper
+
     attr_reader :name, :app_files_dir, :backup_tarball, :files_parent_dir
 
     def initialize(name, app_files_dir)
@@ -35,15 +38,22 @@ module Backup
 
     def restore
       backup_existing_files_dir
-      create_files_dir
 
-      run_pipeline!([%w(gzip -cd), %W(tar -C #{app_files_dir} -xf -)], in: backup_tarball)
+      run_pipeline!([%w(gzip -cd), %W(tar --unlink-first --recursive-unlink -C #{app_files_dir} -xf -)], in: backup_tarball)
     end
 
     def backup_existing_files_dir
-      timestamped_files_path = File.join(files_parent_dir, "#{name}.#{Time.now.to_i}")
+      timestamped_files_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}.#{Time.now.to_i}")
       if File.exist?(app_files_dir)
-        FileUtils.mv(app_files_dir, File.expand_path(timestamped_files_path))
+        # Move all files in the existing repos directory except . and .. to
+        # repositories.old.<timestamp> directory
+        FileUtils.mkdir_p(timestamped_files_path, mode: 0700)
+        files = Dir.glob(File.join(app_files_dir, "*"), File::FNM_DOTMATCH) - [File.join(app_files_dir, "."), File.join(app_files_dir, "..")]
+        begin
+          FileUtils.mv(files, timestamped_files_path)
+        rescue Errno::EACCES
+          access_denied_error(app_files_dir)
+        end
       end
     end
 
diff --git a/lib/backup/helper.rb b/lib/backup/helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a1ee0faefe9e84d9e0340d6d81b6c6e0e5a3d201
--- /dev/null
+++ b/lib/backup/helper.rb
@@ -0,0 +1,17 @@
+module Backup
+  module Helper
+    def access_denied_error(path)
+      message = <<~EOS
+
+      ### NOTICE ###
+      As part of restore, the task tried to move existing content from #{path}.
+      However, it seems that directory contains files/folders that are not owned
+      by the user #{Gitlab.config.gitlab.user}. To proceed, please move the files
+      or folders inside #{path} to a secure location so that #{path} is empty and
+      run restore task again.
+
+      EOS
+      raise message
+    end
+  end
+end
diff --git a/lib/backup/lfs.rb b/lib/backup/lfs.rb
index 4153467fbeeea29b981762f667939aa0440ed5b2..4e234e50a7a9a42bd8caac0669b9e38410696507 100644
--- a/lib/backup/lfs.rb
+++ b/lib/backup/lfs.rb
@@ -5,9 +5,5 @@ module Backup
     def initialize
       super('lfs', Settings.lfs.storage_path)
     end
-
-    def create_files_dir
-      Dir.mkdir(app_files_dir, 0700)
-    end
   end
 end
diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb
index 215ded93bfe04ce84fe5b325a4be23b4827bf631..5830b209d6ebb0251af3f5ff2b0a5ade50a30912 100644
--- a/lib/backup/pages.rb
+++ b/lib/backup/pages.rb
@@ -5,9 +5,5 @@ module Backup
     def initialize
       super('pages', Gitlab.config.pages.path)
     end
-
-    def create_files_dir
-      Dir.mkdir(app_files_dir, 0700)
-    end
   end
 end
diff --git a/lib/backup/registry.rb b/lib/backup/registry.rb
index 67fe023108726beb9e7f29886d3f68aa95d52e4b..916986694027b9874d660a7b3fad22b4a3d289ac 100644
--- a/lib/backup/registry.rb
+++ b/lib/backup/registry.rb
@@ -5,9 +5,5 @@ module Backup
     def initialize
       super('registry', Settings.registry.path)
     end
-
-    def create_files_dir
-      Dir.mkdir(app_files_dir, 0700)
-    end
   end
 end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 6715159a1aae5f745c703717eba13cb1e9154bd8..89e3f1d907662a9cb1346637d65f7acdcb3c4e27 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -1,8 +1,11 @@
 require 'yaml'
+require_relative 'helper'
 
 module Backup
   class Repository
+    include Backup::Helper
     # rubocop:disable Metrics/AbcSize
+
     def dump
       prepare
 
@@ -63,18 +66,27 @@ module Backup
       end
     end
 
-    def restore
+    def prepare_directories
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        path = repository_storage['path']
+        path = repository_storage.legacy_disk_path
         next unless File.exist?(path)
 
-        # Move repos dir to 'repositories.old' dir
-        bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s)
-        FileUtils.mv(path, bk_repos_path)
-        # This is expected from gitlab:check
-        FileUtils.mkdir_p(path, mode: 02770)
+        # Move all files in the existing repos directory except . and .. to
+        # repositories.old.<timestamp> directory
+        bk_repos_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}-repositories.old." + Time.now.to_i.to_s)
+        FileUtils.mkdir_p(bk_repos_path, mode: 0700)
+        files = Dir.glob(File.join(path, "*"), File::FNM_DOTMATCH) - [File.join(path, "."), File.join(path, "..")]
+
+        begin
+          FileUtils.mv(files, bk_repos_path)
+        rescue Errno::EACCES
+          access_denied_error(path)
+        end
       end
+    end
 
+    def restore
+      prepare_directories
       Project.find_each(batch_size: 1000) do |project|
         progress.print " * #{display_repo_path(project)} ... "
         path_to_project_repo = path_to_repo(project)
@@ -200,7 +212,7 @@ module Backup
     end
 
     def repository_storage_paths_args
-      Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
+      Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path }
     end
 
     def progress
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 35118375499f53e553d8577af47b909c5b21574d..d46e2cd869d3300e41949723cb87c6366896786f 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -5,9 +5,5 @@ module Backup
     def initialize
       super('uploads', Rails.root.join('public/uploads'))
     end
-
-    def create_files_dir
-      Dir.mkdir(app_files_dir)
-    end
   end
 end
diff --git a/lib/banzai/commit_renderer.rb b/lib/banzai/commit_renderer.rb
index f5ff95e3eb390402a4a29bee871ec07e8a4cca17..c351a155ae590c78cf57bf1f899a8da59893d08b 100644
--- a/lib/banzai/commit_renderer.rb
+++ b/lib/banzai/commit_renderer.rb
@@ -3,7 +3,7 @@ module Banzai
     ATTRIBUTES = [:description, :title].freeze
 
     def self.render(commits, project, user = nil)
-      obj_renderer = ObjectRenderer.new(project, user)
+      obj_renderer = ObjectRenderer.new(user: user, default_project: project)
 
       ATTRIBUTES.each { |attr| obj_renderer.render(commits, attr) }
     end
diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb
index d8fb7705b2a0121725097030b2c382e8537fb517..3f1e95d4cc0256f74de98eaccb917849d0625e03 100644
--- a/lib/banzai/cross_project_reference.rb
+++ b/lib/banzai/cross_project_reference.rb
@@ -4,7 +4,7 @@ module Banzai
   module CrossProjectReference
     # Given a cross-project reference string, get the Project record
     #
-    # Defaults to value of `context[:project]` if:
+    # Defaults to value of `context[:project]`, or `context[:group]` if:
     # * No reference is given OR
     # * Reference given doesn't exist
     #
@@ -12,7 +12,7 @@ module Banzai
     #
     # Returns a Project, or nil if the reference can't be found
     def parent_from_ref(ref)
-      return context[:project] unless ref
+      return context[:project] || context[:group] unless ref
 
       Project.find_by_full_path(ref)
     end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index c9e3f8ce42bf81b76fd35bcaa7d550e53d367d0e..60a12dca9d359ddb035bcea99b3599f7f6ff1dc7 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -56,29 +56,29 @@ module Banzai
 
       # Implement in child class
       # Example: project.merge_requests.find
-      def find_object(project, id)
+      def find_object(parent_object, id)
       end
 
       # Override if the link reference pattern produces a different ID (global
       # ID vs internal ID, for instance) to the regular reference pattern.
-      def find_object_from_link(project, id)
-        find_object(project, id)
+      def find_object_from_link(parent_object, id)
+        find_object(parent_object, id)
       end
 
       # Implement in child class
       # Example: project_merge_request_url
-      def url_for_object(object, project)
+      def url_for_object(object, parent_object)
       end
 
-      def find_object_cached(project, id)
-        cached_call(:banzai_find_object, id, path: [object_class, project.id]) do
-          find_object(project, id)
+      def find_object_cached(parent_object, id)
+        cached_call(:banzai_find_object, id, path: [object_class, parent_object.id]) do
+          find_object(parent_object, id)
         end
       end
 
-      def find_object_from_link_cached(project, id)
-        cached_call(:banzai_find_object_from_link, id, path: [object_class, project.id]) do
-          find_object_from_link(project, id)
+      def find_object_from_link_cached(parent_object, id)
+        cached_call(:banzai_find_object_from_link, id, path: [object_class, parent_object.id]) do
+          find_object_from_link(parent_object, id)
         end
       end
 
@@ -88,9 +88,9 @@ module Banzai
         end
       end
 
-      def url_for_object_cached(object, project)
-        cached_call(:banzai_url_for_object, object, path: [object_class, project.id]) do
-          url_for_object(object, project)
+      def url_for_object_cached(object, parent_object)
+        cached_call(:banzai_url_for_object, object, path: [object_class, parent_object.id]) do
+          url_for_object(object, parent_object)
         end
       end
 
@@ -171,7 +171,7 @@ module Banzai
           end
 
           if object
-            title = object_link_title(object)
+            title = object_link_title(object, matches)
             klass = reference_class(object_sym)
 
             data = data_attributes_for(link_content || match, parent, object,
@@ -196,13 +196,15 @@ module Banzai
         end
       end
 
-      def data_attributes_for(text, project, object, link_content: false, link_reference: false)
+      def data_attributes_for(text, parent, object, link_content: false, link_reference: false)
+        object_parent_type = parent.is_a?(Group) ? :group : :project
+
         data_attribute(
-          original:       text,
-          link:           link_content,
-          link_reference: link_reference,
-          project:        project.id,
-          object_sym =>   object.id
+          original:             text,
+          link:                 link_content,
+          link_reference:       link_reference,
+          object_parent_type => parent.id,
+          object_sym =>         object.id
         )
       end
 
@@ -213,10 +215,14 @@ module Banzai
           extras << "comment #{$1}"
         end
 
+        extension = matches[:extension] if matches.names.include?("extension")
+
+        extras << extension if extension
+
         extras
       end
 
-      def object_link_title(object)
+      def object_link_title(object, matches)
         object.title
       end
 
@@ -337,6 +343,12 @@ module Banzai
       def parent
         parent_type == :project ? project : group
       end
+
+      def full_group_path(group_ref)
+        return current_parent_path unless group_ref
+
+        group_ref
+      end
     end
   end
 end
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index b8d2673c1a67f7739adb81c35368ec1660d0a638..4a143baeef6bb6a990b859f1db6a5637b01a4b01 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -21,12 +21,13 @@ module Banzai
       #
       # See http://en.wikipedia.org/wiki/URI_scheme
       #
-      # The negative lookbehind ensures that users can paste a URL followed by a
-      # period or comma for punctuation without those characters being included
-      # in the generated link.
+      # The negative lookbehind ensures that users can paste a URL followed by
+      # punctuation without those characters being included in the generated
+      # link. It matches the behaviour of Rinku 2.0.1:
+      # https://github.com/vmg/rinku/blob/v2.0.1/ext/rinku/autolink.c#L65
       #
-      # Rubular: http://rubular.com/r/cxjPyZc7Sb
-      LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?<!,|\.)}
+      # Rubular: http://rubular.com/r/nrL3r9yUiq
+      LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!\?|!|\.|,|:)}
 
       # Text matching LINK_PATTERN inside these elements will not be linked
       IGNORE_PARENTS = %w(a code kbd pre script style).to_set
@@ -35,53 +36,19 @@ module Banzai
       TEXT_QUERY = %Q(descendant-or-self::text()[
         not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')})
         and contains(., '://')
-        and not(starts-with(., 'http'))
-        and not(starts-with(., 'ftp'))
       ]).freeze
 
+      PUNCTUATION_PAIRS = {
+        "'" => "'",
+        '"' => '"',
+        ')' => '(',
+        ']' => '[',
+        '}' => '{'
+      }.freeze
+
       def call
         return doc if context[:autolink] == false
 
-        rinku_parse
-        text_parse
-      end
-
-      private
-
-      # Run the text through Rinku as a first pass
-      #
-      # This will quickly autolink http(s) and ftp links.
-      #
-      # `@doc` will be re-parsed with the HTML String from Rinku.
-      def rinku_parse
-        # Convert the options from a Hash to a String that Rinku expects
-        options = tag_options(link_options)
-
-        # NOTE: We don't parse email links because it will erroneously match
-        # external Commit and CommitRange references.
-        #
-        # The final argument tells Rinku to link short URLs that don't include a
-        # period (e.g., http://localhost:3000/)
-        rinku = Rinku.auto_link(html, :urls, options, IGNORE_PARENTS.to_a, 1)
-
-        return if rinku == html
-
-        # Rinku returns a String, so parse it back to a Nokogiri::XML::Document
-        # for further processing.
-        @doc = parse_html(rinku)
-      end
-
-      # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
-      def contains_unsafe?(scheme)
-        return false unless scheme
-
-        scheme = scheme.strip.downcase
-        Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
-      end
-
-      # Autolinks any text matching LINK_PATTERN that Rinku didn't already
-      # replace
-      def text_parse
         doc.xpath(TEXT_QUERY).each do |node|
           content = node.to_html
 
@@ -97,6 +64,16 @@ module Banzai
         doc
       end
 
+      private
+
+      # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
+      def contains_unsafe?(scheme)
+        return false unless scheme
+
+        scheme = scheme.strip.downcase
+        Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
+      end
+
       def autolink_match(match)
         # start by stripping out dangerous links
         begin
@@ -112,12 +89,34 @@ module Banzai
         match.gsub!(/((?:&[\w#]+;)+)\z/, '')
         dropped = ($1 || '').html_safe
 
-        options = link_options.merge(href: match)
-        content_tag(:a, match, options) + dropped
+        # To match the behaviour of Rinku, if the matched link ends with a
+        # closing part of a matched pair of punctuation, we remove that trailing
+        # character unless there are an equal number of closing and opening
+        # characters in the link.
+        if match.end_with?(*PUNCTUATION_PAIRS.keys)
+          close_character = match[-1]
+          close_count = match.count(close_character)
+          open_character = PUNCTUATION_PAIRS[close_character]
+          open_count = match.count(open_character)
+
+          if open_count != close_count || open_character == close_character
+            dropped += close_character
+            match = match[0..-2]
+          end
+        end
+
+        # match has come from node.to_html above, so we know it's encoded
+        # correctly.
+        html_safe_match = match.html_safe
+        options = link_options.merge(href: html_safe_match)
+
+        content_tag(:a, html_safe_match, options) + dropped
       end
 
       def autolink_filter(text)
-        text.gsub(LINK_PATTERN) { |match| autolink_match(match) }
+        Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_PATTERN) do |link, left:, right:|
+          autolink_match(link)
+        end
       end
 
       def link_options
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index 21bcb1c5ca8555d8e241c3c9b7653b452fac4dea..01b3b0dafb9fedb4dfc042e5d584e6afd6f70f7e 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -23,6 +23,8 @@ module Banzai
       end
 
       def find_object(project, id)
+        return unless project.is_a?(Project)
+
         range = CommitRange.new(id, project)
 
         range.valid_commits? ? range : nil
@@ -34,7 +36,7 @@ module Banzai
                                         range.to_param.merge(only_path: context[:only_path]))
       end
 
-      def object_link_title(range)
+      def object_link_title(range, matches)
         nil
       end
     end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index eedb95197aaaa4f2592cccf33848502cfe18bb31..8cd92a1adba2a54150b39990e4716f05d165cebd 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -17,8 +17,11 @@ module Banzai
       end
 
       def find_object(project, id)
+        return unless project.is_a?(Project)
+
         if project && project.valid_repo?
-          project.commit(id)
+          # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/43894
+          Gitlab::GitalyClient.allow_n_plus_1_calls { project.commit(id) }
         end
       end
 
diff --git a/lib/banzai/filter/commit_trailers_filter.rb b/lib/banzai/filter/commit_trailers_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ef16df1f3ae7f07b33edd5bb4cd2b173a4730f82
--- /dev/null
+++ b/lib/banzai/filter/commit_trailers_filter.rb
@@ -0,0 +1,152 @@
+module Banzai
+  module Filter
+    # HTML filter that replaces users' names and emails in commit trailers
+    # with links to their GitLab accounts or mailto links to their mentioned
+    # emails.
+    #
+    # Commit trailers are special labels in the form of `*-by:` and fall on a
+    # single line, ex:
+    #
+    #   Reported-By: John S. Doe <john.doe@foo.bar>
+    #
+    # More info about this can be found here:
+    # * https://git.wiki.kernel.org/index.php/CommitMessageConventions
+    class CommitTrailersFilter < HTML::Pipeline::Filter
+      include ActionView::Helpers::TagHelper
+      include ApplicationHelper
+      include AvatarsHelper
+
+      TRAILER_REGEXP = /(?<label>[[:alpha:]-]+-by:)/i.freeze
+      AUTHOR_REGEXP = /(?<author_name>.+)/.freeze
+      # Devise.email_regexp wouldn't work here since its designed to match
+      # against strings that only contains email addresses; the \A and \z
+      # around the expression will only match if the string being matched
+      # contains just the email nothing else.
+      MAIL_REGEXP = /&lt;(?<author_email>[^@\s]+@[^@\s]+)&gt;/.freeze
+      FILTER_REGEXP = /(?<trailer>^\s*#{TRAILER_REGEXP}\s*#{AUTHOR_REGEXP}\s+#{MAIL_REGEXP}$)/mi.freeze
+
+      def call
+        doc.xpath('descendant-or-self::text()').each do |node|
+          content = node.to_html
+
+          next unless content.match(FILTER_REGEXP)
+
+          html = trailer_filter(content)
+
+          next if html == content
+
+          node.replace(html)
+        end
+
+        doc
+      end
+
+      private
+
+      # Replace trailer lines with links to GitLab users or mailto links to
+      # non GitLab users.
+      #
+      # text - String text to replace trailers in.
+      #
+      # Returns a String with all trailer lines replaced with links to GitLab
+      # users and mailto links to non GitLab users. All links have `data-trailer`
+      # and `data-user` attributes attached.
+      def trailer_filter(text)
+        text.gsub(FILTER_REGEXP) do |author_match|
+          label = $~[:label]
+          "#{label} #{parse_user($~[:author_name], $~[:author_email], label)}"
+        end
+      end
+
+      # Find a GitLab user using the supplied email and generate
+      # a valid link to them, otherwise, generate a mailto link.
+      #
+      # name - String name used in the commit message for the user
+      # email - String email used in the commit message for the user
+      # trailer - String trailer used in the commit message
+      #
+      # Returns a String with a link to the user.
+      def parse_user(name, email, trailer)
+        link_to_user User.find_by_any_email(email),
+          name: name,
+          email: email,
+          trailer: trailer
+      end
+
+      def urls
+        Gitlab::Routing.url_helpers
+      end
+
+      def link_to_user(user, name:, email:, trailer:)
+        wrapper = link_wrapper(data: {
+          trailer: trailer,
+          user: user.try(:id)
+        })
+
+        avatar = user_avatar_without_link(
+          user: user,
+          user_email: email,
+          css_class: 'avatar-inline',
+          has_tooltip: false
+        )
+
+        link_href = user.nil? ? "mailto:#{email}" : urls.user_url(user)
+
+        avatar_link = link_tag(
+          link_href,
+          content: avatar,
+          title: email
+        )
+
+        name_link = link_tag(
+          link_href,
+          content: name,
+          title: email
+        )
+
+        email_link = link_tag(
+          "mailto:#{email}",
+          content: email,
+          title: email
+        )
+
+        wrapper << "#{avatar_link}#{name_link} <#{email_link}>"
+      end
+
+      def link_wrapper(data: {})
+        data_attributes = data_attributes_from_hash(data)
+
+        doc.document.create_element(
+          'span',
+          data_attributes
+        )
+      end
+
+      def link_tag(url, title: "", content: "", data: {})
+        data_attributes = data_attributes_from_hash(data)
+
+        attributes = data_attributes.merge(
+          href: url,
+          title: title
+        )
+
+        link = doc.document.create_element('a', attributes)
+
+        if content.html_safe?
+          link << content
+        else
+          link.content = content # make sure we escape content using nokogiri's #content=
+        end
+
+        link
+      end
+
+      def data_attributes_from_hash(data = {})
+        data.reject! {|_, value| value.nil?}
+        data.map do |key, value|
+          [%(data-#{key.to_s.dasherize}), value]
+        end.to_h
+      end
+    end
+  end
+end
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index b82c6ca639371a233338c4c506da12cf2c6280ee..e1261e7bbbe341cafb060480c57c6221c216acd6 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -11,7 +11,7 @@ module Banzai
       IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
 
       def call
-        search_text_nodes(doc).each do |node|
+        doc.search(".//text()").each do |node|
           content = node.to_html
           next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
 
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index c2b426733769fb01f7909ae38c303d370bc1f580..f2e9a5a1116f3eb2d0d9e984ca9d348f934e81d7 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -57,7 +57,7 @@ module Banzai
       ALLOWED_IMAGE_EXTENSIONS = /.+(jpg|png|gif|svg|bmp)\z/i.freeze
 
       def call
-        search_text_nodes(doc).each do |node|
+        doc.search(".//text()").each do |node|
           # A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running
           # before this one, it will be converted into `[[<em>TOC</em>]]`, so it
           # needs special-case handling
diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb
index beb21b19ab3b60f075b038b23a6728380174e6ce..73e82a4d7e3a8f306fffca102101b852da9d3826 100644
--- a/lib/banzai/filter/inline_diff_filter.rb
+++ b/lib/banzai/filter/inline_diff_filter.rb
@@ -4,7 +4,7 @@ module Banzai
       IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
 
       def call
-        search_text_nodes(doc).each do |node|
+        doc.search(".//text()").each do |node|
           next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
 
           content = node.to_html
diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb
index 77299abe32427ae9081fbadbf9a1f5753a678723..1a4152325456f1d3718310ca7232efce6095a046 100644
--- a/lib/banzai/filter/issuable_state_filter.rb
+++ b/lib/banzai/filter/issuable_state_filter.rb
@@ -11,13 +11,14 @@ module Banzai
       def call
         return doc unless context[:issuable_state_filter_enabled]
 
-        extractor = Banzai::IssuableExtractor.new(project, current_user)
+        context = RenderContext.new(project, current_user)
+        extractor = Banzai::IssuableExtractor.new(context)
         issuables = extractor.extract([doc])
 
         issuables.each do |node, issuable|
           next if !can_read_cross_project? && issuable.project != project
 
-          if VISIBLE_STATES.include?(issuable.state) && node.inner_html == issuable.reference_link_text(project)
+          if VISIBLE_STATES.include?(issuable.state) && issuable_reference?(node.inner_html, issuable)
             node.content += " (#{issuable.state})"
           end
         end
@@ -27,6 +28,10 @@ module Banzai
 
       private
 
+      def issuable_reference?(text, issuable)
+        text == issuable.reference_link_text(project || group)
+      end
+
       def can_read_cross_project?
         Ability.allowed?(current_user, :read_cross_project)
       end
@@ -38,6 +43,10 @@ module Banzai
       def project
         context[:project]
       end
+
+      def group
+        context[:group]
+      end
     end
   end
 end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index d5360ad8f6890dee3f849191c8e419f0a3a4ceec..a5f38046a4330f615de534282272f3a92999bcc5 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -8,8 +8,8 @@ module Banzai
         Label
       end
 
-      def find_object(project, id)
-        find_labels(project).find(id)
+      def find_object(parent_object, id)
+        find_labels(parent_object).find(id)
       end
 
       def self.references_in(text, pattern = Label.reference_pattern)
@@ -32,16 +32,25 @@ module Banzai
         end
       end
 
-      def find_label(project_ref, label_id, label_name)
-        project = parent_from_ref(project_ref)
-        return unless project
+      def find_label(parent_ref, label_id, label_name)
+        parent = parent_from_ref(parent_ref)
+        return unless parent
 
         label_params = label_params(label_id, label_name)
-        find_labels(project).find_by(label_params)
+        find_labels(parent).find_by(label_params)
       end
 
-      def find_labels(project)
-        LabelsFinder.new(nil, project_id: project.id).execute(skip_authorization: true)
+      def find_labels(parent)
+        params = if parent.is_a?(Group)
+                   { group_id: parent.id,
+                     include_ancestor_groups: true,
+                     only_group_labels: true }
+                 else
+                   { project_id: parent.id,
+                     include_ancestor_groups: true }
+                 end
+
+        LabelsFinder.new(nil, params).execute(skip_authorization: true)
       end
 
       # Parameters to pass to `Label.find_by` based on the given arguments
@@ -59,25 +68,39 @@ module Banzai
         end
       end
 
-      def url_for_object(label, project)
+      def url_for_object(label, parent)
         h = Gitlab::Routing.url_helpers
-        h.project_issues_url(project, label_name: label.name, only_path: context[:only_path])
+
+        if parent.is_a?(Project)
+          h.project_issues_url(parent, label_name: label.name, only_path: context[:only_path])
+        elsif context[:label_url_method]
+          h.public_send(context[:label_url_method], parent, label_name: label.name, only_path: context[:only_path]) # rubocop:disable GitlabSecurity/PublicSend
+        end
       end
 
       def object_link_text(object, matches)
-        project_path     = full_project_path(matches[:namespace], matches[:project])
-        project_from_ref = from_ref_cached(project_path)
-        reference        = project_from_ref.to_human_reference(project)
-        label_suffix     = " <i>in #{reference}</i>" if reference.present?
+        label_suffix = ''
+
+        if project || full_path_ref?(matches)
+          project_path    = full_project_path(matches[:namespace], matches[:project])
+          parent_from_ref = from_ref_cached(project_path)
+          reference       = parent_from_ref.to_human_reference(project || group)
+
+          label_suffix = " <i>in #{reference}</i>" if reference.present?
+        end
 
         LabelsHelper.render_colored_label(object, label_suffix)
       end
 
+      def full_path_ref?(matches)
+        matches[:namespace] && matches[:project]
+      end
+
       def unescape_html_entities(text)
         CGI.unescapeHTML(text.to_s)
       end
 
-      def object_link_title(object)
+      def object_link_title(object, matches)
         # use title of wrapped element instead
         nil
       end
diff --git a/lib/banzai/filter/markdown_engines/common_mark.rb b/lib/banzai/filter/markdown_engines/common_mark.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bc9597df89455c34fb88e0d0bb12ec9281b52672
--- /dev/null
+++ b/lib/banzai/filter/markdown_engines/common_mark.rb
@@ -0,0 +1,45 @@
+# `CommonMark` markdown engine for GitLab's Banzai markdown filter.
+# This module is used in Banzai::Filter::MarkdownFilter.
+# Used gem is `commonmarker` which is a ruby wrapper for libcmark (CommonMark parser)
+# including GitHub's GFM extensions.
+# Homepage: https://github.com/gjtorikian/commonmarker
+
+module Banzai
+  module Filter
+    module MarkdownEngines
+      class CommonMark
+        EXTENSIONS = [
+          :autolink,      # provides support for automatically converting URLs to anchor tags.
+          :strikethrough, # provides support for strikethroughs.
+          :table,         # provides support for tables.
+          :tagfilter      # strips out several "unsafe" HTML tags from being used: https://github.github.com/gfm/#disallowed-raw-html-extension-
+        ].freeze
+
+        PARSE_OPTIONS = [
+          :FOOTNOTES,                  # parse footnotes.
+          :STRIKETHROUGH_DOUBLE_TILDE, # parse strikethroughs by double tildes (as redcarpet does).
+          :VALIDATE_UTF8	             # replace illegal sequences with the replacement character U+FFFD.
+        ].freeze
+
+        # The `:GITHUB_PRE_LANG` option is not used intentionally because
+        # it renders a fence block with language as `<pre lang="LANG"><code>some code\n</code></pre>`
+        # while GitLab's syntax is `<pre><code lang="LANG">some code\n</code></pre>`.
+        # If in the future the syntax is about to be made GitHub-compatible, please, add `:GITHUB_PRE_LANG` render option below
+        # and remove `code_block` method from `lib/banzai/renderer/common_mark/html.rb`.
+        RENDER_OPTIONS = [
+          :DEFAULT # default rendering system. Nothing special.
+        ].freeze
+
+        def initialize
+          @renderer = Banzai::Renderer::CommonMark::HTML.new(options: RENDER_OPTIONS)
+        end
+
+        def render(text)
+          doc = CommonMarker.render_doc(text, PARSE_OPTIONS, EXTENSIONS)
+
+          @renderer.render(doc)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/banzai/filter/markdown_engines/redcarpet.rb b/lib/banzai/filter/markdown_engines/redcarpet.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac99941fefaba6b11995be964dd2f821403c4523
--- /dev/null
+++ b/lib/banzai/filter/markdown_engines/redcarpet.rb
@@ -0,0 +1,32 @@
+# `Redcarpet` markdown engine for GitLab's Banzai markdown filter.
+# This module is used in Banzai::Filter::MarkdownFilter.
+# Used gem is `redcarpet` which is a ruby library for markdown processing.
+# Homepage: https://github.com/vmg/redcarpet
+
+module Banzai
+  module Filter
+    module MarkdownEngines
+      class Redcarpet
+        OPTIONS = {
+          fenced_code_blocks:  true,
+          footnotes:           true,
+          lax_spacing:         true,
+          no_intra_emphasis:   true,
+          space_after_headers: true,
+          strikethrough:       true,
+          superscript:         true,
+          tables:              true
+        }.freeze
+
+        def initialize
+          html_renderer = Banzai::Renderer::Redcarpet::HTML.new
+          @renderer = ::Redcarpet::Markdown.new(html_renderer, OPTIONS)
+        end
+
+        def render(text)
+          @renderer.render(text)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb
index 9cac303e6450584e4250af830633449d683dbbb4..c1e2b6802406691c30c364ece0a2a92010cfe33d 100644
--- a/lib/banzai/filter/markdown_filter.rb
+++ b/lib/banzai/filter/markdown_filter.rb
@@ -1,34 +1,31 @@
 module Banzai
   module Filter
     class MarkdownFilter < HTML::Pipeline::TextFilter
-      # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
-      REDCARPET_OPTIONS = {
-        fenced_code_blocks:  true,
-        footnotes:           true,
-        lax_spacing:         true,
-        no_intra_emphasis:   true,
-        space_after_headers: true,
-        strikethrough:       true,
-        superscript:         true,
-        tables:              true
-      }.freeze
-
       def initialize(text, context = nil, result = nil)
-        super text, context, result
-        @text = @text.delete "\r"
+        super(text, context, result)
+
+        @renderer = renderer(context[:markdown_engine]).new
+        @text = @text.delete("\r")
       end
 
       def call
-        html = self.class.renderer.render(@text)
-        html.rstrip!
-        html
+        @renderer.render(@text).rstrip
+      end
+
+      private
+
+      DEFAULT_ENGINE = :redcarpet
+
+      def engine(engine_from_context)
+        engine_from_context ||= DEFAULT_ENGINE
+
+        engine_from_context.to_s.classify
       end
 
-      def self.renderer
-        Thread.current[:banzai_markdown_renderer] ||= begin
-          renderer = Banzai::Renderer::HTML.new
-          Redcarpet::Markdown.new(renderer, REDCARPET_OPTIONS)
-        end
+      def renderer(engine_from_context)
+        "Banzai::Filter::MarkdownEngines::#{engine(engine_from_context)}".constantize
+      rescue NameError
+        raise NameError, "`#{engine_from_context}` is unknown markdown engine"
       end
     end
   end
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index b3cfa97d0e048b211fd77adf5c76588997c045a7..5cbdb01c130d3a413cbac4b9500318a3a8ad8531 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -17,10 +17,19 @@ module Banzai
                                             only_path: context[:only_path])
       end
 
+      def object_link_title(object, matches)
+        object_link_commit_title(object, matches) || super
+      end
+
       def object_link_text_extras(object, matches)
         extras = super
 
+        if commit_ref = object_link_commit_ref(object, matches)
+          return extras.unshift(commit_ref)
+        end
+
         path = matches[:path] if matches.names.include?("path")
+
         case path
         when '/diffs'
           extras.unshift "diffs"
@@ -38,6 +47,36 @@ module Banzai
           .where(iid: ids.to_a)
           .includes(target_project: :namespace)
       end
+
+      private
+
+      def object_link_commit_title(object, matches)
+        object_link_commit(object, matches)&.title
+      end
+
+      def object_link_commit_ref(object, matches)
+        object_link_commit(object, matches)&.short_id
+      end
+
+      def object_link_commit(object, matches)
+        return unless matches.names.include?('query') && query = matches[:query]
+
+        # Removes leading "?". CGI.parse expects "arg1&arg2&arg3"
+        params = CGI.parse(query.sub(/^\?/, ''))
+
+        return unless commit_sha = params['commit_id']&.first
+
+        if commit = find_commit_by_sha(object, commit_sha)
+          Commit.from_hash(commit.to_hash, object.project)
+        end
+      end
+
+      def find_commit_by_sha(object, commit_sha)
+        @all_commits ||= {}
+        @all_commits[object.id] ||= object.all_commits
+
+        @all_commits[object.id].find { |commit| commit.sha == commit_sha }
+      end
     end
   end
 end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 8ec696ce5fc66dcab5dff7bdbf792c44a4ef1005..b144bd8cf54e4151ca591ff28c82e10d6b480f97 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -12,10 +12,14 @@ module Banzai
       # 'regular' references, we need to use the global ID to disambiguate
       # between group and project milestones.
       def find_object(project, id)
+        return unless project.is_a?(Project)
+
         find_milestone_with_finder(project, id: id)
       end
 
       def find_object_from_link(project, iid)
+        return unless project.is_a?(Project)
+
         find_milestone_with_finder(project, iid: iid)
       end
 
@@ -40,7 +44,7 @@ module Banzai
         project_path = full_project_path(namespace_ref, project_ref)
         project = parent_from_ref(project_path)
 
-        return unless project
+        return unless project && project.is_a?(Project)
 
         milestone_params = milestone_params(milestone_id, milestone_name)
 
@@ -84,7 +88,7 @@ module Banzai
         end
       end
 
-      def object_link_title(object)
+      def object_link_title(object, matches)
         nil
       end
     end
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index 9f9882b3b40d07854dbaae2d0b616d2ca699fc9f..caf11fe94c48feca411efa0b58a2936561033c36 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -7,7 +7,11 @@ module Banzai
     #
     class RedactorFilter < HTML::Pipeline::Filter
       def call
-        Redactor.new(project, current_user).redact([doc]) unless context[:skip_redaction]
+        unless context[:skip_redaction]
+          context = RenderContext.new(project, current_user)
+
+          Redactor.new(context).redact([doc])
+        end
 
         doc
       end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 9bdedeb66153fba665c43dd6b81ccc839026df14..262458a872a56a73e76c28d742dcb7736e77b229 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -84,7 +84,7 @@ module Banzai
           relative_url_root,
           project.full_path,
           uri_type(file_path),
-          Addressable::URI.escape(ref),
+          Addressable::URI.escape(ref).gsub('#', '%23'),
           Addressable::URI.escape(file_path)
         ].compact.join('/').squeeze('/').chomp('/')
 
diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
index 134a192c22bcecd03bdc48eb2feb0c7aa74d513e..881e10afb9f21e78e9ede4b2c891c8cd9e36e838 100644
--- a/lib/banzai/filter/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -12,6 +12,8 @@ module Banzai
       end
 
       def find_object(project, id)
+        return unless project.is_a?(Project)
+
         project.snippets.find_by(id: id)
       end
 
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 0ac7e231b5b787c9e03150ca8c490955e5cb9f7b..6dbf0d68fe8f0d9c05a022ef610341bb81b99a53 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -1,3 +1,4 @@
+require 'rouge/plugins/common_mark'
 require 'rouge/plugins/redcarpet'
 
 module Banzai
diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb
index 49603d0b36309c84adf3c272f5a0e3fbd77889c4..ae7dc71e7eb0cfc9f1de830ad3c603d13d061dc0 100644
--- a/lib/banzai/issuable_extractor.rb
+++ b/lib/banzai/issuable_extractor.rb
@@ -12,11 +12,11 @@ module Banzai
       [@data-reference-type="issue" or @data-reference-type="merge_request"]
     ).freeze
 
-    attr_reader :project, :user
+    attr_reader :context
 
-    def initialize(project, user)
-      @project = project
-      @user = user
+    # context - An instance of Banzai::RenderContext.
+    def initialize(context)
+      @context = context
     end
 
     # Returns Hash in the form { node => issuable_instance }
@@ -25,8 +25,10 @@ module Banzai
         document.xpath(QUERY)
       end
 
-      issue_parser = Banzai::ReferenceParser::IssueParser.new(project, user)
-      merge_request_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, user)
+      issue_parser = Banzai::ReferenceParser::IssueParser.new(context)
+
+      merge_request_parser =
+        Banzai::ReferenceParser::MergeRequestParser.new(context)
 
       issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge(
         merge_request_parser.records_for_nodes(nodes)
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 2691be81623bc897a4c1b4caa6a0bf00189ed81d..a176f1e261b15fd3ee64f971deee9fb6a789f84e 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -13,14 +13,13 @@ module Banzai
   # As an example, rendering the attribute `note` would place the unredacted
   # HTML into `note_html` and the redacted HTML into `redacted_note_html`.
   class ObjectRenderer
-    attr_reader :project, :user
+    attr_reader :context
 
-    # project - A Project to use for redacting Markdown.
+    # default_project - A default Project to use for redacting Markdown.
     # user - The user viewing the Markdown/HTML documents, if any.
     # redaction_context - A Hash containing extra attributes to use during redaction
-    def initialize(project, user = nil, redaction_context = {})
-      @project = project
-      @user = user
+    def initialize(default_project: nil, user: nil, redaction_context: {})
+      @context = RenderContext.new(default_project, user)
       @redaction_context = base_context.merge(redaction_context)
     end
 
@@ -48,17 +47,21 @@ module Banzai
       pipeline = HTML::Pipeline.new([])
 
       objects.map do |object|
-        pipeline.to_document(Banzai.render_field(object, attribute))
+        document = pipeline.to_document(Banzai.render_field(object, attribute))
+
+        context.associate_document(document, object)
+
+        document
       end
     end
 
     def post_process_documents(documents, objects, attribute)
       # Called here to populate cache, refer to IssuableExtractor docs
-      IssuableExtractor.new(project, user).extract(documents)
+      IssuableExtractor.new(context).extract(documents)
 
       documents.zip(objects).map do |document, object|
-        context = context_for(object, attribute)
-        Banzai::Pipeline[:post_process].to_document(document, context)
+        pipeline_context = context_for(document, object, attribute)
+        Banzai::Pipeline[:post_process].to_document(document, pipeline_context)
       end
     end
 
@@ -66,20 +69,21 @@ module Banzai
     #
     # Returns an Array containing the redacted documents.
     def redact_documents(documents)
-      redactor = Redactor.new(project, user)
+      redactor = Redactor.new(context)
 
       redactor.redact(documents)
     end
 
     # Returns a Banzai context for the given object and attribute.
-    def context_for(object, attribute)
-      @redaction_context.merge(object.banzai_render_context(attribute))
+    def context_for(document, object, attribute)
+      @redaction_context.merge(object.banzai_render_context(attribute)).merge(
+        project: context.project_for_node(document)
+      )
     end
 
     def base_context
       {
-        current_user: user,
-        project: project,
+        current_user: context.current_user,
         skip_redaction: true
       }
     end
diff --git a/lib/banzai/pipeline/commit_description_pipeline.rb b/lib/banzai/pipeline/commit_description_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..607c2731ed35385d0876b1a3254f0623e00750a9
--- /dev/null
+++ b/lib/banzai/pipeline/commit_description_pipeline.rb
@@ -0,0 +1,11 @@
+module Banzai
+  module Pipeline
+    class CommitDescriptionPipeline < SingleLinePipeline
+      def self.filters
+        @filters ||= super.concat FilterArray[
+          Filter::CommitTrailersFilter,
+        ]
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 4001b8a85e30c2c05f7cfffd884737045380837f..8b2f05fffecb1c7f6ed9a94e9d9517df04248046 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -2,10 +2,10 @@ module Banzai
   module Pipeline
     class GfmPipeline < BasePipeline
       # These filters convert GitLab Flavored Markdown (GFM) to HTML.
-      # The handlers defined in app/assets/javascripts/copy_as_gfm.js
+      # The handlers defined in app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
       # consequently convert that same HTML to GFM to be copied to the clipboard.
       # Every filter that generates HTML from GFM should have a handler in
-      # app/assets/javascripts/copy_as_gfm.js, in reverse order.
+      # app/assets/javascripts/behaviors/markdown/copy_as_gfm.js, in reverse order.
       # The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
       def self.filters
         @filters ||= FilterArray[
diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb
index fd457bebf03a398824678564f61aa143e1c48633..28928d6f37620bb170c48468faa015952e50ccf1 100644
--- a/lib/banzai/redactor.rb
+++ b/lib/banzai/redactor.rb
@@ -2,13 +2,15 @@ module Banzai
   # Class for removing Markdown references a certain user is not allowed to
   # view.
   class Redactor
-    attr_reader :user, :project
+    attr_reader :context
 
-    # project - A Project to use for redacting links.
-    # user - The currently logged in user (if any).
-    def initialize(project, user = nil)
-      @project = project
-      @user = user
+    # context - An instance of `Banzai::RenderContext`.
+    def initialize(context)
+      @context = context
+    end
+
+    def user
+      context.current_user
     end
 
     # Redacts the references in the given Array of documents.
@@ -70,11 +72,11 @@ module Banzai
     end
 
     def redact_cross_project_references(documents)
-      extractor = Banzai::IssuableExtractor.new(project, user)
+      extractor = Banzai::IssuableExtractor.new(context)
       issuables = extractor.extract(documents)
 
       issuables.each do |node, issuable|
-        next if issuable.project == project
+        next if issuable.project == context.project_for_node(node)
 
         node['class'] = node['class'].gsub('has-tooltip', '')
         node['title'] = nil
@@ -95,7 +97,7 @@ module Banzai
       end
 
       per_type.each do |type, nodes|
-        parser = Banzai::ReferenceParser[type].new(project, user)
+        parser = Banzai::ReferenceParser[type].new(context)
 
         visible.merge(parser.nodes_visible_to_user(user, nodes))
       end
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
index 7e6357f8a008923224d11973b1ee05fd46085d9e..78588299c1828be91ab7ba7fffa22e5c479466e7 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)
+      context = RenderContext.new(project, current_user)
+      processor = Banzai::ReferenceParser[type].new(context)
 
       processor.process(html_documents)
     end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 279fca8d04333cc0eca362e6b082db81601fd046..68752f5bb5a20fd6fa87e76abc0fab4588e9e1a1 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -45,9 +45,13 @@ module Banzai
         @data_attribute ||= "data-#{reference_type.to_s.dasherize}"
       end
 
-      def initialize(project = nil, current_user = nil)
-        @project = project
-        @current_user = current_user
+      # context - An instance of `Banzai::RenderContext`.
+      def initialize(context)
+        @context = context
+      end
+
+      def project_for_node(node)
+        context.project_for_node(node)
       end
 
       # Returns all the nodes containing references that the user can refer to.
@@ -224,7 +228,11 @@ module Banzai
 
       private
 
-      attr_reader :current_user, :project
+      attr_reader :context
+
+      def current_user
+        context.current_user
+      end
 
       # When a feature is disabled or visible only for
       # team members we should not allow team members
diff --git a/lib/banzai/reference_parser/commit_range_parser.rb b/lib/banzai/reference_parser/commit_range_parser.rb
index a50e6f8ef8f3ec78e5920eae7c8d5cc67d60b19b..2920e88693806dfd02575096dcdff89f273076aa 100644
--- a/lib/banzai/reference_parser/commit_range_parser.rb
+++ b/lib/banzai/reference_parser/commit_range_parser.rb
@@ -29,6 +29,8 @@ module Banzai
       end
 
       def find_object(project, id)
+        return unless project.is_a?(Project)
+
         range = CommitRange.new(id, project)
 
         range.valid_commits? ? range : nil
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index 230827129b6795743b9168872ab2763d36c7a049..6bee5ea15b9f1b84fd9a95aad34938de34fb833d 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -5,15 +5,10 @@ module Banzai
 
       def nodes_visible_to_user(user, nodes)
         issues = records_for_nodes(nodes)
-        issues_to_check = issues.values
+        issues_to_check, cross_project_issues = partition_issues(issues, user)
 
-        unless can?(user, :read_cross_project)
-          issues_to_check, cross_project_issues = issues_to_check.partition do |issue|
-            issue.project == project
-          end
-        end
-
-        readable_issues = Ability.issues_readable_by_user(issues_to_check, user).to_set
+        readable_issues =
+          Ability.issues_readable_by_user(issues_to_check, user).to_set
 
         nodes.select do |node|
           issue_in_node = issues[node]
@@ -25,7 +20,7 @@ module Banzai
           # but not the issue.
           if readable_issues.include?(issue_in_node)
             true
-          elsif cross_project_issues&.include?(issue_in_node)
+          elsif cross_project_issues.include?(issue_in_node)
             can_read_reference?(user, issue_in_node)
           else
             false
@@ -33,6 +28,32 @@ module Banzai
         end
       end
 
+      # issues - A Hash mapping HTML nodes to their corresponding Issue
+      #          instances.
+      # user - The current User.
+      def partition_issues(issues, user)
+        return [issues.values, []] if can?(user, :read_cross_project)
+
+        issues_to_check = []
+        cross_project_issues = []
+
+        # We manually partition the data since our input is a Hash and our
+        # output has to be an Array of issues; not an Array of (node, issue)
+        # pairs.
+        issues.each do |node, issue|
+          target =
+            if issue.project == project_for_node(node)
+              issues_to_check
+            else
+              cross_project_issues
+            end
+
+          target << issue
+        end
+
+        [issues_to_check, cross_project_issues]
+      end
+
       def records_for_nodes(nodes)
         @issues_for_nodes ||= grouped_objects_for_nodes(
           nodes,
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index 8932d4f2905c6cdcc75b18cb3d982986a24d836e..ceb7f1d165c4b14d0a3e8e572c5df5d3e8bef3c9 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -58,7 +58,7 @@ module Banzai
       def can_read_project_reference?(node)
         node_id = node.attr('data-project').to_i
 
-        project && project.id == node_id
+        project_for_node(node)&.id == node_id
       end
 
       def nodes_user_can_reference(current_user, nodes)
@@ -71,6 +71,7 @@ module Banzai
         nodes.select do |node|
           project_id = node.attr(project_attr)
           user_id = node.attr(author_attr)
+          project = project_for_node(node)
 
           if project && project_id && project.id == project_id.to_i
             true
diff --git a/lib/banzai/render_context.rb b/lib/banzai/render_context.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e30fc9f469bb76d407a5e4b0b21f77bff1c83fb8
--- /dev/null
+++ b/lib/banzai/render_context.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Banzai
+  # Object storing the current user, project, and other details used when
+  # parsing Markdown references.
+  class RenderContext
+    attr_reader :current_user
+
+    # default_project - The default project to use for all documents, if any.
+    # current_user - The user viewing the document, if any.
+    def initialize(default_project = nil, current_user = nil)
+      @current_user = current_user
+      @projects = Hash.new(default_project)
+    end
+
+    # Associates an HTML document with a Project.
+    #
+    # document - The HTML document to map to a Project.
+    # object - The object that produced the HTML document.
+    def associate_document(document, object)
+      # XML nodes respond to "document" but will return a Document instance,
+      # even when they belong to a DocumentFragment.
+      document = document.document if document.fragment?
+
+      @projects[document] = object.project if object.respond_to?(:project)
+    end
+
+    def project_for_node(node)
+      @projects[node.document]
+    end
+  end
+end
diff --git a/lib/banzai/renderer/common_mark/html.rb b/lib/banzai/renderer/common_mark/html.rb
new file mode 100644
index 0000000000000000000000000000000000000000..46b609c36b0c09193bc493d6fb10b0b78e25f5ae
--- /dev/null
+++ b/lib/banzai/renderer/common_mark/html.rb
@@ -0,0 +1,21 @@
+module Banzai
+  module Renderer
+    module CommonMark
+      class HTML < CommonMarker::HtmlRenderer
+        def code_block(node)
+          block do
+            code      = node.string_content
+            lang      = node.fence_info
+            lang_attr = lang.present? ? %Q{ lang="#{lang}"} : ''
+            result    =
+              "<pre>" \
+                "<code#{lang_attr}>#{ERB::Util.html_escape(code)}</code>" \
+              "</pre>"
+
+            out(result)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/banzai/renderer/html.rb b/lib/banzai/renderer/html.rb
deleted file mode 100644
index 252caa35947380b2456c9887803b3f79fa8efda2..0000000000000000000000000000000000000000
--- a/lib/banzai/renderer/html.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module Banzai
-  module Renderer
-    class HTML < Redcarpet::Render::HTML
-      def block_code(code, lang)
-        lang_attr = lang ? %Q{ lang="#{lang}"} : ''
-
-        "\n<pre>" \
-          "<code#{lang_attr}>#{html_escape(code)}</code>" \
-        "</pre>"
-      end
-    end
-  end
-end
diff --git a/lib/banzai/renderer/redcarpet/html.rb b/lib/banzai/renderer/redcarpet/html.rb
new file mode 100644
index 0000000000000000000000000000000000000000..30e815f1224332e36d83309397dff6a8d6832abe
--- /dev/null
+++ b/lib/banzai/renderer/redcarpet/html.rb
@@ -0,0 +1,15 @@
+module Banzai
+  module Renderer
+    module Redcarpet
+      class HTML < ::Redcarpet::Render::HTML
+        def block_code(code, lang)
+          lang_attr = lang ? %Q{ lang="#{lang}"} : ''
+
+          "\n<pre>" \
+            "<code#{lang_attr}>#{ERB::Util.html_escape(code)}</code>" \
+          "</pre>"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb
index fd2ac2db0a9def532b8c7e15cf0fd3804b2ad87a..87649c5042423a4ce3654c19198932d4fe3c4f3b 100644
--- a/lib/constraints/group_url_constrainer.rb
+++ b/lib/constraints/group_url_constrainer.rb
@@ -1,9 +1,11 @@
-class GroupUrlConstrainer
-  def matches?(request)
-    full_path = request.params[:group_id] || request.params[:id]
+module Constraints
+  class GroupUrlConstrainer
+    def matches?(request)
+      full_path = request.params[:group_id] || request.params[:id]
 
-    return false unless NamespacePathValidator.valid_path?(full_path)
+      return false unless NamespacePathValidator.valid_path?(full_path)
 
-    Group.find_by_full_path(full_path, follow_redirects: request.get?).present?
+      Group.find_by_full_path(full_path, follow_redirects: request.get?).present?
+    end
   end
 end
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index e90ecb5ec69f45896b90e341dca5ae2b6586f549..32aea98f0f7c5462d1a323fd1d03d05747adc826 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -1,13 +1,15 @@
-class ProjectUrlConstrainer
-  def matches?(request)
-    namespace_path = request.params[:namespace_id]
-    project_path = request.params[:project_id] || request.params[:id]
-    full_path = [namespace_path, project_path].join('/')
+module Constraints
+  class ProjectUrlConstrainer
+    def matches?(request)
+      namespace_path = request.params[:namespace_id]
+      project_path = request.params[:project_id] || request.params[:id]
+      full_path = [namespace_path, project_path].join('/')
 
-    return false unless ProjectPathValidator.valid_path?(full_path)
+      return false unless ProjectPathValidator.valid_path?(full_path)
 
-    # We intentionally allow SELECT(*) here so result of this query can be used
-    # as cache for further Project.find_by_full_path calls within request
-    Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
+      # We intentionally allow SELECT(*) here so result of this query can be used
+      # as cache for further Project.find_by_full_path calls within request
+      Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
+    end
   end
 end
diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb
index 3b3ed1c6ddb13efd1b6400197fafa80ab6252769..8afa04d29a4a6d2721ad777e4225284b3128824f 100644
--- a/lib/constraints/user_url_constrainer.rb
+++ b/lib/constraints/user_url_constrainer.rb
@@ -1,9 +1,11 @@
-class UserUrlConstrainer
-  def matches?(request)
-    full_path = request.params[:username]
+module Constraints
+  class UserUrlConstrainer
+    def matches?(request)
+      full_path = request.params[:username]
 
-    return false unless NamespacePathValidator.valid_path?(full_path)
+      return false unless NamespacePathValidator.valid_path?(full_path)
 
-    User.find_by_full_path(full_path, follow_redirects: request.get?).present?
+      User.find_by_full_path(full_path, follow_redirects: request.get?).present?
+    end
   end
 end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index c7263f302ab58055b9b2c842dead4db63534aa55..010ca1ec27be2818102f326cc34c7fc1c7d7233d 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -52,6 +52,8 @@ module ContainerRegistry
         conn.request(:authorization, :bearer, options[:token].to_s)
       end
 
+      yield(conn) if block_given?
+
       conn.adapter :net_http
     end
 
@@ -80,8 +82,7 @@ module ContainerRegistry
 
     def faraday
       @faraday ||= Faraday.new(@base_uri) do |conn|
-        initialize_connection(conn, @options)
-        accept_manifest(conn)
+        initialize_connection(conn, @options, &method(:accept_manifest))
       end
     end
 
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index b1949d693ada43abd37161d42cce62ec337d414d..1dd2855063d7ca5060c592edc0a1afd15bd6d2fd 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -1,6 +1,8 @@
 require_dependency 'declarative_policy/cache'
 require_dependency 'declarative_policy/condition'
-require_dependency 'declarative_policy/dsl'
+require_dependency 'declarative_policy/delegate_dsl'
+require_dependency 'declarative_policy/policy_dsl'
+require_dependency 'declarative_policy/rule_dsl'
 require_dependency 'declarative_policy/preferred_scope'
 require_dependency 'declarative_policy/rule'
 require_dependency 'declarative_policy/runner'
diff --git a/lib/declarative_policy/delegate_dsl.rb b/lib/declarative_policy/delegate_dsl.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f544dffe88874c2597dc11b3b67ee335325b3e0a
--- /dev/null
+++ b/lib/declarative_policy/delegate_dsl.rb
@@ -0,0 +1,16 @@
+module DeclarativePolicy
+  # 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.empty? && !block_given?
+
+      @rule_dsl.delegate(@delegate_name, m)
+    end
+  end
+end
diff --git a/lib/declarative_policy/dsl.rb b/lib/declarative_policy/dsl.rb
deleted file mode 100644
index 6ba1e7a3c5cb5fcd2cbcd959b0d3d58f598459cd..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/dsl.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-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) # rubocop:disable GitlabSecurity/PublicSend
-    end
-
-    def respond_to_missing?(m)
-      @context_class.respond_to?(m) || super
-    end
-  end
-end
diff --git a/lib/declarative_policy/policy_dsl.rb b/lib/declarative_policy/policy_dsl.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f11b6e9f7309d5524a7428bee3b10064b9b23e67
--- /dev/null
+++ b/lib/declarative_policy/policy_dsl.rb
@@ -0,0 +1,44 @@
+module DeclarativePolicy
+  # 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) # rubocop:disable GitlabSecurity/PublicSend
+    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
index b07540981497f5af9c66e20eb11f8fe37bd89256..5c214408dd0a8cfb18ce83a945981982afa21dad 100644
--- a/lib/declarative_policy/preferred_scope.rb
+++ b/lib/declarative_policy/preferred_scope.rb
@@ -1,4 +1,4 @@
-module DeclarativePolicy
+module DeclarativePolicy # rubocop:disable Naming/FileName
   PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope"
 
   class << self
diff --git a/lib/declarative_policy/rule_dsl.rb b/lib/declarative_policy/rule_dsl.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e948b7f2de16eb216c14ffb5e1eaff3407607372
--- /dev/null
+++ b/lib/declarative_policy/rule_dsl.rb
@@ -0,0 +1,45 @@
+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.empty? && !block_given?
+
+      if @context_class.delegations.key?(m)
+        DelegateDsl.new(self, m)
+      else
+        cond(m.to_sym)
+      end
+    end
+  end
+end
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
index 77c9181738215fef1170eeae167e77f6b599b440..87f14b3b0d268714058377742364b618cddc30f4 100644
--- a/lib/declarative_policy/runner.rb
+++ b/lib/declarative_policy/runner.rb
@@ -77,7 +77,7 @@ module DeclarativePolicy
       @state = State.new
 
       steps_by_score do |step, score|
-        return if !debug && @state.prevented?
+        break if !debug && @state.prevented?
 
         passed = nil
         case step.action
diff --git a/lib/forever.rb b/lib/forever.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7df179125440dadf8fc451ef18e35e04b6d9d809
--- /dev/null
+++ b/lib/forever.rb
@@ -0,0 +1,13 @@
+class Forever
+  POSTGRESQL_DATE = DateTime.new(3000, 1, 1)
+  MYSQL_DATE = DateTime.new(2038, 01, 19)
+
+  # MySQL timestamp has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC
+  def self.date
+    if Gitlab::Database.postgresql?
+      POSTGRESQL_DATE
+    else
+      MYSQL_DATE
+    end
+  end
+end
diff --git a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
index 7cb4bccb23c4c1b037da3b9b7093e3a22a6ce774..91175b49c799fe9e38b4bd657623d5f54f8edc4f 100644
--- a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
+++ b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
@@ -3,7 +3,7 @@ require 'rails/generators'
 module Rails
   class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase
     def create_migration_file
-      timestamp = Time.now.strftime('%Y%m%d%H%I%S')
+      timestamp = Time.now.strftime('%Y%m%d%H%M%S')
 
       template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb"
     end
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 11f7c8b95108236670c18c62d966b98a3d53d5e8..f66299825128ddffd8d63d0cd9e3c31e92704f95 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -2,13 +2,23 @@ require_dependency 'gitlab/git'
 
 module Gitlab
   COM_URL = 'https://gitlab.com'.freeze
+  APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
+  SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
 
   def self.com?
-    # Check `staging?` as well to keep parity with gitlab.com
-    Gitlab.config.gitlab.url == COM_URL || staging?
+    # Check `gl_subdomain?` as well to keep parity with gitlab.com
+    Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
   end
 
-  def self.staging?
-    Gitlab.config.gitlab.url == 'https://staging.gitlab.com'
+  def self.org?
+    Gitlab.config.gitlab.url == 'https://dev.gitlab.org'
+  end
+
+  def self.gl_subdomain?
+    SUBDOMAIN_REGEX === Gitlab.config.gitlab.url
+  end
+
+  def self.dev_env_or_com?
+    Rails.env.test? || Rails.env.development? || org? || com?
   end
 end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 86393ee254d28d6b7a6152f2ab17748064533d8e..8e5a985edd7f374afb204cf505a6b05d0dd3e81a 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -5,7 +5,7 @@ module Gitlab
     REGISTRY_SCOPES = [:read_registry].freeze
 
     # Scopes used for GitLab API access
-    API_SCOPES = [:api, :read_user, :sudo].freeze
+    API_SCOPES = [:api, :read_user, :sudo, :read_repository].freeze
 
     # Scopes used for OpenID Connect
     OPENID_SCOPES = [:openid].freeze
@@ -26,6 +26,7 @@ module Gitlab
           lfs_token_check(login, password, project) ||
           oauth_access_token_check(login, password) ||
           personal_access_token_check(password) ||
+          deploy_token_check(login, password) ||
           user_with_password_for_git(login, password) ||
           Gitlab::Auth::Result.new
 
@@ -40,8 +41,8 @@ module Gitlab
       end
 
       def find_with_user_password(login, password)
-        # Avoid resource intensive login checks if password is not provided
-        return unless password.present?
+        # Avoid resource intensive checks if login credentials are not provided
+        return unless login.present? && password.present?
 
         # Nothing to do here if internal auth is disabled and LDAP is
         # not configured
@@ -50,13 +51,29 @@ module Gitlab
         Gitlab::Auth::UniqueIpsLimiter.limit_user! do
           user = User.by_login(login)
 
-          # If no user is found, or it's an LDAP server, try LDAP.
-          #   LDAP users are only authenticated via LDAP
-          if user.nil? || user.ldap_user?
-            # Second chance - try LDAP authentication
-            Gitlab::Auth::LDAP::Authentication.login(login, password)
-          elsif Gitlab::CurrentSettings.password_authentication_enabled_for_git?
-            user if user.active? && user.valid_password?(password)
+          break if user && !user.active?
+
+          authenticators = []
+
+          if user
+            authenticators << Gitlab::Auth::OAuth::Provider.authentication(user, 'database')
+
+            # Add authenticators for all identities if user is not nil
+            user&.identities&.each do |identity|
+              authenticators << Gitlab::Auth::OAuth::Provider.authentication(user, identity.provider)
+            end
+          else
+            # If no user is provided, try LDAP.
+            #   LDAP users are only authenticated via LDAP
+            authenticators << Gitlab::Auth::LDAP::Authentication
+          end
+
+          authenticators.compact!
+
+          # return found user that was authenticated first for given login credentials
+          authenticators.find do |auth|
+            authenticated_user = auth.login(login, password)
+            break authenticated_user if authenticated_user
           end
         end
       end
@@ -147,7 +164,8 @@ module Gitlab
       def abilities_for_scopes(scopes)
         abilities_by_scope = {
           api: full_authentication_abilities,
-          read_registry: [:read_container_image]
+          read_registry: [:read_container_image],
+          read_repository: [:download_code]
         }
 
         scopes.flat_map do |scope|
@@ -155,6 +173,22 @@ module Gitlab
         end.uniq
       end
 
+      def deploy_token_check(login, password)
+        return unless password.present?
+
+        token =
+          DeployToken.active.find_by(token: password)
+
+        return unless token && login
+        return if login != token.username
+
+        scopes = abilities_for_scopes(token.scopes)
+
+        if valid_scoped_token?(token, available_scopes)
+          Gitlab::Auth::Result.new(token, token.project, :deploy_token, scopes)
+        end
+      end
+
       def lfs_token_check(login, password, project)
         deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
 
diff --git a/lib/gitlab/auth/database/authentication.rb b/lib/gitlab/auth/database/authentication.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1234ace0334202d82799392d21e585417a2d437c
--- /dev/null
+++ b/lib/gitlab/auth/database/authentication.rb
@@ -0,0 +1,16 @@
+# These calls help to authenticate to OAuth provider by providing username and password
+#
+
+module Gitlab
+  module Auth
+    module Database
+      class Authentication < Gitlab::Auth::OAuth::Authentication
+        def login(login, password)
+          return false unless Gitlab::CurrentSettings.password_authentication_enabled_for_git?
+
+          return user if user&.valid_password?(password)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/auth/ldap/access.rb b/lib/gitlab/auth/ldap/access.rb
index 77c0ddc2d48ef560e4ce460a063063b820ba7957..34286900e72f53b2e1cc2e5940a0e7ed029365cc 100644
--- a/lib/gitlab/auth/ldap/access.rb
+++ b/lib/gitlab/auth/ldap/access.rb
@@ -52,6 +52,8 @@ module Gitlab
             block_user(user, 'does not exist anymore')
             false
           end
+        rescue LDAPConnectionError
+          false
         end
 
         def adapter
diff --git a/lib/gitlab/auth/ldap/adapter.rb b/lib/gitlab/auth/ldap/adapter.rb
index caf2d18c668b8385dc38bc42a3ff2e2fd74f702b..82ff1e77e5c936f4e054cab86cd344918abe75ac 100644
--- a/lib/gitlab/auth/ldap/adapter.rb
+++ b/lib/gitlab/auth/ldap/adapter.rb
@@ -2,6 +2,9 @@ module Gitlab
   module Auth
     module LDAP
       class Adapter
+        SEARCH_RETRY_FACTOR = [1, 1, 2, 3].freeze
+        MAX_SEARCH_RETRIES = Rails.env.test? ? 1 : SEARCH_RETRY_FACTOR.size.freeze
+
         attr_reader :provider, :ldap
 
         def self.open(provider, &block)
@@ -16,7 +19,7 @@ module Gitlab
 
         def initialize(provider, ldap = nil)
           @provider = provider
-          @ldap = ldap || Net::LDAP.new(config.adapter_options)
+          @ldap = ldap || renew_connection_adapter
         end
 
         def config
@@ -47,8 +50,10 @@ module Gitlab
         end
 
         def ldap_search(*args)
+          retries ||= 0
+
           # Net::LDAP's `time` argument doesn't work. Use Ruby `Timeout` instead.
-          Timeout.timeout(config.timeout) do
+          Timeout.timeout(timeout_time(retries)) do
             results = ldap.search(*args)
 
             if results.nil?
@@ -63,16 +68,26 @@ module Gitlab
               results
             end
           end
-        rescue Net::LDAP::Error => error
-          Rails.logger.warn("LDAP search raised exception #{error.class}: #{error.message}")
-          []
-        rescue Timeout::Error
-          Rails.logger.warn("LDAP search timed out after #{config.timeout} seconds")
-          []
+        rescue Net::LDAP::Error, Timeout::Error => error
+          retries += 1
+          error_message = connection_error_message(error)
+
+          Rails.logger.warn(error_message)
+
+          if retries < MAX_SEARCH_RETRIES
+            renew_connection_adapter
+            retry
+          else
+            raise LDAPConnectionError, error_message
+          end
         end
 
         private
 
+        def timeout_time(retry_number)
+          SEARCH_RETRY_FACTOR[retry_number] * config.timeout
+        end
+
         def user_options(fields, value, limit)
           options = {
             attributes: Gitlab::Auth::LDAP::Person.ldap_attributes(config),
@@ -104,6 +119,18 @@ module Gitlab
             filter
           end
         end
+
+        def connection_error_message(exception)
+          if exception.is_a?(Timeout::Error)
+            "LDAP search timed out after #{config.timeout} seconds"
+          else
+            "LDAP search raised exception #{exception.class}: #{exception.message}"
+          end
+        end
+
+        def renew_connection_adapter
+          @ldap = Net::LDAP.new(config.adapter_options)
+        end
       end
     end
   end
diff --git a/lib/gitlab/auth/ldap/authentication.rb b/lib/gitlab/auth/ldap/authentication.rb
index cbb9cf4bb9c49c39b6162397c9bb45e13e32efb5..7c134fb6438a2512d52163acdd0a9ee7aa8e1df3 100644
--- a/lib/gitlab/auth/ldap/authentication.rb
+++ b/lib/gitlab/auth/ldap/authentication.rb
@@ -7,39 +7,31 @@
 module Gitlab
   module Auth
     module LDAP
-      class Authentication
+      class Authentication < Gitlab::Auth::OAuth::Authentication
         def self.login(login, password)
           return unless Gitlab::Auth::LDAP::Config.enabled?
           return unless login.present? && password.present?
 
-          auth = nil
-          # loop through providers until valid bind
+          # return found user that was authenticated by first provider for given login credentials
           providers.find do |provider|
             auth = new(provider)
-            auth.login(login, password) # true will exit the loop
+            break auth.user if auth.login(login, password) # true will exit the loop
           end
-
-          # If (login, password) was invalid for all providers, the value of auth is now the last
-          # Gitlab::Auth::LDAP::Authentication instance we tried.
-          auth.user
         end
 
         def self.providers
           Gitlab::Auth::LDAP::Config.providers
         end
 
-        attr_accessor :provider, :ldap_user
-
-        def initialize(provider)
-          @provider = provider
-        end
-
         def login(login, password)
-          @ldap_user = adapter.bind_as(
+          result = adapter.bind_as(
             filter: user_filter(login),
             size: 1,
             password: password
           )
+          return unless result
+
+          @user = Gitlab::Auth::LDAP::User.find_by_uid_and_provider(result.dn, provider)
         end
 
         def adapter
@@ -60,12 +52,6 @@ module Gitlab
 
           filter
         end
-
-        def user
-          return nil unless ldap_user
-
-          Gitlab::Auth::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
-        end
       end
     end
   end
diff --git a/lib/gitlab/auth/ldap/ldap_connection_error.rb b/lib/gitlab/auth/ldap/ldap_connection_error.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ef0a695742bd311eae820e306084d93a8eedb9bf
--- /dev/null
+++ b/lib/gitlab/auth/ldap/ldap_connection_error.rb
@@ -0,0 +1,7 @@
+module Gitlab
+  module Auth
+    module LDAP
+      LDAPConnectionError = Class.new(StandardError)
+    end
+  end
+end
diff --git a/lib/gitlab/auth/o_auth/authentication.rb b/lib/gitlab/auth/o_auth/authentication.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d4e7f35c857f6b81f7c9b534ebca16b3387fb915
--- /dev/null
+++ b/lib/gitlab/auth/o_auth/authentication.rb
@@ -0,0 +1,22 @@
+# These calls help to authenticate to OAuth provider by providing username and password
+#
+
+module Gitlab
+  module Auth
+    module OAuth
+      class Authentication
+        attr_reader :provider, :user
+
+        def initialize(provider, user = nil)
+          @provider = provider
+          @user = user
+        end
+
+        # Implementation must return user object if login successful
+        def login(login, password)
+          raise NotImplementedError
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/auth/o_auth/provider.rb b/lib/gitlab/auth/o_auth/provider.rb
index f8ab8ee1388e8392ae5d96934c381301062fe399..5fb61ffe00d7be5b62d179c7b9d464e4ddc09304 100644
--- a/lib/gitlab/auth/o_auth/provider.rb
+++ b/lib/gitlab/auth/o_auth/provider.rb
@@ -8,11 +8,28 @@ module Gitlab
           "google_oauth2"  => "Google"
         }.freeze
 
+        def self.authentication(user, provider)
+          return unless user
+          return unless enabled?(provider)
+
+          authenticator =
+            case provider
+            when /^ldap/
+              Gitlab::Auth::LDAP::Authentication
+            when 'database'
+              Gitlab::Auth::Database::Authentication
+            end
+
+          authenticator&.new(provider, user)
+        end
+
         def self.providers
           Devise.omniauth_providers
         end
 
         def self.enabled?(name)
+          return true if name == 'database'
+
           providers.include?(name.to_sym)
         end
 
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index acd785bb02d918fa2e9ad7134ca48e7ef2c40af9..d0c6b0386ba55edc69e6f461b14fe16c8ab1c694 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -124,6 +124,9 @@ module Gitlab
           Gitlab::Auth::LDAP::Person.find_by_uid(auth_hash.uid, adapter) ||
             Gitlab::Auth::LDAP::Person.find_by_email(auth_hash.uid, adapter) ||
             Gitlab::Auth::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
+
+        rescue Gitlab::Auth::LDAP::LDAPConnectionError
+          nil
         end
 
         def ldap_config
@@ -161,7 +164,7 @@ module Gitlab
 
         def find_by_uid_and_provider
           identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
-          identity && identity.user
+          identity&.user
         end
 
         def build_new_user
diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb
index 75451cf8aa9d5f1af9bc897452c2f12aab2bd365..00cdc94a9ef05f98f5003ad51147d83f54eae03d 100644
--- a/lib/gitlab/auth/result.rb
+++ b/lib/gitlab/auth/result.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module Gitlab # rubocop:disable Naming/FileName
   module Auth
     Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
       def ci?(for_project)
diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb
index e654e7fe4384ccef9381fc9b4988560c4400c0dc..2760b1a3247d00071793a8efd72095732eec53d7 100644
--- a/lib/gitlab/auth/saml/config.rb
+++ b/lib/gitlab/auth/saml/config.rb
@@ -4,7 +4,7 @@ module Gitlab
       class Config
         class << self
           def options
-            Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' }
+            Gitlab::Auth::OAuth::Provider.config_for('saml')
           end
 
           def groups
diff --git a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
index 7bffffec94d36ca861be3d357f9288cdbbea5e50..d5cf9e0d53aca4d2180363f4492b518bc6218854 100644
--- a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
+++ b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
@@ -19,7 +19,7 @@ module Gitlab
             WHERE merge_request_diffs.id = merge_request_diff_commits.merge_request_diff_id
           )'.squish
 
-        MergeRequestDiff.where(id: start_id..stop_id).update_all(update)
+        MergeRequestDiff.where(id: start_id..stop_id).where(commits_count: nil).update_all(update)
       end
     end
   end
diff --git a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
index fd5cbf76e47d6e262057c6950e36f49d2eb44aa2..a357538a885ca72d6c6db22d55e1ef0eaeb8527a 100644
--- a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
+++ b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
@@ -96,7 +96,7 @@ module Gitlab
           commit_hash.merge(
             merge_request_diff_id: merge_request_diff.id,
             relative_order: index,
-            sha: sha_attribute.type_cast_for_database(sha)
+            sha: sha_attribute.serialize(sha)
           )
         end
 
diff --git a/lib/gitlab/background_migration/migrate_build_stage.rb b/lib/gitlab/background_migration/migrate_build_stage.rb
index 8fe4f1a2289d098914ad5fdb3ac2a2c4c293c3c3..242e3143e71db7ced0fb9ee0d6df721756630077 100644
--- a/lib/gitlab/background_migration/migrate_build_stage.rb
+++ b/lib/gitlab/background_migration/migrate_build_stage.rb
@@ -12,6 +12,7 @@ module Gitlab
 
         class Build < ActiveRecord::Base
           self.table_name = 'ci_builds'
+          self.inheritance_column = :_type_disabled
 
           def ensure_stage!(attempts: 2)
             find_stage || create_stage!
diff --git a/lib/gitlab/background_migration/set_confidential_note_events_on_services.rb b/lib/gitlab/background_migration/set_confidential_note_events_on_services.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e5e8837221e9078cb1726f044f6ec08d39d64ab7
--- /dev/null
+++ b/lib/gitlab/background_migration/set_confidential_note_events_on_services.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+  module BackgroundMigration
+    # Ensures services which previously recieved all notes events continue
+    # to recieve confidential ones.
+    class SetConfidentialNoteEventsOnServices
+      class Service < ActiveRecord::Base
+        self.table_name = 'services'
+
+        include ::EachBatch
+
+        def self.services_to_update
+          where(confidential_note_events: nil, note_events: true)
+        end
+      end
+
+      def perform(start_id, stop_id)
+        Service.services_to_update
+               .where(id: start_id..stop_id)
+               .update_all(confidential_note_events: true)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb b/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..171c8ef21b77c531c6252e339c09cdff24ff86b5
--- /dev/null
+++ b/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+  module BackgroundMigration
+    # Ensures hooks which previously recieved all notes events continue
+    # to recieve confidential ones.
+    class SetConfidentialNoteEventsOnWebhooks
+      class WebHook < ActiveRecord::Base
+        self.table_name = 'web_hooks'
+
+        include ::EachBatch
+
+        def self.hooks_to_update
+          where(confidential_note_events: nil, note_events: true)
+        end
+      end
+
+      def perform(start_id, stop_id)
+        WebHook.hooks_to_update
+               .where(id: start_id..stop_id)
+               .update_all(confidential_note_events: true)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb
index 884a3de8f62912230618c2366b9cbc5508819483..1a25138e7d61fd38aa4fa82e4ca4050baee52632 100644
--- a/lib/gitlab/bare_repository_import/importer.rb
+++ b/lib/gitlab/bare_repository_import/importer.rb
@@ -63,7 +63,7 @@ module Gitlab
           log " * Created #{project.name} (#{project_full_path})".color(:green)
 
           project.write_repository_config
-          project.repository.create_hooks
+          Gitlab::Git::Repository.create_hooks(project.repository.path_to_repo, Gitlab.config.gitlab_shell.hooks_path)
 
           ProjectCacheWorker.perform_async(project.id)
         else
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index d48ae17aeaff877ce70178d4f287e5dd7e70638b..f3999e690fa338058747c82790c3b29309b09c03 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -63,7 +63,7 @@ module Gitlab
 
         disk_path = project.wiki.disk_path
         import_url = project.import_url.sub(/\.git\z/, ".git/wiki")
-        gitlab_shell.import_repository(project.repository_storage_path, disk_path, import_url)
+        gitlab_shell.import_repository(project.repository_storage, disk_path, import_url)
       rescue StandardError => e
         errors << { type: :wiki, errors: e.message }
       end
@@ -135,7 +135,7 @@ module Gitlab
           if label.valid?
             @labels[label_params[:title]] = label
           else
-            raise "Failed to create label \"#{label_params[:title]}\" for project \"#{project.name_with_namespace}\""
+            raise "Failed to create label \"#{label_params[:title]}\" for project \"#{project.full_name}\""
           end
         end
       end
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index dba378928639814b014a9aaa9d64419c28c26239..add048d671e19600037b25d5418552541c0dec3a 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -40,7 +40,7 @@ module Gitlab
         end
 
         def self.cache_key_for_project(project)
-          "projects/#{project.id}/pipeline_status"
+          "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status"
         end
 
         def self.update_for_pipeline(pipeline)
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 3ce5f807989b93c903dba54a8fa22439a324a195..51ba09aa129b3f7a9ec9760744b914591838ff2a 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -47,7 +47,7 @@ module Gitlab
       protected
 
       def push_checks
-        if user_access.cannot_do_action?(:push_code)
+        unless can_push?
           raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code]
         end
       end
@@ -183,6 +183,11 @@ module Gitlab
       def commits
         @commits ||= project.repository.new_commits(newrev)
       end
+
+      def can_push?
+        user_access.can_do_action?(:push_code) ||
+          user_access.can_push_to_branch?(branch_name)
+      end
     end
   end
 end
diff --git a/lib/gitlab/checks/lfs_integrity.rb b/lib/gitlab/checks/lfs_integrity.rb
index f7276a380dcc09f585abcda63305559367d74a12..f0e5773ec3c418d2313dfa82e9166922ebfe7e57 100644
--- a/lib/gitlab/checks/lfs_integrity.rb
+++ b/lib/gitlab/checks/lfs_integrity.rb
@@ -15,8 +15,7 @@ module Gitlab
 
         return false unless new_lfs_pointers.present?
 
-        existing_count = @project.lfs_storage_project
-                                 .lfs_objects
+        existing_count = @project.all_lfs_objects
                                  .where(oid: new_lfs_pointers.map(&:lfs_oid))
                                  .count
 
diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb
index 3263790a8768326cd7ecdaf59baa66e90611614c..3a197078d08df5785cd699298a1bae05f22e2567 100644
--- a/lib/gitlab/checks/project_moved.rb
+++ b/lib/gitlab/checks/project_moved.rb
@@ -9,20 +9,16 @@ module Gitlab
         super(project, user, protocol)
       end
 
-      def message(rejected: false)
+      def message
         <<~MESSAGE
         Project '#{redirected_path}' was moved to '#{project.full_path}'.
 
         Please update your Git remote:
 
-          #{remote_url_message(rejected)}
+          git remote set-url origin #{url_to_repo}
         MESSAGE
       end
 
-      def permanent_redirect?
-        RedirectRoute.permanent.exists?(path: redirected_path)
-      end
-
       private
 
       attr_reader :redirected_path
@@ -30,18 +26,6 @@ module Gitlab
       def self.message_key(user_id, project_id)
         "#{REDIRECT_NAMESPACE}:#{user_id}:#{project_id}"
       end
-
-      def remote_url_message(rejected)
-        if rejected
-          "git remote set-url origin #{url_to_repo} and try again."
-        else
-          "git remote set-url origin #{url_to_repo}"
-        end
-      end
-
-      def url
-        protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo
-      end
     end
   end
 end
diff --git a/lib/gitlab/ci/build/policy/kubernetes.rb b/lib/gitlab/ci/build/policy/kubernetes.rb
index b20d374288fa7a3651defeaba3e29d4c4c9c85b9..782f6c4c0af28741b43fc7e7eef73fb0602c2638 100644
--- a/lib/gitlab/ci/build/policy/kubernetes.rb
+++ b/lib/gitlab/ci/build/policy/kubernetes.rb
@@ -9,7 +9,7 @@ module Gitlab
             end
           end
 
-          def satisfied_by?(pipeline)
+          def satisfied_by?(pipeline, seed = nil)
             pipeline.has_kubernetes_active?
           end
         end
diff --git a/lib/gitlab/ci/build/policy/refs.rb b/lib/gitlab/ci/build/policy/refs.rb
index eadc0948d2f7b2f83190983839a111acb835535f..4aa5dc89f47aaf012a2d89b11bb22b2c5145f76b 100644
--- a/lib/gitlab/ci/build/policy/refs.rb
+++ b/lib/gitlab/ci/build/policy/refs.rb
@@ -7,7 +7,7 @@ module Gitlab
             @patterns = Array(refs)
           end
 
-          def satisfied_by?(pipeline)
+          def satisfied_by?(pipeline, seed = nil)
             @patterns.any? do |pattern|
               pattern, path = pattern.split('@', 2)
 
diff --git a/lib/gitlab/ci/build/policy/specification.rb b/lib/gitlab/ci/build/policy/specification.rb
index c317291f29df332b45188caaf599fdcdaf573067..f09ba42c074276f9e8ac8e12c5ab3ae5294613b2 100644
--- a/lib/gitlab/ci/build/policy/specification.rb
+++ b/lib/gitlab/ci/build/policy/specification.rb
@@ -15,7 +15,7 @@ module Gitlab
             @spec = spec
           end
 
-          def satisfied_by?(pipeline)
+          def satisfied_by?(pipeline, seed = nil)
             raise NotImplementedError
           end
         end
diff --git a/lib/gitlab/ci/build/policy/variables.rb b/lib/gitlab/ci/build/policy/variables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9d2a362b7d4b63cf84078b1929bfca6b1cf46395
--- /dev/null
+++ b/lib/gitlab/ci/build/policy/variables.rb
@@ -0,0 +1,24 @@
+module Gitlab
+  module Ci
+    module Build
+      module Policy
+        class Variables < Policy::Specification
+          def initialize(expressions)
+            @expressions = Array(expressions)
+          end
+
+          def satisfied_by?(pipeline, seed)
+            variables = seed.to_resource.scoped_variables_hash
+
+            statements = @expressions.map do |statement|
+              ::Gitlab::Ci::Pipeline::Expression::Statement
+                .new(statement, variables)
+            end
+
+            statements.any?(&:truthful?)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb
index 411f67f8ce7ccfc08768c1363c2eaa77415abe51..0b1ebe4e048b8a5c67c1afcfc60472ba4d048c00 100644
--- a/lib/gitlab/ci/build/step.rb
+++ b/lib/gitlab/ci/build/step.rb
@@ -14,7 +14,7 @@ module Gitlab
             self.new(:script).tap do |step|
               step.script = job.options[:before_script].to_a + job.options[:script].to_a
               step.script = job.commands.split("\n") if step.script.empty?
-              step.timeout = job.timeout
+              step.timeout = job.metadata_timeout
               step.when = WHEN_ON_SUCCESS
             end
           end
@@ -25,7 +25,7 @@ module Gitlab
 
             self.new(:after_script).tap do |step|
               step.script = after_script
-              step.timeout = job.timeout
+              step.timeout = job.metadata_timeout
               step.when = WHEN_ALWAYS
               step.allow_failure = true
             end
diff --git a/lib/gitlab/ci/charts.rb b/lib/gitlab/ci/charts.rb
index 525563a97f558994ee839fe77de59c397ab4a1da..46ed330dbbff7f5236f400633f730cf3c0cd2ebc 100644
--- a/lib/gitlab/ci/charts.rb
+++ b/lib/gitlab/ci/charts.rb
@@ -68,10 +68,11 @@ module Gitlab
 
       class YearChart < Chart
         include MonthlyInterval
+        attr_reader :to, :from
 
         def initialize(*)
-          @to     = Date.today.end_of_month
-          @from   = @to.years_ago(1).beginning_of_month
+          @to     = Date.today.end_of_month.end_of_day
+          @from   = @to.years_ago(1).beginning_of_month.beginning_of_day
           @format = '%d %B %Y'
 
           super
@@ -80,10 +81,11 @@ module Gitlab
 
       class MonthChart < Chart
         include DailyInterval
+        attr_reader :to, :from
 
         def initialize(*)
-          @to     = Date.today
-          @from   = @to - 30.days
+          @to     = Date.today.end_of_day
+          @from   = 1.month.ago.beginning_of_day
           @format = '%d %B'
 
           super
@@ -92,10 +94,11 @@ module Gitlab
 
       class WeekChart < Chart
         include DailyInterval
+        attr_reader :to, :from
 
         def initialize(*)
-          @to     = Date.today
-          @from   = @to - 7.days
+          @to     = Date.today.end_of_day
+          @from   = 1.week.ago.beginning_of_day
           @format = '%d %B'
 
           super
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index f7ff7ea212e0e10acf078909bb6260bb96099eeb..66ac4a40616bd1bda03538f358690c974af83dbf 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -4,7 +4,8 @@ module Gitlab
     # Base GitLab CI Configuration facade
     #
     class Config
-      def initialize(config)
+      # EE would override this and utilize opts argument
+      def initialize(config, opts = {})
         @config = Loader.new(config).load!
 
         @global = Entry::Global.new(@config)
diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb
index 0027e9ec8c5d89a2e62c776406a84a26bc141450..09e8e52b60fb4a355323716348779b90c6d96464 100644
--- a/lib/gitlab/ci/config/entry/policy.rb
+++ b/lib/gitlab/ci/config/entry/policy.rb
@@ -25,15 +25,31 @@ module Gitlab
             include Entry::Validatable
             include Entry::Attributable
 
-            attributes :refs, :kubernetes
+            attributes :refs, :kubernetes, :variables
 
             validations do
               validates :config, presence: true
-              validates :config, allowed_keys: %i[refs kubernetes]
+              validates :config, allowed_keys: %i[refs kubernetes variables]
+              validate :variables_expressions_syntax
 
               with_options allow_nil: true do
                 validates :refs, array_of_strings_or_regexps: true
                 validates :kubernetes, allowed_values: %w[active]
+                validates :variables, array_of_strings: true
+              end
+
+              def variables_expressions_syntax
+                return unless variables.is_a?(Array)
+
+                statements = variables.map do |statement|
+                  ::Gitlab::Ci::Pipeline::Expression::Statement.new(statement)
+                end
+
+                statements.each do |statement|
+                  unless statement.valid?
+                    errors.add(:variables, "Invalid expression syntax")
+                  end
+                end
               end
             end
           end
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 7b19b10e05bae599ce008162d05ac8bcccf4a338..a1849b01c5d98f320eb7ce75a75f47693cfeaaaa 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module Gitlab # rubocop:disable Naming/FileName
   module Ci
     module Pipeline
       module Chain
diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb
index d19a25198036fb4f09c4c318972b45cc85d9c4b0..f4c8d5342c16ae9fd621ca895991b5707304d492 100644
--- a/lib/gitlab/ci/pipeline/chain/create.rb
+++ b/lib/gitlab/ci/pipeline/chain/create.rb
@@ -9,35 +9,24 @@ module Gitlab
             ::Ci::Pipeline.transaction do
               pipeline.save!
 
-              @command.seeds_block&.call(pipeline)
-
-              ::Ci::CreatePipelineStagesService
-                .new(project, current_user)
-                .execute(pipeline)
+              ##
+              # Create environments before the pipeline starts.
+              #
+              pipeline.builds.each do |build|
+                if build.has_environment?
+                  project.environments.find_or_create_by(
+                    name: build.expanded_environment_name
+                  )
+                end
+              end
             end
           rescue ActiveRecord::RecordInvalid => e
             error("Failed to persist the pipeline: #{e}")
-          ensure
-            if pipeline.builds.where(stage_id: nil).any?
-              invalid_builds_counter.increment(node: hostname)
-            end
           end
 
           def break?
             !pipeline.persisted?
           end
-
-          private
-
-          def invalid_builds_counter
-            @counter ||= Gitlab::Metrics
-              .counter(:gitlab_ci_invalid_builds_total,
-                       'Invalid builds without stage assigned counter')
-          end
-
-          def hostname
-            @hostname ||= Socket.gethostname
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d299a5677deaf221b71eaa2d3a84270a76643696
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/populate.rb
@@ -0,0 +1,45 @@
+module Gitlab
+  module Ci
+    module Pipeline
+      module Chain
+        class Populate < Chain::Base
+          include Chain::Helpers
+
+          PopulateError = Class.new(StandardError)
+
+          def perform!
+            ##
+            # Populate pipeline with block argument of CreatePipelineService#execute.
+            #
+            @command.seeds_block&.call(pipeline)
+
+            ##
+            # Populate pipeline with all stages and builds from pipeline seeds.
+            #
+            pipeline.stage_seeds.each do |stage|
+              pipeline.stages << stage.to_resource
+
+              stage.seeds.each do |build|
+                pipeline.builds << build.to_resource
+              end
+            end
+
+            if pipeline.stages.none?
+              return error('No stages / jobs for this pipeline.')
+            end
+
+            if pipeline.invalid?
+              return error('Failed to build the pipeline!')
+            end
+
+            raise Populate::PopulateError if pipeline.persisted?
+          end
+
+          def break?
+            pipeline.errors.any?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/config.rb b/lib/gitlab/ci/pipeline/chain/validate/config.rb
index 075504bcce57394e75ba2937e73486f32a20c6a8..a3bd2a5a23af83a14ce35d9433d16c75224617e8 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/config.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/config.rb
@@ -16,11 +16,7 @@ module Gitlab
                   @pipeline.drop!(:config_error)
                 end
 
-                return error(@pipeline.yaml_errors)
-              end
-
-              unless @pipeline.has_stage_seeds?
-                return error('No stages / jobs for this pipeline.')
+                error(@pipeline.yaml_errors)
               end
             end
 
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/string.rb b/lib/gitlab/ci/pipeline/expression/lexeme/string.rb
index 48bde213d44dc42dccd9c374cfc5b78f6bb5ed40..346c92dc51ea3f8bbdb5900dedd7cb0ce305ccdc 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/string.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/string.rb
@@ -4,7 +4,7 @@ module Gitlab
       module Expression
         module Lexeme
           class String < Lexeme::Value
-            PATTERN = /("(?<string>.+?)")|('(?<string>.+?)')/.freeze
+            PATTERN = /("(?<string>.*?)")|('(?<string>.*?)')/.freeze
 
             def initialize(value)
               @value = value
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb b/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb
index b781c15fd67b73b79ca54465a2ea211b03ae2804..37643c8ef53c03e32a151c1acdf12f82c6a22b93 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb
@@ -11,7 +11,7 @@ module Gitlab
             end
 
             def evaluate(variables = {})
-              HashWithIndifferentAccess.new(variables).fetch(@name, nil)
+              variables.with_indifferent_access.fetch(@name, nil)
             end
 
             def self.build(string)
diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb
index 4f0e101b7301fe9eec4f66d1c68a0561991928ab..09a7c98464b52d49f80588d895b0000054015200 100644
--- a/lib/gitlab/ci/pipeline/expression/statement.rb
+++ b/lib/gitlab/ci/pipeline/expression/statement.rb
@@ -14,12 +14,9 @@ module Gitlab
             %w[variable]
           ].freeze
 
-          def initialize(statement, pipeline)
+          def initialize(statement, variables = {})
             @lexer = Expression::Lexer.new(statement)
-
-            @variables = pipeline.variables.map do |variable|
-              [variable.key, variable.value]
-            end
+            @variables = variables.with_indifferent_access
           end
 
           def parse_tree
@@ -35,6 +32,16 @@ module Gitlab
           def evaluate
             parse_tree.evaluate(@variables.to_h)
           end
+
+          def truthful?
+            evaluate.present?
+          end
+
+          def valid?
+            parse_tree.is_a?(Lexeme::Base)
+          rescue StatementError
+            false
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/pipeline/seed/base.rb b/lib/gitlab/ci/pipeline/seed/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..db9706924bb68781bf9406d3bd286403e22b14cd
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/seed/base.rb
@@ -0,0 +1,21 @@
+module Gitlab
+  module Ci
+    module Pipeline
+      module Seed
+        class Base
+          def attributes
+            raise NotImplementedError
+          end
+
+          def included?
+            raise NotImplementedError
+          end
+
+          def to_resource
+            raise NotImplementedError
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6980b0b7aff29ae56e510b9917d19443cc52c2d7
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/seed/build.rb
@@ -0,0 +1,48 @@
+module Gitlab
+  module Ci
+    module Pipeline
+      module Seed
+        class Build < Seed::Base
+          include Gitlab::Utils::StrongMemoize
+
+          delegate :dig, to: :@attributes
+
+          def initialize(pipeline, attributes)
+            @pipeline = pipeline
+            @attributes = attributes
+
+            @only = Gitlab::Ci::Build::Policy
+              .fabricate(attributes.delete(:only))
+            @except = Gitlab::Ci::Build::Policy
+              .fabricate(attributes.delete(:except))
+          end
+
+          def included?
+            strong_memoize(:inclusion) do
+              @only.all? { |spec| spec.satisfied_by?(@pipeline, self) } &&
+                @except.none? { |spec| spec.satisfied_by?(@pipeline, self) }
+            end
+          end
+
+          def attributes
+            @attributes.merge(
+              pipeline: @pipeline,
+              project: @pipeline.project,
+              user: @pipeline.user,
+              ref: @pipeline.ref,
+              tag: @pipeline.tag,
+              trigger_request: @pipeline.legacy_trigger,
+              protected: @pipeline.protected_ref?
+            )
+          end
+
+          def to_resource
+            strong_memoize(:resource) do
+              ::Ci::Build.new(attributes)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/pipeline/seed/stage.rb b/lib/gitlab/ci/pipeline/seed/stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c101f30d6e81ce252501b8c593fcdd6ae78b32c7
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/seed/stage.rb
@@ -0,0 +1,47 @@
+module Gitlab
+  module Ci
+    module Pipeline
+      module Seed
+        class Stage < Seed::Base
+          include Gitlab::Utils::StrongMemoize
+
+          delegate :size, to: :seeds
+          delegate :dig, to: :seeds
+
+          def initialize(pipeline, attributes)
+            @pipeline = pipeline
+            @attributes = attributes
+
+            @builds = attributes.fetch(:builds).map do |attributes|
+              Seed::Build.new(@pipeline, attributes)
+            end
+          end
+
+          def attributes
+            { name: @attributes.fetch(:name),
+              pipeline: @pipeline,
+              project: @pipeline.project }
+          end
+
+          def seeds
+            strong_memoize(:seeds) do
+              @builds.select(&:included?)
+            end
+          end
+
+          def included?
+            seeds.any?
+          end
+
+          def to_resource
+            strong_memoize(:stage) do
+              ::Ci::Stage.new(attributes).tap do |stage|
+                seeds.each { |seed| stage.builds << seed.to_resource }
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/stage/seed.rb b/lib/gitlab/ci/stage/seed.rb
deleted file mode 100644
index f33c87f554d4a972443b345f870d7ec0d4db139f..0000000000000000000000000000000000000000
--- a/lib/gitlab/ci/stage/seed.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-module Gitlab
-  module Ci
-    module Stage
-      class Seed
-        include ::Gitlab::Utils::StrongMemoize
-
-        attr_reader :pipeline
-
-        delegate :project, to: :pipeline
-        delegate :size, to: :@jobs
-
-        def initialize(pipeline, stage, jobs)
-          @pipeline = pipeline
-          @stage = { name: stage }
-          @jobs = jobs.to_a.dup
-        end
-
-        def user=(current_user)
-          @jobs.map! do |attributes|
-            attributes.merge(user: current_user)
-          end
-        end
-
-        def stage
-          @stage.merge(project: project)
-        end
-
-        def builds
-          trigger = pipeline.trigger_requests.first
-
-          @jobs.map do |attributes|
-            attributes.merge(project: project,
-                             ref: pipeline.ref,
-                             tag: pipeline.tag,
-                             trigger_request: trigger,
-                             protected: protected_ref?)
-          end
-        end
-
-        def create!
-          pipeline.stages.create!(stage).tap do |stage|
-            builds_attributes = builds.map do |attributes|
-              attributes.merge(stage_id: stage.id)
-            end
-
-            pipeline.builds.create!(builds_attributes).each do |build|
-              yield build if block_given?
-            end
-          end
-        end
-
-        private
-
-        def protected_ref?
-          strong_memoize(:protected_ref) do
-            project.protected_for?(pipeline.ref)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index 2d9166d6bdd54ff16882492f10b65085f183bd20..024047d498368964c6f7dd3457078bf92d96b223 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -23,6 +23,10 @@ module Gitlab
             'Cancel'
           end
 
+          def action_button_title
+            _('Cancel this job')
+          end
+
           def self.matches?(build, user)
             build.cancelable?
           end
diff --git a/lib/gitlab/ci/status/build/canceled.rb b/lib/gitlab/ci/status/build/canceled.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c83e2734a735dab7cc5b55059dfe91b628a670bc
--- /dev/null
+++ b/lib/gitlab/ci/status/build/canceled.rb
@@ -0,0 +1,21 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Canceled < Status::Extended
+          def illustration
+            {
+              image: 'illustrations/canceled-job_empty.svg',
+              size: 'svg-430',
+              title: _('This job has been canceled')
+            }
+          end
+
+          def self.matches?(build, user)
+            build.canceled?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
index c0c7c7f5b5dac6764d8eebd64a3dc8838645c8ad..c1fc70ac266d68ee0f74d6cc3a3e7b88ae6309c6 100644
--- a/lib/gitlab/ci/status/build/common.rb
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -3,6 +3,14 @@ module Gitlab
     module Status
       module Build
         module Common
+          def illustration
+            {
+              image: 'illustrations/skipped-job_empty.svg',
+              size: 'svg-430',
+              title: _('This job does not have a trace.')
+            }
+          end
+
           def has_details?
             can?(user, :read_build, subject)
           end
diff --git a/lib/gitlab/ci/status/build/created.rb b/lib/gitlab/ci/status/build/created.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5be8e9de425ef93d02e4bfb4bc436f02cf07a8f3
--- /dev/null
+++ b/lib/gitlab/ci/status/build/created.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Created < Status::Extended
+          def illustration
+            {
+              image: 'illustrations/job_not_triggered.svg',
+              size: 'svg-306',
+              title: _('This job has not been triggered yet'),
+              content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
+            }
+          end
+
+          def self.matches?(build, user)
+            build.created?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/erased.rb b/lib/gitlab/ci/status/build/erased.rb
new file mode 100644
index 0000000000000000000000000000000000000000..495227c2ffb9d516014111757feb8ab934e3c147
--- /dev/null
+++ b/lib/gitlab/ci/status/build/erased.rb
@@ -0,0 +1,21 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Erased < Status::Extended
+          def illustration
+            {
+              image: 'illustrations/erased-log_empty.svg',
+              size: 'svg-430',
+              title: _('Job has been erased')
+            }
+          end
+
+          def self.matches?(build, user)
+            build.erased?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
index c852d6073736d07d81a4d8e64752579e49ee73bb..2b26ebb45a1bff99932ec7e7b6f5f6c8ab119bce 100644
--- a/lib/gitlab/ci/status/build/factory.rb
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -4,12 +4,20 @@ module Gitlab
       module Build
         class Factory < Status::Factory
           def self.extended_statuses
-            [[Status::Build::Cancelable,
+            [[Status::Build::Erased,
+              Status::Build::Manual,
+              Status::Build::Canceled,
+              Status::Build::Created,
+              Status::Build::Pending,
+              Status::Build::Skipped],
+             [Status::Build::Cancelable,
               Status::Build::Retryable],
+             [Status::Build::Failed],
              [Status::Build::FailedAllowed,
               Status::Build::Play,
               Status::Build::Stop],
-             [Status::Build::Action]]
+             [Status::Build::Action],
+             [Status::Build::Retried]]
           end
 
           def self.common_helpers
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
new file mode 100644
index 0000000000000000000000000000000000000000..155f4fc1343aa17f93e3356f0c4d1f3c507e50b5
--- /dev/null
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -0,0 +1,40 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Failed < Status::Extended
+          REASONS = {
+            'unknown_failure' => 'unknown failure',
+            'script_failure' => 'script failure',
+            'api_failure' => 'API failure',
+            'stuck_or_timeout_failure' => 'stuck or timeout failure',
+            'runner_system_failure' => 'runner system failure',
+            'missing_dependency_failure' => 'missing dependency failure'
+          }.freeze
+
+          def status_tooltip
+            base_message
+          end
+
+          def badge_tooltip
+            base_message
+          end
+
+          def self.matches?(build, user)
+            build.failed?
+          end
+
+          private
+
+          def base_message
+            "#{s_('CiStatusLabel|failed')} #{description}"
+          end
+
+          def description
+            "<br> (#{REASONS[subject.failure_reason]})"
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/failed_allowed.rb b/lib/gitlab/ci/status/build/failed_allowed.rb
index dc90f398c7e7258bb2b81fc246168022068d8cce..ca0046fb1f73048cb87461deb14a08594b9694ed 100644
--- a/lib/gitlab/ci/status/build/failed_allowed.rb
+++ b/lib/gitlab/ci/status/build/failed_allowed.rb
@@ -4,7 +4,7 @@ module Gitlab
       module Build
         class FailedAllowed < Status::Extended
           def label
-            'failed (allowed to fail)'
+            "failed #{allowed_to_fail_title}"
           end
 
           def icon
@@ -15,9 +15,19 @@ module Gitlab
             'failed_with_warnings'
           end
 
+          def status_tooltip
+            "#{@status.status_tooltip} #{allowed_to_fail_title}"
+          end
+
           def self.matches?(build, user)
             build.failed? && build.allow_failure?
           end
+
+          private
+
+          def allowed_to_fail_title
+            "(allowed to fail)"
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/status/build/manual.rb b/lib/gitlab/ci/status/build/manual.rb
new file mode 100644
index 0000000000000000000000000000000000000000..042da6392d396691fbc8d6e49b6801de7d75f354
--- /dev/null
+++ b/lib/gitlab/ci/status/build/manual.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Manual < Status::Extended
+          def illustration
+            {
+              image: 'illustrations/manual_action.svg',
+              size: 'svg-394',
+              title: _('This job requires a manual action'),
+              content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
+            }
+          end
+
+          def self.matches?(build, user)
+            build.playable?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/pending.rb b/lib/gitlab/ci/status/build/pending.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9dd9a27ad57401740344f6218f7dc3c792b37176
--- /dev/null
+++ b/lib/gitlab/ci/status/build/pending.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Pending < Status::Extended
+          def illustration
+            {
+              image: 'illustrations/pending_job_empty.svg',
+              size: 'svg-430',
+              title: _('This job has not started yet'),
+              content: _('This job is in pending state and is waiting to be picked by a runner')
+            }
+          end
+
+          def self.matches?(build, user)
+            build.pending?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index b7b45466d3b471e375861882187bb8f20723043e..a8b9ebf08033c9a787db5a3a5eaa2f0474316a36 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -19,6 +19,10 @@ module Gitlab
             'Play'
           end
 
+          def action_button_title
+            _('Trigger this manual action')
+          end
+
           def action_path
             play_project_job_path(subject.project, subject)
           end
diff --git a/lib/gitlab/ci/status/build/retried.rb b/lib/gitlab/ci/status/build/retried.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e190e4ee3c4d800714733d3fc3954d7a6fad561
--- /dev/null
+++ b/lib/gitlab/ci/status/build/retried.rb
@@ -0,0 +1,17 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Retried < Status::Extended
+          def status_tooltip
+            @status.status_tooltip + " (retried)"
+          end
+
+          def self.matches?(build, user)
+            build.retried?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 44ffe783e50270f204b44a14ec58a594067b5b14..5aeb8e51480192dde6c529befb88601f96fb30b5 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -15,6 +15,10 @@ module Gitlab
             'Retry'
           end
 
+          def action_button_title
+            _('Retry this job')
+          end
+
           def action_path
             retry_project_job_path(subject.project, subject)
           end
diff --git a/lib/gitlab/ci/status/build/skipped.rb b/lib/gitlab/ci/status/build/skipped.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e678d0baee824abdea387ae7864e83b73d3df77
--- /dev/null
+++ b/lib/gitlab/ci/status/build/skipped.rb
@@ -0,0 +1,21 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Skipped < Status::Extended
+          def illustration
+            {
+              image: 'illustrations/skipped-job_empty.svg',
+              size: 'svg-430',
+              title: _('This job has been skipped')
+            }
+          end
+
+          def self.matches?(build, user)
+            build.skipped?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index 46e730797e4fd77db7c2010cec69049572a95442..dea838bfa3928a772b8fbc6e55dff6dc12d30d53 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -19,6 +19,10 @@ module Gitlab
             'Stop'
           end
 
+          def action_button_title
+            _('Stop this environment')
+          end
+
           def action_path
             play_project_job_path(subject.project, subject)
           end
diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb
index d4fd83b93f8f023052d2811c5ba641338e2e8c59..9d6a2f51c11363eb05d5e32507459db219f19e20 100644
--- a/lib/gitlab/ci/status/core.rb
+++ b/lib/gitlab/ci/status/core.rb
@@ -22,6 +22,10 @@ module Gitlab
           raise NotImplementedError
         end
 
+        def illustration
+          raise NotImplementedError
+        end
+
         def label
           raise NotImplementedError
         end
@@ -57,6 +61,20 @@ module Gitlab
         def action_title
           raise NotImplementedError
         end
+
+        def action_button_title
+          raise NotImplementedError
+        end
+
+        # Hint that appears on all the pipeline graph tooltips and builds on the right sidebar in Job detail view
+        def status_tooltip
+          label
+        end
+
+        # Hint that appears on the build badges
+        def badge_tooltip
+          subject.status
+        end
       end
     end
   end
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index f2e5124c8a81c60a668a466af9034822de26fd24..47b67930c6d188b743dedf9495a30e7436524ebf 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -1,6 +1,8 @@
 module Gitlab
   module Ci
     class Trace
+      ArchiveError = Class.new(StandardError)
+
       attr_reader :job
 
       delegate :old_trace, to: :job
@@ -43,7 +45,7 @@ module Gitlab
       def append(data, offset)
         write do |stream|
           current_length = stream.size
-          return -current_length unless current_length == offset
+          break -current_length unless current_length == offset
 
           data = job.hide_secrets(data)
           stream.append(data, offset)
@@ -93,8 +95,53 @@ module Gitlab
         job.erase_old_trace!
       end
 
+      def archive!
+        raise ArchiveError, 'Already archived' if trace_artifact
+        raise ArchiveError, 'Job is not finished yet' unless job.complete?
+
+        if current_path
+          File.open(current_path) do |stream|
+            archive_stream!(stream)
+            FileUtils.rm(current_path)
+          end
+        elsif old_trace
+          StringIO.new(old_trace, 'rb').tap do |stream|
+            archive_stream!(stream)
+            job.erase_old_trace!
+          end
+        end
+      end
+
       private
 
+      def archive_stream!(stream)
+        clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path|
+          create_job_trace!(job, clone_path)
+        end
+      end
+
+      def clone_file!(src_stream, temp_dir)
+        FileUtils.mkdir_p(temp_dir)
+        Dir.mktmpdir('tmp-trace', temp_dir) do |dir_path|
+          temp_path = File.join(dir_path, "job.log")
+          FileUtils.touch(temp_path)
+          size = IO.copy_stream(src_stream, temp_path)
+          raise ArchiveError, 'Failed to copy stream' unless size == src_stream.size
+
+          yield(temp_path)
+        end
+      end
+
+      def create_job_trace!(job, path)
+        File.open(path) do |stream|
+          job.create_job_artifacts_trace!(
+            project: job.project,
+            file_type: :trace,
+            file: stream,
+            file_sha256: Digest::SHA256.file(path).hexdigest)
+        end
+      end
+
       def ensure_path
         return current_path if current_path
 
diff --git a/lib/gitlab/ci/trace/http_io.rb b/lib/gitlab/ci/trace/http_io.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cff924e27ef3042ead1aa1b2f180d782adc35403
--- /dev/null
+++ b/lib/gitlab/ci/trace/http_io.rb
@@ -0,0 +1,197 @@
+##
+# This class is compatible with IO class (https://ruby-doc.org/core-2.3.1/IO.html)
+# source: https://gitlab.com/snippets/1685610
+module Gitlab
+  module Ci
+    class Trace
+      class HttpIO
+        BUFFER_SIZE = 128.kilobytes
+
+        InvalidURLError = Class.new(StandardError)
+        FailedToGetChunkError = Class.new(StandardError)
+
+        attr_reader :uri, :size
+        attr_reader :tell
+        attr_reader :chunk, :chunk_range
+
+        alias_method :pos, :tell
+
+        def initialize(url, size)
+          raise InvalidURLError unless ::Gitlab::UrlSanitizer.valid?(url)
+
+          @uri = URI(url)
+          @size = size
+          @tell = 0
+        end
+
+        def close
+          # no-op
+        end
+
+        def binmode
+          # no-op
+        end
+
+        def binmode?
+          true
+        end
+
+        def path
+          nil
+        end
+
+        def url
+          @uri.to_s
+        end
+
+        def seek(pos, where = IO::SEEK_SET)
+          new_pos =
+            case where
+            when IO::SEEK_END
+              size + pos
+            when IO::SEEK_SET
+              pos
+            when IO::SEEK_CUR
+              tell + pos
+            else
+              -1
+            end
+
+          raise 'new position is outside of file' if new_pos < 0 || new_pos > size
+
+          @tell = new_pos
+        end
+
+        def eof?
+          tell == size
+        end
+
+        def each_line
+          until eof?
+            line = readline
+            break if line.nil?
+
+            yield(line)
+          end
+        end
+
+        def read(length = nil, outbuf = "")
+          out = ""
+
+          length ||= size - tell
+
+          until length <= 0 || eof?
+            data = get_chunk
+            break if data.empty?
+
+            chunk_bytes = [BUFFER_SIZE - chunk_offset, length].min
+            chunk_data = data.byteslice(0, chunk_bytes)
+
+            out << chunk_data
+            @tell += chunk_data.bytesize
+            length -= chunk_data.bytesize
+          end
+
+          # If outbuf is passed, we put the output into the buffer. This supports IO.copy_stream functionality
+          if outbuf
+            outbuf.slice!(0, outbuf.bytesize)
+            outbuf << out
+          end
+
+          out
+        end
+
+        def readline
+          out = ""
+
+          until eof?
+            data = get_chunk
+            new_line = data.index("\n")
+
+            if !new_line.nil?
+              out << data[0..new_line]
+              @tell += new_line + 1
+              break
+            else
+              out << data
+              @tell += data.bytesize
+            end
+          end
+
+          out
+        end
+
+        def write(data)
+          raise NotImplementedError
+        end
+
+        def truncate(offset)
+          raise NotImplementedError
+        end
+
+        def flush
+          raise NotImplementedError
+        end
+
+        def present?
+          true
+        end
+
+        private
+
+        ##
+        # The below methods are not implemented in IO class
+        #
+        def in_range?
+          @chunk_range&.include?(tell)
+        end
+
+        def get_chunk
+          unless in_range?
+            response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
+              http.request(request)
+            end
+
+            raise FailedToGetChunkError unless response.code == '200' || response.code == '206'
+
+            @chunk = response.body.force_encoding(Encoding::BINARY)
+            @chunk_range = response.content_range
+
+            ##
+            # Note: If provider does not return content_range, then we set it as we requested
+            # Provider: minio
+            # - When the file size is larger than requested Content-range, the Content-range is included in responces with Net::HTTPPartialContent 206
+            # - When the file size is smaller than requested Content-range, the Content-range is included in responces with Net::HTTPPartialContent 206
+            # Provider: AWS
+            # - When the file size is larger than requested Content-range, the Content-range is included in responces with Net::HTTPPartialContent 206
+            # - When the file size is smaller than requested Content-range, the Content-range is included in responces with Net::HTTPPartialContent 206
+            # Provider: GCS
+            # - When the file size is larger than requested Content-range, the Content-range is included in responces with Net::HTTPPartialContent 206
+            # - When the file size is smaller than requested Content-range, the Content-range is included in responces with Net::HTTPOK 200
+            @chunk_range ||= (chunk_start...(chunk_start + @chunk.bytesize))
+          end
+
+          @chunk[chunk_offset..BUFFER_SIZE]
+        end
+
+        def request
+          Net::HTTP::Get.new(uri).tap do |request|
+            request.set_range(chunk_start, BUFFER_SIZE)
+          end
+        end
+
+        def chunk_offset
+          tell % BUFFER_SIZE
+        end
+
+        def chunk_start
+          (tell / BUFFER_SIZE) * BUFFER_SIZE
+        end
+
+        def chunk_end
+          [chunk_start + BUFFER_SIZE, size].min
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index d52194f688b9e444b8bdd3ce6a6452ea800f8851..187ad8b833a4d8372b02f0e95a6b56cabec68746 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -8,9 +8,11 @@ module Gitlab
 
         attr_reader :stream
 
-        delegate :close, :tell, :seek, :size, :path, :truncate, to: :stream, allow_nil: true
+        delegate :close, :tell, :seek, :size, :url, :truncate, to: :stream, allow_nil: true
 
-        delegate :valid?, to: :stream, as: :present?, allow_nil: true
+        delegate :valid?, to: :stream, allow_nil: true
+
+        alias_method :present?, :valid?
 
         def initialize
           @stream = yield
@@ -25,6 +27,10 @@ module Gitlab
           self.path.present?
         end
 
+        def path
+          self.stream.path if self.stream.respond_to?(:path)
+        end
+
         def limit(last_bytes = LIMIT_SIZE)
           if last_bytes < size
             stream.seek(-last_bytes, IO::SEEK_END)
@@ -81,7 +87,7 @@ module Gitlab
 
             match = matches.flatten.last
             coverage = match.gsub(/\d+(\.\d+)?/).first
-            return coverage if coverage.present?
+            return coverage if coverage.present? # rubocop:disable Cop/AvoidReturnFromBlocks
           end
 
           nil
diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ad30b3f427c21e858d0d119aa24c735d909bb3db
--- /dev/null
+++ b/lib/gitlab/ci/variables/collection.rb
@@ -0,0 +1,44 @@
+module Gitlab
+  module Ci
+    module Variables
+      class Collection
+        include Enumerable
+
+        def initialize(variables = [])
+          @variables = []
+
+          variables.each { |variable| self.append(variable) }
+        end
+
+        def append(resource)
+          tap { @variables.append(Collection::Item.fabricate(resource)) }
+        end
+
+        def concat(resources)
+          tap { resources.each { |variable| self.append(variable) } }
+        end
+
+        def each
+          @variables.each { |variable| yield variable }
+        end
+
+        def +(other)
+          self.class.new.tap do |collection|
+            self.each { |variable| collection.append(variable) }
+            other.each { |variable| collection.append(variable) }
+          end
+        end
+
+        def to_runner_variables
+          self.map(&:to_runner_variable)
+        end
+
+        def to_hash
+          self.to_runner_variables
+            .map { |env| [env.fetch(:key), env.fetch(:value)] }
+            .to_h.with_indifferent_access
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d00e5b07f957d2b527863a9975cd56e96e98dd5c
--- /dev/null
+++ b/lib/gitlab/ci/variables/collection/item.rb
@@ -0,0 +1,47 @@
+module Gitlab
+  module Ci
+    module Variables
+      class Collection
+        class Item
+          def initialize(key:, value:, public: true, file: false)
+            @variable = {
+              key: key, value: value, public: public, file: file
+            }
+          end
+
+          def [](key)
+            @variable.fetch(key)
+          end
+
+          def ==(other)
+            to_runner_variable == self.class.fabricate(other).to_runner_variable
+          end
+
+          ##
+          # If `file: true` has been provided we expose it, otherwise we
+          # don't expose `file` attribute at all (stems from what the runner
+          # expects).
+          #
+          def to_runner_variable
+            @variable.reject do |hash_key, hash_value|
+              hash_key == :file && hash_value == false
+            end
+          end
+
+          def self.fabricate(resource)
+            case resource
+            when Hash
+              self.new(resource)
+            when ::HasVariable
+              self.new(resource.to_runner_variable)
+            when self
+              resource.dup
+            else
+              raise ArgumentError, "Unknown `#{resource.class}` variable resource!"
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index a7285ac8f9d03e1f196960f3587442da9d19a9da..e829f2a95f884ec75d7e4a76b69ec2890267cce8 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -7,8 +7,8 @@ module Gitlab
 
       attr_reader :cache, :stages, :jobs
 
-      def initialize(config)
-        @ci_config = Gitlab::Ci::Config.new(config)
+      def initialize(config, opts = {})
+        @ci_config = Gitlab::Ci::Config.new(config, opts)
         @config = @ci_config.to_hash
 
         unless @ci_config.valid?
@@ -27,7 +27,7 @@ module Gitlab
       end
 
       def build_attributes(name)
-        job = @jobs[name.to_sym] || {}
+        job = @jobs.fetch(name.to_sym, {})
 
         { stage_idx: @stages.index(job[:stage]),
           stage: job[:stage],
@@ -53,37 +53,31 @@ module Gitlab
           }.compact }
       end
 
-      def pipeline_stage_builds(stage, pipeline)
-        selected_jobs = @jobs.select do |_, job|
-          next unless job[:stage] == stage
-
-          only_specs = Gitlab::Ci::Build::Policy
-            .fabricate(job.fetch(:only, {}))
-          except_specs = Gitlab::Ci::Build::Policy
-            .fabricate(job.fetch(:except, {}))
-
-          only_specs.all? { |spec| spec.satisfied_by?(pipeline) } &&
-            except_specs.none? { |spec| spec.satisfied_by?(pipeline) }
-        end
-
-        selected_jobs.map { |_, job| build_attributes(job[:name]) }
+      def stage_builds_attributes(stage)
+        @jobs.values
+          .select { |job| job[:stage] == stage }
+          .map { |job| build_attributes(job[:name]) }
       end
 
-      def stage_seeds(pipeline)
-        seeds = @stages.uniq.map do |stage|
-          builds = pipeline_stage_builds(stage, pipeline)
+      def stages_attributes
+        @stages.uniq.map do |stage|
+          seeds = stage_builds_attributes(stage).map do |attributes|
+            job = @jobs.fetch(attributes[:name].to_sym)
 
-          Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
-        end
+            attributes
+              .merge(only: job.fetch(:only, {}))
+              .merge(except: job.fetch(:except, {}))
+          end
 
-        seeds.compact
+          { name: stage, index: @stages.index(stage), builds: seeds }
+        end
       end
 
-      def self.validation_message(content)
+      def self.validation_message(content, opts = {})
         return 'Please provide content of .gitlab-ci.yml' if content.blank?
 
         begin
-          Gitlab::Ci::YamlProcessor.new(content)
+          Gitlab::Ci::YamlProcessor.new(content, opts)
           nil
         rescue ValidationError => e
           e.message
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index 0a3ae2c3760747d80c0b8a843b46e8d65603f1f9..65a65b67975325d26c7cb04419140b17ddf2fc02 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -1,14 +1,18 @@
 module Gitlab
   module Conflict
     class FileCollection
+      include Gitlab::RepositoryCacheAdapter
+
       attr_reader :merge_request, :resolver
 
       def initialize(merge_request)
         our_commit = merge_request.source_branch_head.raw
         their_commit = merge_request.target_branch_head.raw
-        target_repo = merge_request.target_project.repository.raw
+        @target_repo = merge_request.target_project.repository
         @source_repo = merge_request.source_project.repository.raw
-        @resolver = Gitlab::Git::Conflict::Resolver.new(target_repo, our_commit.id, their_commit.id)
+        @our_commit_id = our_commit.id
+        @their_commit_id = their_commit.id
+        @resolver = Gitlab::Git::Conflict::Resolver.new(@target_repo.raw, @our_commit_id, @their_commit_id)
         @merge_request = merge_request
       end
 
@@ -30,6 +34,20 @@ module Gitlab
         end
       end
 
+      def can_be_resolved_in_ui?
+        # Try to parse each conflict. If the MR's mergeable status hasn't been
+        # updated, ensure that we don't say there are conflicts to resolve
+        # when there are no conflict files.
+        files.each(&:lines)
+        files.any?
+      rescue Gitlab::Git::CommandError,
+             Gitlab::Git::Conflict::Parser::UnresolvableError,
+             Gitlab::Git::Conflict::Resolver::ConflictSideMissing,
+             Gitlab::Git::Conflict::File::UnsupportedEncoding
+        false
+      end
+      cache_method :can_be_resolved_in_ui?
+
       def file_for_path(old_path, new_path)
         files.find { |file| file.their_path == old_path && file.our_path == new_path }
       end
@@ -56,6 +74,19 @@ Merge branch '#{merge_request.target_branch}' into '#{merge_request.source_branc
 #{conflict_filenames.join("\n")}
 EOM
       end
+
+      private
+
+      def cache
+        @cache ||= begin
+          # Use the commit ids as a namespace so if the MR branches get
+          # updated we instantiate the cache under a different namespace. That
+          # way don't have to worry about explicitly invalidating the cache
+          namespace = "#{@our_commit_id}:#{@their_commit_id}"
+
+          Gitlab::RepositoryCache.new(@target_repo, extra_namespace: namespace)
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 9576d5a3fd81bfc3421ee8bc169a4b0359855eed..d7369060cc54f58bee5afbbd61005dfc1426ddb7 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -23,7 +23,7 @@ module Gitlab
       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")
+        .having(action: [Event::COMMENTED])
 
       union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
       events = Event.find_by_sql(union.to_sql).map(&:attributes)
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index b7c596a973d73c399ea8ee2fd854b3c91e1f9203..e392a015b91b525c584b383529e4244c877daca4 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -70,7 +70,7 @@ module Gitlab
         active_db_connection = ActiveRecord::Base.connection.active? rescue false
 
         active_db_connection &&
-          ActiveRecord::Base.connection.table_exists?('application_settings')
+          Gitlab::Database.cached_table_exists?('application_settings')
       rescue ActiveRecord::NoDatabaseError
         false
       end
diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb
index 633de9f9776976743ff9fad2160a4fe2bf6a8e3a..bd14c7eece3fee3fc40255ea40ab4e6039a57548 100644
--- a/lib/gitlab/daemon.rb
+++ b/lib/gitlab/daemon.rb
@@ -30,7 +30,7 @@ module Gitlab
       return unless enabled?
 
       @mutex.synchronize do
-        return thread if thread?
+        break thread if thread?
 
         @thread = Thread.new { start_working }
       end
@@ -38,7 +38,7 @@ module Gitlab
 
     def stop
       @mutex.synchronize do
-        return unless thread?
+        break unless thread?
 
         stop_working
 
diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb
index 8e74e18a3115452a5a52985af70d63736d424d49..2f1445a050af093b8aa445785b69e29289be2470 100644
--- a/lib/gitlab/data_builder/build.rb
+++ b/lib/gitlab/data_builder/build.rb
@@ -31,7 +31,7 @@ module Gitlab
 
           # TODO: do we still need it?
           project_id: project.id,
-          project_name: project.name_with_namespace,
+          project_name: project.full_name,
 
           user: {
             id: user.try(:id),
diff --git a/lib/gitlab/data_builder/note.rb b/lib/gitlab/data_builder/note.rb
index 50fea1232af72daa78b7226bfdff5d8bdafba414..f573368e57297d1e4ba74351857dc3c600f909ea 100644
--- a/lib/gitlab/data_builder/note.rb
+++ b/lib/gitlab/data_builder/note.rb
@@ -9,6 +9,7 @@ module Gitlab
       #
       # data = {
       #   object_kind: "note",
+      #   event_type: "confidential_note",
       #   user: {
       #     name: String,
       #     username: String,
@@ -51,8 +52,11 @@ module Gitlab
       end
 
       def build_base_data(project, user, note)
+        event_type = note.confidential? ? 'confidential_note' : 'note'
+
         base_data = {
           object_kind: "note",
+          event_type: event_type,
           user: user.hook_attrs,
           project_id: project.id,
           project: project.hook_attrs,
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index e47fb85b5ee12cd78688783b109a4a7037589dd1..1e283cc092bcd3cc3c25cf61b42e7b28cb0aa92b 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -22,6 +22,7 @@ module Gitlab
           sha: pipeline.sha,
           before_sha: pipeline.before_sha,
           status: pipeline.status,
+          detailed_status: pipeline.detailed_status(nil).label,
           stages: pipeline.stages_names,
           created_at: pipeline.created_at,
           finished_at: pipeline.finished_at,
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index e51794fef991681d761529370158c5ec97ca3947..76501dd50e8e51db02cbbc9d7de175261fcef90d 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -183,6 +183,15 @@ module Gitlab
       ActiveRecord::Base.connection
     end
 
+    def self.cached_column_exists?(table_name, column_name)
+      connection.schema_cache.columns_hash(table_name).has_key?(column_name.to_s)
+    end
+
+    def self.cached_table_exists?(table_name)
+      # Rails 5 uses data_source_exists? instead of table_exists?
+      connection.schema_cache.table_exists?(table_name)
+    end
+
     private_class_method :connection
 
     def self.database_version
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index dbe6259fce722b9361372096cfc1dda99e61c57e..77079e5e72bdbb89029b6b52914adbd72c759cd8 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -59,6 +59,11 @@ module Gitlab
           disable_statement_timeout
         end
 
+        if index_exists?(table_name, column_name, options)
+          Rails.logger.warn "Index not created because it already exists (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}"
+          return
+        end
+
         add_index(table_name, column_name, options)
       end
 
@@ -83,6 +88,11 @@ module Gitlab
           disable_statement_timeout
         end
 
+        unless index_exists?(table_name, column_name, options)
+          Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}"
+          return
+        end
+
         remove_index(table_name, options.merge({ column: column_name }))
       end
 
@@ -107,6 +117,11 @@ module Gitlab
           disable_statement_timeout
         end
 
+        unless index_exists_by_name?(table_name, index_name)
+          Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, index_name: #{index_name}"
+          return
+        end
+
         remove_index(table_name, options.merge({ name: index_name }))
       end
 
@@ -140,6 +155,13 @@ module Gitlab
         # of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
         # back to the normal foreign key procedure.
         if Database.mysql?
+          if foreign_key_exists?(source, target, column: column)
+            Rails.logger.warn "Foreign key not created because it exists already " \
+              "(this may be due to an aborted migration or similar): " \
+              "source: #{source}, target: #{target}, column: #{column}"
+            return
+          end
+
           return add_foreign_key(source, target,
                                  column: column,
                                  on_delete: on_delete)
@@ -151,25 +173,43 @@ module Gitlab
 
         key_name = concurrent_foreign_key_name(source, column)
 
-        # Using NOT VALID allows us to create a key without immediately
-        # validating it. This means we keep the ALTER TABLE lock only for a
-        # short period of time. The key _is_ enforced for any newly created
-        # data.
-        execute <<-EOF.strip_heredoc
-        ALTER TABLE #{source}
-        ADD CONSTRAINT #{key_name}
-        FOREIGN KEY (#{column})
-        REFERENCES #{target} (id)
-        #{on_delete ? "ON DELETE #{on_delete.upcase}" : ''}
-        NOT VALID;
-        EOF
+        unless foreign_key_exists?(source, target, column: column)
+          Rails.logger.warn "Foreign key not created because it exists already " \
+            "(this may be due to an aborted migration or similar): " \
+            "source: #{source}, target: #{target}, column: #{column}"
+
+          # Using NOT VALID allows us to create a key without immediately
+          # validating it. This means we keep the ALTER TABLE lock only for a
+          # short period of time. The key _is_ enforced for any newly created
+          # data.
+          execute <<-EOF.strip_heredoc
+          ALTER TABLE #{source}
+          ADD CONSTRAINT #{key_name}
+          FOREIGN KEY (#{column})
+          REFERENCES #{target} (id)
+          #{on_delete ? "ON DELETE #{on_delete.upcase}" : ''}
+          NOT VALID;
+          EOF
+        end
 
         # Validate the existing constraint. This can potentially take a very
         # long time to complete, but fortunately does not lock the source table
         # while running.
+        #
+        # Note this is a no-op in case the constraint is VALID already
         execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
       end
 
+      def foreign_key_exists?(source, target = nil, column: nil)
+        foreign_keys(source).any? do |key|
+          if column
+            key.options[:column].to_s == column.to_s
+          else
+            key.to_table.to_s == target.to_s
+          end
+        end
+      end
+
       # Returns the name for a concurrent foreign key.
       #
       # PostgreSQL constraint names have a limit of 63 bytes. The logic used
@@ -820,7 +860,7 @@ into similar problems in the future (e.g. when new tables are created).
       # Each job is scheduled with a `delay_interval` in between.
       # If you use a small interval, then some jobs may run at the same time.
       #
-      # model_class - The table being iterated over
+      # model_class - The table or relation being iterated over
       # job_class_name - The background migration job class as a string
       # delay_interval - The duration between each job's scheduled time (must respond to `to_f`)
       # batch_size - The maximum number of rows per job
@@ -859,6 +899,44 @@ into similar problems in the future (e.g. when new tables are created).
           BackgroundMigrationWorker.perform_in(delay_interval * index, job_class_name, [start_id, end_id])
         end
       end
+
+      # Fetches indexes on a column by name for postgres.
+      #
+      # This will include indexes using an expression on the column, for example:
+      # `CREATE INDEX CONCURRENTLY index_name ON table (LOWER(column));`
+      #
+      # For mysql, it falls back to the default ActiveRecord implementation that
+      # will not find custom indexes. But it will select by name without passing
+      # a column.
+      #
+      # We can remove this when upgrading to Rails 5 with an updated `index_exists?`:
+      # - https://github.com/rails/rails/commit/edc2b7718725016e988089b5fb6d6fb9d6e16882
+      #
+      # Or this can be removed when we no longer support postgres < 9.5, so we
+      # can use `CREATE INDEX IF NOT EXISTS`.
+      def index_exists_by_name?(table, index)
+        # We can't fall back to the normal `index_exists?` method because that
+        # does not find indexes without passing a column name.
+        if indexes(table).map(&:name).include?(index.to_s)
+          true
+        elsif Gitlab::Database.postgresql?
+          postgres_exists_by_name?(table, index)
+        else
+          false
+        end
+      end
+
+      def postgres_exists_by_name?(table, name)
+        index_sql = <<~SQL
+          SELECT COUNT(*)
+          FROM pg_index
+          JOIN pg_class i ON (indexrelid=i.oid)
+          JOIN pg_class t ON (indrelid=t.oid)
+          WHERE i.relname = '#{name}' AND t.relname = '#{table}'
+        SQL
+
+        connection.select_value(index_sql).to_i > 0
+      end
     end
   end
 end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
index fd4a8832ec2726c773734c49f0e68c6c1840a868..62d4d0a92a616ecefe44c33ab5d30c939a3189b8 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
@@ -74,7 +74,7 @@ module Gitlab
             }.freeze
 
             def repository_storage_path
-              Gitlab.config.repositories.storages[repository_storage]['path']
+              Gitlab.config.repositories.storages[repository_storage].legacy_disk_path
             end
 
             # Overridden to have the correct `source_type` for the `route` relation
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
index d9400e04b83997e0e3ecd9fa289848ba47375942..b2d8ee81977bc8c8d051f199c7367feae5aa0303 100644
--- a/lib/gitlab/database/sha_attribute.rb
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -1,12 +1,20 @@
 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
+    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
+        # In Rails 5.0 `Type` has been moved from `ActiveRecord` to `ActiveModel`
+        # https://github.com/rails/rails/commit/9cc8c6f3730df3d94c81a55be9ee1b7b4ffd29f6#diff-f8ba7983a51d687976e115adcd95822b
+        # Remove this method and leave just `ActiveModel::Type::Binary` when removing Gitlab.rails5? code.
+        if Gitlab.rails5?
+          ActiveModel::Type::Binary
+        else
+          ActiveRecord::Type::Binary
+        end
+      end
 
     # Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa).
     #
@@ -16,18 +24,39 @@ module Gitlab
     class ShaAttribute < BINARY_TYPE
       PACK_FORMAT = 'H*'.freeze
 
-      # Casts binary data to a SHA1 in hexadecimal.
+      # It is called from activerecord-4.2.10/lib/active_record internal methods.
+      # Remove this method when removing Gitlab.rails5? code.
       def type_cast_from_database(value)
-        value = super
+        unpack_sha(super)
+      end
+
+      # It is called from activerecord-4.2.10/lib/active_record internal methods.
+      # Remove this method when removing Gitlab.rails5? code.
+      def type_cast_for_database(value)
+        serialize(value)
+      end
 
+      # It is called from activerecord-5.0.6/lib/active_record/attribute.rb
+      # Remove this method when removing Gitlab.rails5? code..
+      def deserialize(value)
+        value = Gitlab.rails5? ? super : method(:type_cast_from_database).super_method.call(value)
+
+        unpack_sha(value)
+      end
+
+      # Rename this method to `deserialize(value)` removing Gitlab.rails5? code.
+      # Casts binary data to a SHA1 in hexadecimal.
+      def unpack_sha(value)
+        # Uncomment this line when removing Gitlab.rails5? code.
+        # 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)
+      def serialize(value)
         arg = value ? [value].pack(PACK_FORMAT) : nil
 
-        super(arg)
+        Gitlab.rails5? ? super(arg) : method(:type_cast_for_database).super_method.call(arg)
       end
     end
   end
diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
index 88e0db830f6f2a5842e0ed9417317b8d0405d042..81df47964be7351e56ea5fa04debd3bbb1bf2a02 100644
--- a/lib/gitlab/diff/diff_refs.rb
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -44,7 +44,11 @@ module Gitlab
           project.commit(head_sha)
         else
           straight = start_sha == base_sha
-          CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
+
+          CompareService.new(project, head_sha).execute(project,
+                                                        start_sha,
+                                                        base_sha: base_sha,
+                                                        straight: straight)
         end
       end
     end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 34b070dd375de0b7dec0d759b0c88527560d5587..014854da55c136259bd3689b64c8580683fbc175 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -27,8 +27,8 @@ module Gitlab
         @fallback_diff_refs = fallback_diff_refs
 
         # Ensure items are collected in the the batch
-        new_blob
-        old_blob
+        new_blob_lazy
+        old_blob_lazy
       end
 
       def position(position_marker, position_type: :text)
@@ -101,25 +101,19 @@ module Gitlab
       end
 
       def new_blob
-        return unless new_content_sha
-
-        Blob.lazy(repository.project, new_content_sha, file_path)
+        new_blob_lazy&.itself
       end
 
       def old_blob
-        return unless old_content_sha
-
-        Blob.lazy(repository.project, old_content_sha, old_path)
+        old_blob_lazy&.itself
       end
 
       def content_sha
         new_content_sha || old_content_sha
       end
 
-      # Use #itself to check the value wrapped by a BatchLoader instance, rather
-      # than if the BatchLoader instance itself is falsey.
       def blob
-        new_blob&.itself || old_blob&.itself
+        new_blob || old_blob
       end
 
       attr_writer :highlighted_diff_lines
@@ -237,17 +231,14 @@ module Gitlab
 
       private
 
-      # The blob instances are instances of BatchLoader, which means calling
-      # &. directly on them won't work. Object#try also won't work, because Blob
-      # doesn't inherit from Object, but from BasicObject (via SimpleDelegator).
+      # We can't use Object#try because Blob doesn't inherit from Object, but
+      # from BasicObject (via SimpleDelegator).
       def try_blobs(meth)
-        old_blob&.itself&.public_send(meth) || new_blob&.itself&.public_send(meth)
+        old_blob&.public_send(meth) || new_blob&.public_send(meth)
       end
 
-      # We can't use #compact for the same reason we can't use &., but calling
-      # #nil? explicitly does work because it is proxied to the blob itself.
       def valid_blobs
-        [old_blob, new_blob].reject(&:nil?)
+        [old_blob, new_blob].compact
       end
 
       def text_position_properties(line)
@@ -262,6 +253,18 @@ module Gitlab
         old_blob && new_blob && old_blob.id != new_blob.id
       end
 
+      def new_blob_lazy
+        return unless new_content_sha
+
+        Blob.lazy(repository.project, new_content_sha, file_path)
+      end
+
+      def old_blob_lazy
+        return unless old_content_sha
+
+        Blob.lazy(repository.project, old_content_sha, old_path)
+      end
+
       def simple_viewer_class
         return DiffViewer::NotDiffable unless diffable?
 
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb
index fcda1fe2233c0a5f9782fe78f3b0d493e3d2b2e6..c358ae428cf9542fcbe66d70619dd1f16749900a 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb
@@ -13,22 +13,32 @@ module Gitlab
         end
 
         def diff_files
-          super.tap { |_| store_highlight_cache }
+          # Make sure to _not_ send any method call to Gitlab::Diff::File
+          # _before_ all of them were collected (`super`). Premature method calls will
+          # trigger N+1 RPCs to Gitaly through BatchLoader records (Blob.lazy).
+          #
+          diff_files = super
+
+          diff_files.each { |diff_file| cache_highlight!(diff_file) if cacheable?(diff_file) }
+          store_highlight_cache
+
+          diff_files
         end
 
         def real_size
           @merge_request_diff.real_size
         end
 
-        private
+        def clear_cache!
+          Rails.cache.delete(cache_key)
+        end
 
-        # Extracted method to highlight in the same iteration to the diff_collection.
-        def decorate_diff!(diff)
-          diff_file = super
-          cache_highlight!(diff_file) if cacheable?(diff_file)
-          diff_file
+        def cache_key
+          [@merge_request_diff, 'highlighted-diff-files', diff_options]
         end
 
+        private
+
         def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
           diff_file.highlighted_diff_lines = cache_diff_lines.map do |line|
             Gitlab::Diff::Line.init_from_hash(line)
@@ -62,16 +72,12 @@ module Gitlab
         end
 
         def store_highlight_cache
-          Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty
+          Rails.cache.write(cache_key, highlight_cache, expires_in: 1.week) if @highlight_cache_was_empty
         end
 
         def cacheable?(diff_file)
           @merge_request_diff.present? && diff_file.text? && diff_file.diffable?
         end
-
-        def cache_key
-          [@merge_request_diff, 'highlighted-diff-files', diff_options]
-        end
       end
     end
   end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 269016daac290f2b4b17933ddd53e4b4211626f5..5c1baa19b66ff074fdecf335d4aeb70f4dd5485a 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -33,10 +33,7 @@ module Gitlab
             # match the blob, which is a bug. But we shouldn't fail to render
             # completely in that case, even though we want to report the error.
             rescue RangeError => e
-              if Gitlab::Sentry.enabled?
-                Gitlab::Sentry.context
-                Raven.capture_exception(e)
-              end
+              Gitlab::Sentry.track_exception(e, issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/45441')
             end
           end
 
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index 010b4be7b4031c39d20d8c3370c81843feba3521..81e91ea0ab7c2d8f1544f73218c79a948aafd356 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -1,11 +1,14 @@
 module Gitlab
   module Diff
     class InlineDiffMarker < Gitlab::StringRangeMarker
+      def initialize(line, rich_line = nil)
+        super(line, rich_line || line)
+      end
+
       def mark(line_inline_diffs, mode: nil)
-        mark = super(line_inline_diffs) do |text, left:, right:|
+        super(line_inline_diffs) do |text, left:, right:|
           %{<span class="#{html_class_names(left, right, mode)}">#{text}</span>}
         end
-        mark.html_safe
       end
 
       private
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 0fb71976883c7030012a215de279e6c2c09f7e95..8cf59fa8e284b54d8c21886fa177448325e2f711 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -2,66 +2,90 @@
 module Gitlab
   # Checks if a set of migrations requires downtime or not.
   class EeCompatCheck
-    DEFAULT_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
-    EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
+    CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
+    CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
     CHECK_DIR = Rails.root.join('ee_compat_check')
-    IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb}i.freeze
+    IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb|locale/gitlab\.pot}i.freeze
     PLEASE_READ_THIS_BANNER = %Q{
       ============================================================
       ===================== PLEASE READ THIS =====================
       ============================================================
     }.freeze
+    STAY_STRONG_LINK_TO_DOCS = %Q{
+      Stay 馃挭! For more information, see
+      https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
+    }.freeze
     THANKS_FOR_READING_BANNER = %Q{
       ============================================================
       ==================== THANKS FOR READING ====================
       ============================================================\n
     }.freeze
 
-    attr_reader :ee_repo_dir, :patches_dir, :ce_project_url, :ce_repo_url, :ce_branch, :ee_branch_found
+    attr_reader :ee_repo_dir, :patches_dir
+    attr_reader :ce_project_url, :ee_repo_url
+    attr_reader :ce_branch, :ee_remote_with_branch, :ee_branch_found
     attr_reader :job_id, :failed_files
 
-    def initialize(branch:, ce_project_url: DEFAULT_CE_PROJECT_URL, job_id: nil)
+    def initialize(branch:, ce_project_url: CANONICAL_CE_PROJECT_URL, job_id: nil)
       @ee_repo_dir = CHECK_DIR.join('ee-repo')
       @patches_dir = CHECK_DIR.join('patches')
       @ce_branch = branch
       @ce_project_url = ce_project_url
-      @ce_repo_url = "#{ce_project_url}.git"
+      @ee_repo_url = ce_public_repo_url.sub('gitlab-ce', 'gitlab-ee')
       @job_id = job_id
     end
 
     def check
       ensure_patches_dir
-      add_remote('canonical-ce', "#{DEFAULT_CE_PROJECT_URL}.git")
-      generate_patch(branch: ce_branch, patch_path: ce_patch_full_path, remote: 'canonical-ce')
+      # We're generating the patch against the canonical-ce remote since forks'
+      # master branch are not necessarily up-to-date.
+      add_remote('canonical-ce', "#{CANONICAL_CE_PROJECT_URL}.git")
+      generate_patch(branch: ce_branch, patch_path: ce_patch_full_path, branch_remote: 'origin', master_remote: 'canonical-ce')
 
       ensure_ee_repo
       Dir.chdir(ee_repo_dir) do
         step("In the #{ee_repo_dir} directory")
 
-        add_remote('canonical-ee', EE_REPO_URL)
+        ee_remotes.each do |key, url|
+          add_remote(key, url)
+        end
+        fetch(branch: 'master', depth: 20, remote: 'canonical-ee')
 
         status = catch(:halt_check) do
           ce_branch_compat_check!
           delete_ee_branches_locally!
           ee_branch_presence_check!
 
-          step("Checking out #{ee_branch_found}", %W[git checkout -b #{ee_branch_found} canonical-ee/#{ee_branch_found}])
-          generate_patch(branch: ee_branch_found, patch_path: ee_patch_full_path, remote: 'canonical-ee')
+          step("Checking out #{ee_remote_with_branch}/#{ee_branch_found}", %W[git checkout -b #{ee_branch_found} #{ee_remote_with_branch}/#{ee_branch_found}])
+          generate_patch(branch: ee_branch_found, patch_path: ee_patch_full_path, branch_remote: ee_remote_with_branch, master_remote: 'canonical-ee')
           ee_branch_compat_check!
         end
 
         delete_ee_branches_locally!
 
-        if status.nil?
-          true
-        else
-          false
-        end
+        status.nil?
       end
     end
 
     private
 
+    def fork?
+      ce_project_url != CANONICAL_CE_PROJECT_URL
+    end
+
+    def ee_remotes
+      return @ee_remotes if defined?(@ee_remotes)
+
+      remotes =
+        {
+          'ee' => ee_repo_url,
+          'canonical-ee' => CANONICAL_EE_REPO_URL
+        }
+      remotes.delete('ee') unless fork?
+
+      @ee_remotes = remotes
+    end
+
     def add_remote(name, url)
       step(
         "Adding the #{name} remote (#{url})",
@@ -70,28 +94,32 @@ module Gitlab
     end
 
     def ensure_ee_repo
-      if Dir.exist?(ee_repo_dir)
-        step("#{ee_repo_dir} already exists")
-      else
-        step(
-          "Cloning #{EE_REPO_URL} into #{ee_repo_dir}",
-          %W[git clone --branch master --single-branch --depth=200 #{EE_REPO_URL} #{ee_repo_dir}]
-        )
+      unless clone_repo(ee_repo_url, ee_repo_dir)
+        # Fallback to using the canonical EE if there is no forked EE
+        clone_repo(CANONICAL_EE_REPO_URL, ee_repo_dir)
       end
     end
 
+    def clone_repo(url, dir)
+      _, status = step(
+        "Cloning #{url} into #{dir}",
+        %W[git clone --branch master --single-branch --depth=200 #{url} #{dir}]
+      )
+      status.zero?
+    end
+
     def ensure_patches_dir
       FileUtils.mkdir_p(patches_dir)
     end
 
-    def generate_patch(branch:, patch_path:, remote:)
+    def generate_patch(branch:, patch_path:, branch_remote:, master_remote:)
       FileUtils.rm(patch_path, force: true)
 
-      find_merge_base_with_master(branch: branch, master_remote: remote)
+      find_merge_base_with_master(branch: branch, branch_remote: branch_remote, master_remote: master_remote)
 
       step(
-        "Generating the patch against #{remote}/master in #{patch_path}",
-        %W[git diff --binary #{remote}/master...origin/#{branch}]
+        "Generating the patch against #{master_remote}/master in #{patch_path}",
+        %W[git diff --binary #{master_remote}/master...#{branch_remote}/#{branch}]
       ) do |output, status|
         throw(:halt_check, :ko) unless status.zero?
 
@@ -109,23 +137,22 @@ module Gitlab
     end
 
     def ee_branch_presence_check!
-      _, status = step("Fetching origin/#{ee_branch_prefix}", %W[git fetch canonical-ee #{ee_branch_prefix}])
-
-      if status.zero?
-        @ee_branch_found = ee_branch_prefix
-        return
+      ee_remotes.keys.each do |remote|
+        [ee_branch_prefix, ee_branch_suffix].each do |branch|
+          _, status = step("Fetching #{remote}/#{ee_branch_prefix}", %W[git fetch #{remote} #{branch}])
+
+          if status.zero?
+            @ee_remote_with_branch = remote
+            @ee_branch_found = branch
+            return true
+          end
+        end
       end
 
-      _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch canonical-ee #{ee_branch_suffix}])
-
-      if status.zero?
-        @ee_branch_found = ee_branch_suffix
-      else
-        puts
-        puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
+      puts
+      puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
 
-        throw(:halt_check, :ko)
-      end
+      throw(:halt_check, :ko)
     end
 
     def ee_branch_compat_check!
@@ -181,10 +208,10 @@ module Gitlab
       command(%W[git branch --delete --force #{ee_branch_suffix}])
     end
 
-    def merge_base_found?(master_remote:, branch:)
+    def merge_base_found?(branch:, branch_remote:, master_remote:)
       step(
         "Finding merge base with #{master_remote}/master",
-        %W[git merge-base #{master_remote}/master origin/#{branch}]
+        %W[git merge-base #{master_remote}/master #{branch_remote}/#{branch}]
       ) do |output, status|
         if status.zero?
           puts "Merge base was found: #{output}"
@@ -193,7 +220,7 @@ module Gitlab
       end
     end
 
-    def find_merge_base_with_master(branch:, master_remote:)
+    def find_merge_base_with_master(branch:, branch_remote:, master_remote:)
       # Start with (Math.exp(3).to_i = 20) until (Math.exp(6).to_i = 403)
       # In total we go (20 + 54 + 148 + 403 = 625) commits deeper
       depth = 20
@@ -202,10 +229,10 @@ module Gitlab
           depth += Math.exp(factor).to_i
           # Repository is initially cloned with a depth of 20 so we need to fetch
           # deeper in the case the branch has more than 20 commits on top of master
-          fetch(branch: branch, depth: depth, remote: 'origin')
+          fetch(branch: branch, depth: depth, remote: branch_remote)
           fetch(branch: 'master', depth: depth, remote: master_remote)
 
-          merge_base_found?(master_remote: master_remote, branch: branch)
+          merge_base_found?(branch: branch, branch_remote: branch_remote, master_remote: master_remote)
         end
 
       raise "\n#{branch} is too far behind #{master_remote}/master, please rebase it!\n" unless success
@@ -274,6 +301,13 @@ module Gitlab
       Gitlab::Popen.popen(cmd)
     end
 
+    # We're "re-creating" the repo URL because ENV['CI_REPOSITORY_URL'] contains
+    # redacted credentials (e.g. "***:****") which are useless in instructions
+    # the job gives.
+    def ce_public_repo_url
+      "#{ce_project_url}.git"
+    end
+
     def applies_cleanly_msg(branch)
       %Q{
         #{PLEASE_READ_THIS_BANNER}
@@ -288,13 +322,15 @@ module Gitlab
     end
 
     def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
+      ee_repos = ee_remotes.values.uniq
+
       %Q{
         #{PLEASE_READ_THIS_BANNER}
         馃挜 Oh no! 馃挜
 
         The `#{ce_branch}` branch does not apply cleanly to the current
         EE/master, and no `#{ee_branch_prefix}` or `#{ee_branch_suffix}` branch
-        was found in the EE repository.
+        was found in #{ee_repos.join(' nor in ')}.
 
         If you're a community contributor, don't worry, someone from
         GitLab Inc. will take care of this, and you don't have to do anything.
@@ -314,17 +350,17 @@ module Gitlab
         1. Create a new branch from master and cherry-pick your CE commits
 
           # In the EE repo
-          $ git fetch #{EE_REPO_URL} master
+          $ git fetch #{CANONICAL_EE_REPO_URL} master
           $ git checkout -b #{ee_branch_prefix} FETCH_HEAD
-          $ git fetch #{ce_repo_url} #{ce_branch}
+          $ git fetch #{ce_public_repo_url} #{ce_branch}
           $ git cherry-pick SHA # Repeat for all the commits you want to pick
 
-          You can squash the `#{ce_branch}` commits into a single "Port of #{ce_branch} to EE" commit.
+          Note: You can squash the `#{ce_branch}` commits into a single "Port of #{ce_branch} to EE" commit.
 
         2. Apply your branch's patch to EE
 
           # In the EE repo
-          $ git fetch #{EE_REPO_URL} master
+          $ git fetch #{CANONICAL_EE_REPO_URL} master
           $ git checkout -b #{ee_branch_prefix} FETCH_HEAD
           $ wget #{patch_url} && git apply --3way #{ce_patch_name}
 
@@ -356,10 +392,9 @@ module Gitlab
         鈿狅笍 Also, don't forget to create a new merge request on gitlab-ee and
         cross-link it with the CE merge request.
 
-        Once this is done, you can retry this failed build, and it should pass.
+        Once this is done, you can retry this failed job, and it should pass.
 
-        Stay 馃挭 ! For more information, see
-        https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
+        #{STAY_STRONG_LINK_TO_DOCS}
         #{THANKS_FOR_READING_BANNER}
       }
     end
@@ -371,16 +406,15 @@ module Gitlab
 
         The `#{ce_branch}` does not apply cleanly to the current EE/master, and
         even though a `#{ee_branch_found}` branch
-        exists in the EE repository, it does not apply cleanly either to
+        exists in #{ee_repo_url}, it does not apply cleanly either to
         EE/master!
 
         #{conflicting_files_msg}
 
         Please update the `#{ee_branch_found}`, push it again to gitlab-ee, and
-        retry this build.
+        retry this job.
 
-        Stay 馃挭 ! For more information, see
-        https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
+        #{STAY_STRONG_LINK_TO_DOCS}
         #{THANKS_FOR_READING_BANNER}
       }
     end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index a616a80e8f5c3f0cbc2137dc1bb37d05224a458c..05a60deb7d39042eb7cbd996ae2594565a252fdb 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -14,7 +14,7 @@ module Gitlab
         end
 
         def can_handle?
-          !incoming_email_token.nil?
+          !incoming_email_token.nil? && !incoming_email_token.include?("+") && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)
         end
 
         def execute
diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb
index 3436306e1229d09431d0efe21d08aa3335db2eb2..2f864f2082b6399a13c81950a3c523bfe6130ca6 100644
--- a/lib/gitlab/email/handler/create_merge_request_handler.rb
+++ b/lib/gitlab/email/handler/create_merge_request_handler.rb
@@ -23,7 +23,8 @@ module Gitlab
         def execute
           raise ProjectNotFound unless project
 
-          validate_permission!(:create_merge_request)
+          validate_permission!(:create_merge_request_in)
+          validate_permission!(:create_merge_request_from)
 
           verify_record!(
             record: create_merge_request,
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index 6659efa0961cd59032e87a0ed1a1adf3d50c13c7..0b8f6cfe3cb6d049071d3b12b8b40de0cdc0b89a 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -90,7 +90,7 @@ module Gitlab
     end
 
     def clean(message)
-      message.encode("UTF-16BE", undef: :replace, invalid: :replace, replace: "")
+      message.encode("UTF-16BE", undef: :replace, invalid: :replace, replace: "".encode("UTF-16BE"))
         .encode("UTF-8")
         .gsub("\0".encode("UTF-8"), "")
     end
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 1d6f5bb5e1c98fa43f55698d137f3d7c0865eee1..d5d35dbd97f69b85a8c54f16e3f90885003714a1 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -50,7 +50,7 @@ module Gitlab
 
         status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429
 
-        [status_code, { 'ETag' => etag }, []]
+        [status_code, { 'ETag' => etag, 'X-Gitlab-From-Cache' => 'true' }, []]
       end
 
       def track_cache_miss(if_none_match, cached_value_present, route)
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index dbb8f317afec136f1a33ea4e2b48eb7ee5426fa4..12b5e2409627bcb7c34ef47b9fb07d52c8ae2d51 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -41,6 +41,16 @@ module Gitlab
       "gitlab:exclusive_lease:#{key}"
     end
 
+    # Removes any existing exclusive_lease from redis
+    # Don't run this in a live system without making sure no one is using the leases
+    def self.reset_all!(scope = '*')
+      Gitlab::Redis::SharedState.with do |redis|
+        redis.scan_each(match: redis_shared_state_key(scope)).each do |key|
+          redis.del(key)
+        end
+      end
+    end
+
     def initialize(key, uuid: nil, timeout:)
       @redis_shared_state_key = self.class.redis_shared_state_key(key)
       @timeout = timeout
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
index 1b74f7356793a7cee7b6ad96512d55e9bcf671c3..b6eeb5d9a2b52d06d703194fc42ee05561f74ceb 100644
--- a/lib/gitlab/gfm/uploads_rewriter.rb
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -21,7 +21,7 @@ module Gitlab
 
         @text.gsub(@pattern) do |markdown|
           file = find_file(@source_project, $~[:secret], $~[:file])
-          return markdown unless file.try(:exists?)
+          break markdown unless file.try(:exists?)
 
           new_uploader = FileUploader.new(target_project)
           with_link_in_tmp_dir(file.file) do |open_tmp_file|
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index d4e893b881c1e919892106129fcfee3b6fc1c898..c9abea90d213f9c7211d5e222a7345d02b01f3b1 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -1,5 +1,9 @@
 module Gitlab
   module Git
+    # The ID of empty tree.
+    # See http://stackoverflow.com/a/40884093/1856239 and
+    # https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
+    EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
     BLANK_SHA = ('0' * 40).freeze
     TAG_REF_PREFIX = "refs/tags/".freeze
     BRANCH_REF_PREFIX = "refs/heads/".freeze
diff --git a/lib/gitlab/git/attributes_parser.rb b/lib/gitlab/git/attributes_parser.rb
index d8aeabb6cba0488c8a5de64c13cb48648a54953f..08f4d7d4f5c68e2d242c484e6645d3cc2c944394 100644
--- a/lib/gitlab/git/attributes_parser.rb
+++ b/lib/gitlab/git/attributes_parser.rb
@@ -3,12 +3,8 @@ module Gitlab
     # Class for parsing Git attribute files and extracting the attributes for
     # file patterns.
     class AttributesParser
-      def initialize(attributes_data)
+      def initialize(attributes_data = "")
         @data = attributes_data || ""
-
-        if @data.is_a?(File)
-          @patterns = parse_file
-        end
       end
 
       # Returns all the Git attributes for the given path.
@@ -28,7 +24,7 @@ module Gitlab
 
       # Returns a Hash containing the file patterns and their attributes.
       def patterns
-        @patterns ||= parse_file
+        @patterns ||= parse_data
       end
 
       # Parses an attribute string.
@@ -91,8 +87,8 @@ module Gitlab
 
       private
 
-      # Parses the Git attributes file.
-      def parse_file
+      # Parses the Git attributes file contents.
+      def parse_data
         pairs = []
         comment = '#'
 
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index b2fca2c16dea469813e051696ccb80f079b0070a..eabcf46cf580ed5e8e0b12d7f96fb13be5461b6e 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -238,9 +238,9 @@ module Gitlab
           self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
         end
 
-        @loaded_all_data = false
         # Retain the actual size before it is encoded
         @loaded_size = @data.bytesize if @data
+        @loaded_all_data = @loaded_size == size
       end
 
       def binary?
@@ -255,10 +255,15 @@ module Gitlab
       # memory as a Ruby string.
       def load_all_data!(repository)
         return if @data == '' # don't mess with submodule blobs
-        return @data if @loaded_all_data
 
-        Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
-          @data = begin
+        # Even if we return early, recalculate wether this blob is binary in
+        # case a blob was initialized as text but the full data isn't
+        @binary = nil
+
+        return if @loaded_all_data
+
+        @data = Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
+          begin
             if is_enabled
               repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data
             else
@@ -269,7 +274,6 @@ module Gitlab
 
         @loaded_all_data = true
         @loaded_size = @data.bytesize
-        @binary = nil
       end
 
       def name
diff --git a/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb
index ae7e88f050330f3a4244d7561bbac88defda74b5..6351cfb83e3334810b398b2f442474c1bcad0368 100644
--- a/lib/gitlab/git/branch.rb
+++ b/lib/gitlab/git/branch.rb
@@ -1,6 +1,8 @@
 module Gitlab
   module Git
     class Branch < Ref
+      STALE_BRANCH_THRESHOLD = 3.months
+
       def self.find(repo, branch_name)
         if branch_name.is_a?(Gitlab::Git::Branch)
           branch_name
@@ -12,6 +14,18 @@ module Gitlab
       def initialize(repository, name, target, target_commit)
         super(repository, name, target, target_commit)
       end
+
+      def active?
+        self.dereferenced_target.committed_date >= STALE_BRANCH_THRESHOLD.ago
+      end
+
+      def stale?
+        !active?
+      end
+
+      def state
+        active? ? :active : :stale
+      end
     end
   end
 end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 594b6a9cbc58f392231ef67beba0513c48b8badd..fabcd46c8e9eec599d3c50d629d76d609ae9dad9 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -231,7 +231,8 @@ module Gitlab
         # relation to each other. The last 10 commits for a branch for example,
         # should go through .where
         def batch_by_oid(repo, oids)
-          repo.gitaly_migrate(:list_commits_by_oid) do |is_enabled|
+          repo.gitaly_migrate(:list_commits_by_oid,
+                              status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
             if is_enabled
               repo.gitaly_commit_client.list_commits_by_oid(oids)
             else
@@ -347,7 +348,7 @@ module Gitlab
       #
       # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/324
       def to_diff
-        Gitlab::GitalyClient.migrate(:commit_patch) do |is_enabled|
+        Gitlab::GitalyClient.migrate(:commit_patch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             @repository.gitaly_commit_client.patch(id)
           else
@@ -485,6 +486,8 @@ module Gitlab
       end
 
       def tree_entry(path)
+        return unless path.present?
+
         @repository.gitaly_migrate(:commit_tree_entry) do |is_migrated|
           if is_migrated
             gitaly_tree_entry(path)
diff --git a/lib/gitlab/git/conflict/file.rb b/lib/gitlab/git/conflict/file.rb
index 2a9cf10a0680e4b9fe8f09dc80421dcba1428866..f08dab59ce46302c8f028b9b4701740271174c56 100644
--- a/lib/gitlab/git/conflict/file.rb
+++ b/lib/gitlab/git/conflict/file.rb
@@ -2,17 +2,19 @@ module Gitlab
   module Git
     module Conflict
       class File
+        UnsupportedEncoding = Class.new(StandardError)
+
         attr_reader :their_path, :our_path, :our_mode, :repository, :commit_oid
 
-        attr_accessor :content
+        attr_accessor :raw_content
 
-        def initialize(repository, commit_oid, conflict, content)
+        def initialize(repository, commit_oid, conflict, raw_content)
           @repository = repository
           @commit_oid = commit_oid
           @their_path = conflict[:theirs][:path]
           @our_path = conflict[:ours][:path]
           @our_mode = conflict[:ours][:mode]
-          @content = content
+          @raw_content = raw_content
         end
 
         def lines
@@ -29,6 +31,14 @@ module Gitlab
           end
         end
 
+        def content
+          @content ||= @raw_content.dup.force_encoding('UTF-8')
+
+          raise UnsupportedEncoding unless @content.valid_encoding?
+
+          @content
+        end
+
         def type
           lines unless @type
 
diff --git a/lib/gitlab/git/conflict/parser.rb b/lib/gitlab/git/conflict/parser.rb
index 3effa9d2d31efcb3fcfa6361bb2e1689906bd58f..fb5717dd556708269d39e89e2c921e0565a15446 100644
--- a/lib/gitlab/git/conflict/parser.rb
+++ b/lib/gitlab/git/conflict/parser.rb
@@ -4,7 +4,6 @@ module Gitlab
       class Parser
         UnresolvableError = Class.new(StandardError)
         UnmergeableFile = Class.new(UnresolvableError)
-        UnsupportedEncoding = Class.new(UnresolvableError)
 
         # Recoverable errors - the conflict can be resolved in an editor, but not with
         # sections.
@@ -75,10 +74,6 @@ module Gitlab
           def validate_text!(text)
             raise UnmergeableFile if text.blank? # Typically a binary file
             raise UnmergeableFile if text.length > 200.kilobytes
-
-            text.force_encoding('UTF-8')
-
-            raise UnsupportedEncoding unless text.valid_encoding?
           end
 
           def validate_delimiter!(condition)
diff --git a/lib/gitlab/git/conflict/resolver.rb b/lib/gitlab/git/conflict/resolver.rb
index 07b7e811a34694a1fbe8a1218b5feb8a7fbe3edc..c3cb0264112d7875c7431b0619136a2f6f305a3f 100644
--- a/lib/gitlab/git/conflict/resolver.rb
+++ b/lib/gitlab/git/conflict/resolver.rb
@@ -23,7 +23,7 @@ module Gitlab
           end
         rescue GRPC::FailedPrecondition => e
           raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message)
-        rescue Rugged::OdbError, GRPC::BadStatus => e
+        rescue Rugged::ReferenceError, Rugged::OdbError, GRPC::BadStatus => e
           raise Gitlab::Git::CommandError.new(e)
         end
 
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index a203587aec15e2f3f260ec228a360c5a0f2b5fea..b58296375efce5e69556c67a580c0dd868efa209 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -249,7 +249,7 @@ module Gitlab
 
             if size >= SIZE_LIMIT
               too_large!
-              return true
+              return true # rubocop:disable Cop/AvoidReturnFromBlocks
             end
           end
         end
diff --git a/lib/gitlab/git/env.rb b/lib/gitlab/git/env.rb
deleted file mode 100644
index 9d0b47a1a6d98fd0e1d7925d3ff8400d1333066e..0000000000000000000000000000000000000000
--- a/lib/gitlab/git/env.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# Gitaly note: JV: no RPC's here.
-
-module Gitlab
-  module Git
-    # Ephemeral (per request) storage for environment variables that some Git
-    # commands may need.
-    #
-    # For example, in pre-receive hooks, new objects are put in a temporary
-    # $GIT_OBJECT_DIRECTORY. Without it set, the new objects cannot be retrieved
-    # (this would break push rules for instance).
-    #
-    # This class is thread-safe via RequestStore.
-    class Env
-      WHITELISTED_VARIABLES = %w[
-        GIT_OBJECT_DIRECTORY
-        GIT_OBJECT_DIRECTORY_RELATIVE
-        GIT_ALTERNATE_OBJECT_DIRECTORIES
-        GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
-      ].freeze
-
-      def self.set(env)
-        return unless RequestStore.active?
-
-        RequestStore.store[:gitlab_git_env] = whitelist_git_env(env)
-      end
-
-      def self.all
-        return {} unless RequestStore.active?
-
-        RequestStore.fetch(:gitlab_git_env) { {} }
-      end
-
-      def self.to_env_hash
-        env = {}
-
-        all.compact.each do |key, value|
-          value = value.join(File::PATH_SEPARATOR) if value.is_a?(Array)
-          env[key.to_s] = value
-        end
-
-        env
-      end
-
-      def self.[](key)
-        all[key]
-      end
-
-      def self.whitelist_git_env(env)
-        env.select { |key, _| WHITELISTED_VARIABLES.include?(key.to_s) }.with_indifferent_access
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb
index e5a747cb98787fa2cde09aad7c2f32f696b1b3f7..099709620b31cc4edcc1ac4dbfb4a4f46cff40b0 100644
--- a/lib/gitlab/git/gitlab_projects.rb
+++ b/lib/gitlab/git/gitlab_projects.rb
@@ -4,20 +4,14 @@ module Gitlab
       include Gitlab::Git::Popen
       include Gitlab::Utils::StrongMemoize
 
-      ShardNameNotFoundError = Class.new(StandardError)
-
-      # Absolute path to directory where repositories are stored.
-      # Example: /home/git/repositories
-      attr_reader :shard_path
+      # Name of shard where repositories are stored.
+      # Example: nfs-file06
+      attr_reader :shard_name
 
       # Relative path is a directory name for repository with .git at the end.
       # Example: gitlab-org/gitlab-test.git
       attr_reader :repository_relative_path
 
-      # Absolute path to the repository.
-      # Example: /home/git/repositorities/gitlab-org/gitlab-test.git
-      attr_reader :repository_absolute_path
-
       # This is the path at which the gitlab-shell hooks directory can be found.
       # It's essential for integration between git and GitLab proper. All new
       # repositories should have their hooks directory symlinked here.
@@ -25,13 +19,12 @@ module Gitlab
 
       attr_reader :logger
 
-      def initialize(shard_path, repository_relative_path, global_hooks_path:, logger:)
-        @shard_path = shard_path
+      def initialize(shard_name, repository_relative_path, global_hooks_path:, logger:)
+        @shard_name = shard_name
         @repository_relative_path = repository_relative_path
 
         @logger = logger
         @global_hooks_path = global_hooks_path
-        @repository_absolute_path = File.join(shard_path, repository_relative_path)
         @output = StringIO.new
       end
 
@@ -41,6 +34,22 @@ module Gitlab
         io.read
       end
 
+      # Absolute path to the repository.
+      # Example: /home/git/repositorities/gitlab-org/gitlab-test.git
+      # Probably will be removed when we fully migrate to Gitaly, part of
+      # https://gitlab.com/gitlab-org/gitaly/issues/1124.
+      def repository_absolute_path
+        strong_memoize(:repository_absolute_path) do
+          File.join(shard_path, repository_relative_path)
+        end
+      end
+
+      def shard_path
+        strong_memoize(:shard_path) do
+          Gitlab.config.repositories.storages.fetch(shard_name).legacy_disk_path
+        end
+      end
+
       # Import project via git clone --bare
       # URL must be publicly cloneable
       def import_project(source, timeout)
@@ -53,21 +62,22 @@ module Gitlab
         end
       end
 
-      def fork_repository(new_shard_path, new_repository_relative_path)
+      def fork_repository(new_shard_name, new_repository_relative_path)
         Gitlab::GitalyClient.migrate(:fork_repository) do |is_enabled|
           if is_enabled
-            gitaly_fork_repository(new_shard_path, new_repository_relative_path)
+            gitaly_fork_repository(new_shard_name, new_repository_relative_path)
           else
-            git_fork_repository(new_shard_path, new_repository_relative_path)
+            git_fork_repository(new_shard_name, new_repository_relative_path)
           end
         end
       end
 
-      def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil)
+      def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil, prune: true)
         tags_option = tags ? '--tags' : '--no-tags'
 
         logger.info "Fetching remote #{name} for repository #{repository_absolute_path}."
-        cmd = %W(git fetch #{name} --prune --quiet)
+        cmd = %W(#{Gitlab.config.git.bin_path} fetch #{name} --quiet)
+        cmd << '--prune' if prune
         cmd << '--force' if force
         cmd << tags_option
 
@@ -84,7 +94,7 @@ module Gitlab
 
       def push_branches(remote_name, timeout, force, branch_names)
         logger.info "Pushing branches from #{repository_absolute_path} to remote #{remote_name}: #{branch_names}"
-        cmd = %w(git push)
+        cmd = %W(#{Gitlab.config.git.bin_path} push)
         cmd << '--force' if force
         cmd += %W(-- #{remote_name}).concat(branch_names)
 
@@ -101,7 +111,7 @@ module Gitlab
         branches = branch_names.map { |branch_name| ":#{branch_name}" }
 
         logger.info "Pushing deleted branches from #{repository_absolute_path} to remote #{remote_name}: #{branch_names}"
-        cmd = %W(git push -- #{remote_name}).concat(branches)
+        cmd = %W(#{Gitlab.config.git.bin_path} push -- #{remote_name}).concat(branches)
 
         success = run(cmd, repository_absolute_path)
 
@@ -142,7 +152,7 @@ module Gitlab
       end
 
       def remove_origin_in_repo
-        cmd = %w(git remote rm origin)
+        cmd = %W(#{Gitlab.config.git.bin_path} remote rm origin)
         run(cmd, repository_absolute_path)
       end
 
@@ -204,17 +214,6 @@ module Gitlab
 
       private
 
-      def shard_name
-        strong_memoize(:shard_name) do
-          shard_name_from_shard_path(shard_path)
-        end
-      end
-
-      def shard_name_from_shard_path(shard_path)
-        Gitlab.config.repositories.storages.find { |_, info| info['path'] == shard_path }&.first ||
-          raise(ShardNameNotFoundError, "no shard found for path '#{shard_path}'")
-      end
-
       def git_import_repository(source, timeout)
         # Skip import if repo already exists
         return false if File.exist?(repository_absolute_path)
@@ -222,7 +221,7 @@ module Gitlab
         masked_source = mask_password_in_url(source)
 
         logger.info "Importing project from <#{masked_source}> to <#{repository_absolute_path}>."
-        cmd = %W(git clone --bare -- #{source} #{repository_absolute_path})
+        cmd = %W(#{Gitlab.config.git.bin_path} clone --bare -- #{source} #{repository_absolute_path})
 
         success = run_with_timeout(cmd, timeout, nil)
 
@@ -251,8 +250,9 @@ module Gitlab
         false
       end
 
-      def git_fork_repository(new_shard_path, new_repository_relative_path)
+      def git_fork_repository(new_shard_name, new_repository_relative_path)
         from_path = repository_absolute_path
+        new_shard_path = Gitlab.config.repositories.storages.fetch(new_shard_name).legacy_disk_path
         to_path = File.join(new_shard_path, new_repository_relative_path)
 
         # The repository cannot already exist
@@ -265,13 +265,13 @@ module Gitlab
         FileUtils.mkdir_p(File.dirname(to_path), mode: 0770)
 
         logger.info "Forking repository from <#{from_path}> to <#{to_path}>."
-        cmd = %W(git clone --bare --no-local -- #{from_path} #{to_path})
+        cmd = %W(#{Gitlab.config.git.bin_path} clone --bare --no-local -- #{from_path} #{to_path})
 
         run(cmd, nil) && Gitlab::Git::Repository.create_hooks(to_path, global_hooks_path)
       end
 
-      def gitaly_fork_repository(new_shard_path, new_repository_relative_path)
-        target_repository = Gitlab::Git::Repository.new(shard_name_from_shard_path(new_shard_path), new_repository_relative_path, nil)
+      def gitaly_fork_repository(new_shard_name, new_repository_relative_path)
+        target_repository = Gitlab::Git::Repository.new(new_shard_name, new_repository_relative_path, nil)
         raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil)
 
         Gitlab::GitalyClient::RepositoryService.new(target_repository).fork_repository(raw_repository)
diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb
index 4a43b9b444d41c0115cf0a8c157daf519d006792..4b505312f60d03dad953870ea79f901798f1ef52 100644
--- a/lib/gitlab/git/gitmodules_parser.rb
+++ b/lib/gitlab/git/gitmodules_parser.rb
@@ -46,6 +46,8 @@ module Gitlab
         iterator = State.new
 
         @content.split("\n").each_with_object(iterator) do |text, iterator|
+          text.chomp!
+
           next if text =~ /^\s*#/
 
           if text =~ /\A\[submodule "(?<name>[^"]+)"\]\z/
@@ -55,7 +57,7 @@ module Gitlab
 
             next unless text =~ /\A\s*(?<key>\w+)\s*=\s*(?<value>.*)\z/
 
-            value = $~[:value].chomp
+            value = $~[:value]
             iterator.set_attribute($~[:key], value)
           end
         end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index 24f027d8da4e018f5a6ba0df9c202c5c2b48e409..7c201c6169bf5a6441382a4a64c7eab8c1e53698 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -95,13 +95,13 @@ module Gitlab
         args = [ref, oldrev, newrev]
 
         stdout, stderr, status = Open3.capture3(env, path, *args, options)
-        [status.success?, (stderr.presence || stdout).gsub(/\R/, "<br>").html_safe]
+        [status.success?, Gitlab::Utils.nlbr(stderr.presence || stdout)]
       end
 
       def retrieve_error_message(stderr, stdout)
         err_message = stderr.read
         err_message = err_message.blank? ? stdout.read : err_message
-        err_message.gsub(/\R/, "<br>").html_safe
+        Gitlab::Utils.nlbr(err_message)
       end
     end
   end
diff --git a/lib/gitlab/git/hook_env.rb b/lib/gitlab/git/hook_env.rb
new file mode 100644
index 0000000000000000000000000000000000000000..455e8451c10d1b6419e7d68f13cf9ba980ef9681
--- /dev/null
+++ b/lib/gitlab/git/hook_env.rb
@@ -0,0 +1,51 @@
+# Gitaly note: JV: no RPC's here.
+
+module Gitlab
+  module Git
+    # Ephemeral (per request) storage for environment variables that some Git
+    # commands need during internal API calls made from Git push hooks.
+    #
+    # For example, in pre-receive hooks, new objects are put in a temporary
+    # $GIT_OBJECT_DIRECTORY. Without it set, the new objects cannot be retrieved
+    # (this would break push rules for instance).
+    #
+    # This class is thread-safe via RequestStore.
+    class HookEnv
+      WHITELISTED_VARIABLES = %w[
+        GIT_OBJECT_DIRECTORY_RELATIVE
+        GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
+      ].freeze
+
+      def self.set(gl_repository, env)
+        return unless RequestStore.active?
+
+        raise "missing gl_repository" if gl_repository.blank?
+
+        RequestStore.store[:gitlab_git_env] ||= {}
+        RequestStore.store[:gitlab_git_env][gl_repository] = whitelist_git_env(env)
+      end
+
+      def self.all(gl_repository)
+        return {} unless RequestStore.active?
+
+        h = RequestStore.fetch(:gitlab_git_env) { {} }
+        h.fetch(gl_repository, {})
+      end
+
+      def self.to_env_hash(gl_repository)
+        env = {}
+
+        all(gl_repository).compact.each do |key, value|
+          value = value.join(File::PATH_SEPARATOR) if value.is_a?(Array)
+          env[key.to_s] = value
+        end
+
+        env
+      end
+
+      def self.whitelist_git_env(env)
+        env.select { |key, _| WHITELISTED_VARIABLES.include?(key.to_s) }.with_indifferent_access
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/git/info_attributes.rb b/lib/gitlab/git/info_attributes.rb
deleted file mode 100644
index e79a440950bacfe2c0b5c1166f1a7dc2950600f4..0000000000000000000000000000000000000000
--- a/lib/gitlab/git/info_attributes.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# Gitaly note: JV: not sure what to make of this class. Why does it use
-# the full disk path of the repository to look up attributes This is
-# problematic in Gitaly, because Gitaly hides the full disk path to the
-# repository from gitlab-ce.
-
-module Gitlab
-  module Git
-    # Parses gitattributes at `$GIT_DIR/info/attributes`
-    #
-    # Unlike Rugged this parser only needs a single IO call (a call to `open`),
-    # vastly reducing the time spent in extracting attributes.
-    #
-    # This class _only_ supports parsing the attributes file located at
-    # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
-    # (`.gitattributes` is copied to this particular path).
-    #
-    # Basic usage:
-    #
-    #     attributes = Gitlab::Git::InfoAttributes.new(some_repo.path)
-    #
-    #     attributes.attributes('README.md') # => { "eol" => "lf }
-    class InfoAttributes
-      delegate :attributes, :patterns, to: :parser
-
-      # path - The path to the Git repository.
-      def initialize(path)
-        @repo_path = File.expand_path(path)
-      end
-
-      def parser
-        @parser ||= begin
-          if File.exist?(attributes_path)
-            File.open(attributes_path, 'r') do |file_handle|
-              AttributesParser.new(file_handle)
-            end
-          else
-            AttributesParser.new("")
-          end
-        end
-      end
-
-      private
-
-      def attributes_path
-        @attributes_path ||= File.join(@repo_path, 'info/attributes')
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/git/lfs_changes.rb b/lib/gitlab/git/lfs_changes.rb
index 48434047fcee484c7dcae70d0e60c23adee8cdde..b9e5cf258f4a55deb00b75bd90b6e05b2b1c2467 100644
--- a/lib/gitlab/git/lfs_changes.rb
+++ b/lib/gitlab/git/lfs_changes.rb
@@ -7,6 +7,28 @@ module Gitlab
       end
 
       def new_pointers(object_limit: nil, not_in: nil)
+        @repository.gitaly_migrate(:blob_get_new_lfs_pointers) do |is_enabled|
+          if is_enabled
+            @repository.gitaly_blob_client.get_new_lfs_pointers(@newrev, object_limit, not_in)
+          else
+            git_new_pointers(object_limit, not_in)
+          end
+        end
+      end
+
+      def all_pointers
+        @repository.gitaly_migrate(:blob_get_all_lfs_pointers) do |is_enabled|
+          if is_enabled
+            @repository.gitaly_blob_client.get_all_lfs_pointers(@newrev)
+          else
+            git_all_pointers
+          end
+        end
+      end
+
+      private
+
+      def git_new_pointers(object_limit, not_in)
         @new_pointers ||= begin
           rev_list.new_objects(not_in: not_in, require_path: true) do |object_ids|
             object_ids = object_ids.take(object_limit) if object_limit
@@ -16,14 +38,12 @@ module Gitlab
         end
       end
 
-      def all_pointers
+      def git_all_pointers
         rev_list.all_objects(require_path: true) do |object_ids|
           Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids)
         end
       end
 
-      private
-
       def rev_list
         Gitlab::Git::RevList.new(@repository, newrev: @newrev)
       end
diff --git a/lib/gitlab/git/lfs_pointer_file.rb b/lib/gitlab/git/lfs_pointer_file.rb
index da12ed7d12574d546b068333d0790b996d037883..2ae0a8895907a10fc1d078a6a7c2fb85b8197b92 100644
--- a/lib/gitlab/git/lfs_pointer_file.rb
+++ b/lib/gitlab/git/lfs_pointer_file.rb
@@ -1,13 +1,16 @@
 module Gitlab
   module Git
     class LfsPointerFile
+      VERSION = "https://git-lfs.github.com/spec/v1".freeze
+      VERSION_LINE = "version #{VERSION}".freeze
+
       def initialize(data)
         @data = data
       end
 
       def pointer
         @pointer ||= <<~FILE
-          version https://git-lfs.github.com/spec/v1
+          #{VERSION_LINE}
           oid sha256:#{sha256}
           size #{size}
         FILE
@@ -20,6 +23,10 @@ module Gitlab
       def sha256
         @sha256 ||= Digest::SHA256.hexdigest(@data)
       end
+
+      def inspect
+        "#<#{self.class}:#{object_id} @size=#{size}, @sha256=#{sha256.inspect}>"
+      end
     end
   end
 end
diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb
index c1767046ff08bbe02af50530673aa9535dc2dab1..f9f24ecc48dd22734406ff9a4f74ef63b21b08a3 100644
--- a/lib/gitlab/git/popen.rb
+++ b/lib/gitlab/git/popen.rb
@@ -25,7 +25,9 @@ module Gitlab
           stdin.close
 
           if lazy_block
-            return [lazy_block.call(stdout.lazy), 0]
+            cmd_output = lazy_block.call(stdout.lazy)
+            cmd_status = 0
+            break
           else
             cmd_output << stdout.read
           end
diff --git a/lib/gitlab/git/raw_diff_change.rb b/lib/gitlab/git/raw_diff_change.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb3d8819239539b7051e6bf98ee2478b58e47594
--- /dev/null
+++ b/lib/gitlab/git/raw_diff_change.rb
@@ -0,0 +1,60 @@
+module Gitlab
+  module Git
+    # This class behaves like a struct with fields :blob_id, :blob_size, :operation, :old_path, :new_path
+    # All those fields are (binary) strings or integers
+    class RawDiffChange
+      attr_reader :blob_id, :blob_size, :old_path, :new_path, :operation
+
+      def initialize(raw_change)
+        parse(raw_change)
+      end
+
+      private
+
+      # Input data has the following format:
+      #
+      # When a file has been modified:
+      # 7e3e39ebb9b2bf433b4ad17313770fbe4051649c 669 M\tfiles/ruby/popen.rb
+      #
+      # When a file has been renamed:
+      # 85bc2f9753afd5f4fc5d7c75f74f8d526f26b4f3 107 R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee
+      def parse(raw_change)
+        @blob_id, @blob_size, @raw_operation, raw_paths = raw_change.split(' ', 4)
+        @operation = extract_operation
+        @old_path, @new_path = extract_paths(raw_paths)
+      end
+
+      def extract_paths(file_path)
+        case operation
+        when :renamed
+          file_path.split(/\t/)
+        when :deleted
+          [file_path, nil]
+        when :added
+          [nil, file_path]
+        else
+          [file_path, file_path]
+        end
+      end
+
+      def extract_operation
+        case @raw_operation&.first(1)
+        when 'A'
+          :added
+        when 'C'
+          :copied
+        when 'D'
+          :deleted
+        when 'M'
+          :modified
+        when 'R'
+          :renamed
+        when 'T'
+          :type_changed
+        else
+          :unknown
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index d7c373ccd6fe5f33edc28ac54245f084801dc27c..3124c426f9749d9da50b9976a23d762e67d73a7f 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -8,6 +8,8 @@ module Gitlab
     class Repository
       include Gitlab::Git::RepositoryMirroring
       include Gitlab::Git::Popen
+      include Gitlab::EncodingHelper
+      include Gitlab::Utils::StrongMemoize
 
       ALLOWED_OBJECT_DIRECTORIES_VARIABLES = %w[
         GIT_OBJECT_DIRECTORY
@@ -22,6 +24,7 @@ module Gitlab
       SQUASH_WORKTREE_PREFIX = 'squash'.freeze
       GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze
       GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout
+      EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze
 
       NoRepository = Class.new(StandardError)
       InvalidBlobName = Class.new(StandardError)
@@ -30,6 +33,7 @@ module Gitlab
       DeleteBranchError = Class.new(StandardError)
       CreateTreeError = Class.new(StandardError)
       TagExistsError = Class.new(StandardError)
+      ChecksumError = Class.new(StandardError)
 
       class << self
         # Unlike `new`, `create` takes the repository path
@@ -72,9 +76,6 @@ module Gitlab
         end
       end
 
-      # Full path to repo
-      attr_reader :path
-
       # Directory name of repo
       attr_reader :name
 
@@ -93,22 +94,26 @@ module Gitlab
         @relative_path = relative_path
         @gl_repository = gl_repository
 
-        storage_path = Gitlab.config.repositories.storages[@storage]['path']
         @gitlab_projects = Gitlab::Git::GitlabProjects.new(
-          storage_path,
+          storage,
           relative_path,
           global_hooks_path: Gitlab.config.gitlab_shell.hooks_path,
           logger: Rails.logger
         )
-        @path = File.join(storage_path, @relative_path)
+
         @name = @relative_path.split("/").last
-        @attributes = Gitlab::Git::InfoAttributes.new(path)
       end
 
       def ==(other)
         path == other.path
       end
 
+      def path
+        @path ||= File.join(
+          Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path
+        )
+      end
+
       # Default branch in the repository
       def root_ref
         @root_ref ||= gitaly_migrate(:root_ref) do |is_enabled|
@@ -137,12 +142,12 @@ module Gitlab
       end
 
       def exists?
-        Gitlab::GitalyClient.migrate(:repository_exists, status:  Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
+        Gitlab::GitalyClient.migrate(:repository_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
           if enabled
             gitaly_repository_client.exists?
           else
             circuit_breaker.perform do
-              File.exist?(File.join(@path, 'refs'))
+              File.exist?(File.join(path, 'refs'))
             end
           end
         end
@@ -227,13 +232,13 @@ module Gitlab
         end
       end
 
+      def expire_has_local_branches_cache
+        clear_memoization(:has_local_branches)
+      end
+
       def has_local_branches?
-        gitaly_migrate(:has_local_branches) do |is_enabled|
-          if is_enabled
-            gitaly_repository_client.has_local_branches?
-          else
-            has_local_branches_rugged?
-          end
+        strong_memoize(:has_local_branches) do
+          uncached_has_local_branches?
         end
       end
 
@@ -295,7 +300,8 @@ module Gitlab
       #
       # Ref names must start with `refs/`.
       def ref_exists?(ref_name)
-        gitaly_migrate(:ref_exists) do |is_enabled|
+        gitaly_migrate(:ref_exists,
+                      status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_ref_exists?(ref_name)
           else
@@ -393,17 +399,24 @@ module Gitlab
         nil
       end
 
-      def archive_prefix(ref, sha)
+      def archive_prefix(ref, sha, append_sha:)
+        append_sha = (ref != sha) if append_sha.nil?
+
         project_name = self.name.chomp('.git')
-        "#{project_name}-#{ref.tr('/', '-')}-#{sha}"
+        formatted_ref = ref.tr('/', '-')
+
+        prefix_segments = [project_name, formatted_ref]
+        prefix_segments << sha if append_sha
+
+        prefix_segments.join('-')
       end
 
-      def archive_metadata(ref, storage_path, format = "tar.gz")
+      def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
         ref ||= root_ref
         commit = Gitlab::Git::Commit.find(self, ref)
         return {} if commit.nil?
 
-        prefix = archive_prefix(ref, commit.id)
+        prefix = archive_prefix(ref, commit.id, append_sha: append_sha)
 
         {
           'RepoPath' => path,
@@ -479,9 +492,8 @@ module Gitlab
           raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
         end
 
-        # TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049
         gitaly_migrate(:find_commits) do |is_enabled|
-          if is_enabled && !options[:all]
+          if is_enabled
             gitaly_commit_client.find_commits(options)
           else
             raw_log(options).map { |c| Commit.decorate(self, c) }
@@ -508,9 +520,8 @@ module Gitlab
       def count_commits(options)
         count_commits_options = process_count_commits_options(options)
 
-        # TODO add support for options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1050
         gitaly_migrate(:count_commits) do |is_enabled|
-          if is_enabled && !options[:all]
+          if is_enabled
             count_commits_by_gitaly(count_commits_options)
           else
             count_commits_by_shelling_out(count_commits_options)
@@ -518,10 +529,6 @@ module Gitlab
         end
       end
 
-      def sha_from_ref(ref)
-        rev_parse_target(ref).oid
-      end
-
       # Return the object that +revspec+ points to.  If +revspec+ is an
       # annotated tag, then return the tag's target instead.
       def rev_parse_target(revspec)
@@ -555,6 +562,24 @@ module Gitlab
         count_commits(from: from, to: to, **options)
       end
 
+      # old_rev and new_rev are commit ID's
+      # the result of this method is an array of Gitlab::Git::RawDiffChange
+      def raw_changes_between(old_rev, new_rev)
+        result = []
+
+        circuit_breaker.perform do
+          Open3.pipeline_r(git_diff_cmd(old_rev, new_rev), format_git_cat_file_script, git_cat_file_cmd) do |last_stdout, wait_threads|
+            last_stdout.each_line { |line| result << ::Gitlab::Git::RawDiffChange.new(line.chomp!) }
+
+            if wait_threads.any? { |waiter| !waiter.value&.success? }
+              raise ::Gitlab::Git::Repository::GitError, "Unabled to obtain changes between #{old_rev} and #{new_rev}"
+            end
+          end
+        end
+
+        result
+      end
+
       # Returns the SHA of the most recent common ancestor of +from+ and +to+
       def merge_base(from, to)
         gitaly_migrate(:merge_base) do |is_enabled|
@@ -717,7 +742,7 @@ module Gitlab
       end
 
       def add_branch(branch_name, user:, target:)
-        gitaly_migrate(:operation_user_create_branch) do |is_enabled|
+        gitaly_migrate(:operation_user_create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_add_branch(branch_name, user, target)
           else
@@ -727,7 +752,7 @@ module Gitlab
       end
 
       def add_tag(tag_name, user:, target:, message: nil)
-        gitaly_migrate(:operation_user_add_tag) do |is_enabled|
+        gitaly_migrate(:operation_user_add_tag, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_add_tag(tag_name, user: user, target: target, message: message)
           else
@@ -737,7 +762,7 @@ module Gitlab
       end
 
       def rm_branch(branch_name, user:)
-        gitaly_migrate(:operation_user_delete_branch) do |is_enabled|
+        gitaly_migrate(:operation_user_delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_operations_client.user_delete_branch(branch_name, user)
           else
@@ -812,7 +837,7 @@ module Gitlab
       end
 
       def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
-        gitaly_migrate(:revert) do |is_enabled|
+        gitaly_migrate(:revert, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           args = {
             user: user,
             commit: commit,
@@ -878,7 +903,7 @@ module Gitlab
 
       # Delete the specified branch from the repository
       def delete_branch(branch_name)
-        gitaly_migrate(:delete_branch) do |is_enabled|
+        gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_ref_client.delete_branch(branch_name)
           else
@@ -890,7 +915,8 @@ module Gitlab
       end
 
       def delete_refs(*ref_names)
-        gitaly_migrate(:delete_refs) do |is_enabled|
+        gitaly_migrate(:delete_refs,
+                      status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_delete_refs(*ref_names)
           else
@@ -905,7 +931,7 @@ module Gitlab
       #   create_branch("feature")
       #   create_branch("other-feature", "master")
       def create_branch(ref, start_point = "HEAD")
-        gitaly_migrate(:create_branch) do |is_enabled|
+        gitaly_migrate(:create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_ref_client.create_branch(ref, start_point)
           else
@@ -988,11 +1014,32 @@ module Gitlab
         raise InvalidRef
       end
 
+      def info_attributes
+        return @info_attributes if @info_attributes
+
+        content =
+          gitaly_migrate(:get_info_attributes) do |is_enabled|
+            if is_enabled
+              gitaly_repository_client.info_attributes
+            else
+              attributes_path = File.join(File.expand_path(path), 'info', 'attributes')
+
+              if File.exist?(attributes_path)
+                File.read(attributes_path)
+              else
+                ""
+              end
+            end
+          end
+
+        @info_attributes = AttributesParser.new(content)
+      end
+
       # Returns the Git attributes for the given file path.
       #
       # See `Gitlab::Git::Attributes` for more information.
       def attributes(path)
-        @attributes.attributes(path)
+        info_attributes.attributes(path)
       end
 
       def gitattribute(path, name)
@@ -1004,15 +1051,16 @@ module Gitlab
       # This only checks the root .gitattributes file,
       # it does not traverse subfolders to find additional .gitattributes files
       #
-      # This method is around 30 times slower than `attributes`,
-      # which uses `$GIT_DIR/info/attributes`
+      # This method is around 30 times slower than `attributes`, which uses
+      # `$GIT_DIR/info/attributes`. Consider caching AttributesAtRefParser
+      # and reusing that for multiple calls instead of this method.
       def attributes_at(ref, file_path)
         parser = AttributesAtRefParser.new(self, ref)
         parser.attributes(file_path)
       end
 
       def languages(ref = nil)
-        Gitlab::GitalyClient.migrate(:commit_languages) do |is_enabled|
+        gitaly_migrate(:commit_languages, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_commit_client.languages(ref)
           else
@@ -1039,7 +1087,8 @@ module Gitlab
       end
 
       def license_short_name
-        gitaly_migrate(:license_short_name) do |is_enabled|
+        gitaly_migrate(:license_short_name,
+                       status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_repository_client.license_short_name
           else
@@ -1191,15 +1240,9 @@ module Gitlab
       end
 
       def fsck
-        gitaly_migrate(:git_fsck) do |is_enabled|
-          msg, status = if is_enabled
-                          gitaly_fsck
-                        else
-                          shell_fsck
-                        end
+        msg, status = gitaly_repository_client.fsck
 
-          raise GitError.new("Could not fsck repository: #{msg}") unless status.zero?
-        end
+        raise GitError.new("Could not fsck repository: #{msg}") unless status.zero?
       end
 
       def create_from_bundle(bundle_path)
@@ -1371,8 +1414,21 @@ module Gitlab
         raise CommandError.new(e)
       end
 
+      def clean_stale_repository_files
+        gitaly_migrate(:repository_cleanup, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
+          gitaly_repository_client.cleanup if is_enabled && exists?
+        end
+      rescue Gitlab::Git::CommandError => e # Don't fail if we can't cleanup
+        Rails.logger.error("Unable to clean repository on storage #{storage} with path #{path}: #{e.message}")
+        Gitlab::Metrics.counter(
+          :failed_repository_cleanup_total,
+          'Number of failed repository cleanup events'
+        ).increment
+      end
+
       def branch_names_contains_sha(sha)
-        gitaly_migrate(:branch_names_contains_sha) do |is_enabled|
+        gitaly_migrate(:branch_names_contains_sha,
+                      status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_ref_client.branch_names_contains_sha(sha)
           else
@@ -1382,7 +1438,8 @@ module Gitlab
       end
 
       def tag_names_contains_sha(sha)
-        gitaly_migrate(:tag_names_contains_sha) do |is_enabled|
+        gitaly_migrate(:tag_names_contains_sha,
+                       status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
           if is_enabled
             gitaly_ref_client.tag_names_contains_sha(sha)
           else
@@ -1397,7 +1454,7 @@ module Gitlab
         offset = 2
         args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
 
-        run_git(args).first.scrub.split(/^--$/)
+        run_git(args).first.scrub.split(/^--\n/)
       end
 
       def can_be_merged?(source_sha, target_branch)
@@ -1415,7 +1472,7 @@ module Gitlab
 
         return [] if empty? || safe_query.blank?
 
-        args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
+        args = %W(ls-tree -r --name-status --full-tree #{ref || root_ref} -- #{safe_query})
 
         run_git(args).first.lines.map(&:strip)
       end
@@ -1435,22 +1492,12 @@ module Gitlab
         output
       end
 
-      def can_be_merged?(source_sha, target_branch)
-        gitaly_migrate(:can_be_merged) do |is_enabled|
-          if is_enabled
-            gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
-          else
-            rugged_can_be_merged?(source_sha, target_branch)
-          end
-        end
-      end
-
-      def last_commit_id_for_path(sha, path)
+      def last_commit_for_path(sha, path)
         gitaly_migrate(:last_commit_for_path) do |is_enabled|
           if is_enabled
-            last_commit_for_path_by_gitaly(sha, path).id
+            last_commit_for_path_by_gitaly(sha, path)
           else
-            last_commit_id_for_path_by_shelling_out(sha, path)
+            last_commit_for_path_by_rugged(sha, path)
           end
         end
       end
@@ -1475,8 +1522,55 @@ module Gitlab
         run_git!(['rev-list', '--max-count=1', oldrev, "^#{newrev}"])
       end
 
+      def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:)
+        base_args = %w(worktree add --detach)
+
+        # Note that we _don't_ want to test for `.present?` here: If the caller
+        # passes an non nil empty value it means it still wants sparse checkout
+        # but just isn't interested in any file, perhaps because it wants to
+        # checkout files in by a changeset but that changeset only adds files.
+        if sparse_checkout_files
+          # Create worktree without checking out
+          run_git!(base_args + ['--no-checkout', worktree_path], env: env)
+          worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path).chomp
+
+          configure_sparse_checkout(worktree_git_path, sparse_checkout_files)
+
+          # After sparse checkout configuration, checkout `branch` in worktree
+          run_git!(%W(checkout --detach #{branch}), chdir: worktree_path, env: env)
+        else
+          # Create worktree and checkout `branch` in it
+          run_git!(base_args + [worktree_path, branch], env: env)
+        end
+
+        yield
+      ensure
+        FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path)
+        FileUtils.rm_rf(worktree_git_path) if worktree_git_path && File.exist?(worktree_git_path)
+      end
+
+      def checksum
+        gitaly_migrate(:calculate_checksum) do |is_enabled|
+          if is_enabled
+            gitaly_repository_client.calculate_checksum
+          else
+            calculate_checksum_by_shelling_out
+          end
+        end
+      end
+
       private
 
+      def uncached_has_local_branches?
+        gitaly_migrate(:has_local_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
+          if is_enabled
+            gitaly_repository_client.has_local_branches?
+          else
+            has_local_branches_rugged?
+          end
+        end
+      end
+
       def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
         if shell
           shell_write_ref(ref_path, ref, old_ref)
@@ -1500,7 +1594,7 @@ module Gitlab
         names.lines.each do |line|
           next unless line.start_with?(refs_prefix)
 
-          refs << line.rstrip[left_slice_count..-1]
+          refs << encode_utf8(line.rstrip[left_slice_count..-1])
         end
 
         refs
@@ -1561,33 +1655,6 @@ module Gitlab
         File.exist?(path) && !clean_stuck_worktree(path)
       end
 
-      def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:)
-        base_args = %w(worktree add --detach)
-
-        # Note that we _don't_ want to test for `.present?` here: If the caller
-        # passes an non nil empty value it means it still wants sparse checkout
-        # but just isn't interested in any file, perhaps because it wants to
-        # checkout files in by a changeset but that changeset only adds files.
-        if sparse_checkout_files
-          # Create worktree without checking out
-          run_git!(base_args + ['--no-checkout', worktree_path], env: env)
-          worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path).chomp
-
-          configure_sparse_checkout(worktree_git_path, sparse_checkout_files)
-
-          # After sparse checkout configuration, checkout `branch` in worktree
-          run_git!(%W(checkout --detach #{branch}), chdir: worktree_path, env: env)
-        else
-          # Create worktree and checkout `branch` in it
-          run_git!(base_args + [worktree_path, branch], env: env)
-        end
-
-        yield
-      ensure
-        FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path)
-        FileUtils.rm_rf(worktree_git_path) if worktree_git_path && File.exist?(worktree_git_path)
-      end
-
       def clean_stuck_worktree(path)
         return false unless File.mtime(path) < 15.minutes.ago
 
@@ -1608,14 +1675,6 @@ module Gitlab
         File.write(File.join(worktree_info_path, 'sparse-checkout'), files)
       end
 
-      def gitaly_fsck
-        gitaly_repository_client.fsck
-      end
-
-      def shell_fsck
-        run_git(%W[--git-dir=#{path} fsck], nice: true)
-      end
-
       def rugged_fetch_source_branch(source_repository, source_branch, local_ref)
         with_repo_branch_commit(source_repository, source_branch) do |commit|
           if commit
@@ -1773,21 +1832,11 @@ module Gitlab
       end
 
       def alternate_object_directories
-        relative_paths = relative_object_directories
-
-        if relative_paths.any?
-          relative_paths.map { |d| File.join(path, d) }
-        else
-          absolute_object_directories.flat_map { |d| d.split(File::PATH_SEPARATOR) }
-        end
+        relative_object_directories.map { |d| File.join(path, d) }
       end
 
       def relative_object_directories
-        Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
-      end
-
-      def absolute_object_directories
-        Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).flatten.compact
+        Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
       end
 
       # Get the content of a blob for a given commit.  If the blob is a commit
@@ -1898,7 +1947,7 @@ module Gitlab
       end
 
       def last_commit_for_path_by_rugged(sha, path)
-        sha = last_commit_id_for_path(sha, path)
+        sha = last_commit_id_for_path_by_shelling_out(sha, path)
         commit(sha)
       end
 
@@ -2406,14 +2455,6 @@ module Gitlab
           .map { |c| commit(c) }
       end
 
-      def gitaly_can_be_merged?(their_commit, our_commit)
-        !gitaly_conflicts_client(our_commit, their_commit).conflicts?
-      end
-
-      def rugged_can_be_merged?(their_commit, our_commit)
-        !rugged.merge_commits(our_commit, their_commit).conflicts?
-      end
-
       def last_commit_for_path_by_gitaly(sha, path)
         gitaly_commit_client.last_commit_for_path(sha, path)
       end
@@ -2442,6 +2483,67 @@ module Gitlab
       def rev_list_param(spec)
         spec == :all ? ['--all'] : spec
       end
+
+      def sha_from_ref(ref)
+        rev_parse_target(ref).oid
+      end
+
+      def calculate_checksum_by_shelling_out
+        raise NoRepository unless exists?
+
+        args = %W(--git-dir=#{path} show-ref --heads --tags)
+        output, status = run_git(args)
+
+        if status.nil? || !status.zero?
+          # Empty repositories return with a non-zero status and an empty output.
+          return EMPTY_REPOSITORY_CHECKSUM if output&.empty?
+
+          raise ChecksumError, output
+        end
+
+        refs = output.split("\n")
+
+        result = refs.inject(nil) do |checksum, ref|
+          value = Digest::SHA1.hexdigest(ref).hex
+
+          if checksum.nil?
+            value
+          else
+            checksum ^ value
+          end
+        end
+
+        result.to_s(16)
+      end
+
+      def build_git_cmd(*args)
+        object_directories = alternate_object_directories.join(File::PATH_SEPARATOR)
+
+        env = { 'PWD' => self.path }
+        env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = object_directories if object_directories.present?
+
+        [
+          env,
+          ::Gitlab.config.git.bin_path,
+          *args,
+          { chdir: self.path }
+        ]
+      end
+
+      def git_diff_cmd(old_rev, new_rev)
+        old_rev = old_rev == ::Gitlab::Git::BLANK_SHA ? ::Gitlab::Git::EMPTY_TREE_ID : old_rev
+
+        build_git_cmd('diff', old_rev, new_rev, '--raw')
+      end
+
+      def git_cat_file_cmd
+        format = '%(objectname) %(objectsize) %(rest)'
+        build_git_cmd('cat-file', "--batch-check=#{format}")
+      end
+
+      def format_git_cat_file_script
+        File.expand_path('../support/format-git-cat-file-input', __FILE__)
+      end
     end
   end
 end
diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb
index dc424a433fb3636487eed61e875184a608569515..8a01f92e2afdb51efd0efa2cb3a7aab10f18a709 100644
--- a/lib/gitlab/git/repository_mirroring.rb
+++ b/lib/gitlab/git/repository_mirroring.rb
@@ -26,7 +26,7 @@ module Gitlab
           # When the remote repo does not have tags.
           if target.nil? || path.nil?
             Rails.logger.info "Empty or invalid list of tags for remote: #{remote}. Output: #{output}"
-            return []
+            break []
           end
 
           name = path.split('/', 3).last
diff --git a/lib/gitlab/git/storage/checker.rb b/lib/gitlab/git/storage/checker.rb
index d3c37f82101a5a691b79fbf01a83e1cd5b1568b9..2f611cef37b50e1fb5c64b8d2fd54b9ba4d88b9f 100644
--- a/lib/gitlab/git/storage/checker.rb
+++ b/lib/gitlab/git/storage/checker.rb
@@ -35,7 +35,7 @@ module Gitlab
         def initialize(storage, logger = Rails.logger)
           @storage = storage
           config = Gitlab.config.repositories.storages[@storage]
-          @storage_path = config['path']
+          @storage_path = config.legacy_disk_path
           @logger = logger
 
           @hostname = Gitlab::Environment.hostname
diff --git a/lib/gitlab/git/storage/circuit_breaker.rb b/lib/gitlab/git/storage/circuit_breaker.rb
index 898bb1b65be102cee36dcc7fdd11c3076dc2049c..e35054466ff8a6d51aaa168a5cda41d3f094dee2 100644
--- a/lib/gitlab/git/storage/circuit_breaker.rb
+++ b/lib/gitlab/git/storage/circuit_breaker.rb
@@ -25,7 +25,7 @@ module Gitlab
 
           if !config.present?
             NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Storage '#{storage}' is not configured"))
-          elsif !config['path'].present?
+          elsif !config.legacy_disk_path.present?
             NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Path for storage '#{storage}' is not configured"))
           else
             new(storage, hostname)
diff --git a/lib/gitlab/git/support/format-git-cat-file-input b/lib/gitlab/git/support/format-git-cat-file-input
new file mode 100755
index 0000000000000000000000000000000000000000..2e93c646d0f9139874f1424b1899bb0d21b8302c
--- /dev/null
+++ b/lib/gitlab/git/support/format-git-cat-file-input
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+# This script formats the output of the `git diff <old_rev> <new_rev> --raw`
+# command so it can be processed by `git cat-file`
+
+# We need to convert this:
+# ":100644 100644 5f53439... 85bc2f9... R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee"
+# To:
+# "85bc2f9 R\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee"
+
+ARGF.each do |line|
+  _, _, old_blob_id, new_blob_id, rest = line.split(/\s/, 5)
+
+  old_blob_id.gsub!(/[^\h]/, '')
+  new_blob_id.gsub!(/[^\h]/, '')
+
+  # We can't pass '0000000...' to `git cat-file` given it will not return info about the deleted file
+  blob_id = new_blob_id =~ /\A0+\z/ ? old_blob_id : new_blob_id
+
+  $stdout.puts "#{blob_id} #{rest}"
+end
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index 52b44b9b3c568170b54733ff26cc5f25834b6bd9..821436911ab50bd2abe9b7ff3f604c0d2ae712cf 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -2,10 +2,11 @@ module Gitlab
   module Git
     class Wiki
       DuplicatePageError = Class.new(StandardError)
+      OperationError = Class.new(StandardError)
 
-      CommitDetails = Struct.new(:name, :email, :message) do
+      CommitDetails = Struct.new(:user_id, :username, :name, :email, :message) do
         def to_h
-          { name: name, email: email, message: message }
+          { user_id: user_id, username: username, name: name, email: email, message: message }
         end
       end
       PageBlob = Struct.new(:name)
@@ -29,7 +30,6 @@ module Gitlab
         @repository.gitaly_migrate(:wiki_write_page) do |is_enabled|
           if is_enabled
             gitaly_write_page(name, format, content, commit_details)
-            gollum_wiki.clear_cache
           else
             gollum_write_page(name, format, content, commit_details)
           end
@@ -40,7 +40,6 @@ module Gitlab
         @repository.gitaly_migrate(:wiki_delete_page) do |is_enabled|
           if is_enabled
             gitaly_delete_page(page_path, commit_details)
-            gollum_wiki.clear_cache
           else
             gollum_delete_page(page_path, commit_details)
           end
@@ -51,7 +50,6 @@ module Gitlab
         @repository.gitaly_migrate(:wiki_update_page) do |is_enabled|
           if is_enabled
             gitaly_update_page(page_path, title, format, content, commit_details)
-            gollum_wiki.clear_cache
           else
             gollum_update_page(page_path, title, format, content, commit_details)
           end
@@ -143,6 +141,10 @@ module Gitlab
         end
       end
 
+      def gollum_wiki
+        @gollum_wiki ||= Gollum::Wiki.new(@repository.path)
+      end
+
       private
 
       # options:
@@ -161,10 +163,6 @@ module Gitlab
                         offset: options[:offset])
       end
 
-      def gollum_wiki
-        @gollum_wiki ||= Gollum::Wiki.new(@repository.path)
-      end
-
       def gollum_page_by_path(page_path)
         page_name = Gollum::Page.canonicalize_filename(page_path)
         page_dir = File.split(page_path).first
@@ -204,12 +202,12 @@ module Gitlab
         assert_type!(format, Symbol)
         assert_type!(commit_details, CommitDetails)
 
-        filename = File.basename(name)
-        dir = (tmp_dir = File.dirname(name)) == '.' ? '' : tmp_dir
-
-        gollum_wiki.write_page(filename, format, content, commit_details.to_h, dir)
+        with_committer_with_hooks(commit_details) do |committer|
+          filename = File.basename(name)
+          dir = (tmp_dir = File.dirname(name)) == '.' ? '' : tmp_dir
 
-        nil
+          gollum_wiki.write_page(filename, format, content, { committer: committer }, dir)
+        end
       rescue Gollum::DuplicatePageError => e
         raise Gitlab::Git::Wiki::DuplicatePageError, e.message
       end
@@ -217,24 +215,23 @@ module Gitlab
       def gollum_delete_page(page_path, commit_details)
         assert_type!(commit_details, CommitDetails)
 
-        gollum_wiki.delete_page(gollum_page_by_path(page_path), commit_details.to_h)
-        nil
+        with_committer_with_hooks(commit_details) do |committer|
+          gollum_wiki.delete_page(gollum_page_by_path(page_path), committer: committer)
+        end
       end
 
       def gollum_update_page(page_path, title, format, content, commit_details)
         assert_type!(format, Symbol)
         assert_type!(commit_details, CommitDetails)
 
-        page = gollum_page_by_path(page_path)
-        committer = Gollum::Committer.new(page.wiki, commit_details.to_h)
-
-        # Instead of performing two renames if the title has changed,
-        # the update_page will only update the format and content and
-        # the rename_page will do anything related to moving/renaming
-        gollum_wiki.update_page(page, page.name, format, content, committer: committer)
-        gollum_wiki.rename_page(page, title, committer: committer)
-        committer.commit
-        nil
+        with_committer_with_hooks(commit_details) do |committer|
+          page = gollum_page_by_path(page_path)
+          # Instead of performing two renames if the title has changed,
+          # the update_page will only update the format and content and
+          # the rename_page will do anything related to moving/renaming
+          gollum_wiki.update_page(page, page.name, format, content, committer: committer)
+          gollum_wiki.rename_page(page, title, committer: committer)
+        end
       end
 
       def gollum_find_page(title:, version: nil, dir: nil)
@@ -291,6 +288,20 @@ module Gitlab
           Gitlab::Git::WikiPage.new(wiki_page, version)
         end
       end
+
+      def committer_with_hooks(commit_details)
+        Gitlab::Wiki::CommitterWithHooks.new(self, commit_details.to_h)
+      end
+
+      def with_committer_with_hooks(commit_details, &block)
+        committer = committer_with_hooks(commit_details)
+
+        yield committer
+
+        committer.commit
+
+        nil
+      end
     end
   end
 end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 6400089a22f7a4cc47d5649418d7bc097fb35d83..0d1ee73ca1a34a7ef6e2221761f0b4298865581c 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -29,9 +29,9 @@ module Gitlab
     PUSH_COMMANDS = %w{ git-receive-pack }.freeze
     ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
 
-    attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path
+    attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type
 
-    def initialize(actor, project, protocol, authentication_abilities:, namespace_path: nil, project_path: nil, redirected_path: nil)
+    def initialize(actor, project, protocol, authentication_abilities:, namespace_path: nil, project_path: nil, redirected_path: nil, auth_result_type: nil)
       @actor    = actor
       @project  = project
       @protocol = protocol
@@ -39,6 +39,7 @@ module Gitlab
       @namespace_path = namespace_path
       @project_path = project_path
       @redirected_path = redirected_path
+      @auth_result_type = auth_result_type
     end
 
     def check(cmd, changes)
@@ -53,7 +54,7 @@ module Gitlab
       ensure_project_on_push!(cmd, changes)
 
       check_project_accessibility!
-      check_project_moved!
+      add_project_moved_message!
       check_repository_existence!
 
       case cmd
@@ -78,6 +79,12 @@ module Gitlab
       authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
     end
 
+    def request_from_ci_build?
+      return false unless protocol == 'http'
+
+      auth_result_type == :build || auth_result_type == :ci
+    end
+
     def protocol_allowed?
       Gitlab::ProtocolAccess.allowed?(protocol)
     end
@@ -93,14 +100,14 @@ module Gitlab
     end
 
     def check_protocol!
+      return if request_from_ci_build?
+
       unless protocol_allowed?
         raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
       end
     end
 
     def check_active_user!
-      return if deploy_key?
-
       if user && !user_access.allowed?
         raise UnauthorizedError, ERROR_MESSAGES[:account_blocked]
       end
@@ -125,16 +132,12 @@ module Gitlab
       end
     end
 
-    def check_project_moved!
+    def add_project_moved_message!
       return if redirected_path.nil?
 
       project_moved = Checks::ProjectMoved.new(project, user, protocol, redirected_path)
 
-      if project_moved.permanent_redirect?
-        project_moved.add_message
-      else
-        raise ProjectMovedError, project_moved.message(rejected: true)
-      end
+      project_moved.add_message
     end
 
     def check_command_disabled!(cmd)
@@ -205,6 +208,7 @@ module Gitlab
 
     def check_download_access!
       passed = deploy_key? ||
+        deploy_token? ||
         user_can_download_code? ||
         build_can_download_code? ||
         guest_can_download_code?
@@ -219,7 +223,7 @@ module Gitlab
         raise UnauthorizedError, ERROR_MESSAGES[:read_only]
       end
 
-      if deploy_key
+      if deploy_key?
         unless deploy_key.can_push_to?(project)
           raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload]
         end
@@ -235,6 +239,11 @@ module Gitlab
     end
 
     def check_change_access!(changes)
+      # If there are worktrees with a HEAD pointing to a non-existent object,
+      # calls to `git rev-list --all` will fail in git 2.15+. This should also
+      # clear stale lock files.
+      project.repository.clean_stale_repository_files
+
       changes_list = Gitlab::ChangesList.new(changes)
 
       # Iterate over all changes to find if user allowed all of them to be applied
@@ -266,6 +275,14 @@ module Gitlab
       actor.is_a?(DeployKey)
     end
 
+    def deploy_token
+      actor if deploy_token?
+    end
+
+    def deploy_token?
+      actor.is_a?(DeployToken)
+    end
+
     def ci?
       actor == :ci
     end
@@ -273,6 +290,8 @@ module Gitlab
     def can_read_project?
       if deploy_key?
         deploy_key.has_access_to?(project)
+      elsif deploy_token?
+        deploy_token.has_access_to?(project)
       elsif user
         user.can?(:read_project, project)
       elsif ci?
@@ -309,8 +328,10 @@ module Gitlab
         case actor
         when User
           actor
+        when DeployKey
+          nil
         when Key
-          actor.user unless actor.is_a?(DeployKey)
+          actor.user
         when :ci
           nil
         end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 9cd7663048439ec0692c264be10b7a2caf89c912..0abae70c443cbc2288f7a54bab6320fd1180c442 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -83,6 +83,10 @@ module Gitlab
       end
     end
 
+    def self.random_storage
+      Gitlab.config.repositories.storages.keys.sample
+    end
+
     def self.address(storage)
       params = Gitlab.config.repositories.storages[storage]
       raise "storage not found: #{storage.inspect}" if params.nil?
@@ -119,6 +123,9 @@ module Gitlab
     #
     def self.call(storage, service, rpc, request, remote_storage: nil, timeout: nil)
       start = Gitlab::Metrics::System.monotonic_time
+      request_hash = request.is_a?(Google::Protobuf::MessageExts) ? request.to_h : {}
+      @current_call_id ||= SecureRandom.uuid
+
       enforce_gitaly_request_limits(:call)
 
       kwargs = request_kwargs(storage, timeout, remote_storage: remote_storage)
@@ -135,6 +142,10 @@ module Gitlab
       gitaly_controller_action_duration_seconds.observe(
         current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
         duration)
+
+      add_call_details(id: @current_call_id, feature: service, duration: duration, request: request_hash)
+
+      @current_call_id = nil
     end
 
     def self.handle_grpc_unavailable!(ex)
@@ -252,12 +263,16 @@ module Gitlab
           feature_stack.unshift(feature)
           begin
             start = Gitlab::Metrics::System.monotonic_time
+            @current_call_id = SecureRandom.uuid
+            call_details = { id: @current_call_id }
             yield is_enabled
           ensure
             total_time = Gitlab::Metrics::System.monotonic_time - start
             gitaly_migrate_call_duration_seconds.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
             feature_stack.shift
             Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty?
+
+            add_call_details(call_details.merge(feature: feature, duration: total_time))
           end
         end
       end
@@ -344,6 +359,22 @@ module Gitlab
       end
     end
 
+    def self.add_call_details(details)
+      id = details.delete(:id)
+
+      return unless id && RequestStore.active? && RequestStore.store[:peek_enabled]
+
+      RequestStore.store['gitaly_call_details'] ||= {}
+      RequestStore.store['gitaly_call_details'][id] ||= {}
+      RequestStore.store['gitaly_call_details'][id].merge!(details)
+    end
+
+    def self.list_call_details
+      return {} unless RequestStore.active? && RequestStore.store[:peek_enabled]
+
+      RequestStore.store['gitaly_call_details'] || {}
+    end
+
     def self.expected_server_version
       path = Rails.root.join(SERVER_VERSION_FILE)
       path.read.chomp
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index dfa0fa43b0f9457f0185940fbd92bf7fcd3cb1c6..285542089844fb32d7e7553ef70caf6d843baef7 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -45,16 +45,7 @@ module Gitlab
 
         response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request)
 
-        response.flat_map do |message|
-          message.lfs_pointers.map do |lfs_pointer|
-            Gitlab::Git::Blob.new(
-              id: lfs_pointer.oid,
-              size: lfs_pointer.size,
-              data: lfs_pointer.data,
-              binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
-            )
-          end
-        end
+        map_lfs_pointers(response)
       end
 
       def get_blobs(revision_paths, limit = -1)
@@ -80,6 +71,50 @@ module Gitlab
 
         GitalyClient::BlobsStitcher.new(response)
       end
+
+      def get_new_lfs_pointers(revision, limit, not_in)
+        request = Gitaly::GetNewLFSPointersRequest.new(
+          repository: @gitaly_repo,
+          revision: encode_binary(revision),
+          limit: limit || 0
+        )
+
+        if not_in.nil? || not_in == :all
+          request.not_in_all = true
+        else
+          request.not_in_refs += not_in
+        end
+
+        response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request)
+
+        map_lfs_pointers(response)
+      end
+
+      def get_all_lfs_pointers(revision)
+        request = Gitaly::GetNewLFSPointersRequest.new(
+          repository: @gitaly_repo,
+          revision: encode_binary(revision)
+        )
+
+        response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request)
+
+        map_lfs_pointers(response)
+      end
+
+      private
+
+      def map_lfs_pointers(response)
+        response.flat_map do |message|
+          message.lfs_pointers.map do |lfs_pointer|
+            Gitlab::Git::Blob.new(
+              id: lfs_pointer.oid,
+              size: lfs_pointer.size,
+              data: lfs_pointer.data,
+              binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
+            )
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 1ad0bf1d060bccf8991db8b67dee880d424abb4f..a36e6c822f995bf42eae152da36fd26872030151 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -3,10 +3,6 @@ module Gitlab
     class CommitService
       include Gitlab::EncodingHelper
 
-      # The ID of empty tree.
-      # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
-      EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
-
       def initialize(repository)
         @gitaly_repo = repository.gitaly_repository
         @repository = repository
@@ -37,7 +33,7 @@ module Gitlab
       def diff(from, to, options = {})
         from_id = case from
                   when NilClass
-                    EMPTY_TREE_ID
+                    Gitlab::Git::EMPTY_TREE_ID
                   else
                     if from.respond_to?(:oid)
                       # This is meant to match a Rugged::Commit. This should be impossible in
@@ -50,7 +46,7 @@ module Gitlab
 
         to_id = case to
                 when NilClass
-                  EMPTY_TREE_ID
+                  Gitlab::Git::EMPTY_TREE_ID
                 else
                   if to.respond_to?(:oid)
                     # This is meant to match a Rugged::Commit. This should be impossible in
@@ -134,7 +130,8 @@ module Gitlab
       def commit_count(ref, options = {})
         request = Gitaly::CountCommitsRequest.new(
           repository: @gitaly_repo,
-          revision: encode_binary(ref)
+          revision: encode_binary(ref),
+          all: !!options[:all]
         )
         request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
         request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
@@ -269,6 +266,7 @@ module Gitlab
           offset:       options[:offset],
           follow:       options[:follow],
           skip_merges:  options[:skip_merges],
+          all:          !!options[:all],
           disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
         )
         request.after    = GitalyClient.timestamp(options[:after]) if options[:after]
@@ -350,7 +348,7 @@ module Gitlab
       end
 
       def diff_from_parent_request_params(commit, options = {})
-        parent_id = commit.parent_ids.first || EMPTY_TREE_ID
+        parent_id = commit.parent_ids.first || Gitlab::Git::EMPTY_TREE_ID
 
         diff_between_commits_request_params(parent_id, commit.id, options)
       end
diff --git a/lib/gitlab/gitaly_client/conflict_files_stitcher.rb b/lib/gitlab/gitaly_client/conflict_files_stitcher.rb
index 97c13d1fdb0051049b0622c2c846b80c6b91510f..c275a065bceabc471f0f2d08ada34a20625f6abf 100644
--- a/lib/gitlab/gitaly_client/conflict_files_stitcher.rb
+++ b/lib/gitlab/gitaly_client/conflict_files_stitcher.rb
@@ -17,7 +17,7 @@ module Gitlab
 
               current_file = file_from_gitaly_header(gitaly_file.header)
             else
-              current_file.content << gitaly_file.content
+              current_file.raw_content << gitaly_file.content
             end
           end
         end
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 58c356edfd1e58bd786369375d074c535ca48a26..f2d699d9dfb1e0edeb0e648f05fac4f7a7bc9502 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -3,6 +3,17 @@ module Gitlab
     class RemoteService
       MAX_MSG_SIZE = 128.kilobytes.freeze
 
+      def self.exists?(remote_url)
+        request = Gitaly::FindRemoteRepositoryRequest.new(remote: remote_url)
+
+        response = GitalyClient.call(GitalyClient.random_storage,
+                                     :remote_service,
+                                     :find_remote_repository, request,
+                                     timeout: GitalyClient.medium_timeout)
+
+        response.exists
+      end
+
       def initialize(repository)
         @repository = repository
         @gitaly_repo = repository.gitaly_repository
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index fdb3247cf4d867cc4a06ee9675c98de8a62f4f59..39057beefba410f7aa629f806a0de44449b3e15a 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -19,6 +19,11 @@ module Gitlab
         response.exists
       end
 
+      def cleanup
+        request = Gitaly::CleanupRequest.new(repository: @gitaly_repo)
+        GitalyClient.call(@storage, :repository_service, :cleanup, request)
+      end
+
       def garbage_collect(create_bitmap)
         request = Gitaly::GarbageCollectRequest.new(repository: @gitaly_repo, create_bitmap: create_bitmap)
         GitalyClient.call(@storage, :repository_service, :garbage_collect, request)
@@ -45,10 +50,19 @@ module Gitlab
         GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
       end
 
-      def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:)
+      def info_attributes
+        request = Gitaly::GetInfoAttributesRequest.new(repository: @gitaly_repo)
+
+        response = GitalyClient.call(@storage, :repository_service, :get_info_attributes, request)
+        response.each_with_object("") do |message, attributes|
+          attributes << message.attributes
+        end
+      end
+
+      def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true)
         request = Gitaly::FetchRemoteRequest.new(
           repository: @gitaly_repo, remote: remote, force: forced,
-          no_tags: no_tags, timeout: timeout
+          no_tags: no_tags, timeout: timeout, no_prune: !prune
         )
 
         if ssh_auth&.ssh_import?
@@ -257,6 +271,12 @@ module Gitlab
 
         response.license_short_name.presence
       end
+
+      def calculate_checksum
+        request  = Gitaly::CalculateChecksumRequest.new(repository: @gitaly_repo)
+        response = GitalyClient.call(@storage, :repository_service, :calculate_checksum, request)
+        response.checksum.presence
+      end
     end
   end
 end
diff --git a/lib/gitlab/gitaly_client/storage_settings.rb b/lib/gitlab/gitaly_client/storage_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8668caf0c5508e99753d9f196adcced46e750c8e
--- /dev/null
+++ b/lib/gitlab/gitaly_client/storage_settings.rb
@@ -0,0 +1,35 @@
+module Gitlab
+  module GitalyClient
+    # This is a chokepoint that is meant to help us stop remove all places
+    # where production code (app, config, db, lib) touches Git repositories
+    # directly.
+    class StorageSettings
+      DirectPathAccessError = Class.new(StandardError)
+
+      # This class will give easily recognizable NoMethodErrors
+      Deprecated = Class.new
+
+      attr_reader :legacy_disk_path
+
+      def initialize(storage)
+        raise "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash)
+
+        # Support a nil 'path' field because some of the circuit breaker tests use it.
+        @legacy_disk_path = File.expand_path(storage['path'], Rails.root) if storage['path']
+
+        storage['path'] = Deprecated
+        @hash = storage
+      end
+
+      def gitaly_address
+        @hash.fetch(:gitaly_address)
+      end
+
+      private
+
+      def method_missing(m, *args, &block)
+        @hash.public_send(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb
index a8c6d478de8f36f5fb69497b87603cbcdbc37a26..405567db94aff76806d0df4ef1d26f76a26fd673 100644
--- a/lib/gitlab/gitaly_client/util.rb
+++ b/lib/gitlab/gitaly_client/util.rb
@@ -3,11 +3,9 @@ module Gitlab
     module Util
       class << self
         def repository(repository_storage, relative_path, gl_repository)
-          git_object_directory = Gitlab::Git::Env['GIT_OBJECT_DIRECTORY_RELATIVE'].presence ||
-            Gitlab::Git::Env['GIT_OBJECT_DIRECTORY'].presence
-          git_alternate_object_directories =
-            Array.wrap(Gitlab::Git::Env['GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE']).presence ||
-            Array.wrap(Gitlab::Git::Env['GIT_ALTERNATE_OBJECT_DIRECTORIES']).flat_map { |d| d.split(File::PATH_SEPARATOR) }
+          git_env = Gitlab::Git::HookEnv.all(gl_repository)
+          git_object_directory = git_env['GIT_OBJECT_DIRECTORY_RELATIVE'].presence
+          git_alternate_object_directories = Array.wrap(git_env['GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE'])
 
           Gitaly::Repository.new(
             storage_name: repository_storage,
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 0d8dd5cb8f4612e3741743ec409eaa73435000ce..2dfe055a496b9548ee851b0c2d32e2c13959ba81 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -136,7 +136,7 @@ module Gitlab
         wiki_file = nil
 
         response.each do |message|
-          next unless message.name.present?
+          next unless message.name.present? || wiki_file
 
           if wiki_file
             wiki_file.raw_data << message.raw_data
@@ -200,6 +200,8 @@ module Gitlab
 
       def gitaly_commit_details(commit_details)
         Gitaly::WikiCommitDetails.new(
+          user_id: commit_details.user_id,
+          user_name: encode_binary(commit_details.username),
           name: encode_binary(commit_details.name),
           email: encode_binary(commit_details.email),
           message: encode_binary(commit_details.message)
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 4f160e4a447e13c3344e4c628524779f7abaa743..a61beafae0d25cbab41a59baf5b6e619c6a2dc72 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -197,10 +197,7 @@ module Gitlab
       end
 
       def github_omniauth_provider
-        @github_omniauth_provider ||=
-          Gitlab.config.omniauth.providers
-                .find { |provider| provider.name == 'github' }
-                .to_h
+        @github_omniauth_provider ||= Gitlab::Auth::OAuth::Provider.config_for('github').to_h
       end
 
       def rate_limit_counter
diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb
index ab0b751fe242e73afcff3301666f9e63402d5a2e..01168abde6cc5465f29c81c947a8a842a3f45334 100644
--- a/lib/gitlab/github_import/importer/repository_importer.rb
+++ b/lib/gitlab/github_import/importer/repository_importer.rb
@@ -16,7 +16,8 @@ module Gitlab
         # Returns true if we should import the wiki for the project.
         def import_wiki?
           client.repository(project.import_source)&.has_wiki &&
-            !project.wiki_repository_exists?
+            !project.wiki_repository_exists? &&
+            Gitlab::GitalyClient::RemoteService.exists?(wiki_url)
         end
 
         # Imports the repository data.
@@ -55,10 +56,8 @@ module Gitlab
 
         def import_wiki_repository
           wiki_path = "#{project.disk_path}.wiki"
-          wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git')
-          storage_path = project.repository_storage_path
 
-          gitlab_shell.import_repository(storage_path, wiki_path, wiki_url)
+          gitlab_shell.import_repository(project.repository_storage, wiki_path, wiki_url)
 
           true
         rescue Gitlab::Shell::Error => e
@@ -70,6 +69,10 @@ module Gitlab
           end
         end
 
+        def wiki_url
+          project.import_url.sub(/\.git\z/, '.wiki.git')
+        end
+
         def update_clone_time
           project.update_column(:last_repository_updated_at, Time.zone.now)
         end
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
index 075b3982608c2a9096b155e91ffbbef15ca3226a..5482504e72e246c8a1a0d2d4022c062a0b60ae72 100644
--- a/lib/gitlab/gitlab_import/client.rb
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -72,7 +72,7 @@ module Gitlab
       end
 
       def config
-        Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"}
+        Gitlab::Auth::OAuth::Provider.config_for('gitlab')
       end
 
       def gitlab_options
diff --git a/lib/gitlab/gl_id.rb b/lib/gitlab/gl_id.rb
index 624fd00367e0f5c128497449630242824b62e416..a53d156b41f3fa7e28204d5b301c4fed62329153 100644
--- a/lib/gitlab/gl_id.rb
+++ b/lib/gitlab/gl_id.rb
@@ -2,10 +2,14 @@ module Gitlab
   module GlId
     def self.gl_id(user)
       if user.present?
-        "user-#{user.id}"
+        gl_id_from_id_value(user.id)
       else
-        ""
+        ''
       end
     end
+
+    def self.gl_id_from_id_value(id)
+      "user-#{id}"
+    end
   end
 end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 92f0e0402a81995256a3ed9c33b396a028b85349..a7e055ac444d5c86efdaeadb73b46591cd89e0ff 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -20,6 +20,7 @@ module Gitlab
       gon.sprite_icons           = IconsHelper.sprite_icon_path
       gon.sprite_file_icons      = IconsHelper.sprite_file_icons_path
       gon.test_env               = Rails.env.test?
+      gon.suggested_label_colors = LabelsHelper.suggested_colors
 
       if current_user
         gon.current_user_id = current_user.id
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
index afaa59b1018446e9c39495eeffe2f30dcb0b0ce7..6e55438327083b58af223e7a7a9979d262610ad7 100644
--- a/lib/gitlab/health_checks/fs_shards_check.rb
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -77,7 +77,7 @@ module Gitlab
         end
 
         def storage_path(storage_name)
-          storages_paths&.dig(storage_name, 'path')
+          storages_paths[storage_name]&.legacy_disk_path
         end
 
         # All below test methods use shell commands to perform actions on storage volumes.
diff --git a/lib/gitlab/health_checks/metric.rb b/lib/gitlab/health_checks/metric.rb
index 1a2eab0b005fb3ff52d6bb61dbf561000d247875..d62d9136886df08b620e581af593fde4ed4a9bad 100644
--- a/lib/gitlab/health_checks/metric.rb
+++ b/lib/gitlab/health_checks/metric.rb
@@ -1,3 +1,3 @@
-module Gitlab::HealthChecks
+module Gitlab::HealthChecks # rubocop:disable Naming/FileName
   Metric = Struct.new(:name, :value, :labels)
 end
diff --git a/lib/gitlab/health_checks/result.rb b/lib/gitlab/health_checks/result.rb
index 8086760023e5e833062dd16481bdc19cebac6dff..e323e2c972343bb458eed7c468d164e6f5e721a1 100644
--- a/lib/gitlab/health_checks/result.rb
+++ b/lib/gitlab/health_checks/result.rb
@@ -1,3 +1,3 @@
-module Gitlab::HealthChecks
+module Gitlab::HealthChecks # rubocop:disable Naming/FileName
   Result = Struct.new(:success, :message, :labels)
 end
diff --git a/lib/gitlab/hook_data/issuable_builder.rb b/lib/gitlab/hook_data/issuable_builder.rb
index 4febb0ab4306dfb115b9cdff1a1590d33f3c0177..6ab3667612719a2e9de223d77f1de4cde30e4bc0 100644
--- a/lib/gitlab/hook_data/issuable_builder.rb
+++ b/lib/gitlab/hook_data/issuable_builder.rb
@@ -11,7 +11,8 @@ module Gitlab
 
       def build(user: nil, changes: {})
         hook_data = {
-          object_kind: issuable.class.name.underscore,
+          object_kind: object_kind,
+          event_type: event_type,
           user: user.hook_attrs,
           project: issuable.project.hook_attrs,
           object_attributes: issuable.hook_attrs,
@@ -36,6 +37,18 @@ module Gitlab
 
       private
 
+      def object_kind
+        issuable.class.name.underscore
+      end
+
+      def event_type
+        if issuable.try(:confidential?)
+          "confidential_#{object_kind}"
+        else
+          object_kind
+        end
+      end
+
       def issuable_builder
         case issuable
         when Issue
diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9aca3b0fb264e007f5a5c8235fdb62a88d16bf8f
--- /dev/null
+++ b/lib/gitlab/http.rb
@@ -0,0 +1,13 @@
+# This class is used as a proxy for all outbounding http connection
+# coming from callbacks, services and hooks. The direct use of the HTTParty
+# is discouraged because it can lead to several security problems, like SSRF
+# calling internal IP or services.
+module Gitlab
+  class HTTP
+    BlockedUrlError = Class.new(StandardError)
+
+    include HTTParty # rubocop:disable Gitlab/HTTParty
+
+    connection_adapter ProxyHTTPConnectionAdapter
+  end
+end
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index bdc0f04b56b900f6ca7d46f1c52f37058804a8ef..3772ef11c7f3e1fa2c132df2f62a216eeb7b7e61 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -18,7 +18,10 @@ module Gitlab
       'uk' => '校泻褉邪褩薪褋褜泻邪',
       'ja' => '鏃ユ湰瑾�',
       'ko' => '頃滉淡鞏�',
-      'nl_NL' => 'Nederlands'
+      'nl_NL' => 'Nederlands',
+      'tr_TR' => 'T眉rk莽e',
+      'id_ID' => 'Bahasa Indonesia',
+      'fil_PH' => 'Filipino'
     }.freeze
 
     def available_locales
diff --git a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aef371d81ebce961140401392eeb2183775b5037
--- /dev/null
+++ b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
@@ -0,0 +1,83 @@
+module Gitlab
+  module ImportExport
+    module AfterExportStrategies
+      class BaseAfterExportStrategy
+        include ActiveModel::Validations
+        extend Forwardable
+
+        StrategyError = Class.new(StandardError)
+
+        AFTER_EXPORT_LOCK_FILE_NAME = '.after_export_action'.freeze
+
+        private
+
+        attr_reader :project, :current_user
+
+        public
+
+        def initialize(attributes = {})
+          @options = OpenStruct.new(attributes)
+
+          self.class.instance_eval do
+            def_delegators :@options, *attributes.keys
+          end
+        end
+
+        def execute(current_user, project)
+          return unless project&.export_project_path
+
+          @project = project
+          @current_user = current_user
+
+          if invalid?
+            log_validation_errors
+
+            return
+          end
+
+          create_or_update_after_export_lock
+          strategy_execute
+
+          true
+        rescue => e
+          project.import_export_shared.error(e)
+          false
+        ensure
+          delete_after_export_lock
+        end
+
+        def to_json(options = {})
+          @options.to_h.merge!(klass: self.class.name).to_json
+        end
+
+        def self.lock_file_path(project)
+          return unless project&.export_path
+
+          File.join(project.export_path, AFTER_EXPORT_LOCK_FILE_NAME)
+        end
+
+        protected
+
+        def strategy_execute
+          raise NotImplementedError
+        end
+
+        private
+
+        def create_or_update_after_export_lock
+          FileUtils.touch(self.class.lock_file_path(project))
+        end
+
+        def delete_after_export_lock
+          lock_file = self.class.lock_file_path(project)
+
+          FileUtils.rm(lock_file) if lock_file.present? && File.exist?(lock_file)
+        end
+
+        def log_validation_errors
+          errors.full_messages.each { |msg| project.import_export_shared.add_error_message(msg) }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/after_export_strategies/download_notification_strategy.rb b/lib/gitlab/import_export/after_export_strategies/download_notification_strategy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4371a7eff5663fd28fa41c02e434214fae60cc63
--- /dev/null
+++ b/lib/gitlab/import_export/after_export_strategies/download_notification_strategy.rb
@@ -0,0 +1,17 @@
+module Gitlab
+  module ImportExport
+    module AfterExportStrategies
+      class DownloadNotificationStrategy < BaseAfterExportStrategy
+        private
+
+        def strategy_execute
+          notification_service.project_exported(project, current_user)
+        end
+
+        def notification_service
+          @notification_service ||= NotificationService.new
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..938664a95a1503d1646bb5db9492b2df666c4fa7
--- /dev/null
+++ b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
@@ -0,0 +1,61 @@
+module Gitlab
+  module ImportExport
+    module AfterExportStrategies
+      class WebUploadStrategy < BaseAfterExportStrategy
+        PUT_METHOD = 'PUT'.freeze
+        POST_METHOD = 'POST'.freeze
+        INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'.freeze
+
+        validates :url, url: true
+
+        validate do
+          unless [PUT_METHOD, POST_METHOD].include?(http_method.upcase)
+            errors.add(:http_method, INVALID_HTTP_METHOD)
+          end
+        end
+
+        def initialize(url:, http_method: PUT_METHOD)
+          super
+        end
+
+        protected
+
+        def strategy_execute
+          handle_response_error(send_file)
+
+          project.remove_exported_project_file
+        end
+
+        def handle_response_error(response)
+          unless response.success?
+            error_code = response.dig('Error', 'Code') || response.code
+            error_message = response.dig('Error', 'Message') || response.message
+
+            raise StrategyError.new("Error uploading the project. Code #{error_code}: #{error_message}")
+          end
+        end
+
+        private
+
+        def send_file
+          export_file = File.open(project.export_project_path)
+
+          Gitlab::HTTP.public_send(http_method.downcase, url, send_file_options(export_file)) # rubocop:disable GitlabSecurity/PublicSend
+        ensure
+          export_file.close if export_file
+        end
+
+        def send_file_options(export_file)
+          {
+            body_stream: export_file,
+            headers: headers
+          }
+        end
+
+        def headers
+          { 'Content-Length' => File.size(project.export_project_path).to_s }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/after_export_strategy_builder.rb b/lib/gitlab/import_export/after_export_strategy_builder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7eabcae23805bab217e00925e0ff2ec8290cab15
--- /dev/null
+++ b/lib/gitlab/import_export/after_export_strategy_builder.rb
@@ -0,0 +1,24 @@
+module Gitlab
+  module ImportExport
+    class AfterExportStrategyBuilder
+      StrategyNotFoundError = Class.new(StandardError)
+
+      def self.build!(strategy_klass, attributes = {})
+        return default_strategy.new unless strategy_klass
+
+        attributes ||= {}
+        klass = strategy_klass.constantize rescue nil
+
+        unless klass && klass < AfterExportStrategies::BaseAfterExportStrategy
+          raise StrategyNotFoundError.new("Strategy #{strategy_klass} not found")
+        end
+
+        klass.new(**attributes.symbolize_keys)
+      end
+
+      def self.default_strategy
+        AfterExportStrategies::DownloadNotificationStrategy
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 9f404003125a3a82b84976eb35b10b5581098a64..cd840bd5b013384bdd51d67e0a9205d17f700e36 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -65,6 +65,7 @@ project_tree:
     - :create_access_levels
   - :project_feature
   - :custom_attributes
+  - :project_badges
 
 # Only include the following attributes for the models specified.
 included_attributes:
@@ -104,6 +105,7 @@ excluded_attributes:
     - :last_repository_updated_at
     - :last_repository_check_at
     - :storage_version
+    - :description_html
   snippets:
     - :expired_at
   merge_request_diff:
@@ -123,8 +125,12 @@ excluded_attributes:
     - :trace
     - :token
     - :when
+    - :artifacts_file
+    - :artifacts_metadata
   push_event_payload:
     - :event_id
+  project_badges:
+    - :group_id
 
 methods:
   labels:
@@ -141,9 +147,9 @@ methods:
     - :diff_head_sha
     - :source_branch_sha
     - :target_branch_sha
-  project:
-    - :description_html
   events:
     - :action
   push_event_payload:
     - :action
+  project_badges:
+    - :type
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index a00795f553ebc4a2049a97eba5c4d5c92c8af35e..63cab07324a7e253c25dbedb4b5c83ff8b0aad1f 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -1,6 +1,9 @@
 module Gitlab
   module ImportExport
     class Importer
+      include Gitlab::Allowable
+      include Gitlab::Utils::StrongMemoize
+
       def self.imports_repository?
         true
       end
@@ -9,21 +12,28 @@ module Gitlab
         @archive_file = project.import_source
         @current_user = project.creator
         @project = project
-        @shared = Gitlab::ImportExport::Shared.new(relative_path: path_with_namespace)
+        @shared = project.import_export_shared
       end
 
       def execute
-        if import_file && check_version! && [repo_restorer, wiki_restorer, project_tree, avatar_restorer, uploads_restorer].all?(&:restore)
+        if import_file && check_version! && restorers.all?(&:restore) && overwrite_project
           project_tree.restored_project
         else
           raise Projects::ImportService::Error.new(@shared.errors.join(', '))
         end
-
+      rescue => e
+        raise Projects::ImportService::Error.new(e.message)
+      ensure
         remove_import_file
       end
 
       private
 
+      def restorers
+        [repo_restorer, wiki_restorer, project_tree, avatar_restorer,
+         uploads_restorer, lfs_restorer, statistics_restorer]
+      end
+
       def import_file
         Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file,
                                                   shared: @shared)
@@ -60,6 +70,14 @@ module Gitlab
         Gitlab::ImportExport::UploadsRestorer.new(project: project_tree.restored_project, shared: @shared)
       end
 
+      def lfs_restorer
+        Gitlab::ImportExport::LfsRestorer.new(project: project_tree.restored_project, shared: @shared)
+      end
+
+      def statistics_restorer
+        Gitlab::ImportExport::StatisticsRestorer.new(project: project_tree.restored_project, shared: @shared)
+      end
+
       def path_with_namespace
         File.join(@project.namespace.full_path, @project.path)
       end
@@ -75,6 +93,33 @@ module Gitlab
       def remove_import_file
         FileUtils.rm_rf(@archive_file)
       end
+
+      def overwrite_project
+        project = project_tree.restored_project
+
+        return unless can?(@current_user, :admin_namespace, project.namespace)
+
+        if overwrite_project?
+          ::Projects::OverwriteProjectService.new(project, @current_user)
+                                             .execute(project_to_overwrite)
+        end
+
+        true
+      end
+
+      def original_path
+        @project.import_data&.data&.fetch('original_path', nil)
+      end
+
+      def overwrite_project?
+        original_path.present? && project_to_overwrite.present?
+      end
+
+      def project_to_overwrite
+        strong_memoize(:project_to_overwrite) do
+          Project.find_by_full_path("#{@project.namespace.full_path}/#{original_path}")
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/lfs_restorer.rb b/lib/gitlab/import_export/lfs_restorer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b28c3c161b7716165eb5cdc854327a9bcfd62f4a
--- /dev/null
+++ b/lib/gitlab/import_export/lfs_restorer.rb
@@ -0,0 +1,43 @@
+module Gitlab
+  module ImportExport
+    class LfsRestorer
+      def initialize(project:, shared:)
+        @project = project
+        @shared = shared
+      end
+
+      def restore
+        return true if lfs_file_paths.empty?
+
+        lfs_file_paths.each do |file_path|
+          link_or_create_lfs_object!(file_path)
+        end
+
+        true
+      rescue => e
+        @shared.error(e)
+        false
+      end
+
+      private
+
+      def link_or_create_lfs_object!(path)
+        size = File.size(path)
+        oid = LfsObject.calculate_oid(path)
+
+        lfs_object = LfsObject.find_or_initialize_by(oid: oid, size: size)
+        lfs_object.file = File.open(path) unless lfs_object.file&.exists?
+
+        @project.all_lfs_objects << lfs_object
+      end
+
+      def lfs_file_paths
+        @lfs_file_paths ||= Dir.glob("#{lfs_storage_path}/*")
+      end
+
+      def lfs_storage_path
+        File.join(@shared.export_path, 'lfs-objects')
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/lfs_saver.rb b/lib/gitlab/import_export/lfs_saver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..29410e2331c41eade626ea49ee4371e40953a077
--- /dev/null
+++ b/lib/gitlab/import_export/lfs_saver.rb
@@ -0,0 +1,55 @@
+module Gitlab
+  module ImportExport
+    class LfsSaver
+      include Gitlab::ImportExport::CommandLineUtil
+
+      def initialize(project:, shared:)
+        @project = project
+        @shared = shared
+      end
+
+      def save
+        @project.all_lfs_objects.each do |lfs_object|
+          save_lfs_object(lfs_object)
+        end
+
+        true
+      rescue => e
+        @shared.error(e)
+
+        false
+      end
+
+      private
+
+      def save_lfs_object(lfs_object)
+        if lfs_object.local_store?
+          copy_file_for_lfs_object(lfs_object)
+        else
+          download_file_for_lfs_object(lfs_object)
+        end
+      end
+
+      def download_file_for_lfs_object(lfs_object)
+        destination = destination_path_for_object(lfs_object)
+        mkdir_p(File.dirname(destination))
+
+        File.open(destination, 'w') do |file|
+          IO.copy_stream(URI.parse(lfs_object.file.url).open, file)
+        end
+      end
+
+      def copy_file_for_lfs_object(lfs_object)
+        copy_files(lfs_object.file.path, destination_path_for_object(lfs_object))
+      end
+
+      def destination_path_for_object(lfs_object)
+        File.join(lfs_export_path, lfs_object.oid)
+      end
+
+      def lfs_export_path
+        File.join(@shared.export_path, 'lfs-objects')
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 4b5f9f3a926a81729e8880c55eabf7e7906ba673..d5590dde40f3c67e3a1fff49396c64d63623b348 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -35,6 +35,8 @@ module Gitlab
       end
 
       def restored_project
+        return @project unless @tree_hash
+
         @restored_project ||= restore_project
       end
 
@@ -75,20 +77,31 @@ module Gitlab
       end
 
       def default_relation_list
-        Gitlab::ImportExport::Reader.new(shared: @shared).tree.reject do |model|
+        reader.tree.reject do |model|
           model.is_a?(Hash) && model[:project_members]
         end
       end
 
       def restore_project
-        return @project unless @tree_hash
-
         @project.update_columns(project_params)
         @project
       end
 
       def project_params
-        @tree_hash.reject do |key, value|
+        @project_params ||= json_params.merge(override_params)
+      end
+
+      def override_params
+        return {} unless params = @project.import_data&.data&.fetch('override_params', nil)
+
+        @override_params ||= params.select do |key, _value|
+          Project.column_names.include?(key.to_s) &&
+            !reader.project_tree[:except].include?(key.to_sym)
+        end
+      end
+
+      def json_params
+        @json_params ||= @tree_hash.reject do |key, value|
           # return params that are not 1 to many or 1 to 1 relations
           value.respond_to?(:each) && !Project.column_names.include?(key)
         end
@@ -175,6 +188,10 @@ module Gitlab
 
         relation_hash.merge(params)
       end
+
+      def reader
+        @reader ||= Gitlab::ImportExport::Reader.new(shared: @shared)
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/project_tree_saver.rb b/lib/gitlab/import_export/project_tree_saver.rb
index 3473b46693612c3c85f80720d55c9bdace81e2a9..5510c0b8b2fc50a6da5f77bf4f3439698ad40eb0 100644
--- a/lib/gitlab/import_export/project_tree_saver.rb
+++ b/lib/gitlab/import_export/project_tree_saver.rb
@@ -5,7 +5,8 @@ module Gitlab
 
       attr_reader :full_path
 
-      def initialize(project:, current_user:, shared:)
+      def initialize(project:, current_user:, shared:, params: {})
+        @params = params
         @project = project
         @current_user = current_user
         @shared = shared
@@ -25,6 +26,10 @@ module Gitlab
       private
 
       def project_json_tree
+        if @params[:description].present?
+          project_json['description'] = @params[:description]
+        end
+
         project_json['project_members'] += group_members_json
 
         project_json.to_json
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 759833a5ee51bb5c01a30326bee93fc0b19d0938..598832fb2dff6badd5d94e5544c2e8598cfc2c58 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -16,9 +16,10 @@ module Gitlab
                     priorities: :label_priorities,
                     auto_devops: :project_auto_devops,
                     label: :project_label,
-                    custom_attributes: 'ProjectCustomAttribute' }.freeze
+                    custom_attributes: 'ProjectCustomAttribute',
+                    project_badges: 'Badge' }.freeze
 
-      USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id].freeze
+      USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
 
       PROJECT_REFERENCES = %w[project_id source_project_id target_project_id].freeze
 
@@ -69,6 +70,7 @@ module Gitlab
 
         update_user_references
         update_project_references
+        remove_duplicate_assignees
 
         reset_tokens!
         remove_encrypted_attributes!
@@ -82,6 +84,14 @@ module Gitlab
         end
       end
 
+      def remove_duplicate_assignees
+        return unless @relation_hash['issue_assignees']
+
+        # When an assignee did not exist in the members mapper, the importer is
+        # assigned. We only need to assign each user once.
+        @relation_hash['issue_assignees'].uniq!(&:user_id)
+      end
+
       def setup_note
         set_note_author
         # attachment is deprecated and note uploads are handled by Markdown uploader
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index b34cafc687652e9e47cf30bf79c237beab3dbca1..6d7c36ce38b36a2ecac822153cb8ab40ad8fe38a 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -1,13 +1,17 @@
 module Gitlab
   module ImportExport
     class Shared
-      attr_reader :errors, :opts
+      attr_reader :errors, :project
 
-      def initialize(opts)
-        @opts = opts
+      def initialize(project)
+        @project = project
         @errors = []
       end
 
+      def active_export_count
+        Dir[File.join(archive_path, '*')].count { |name| File.directory?(name) }
+      end
+
       def export_path
         @export_path ||= Gitlab::ImportExport.export_path(relative_path: relative_path)
       end
@@ -18,7 +22,7 @@ module Gitlab
 
       def error(error)
         error_out(error.message, caller[0].dup)
-        @errors << error.message
+        add_error_message(error.message)
 
         # Debug:
         if error.backtrace
@@ -28,19 +32,31 @@ module Gitlab
         end
       end
 
+      def add_error_message(error_message)
+        @errors << error_message
+      end
+
+      def after_export_in_progress?
+        File.exist?(after_export_lock_file)
+      end
+
       private
 
       def relative_path
-        File.join(opts[:relative_path], SecureRandom.hex)
+        File.join(relative_archive_path, SecureRandom.hex)
       end
 
       def relative_archive_path
-        File.join(opts[:relative_path], '..')
+        @project.disk_path
       end
 
       def error_out(message, caller)
         Rails.logger.error("Import/Export error raised on #{caller}: #{message}")
       end
+
+      def after_export_lock_file
+        AfterExportStrategies::BaseAfterExportStrategy.lock_file_path(project)
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/statistics_restorer.rb b/lib/gitlab/import_export/statistics_restorer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bcdd9c12c85f36d55aa652b8fe0f611da8cfef7a
--- /dev/null
+++ b/lib/gitlab/import_export/statistics_restorer.rb
@@ -0,0 +1,17 @@
+module Gitlab
+  module ImportExport
+    class StatisticsRestorer
+      def initialize(project:, shared:)
+        @project = project
+        @shared = shared
+      end
+
+      def restore
+        @project.statistics.refresh!
+      rescue => e
+        @shared.error(e)
+        false
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/kubernetes/namespace.rb b/lib/gitlab/kubernetes/namespace.rb
index fbbddb7bffaeba50963041fb1d46ae6b896989f7..e6ff6160ab9f29250359d6d9be54035569d6ac0b 100644
--- a/lib/gitlab/kubernetes/namespace.rb
+++ b/lib/gitlab/kubernetes/namespace.rb
@@ -10,7 +10,7 @@ module Gitlab
 
       def exists?
         @client.get_namespace(name)
-      rescue ::KubeException => ke
+      rescue ::Kubeclient::HttpError => ke
         raise ke unless ke.error_code == 404
 
         false
diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb
index 53c910d44bd2ed9135a5f3ded386fc597a610d0e..d8ed0ebca9d3baee7ffedabc2a5d4ad098724c0d 100644
--- a/lib/gitlab/legacy_github_import/client.rb
+++ b/lib/gitlab/legacy_github_import/client.rb
@@ -83,7 +83,7 @@ module Gitlab
       end
 
       def config
-        Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" }
+        Gitlab::Auth::OAuth::Provider.config_for('github')
       end
 
       def github_options
diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb
index 0526ef9eb1327de8fd37576d29876a115785ae72..7edd0ad2033c5bc7aa5c189744bd36b68e1bd7af 100644
--- a/lib/gitlab/legacy_github_import/importer.rb
+++ b/lib/gitlab/legacy_github_import/importer.rb
@@ -259,7 +259,7 @@ module Gitlab
       def import_wiki
         unless project.wiki.repository_exists?
           wiki = WikiFormatter.new(project)
-          gitlab_shell.import_repository(project.repository_storage_path, wiki.disk_path, wiki.import_url)
+          gitlab_shell.import_repository(project.repository_storage, wiki.disk_path, wiki.import_url)
         end
       rescue Gitlab::Shell::Error => e
         # GitHub error message when the wiki repo has not been created,
diff --git a/lib/gitlab/legacy_github_import/project_creator.rb b/lib/gitlab/legacy_github_import/project_creator.rb
index cbabe5454cacc730aef3eed0b844fb6e122db496..3ce245a805045256764b31b77592162f69ae8c06 100644
--- a/lib/gitlab/legacy_github_import/project_creator.rb
+++ b/lib/gitlab/legacy_github_import/project_creator.rb
@@ -12,9 +12,8 @@ module Gitlab
         @type = type
       end
 
-      def execute
-        ::Projects::CreateService.new(
-          current_user,
+      def execute(extra_attrs = {})
+        attrs = {
           name: name,
           path: name,
           description: repo.description,
@@ -24,7 +23,9 @@ module Gitlab
           import_source: repo.full_name,
           import_url: import_url,
           skip_wiki: skip_wiki
-        ).execute
+        }.merge!(extra_attrs)
+
+        ::Projects::CreateService.new(current_user, attrs).execute
       end
 
       private
diff --git a/lib/gitlab/metrics/methods.rb b/lib/gitlab/metrics/methods.rb
index cd7c1e507f7b1796046d6e1a7dfb05392c5d3c69..f79eb0cd1bf1b36ca1f6a394c179a2e9707dfd2d 100644
--- a/lib/gitlab/metrics/methods.rb
+++ b/lib/gitlab/metrics/methods.rb
@@ -50,7 +50,7 @@ module Gitlab
         end
 
         def disabled_by_feature(options)
-          options.with_feature && !Feature.get(options.with_feature).enabled?
+          options.with_feature && !::Feature.get(options.with_feature).enabled?
         end
 
         def build_metric!(type, name, options)
diff --git a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
index db8bdde74b2916b0d1474d5d2f16b9fb7442aea2..47b4af5d6490da24127fe55dfb4b65135b4d1dbc 100644
--- a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
+++ b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
@@ -4,6 +4,8 @@ require 'prometheus/client/rack/exporter'
 module Gitlab
   module Metrics
     class SidekiqMetricsExporter < Daemon
+      LOG_FILENAME = File.join(Rails.root, 'log', 'sidekiq_exporter.log')
+
       def enabled?
         Gitlab::Metrics.metrics_folder_present? && settings.enabled
       end
@@ -17,7 +19,13 @@ module Gitlab
       attr_reader :server
 
       def start_working
-        @server = ::WEBrick::HTTPServer.new(Port: settings.port, BindAddress: settings.address)
+        logger = WEBrick::Log.new(LOG_FILENAME)
+        access_log = [
+          [logger, WEBrick::AccessLog::COMBINED_LOG_FORMAT]
+        ]
+
+        @server = ::WEBrick::HTTPServer.new(Port: settings.port, BindAddress: settings.address,
+                                            Logger: logger, AccessLog: access_log)
         server.mount "/", Rack::Handler::WEBrick, rack_app
         server.start
       end
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index d4c54049b7436deb1e1c74c907c3856527a2877d..a5f5d719cc1ba8603a7e0331048e272f3436bb13 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -82,7 +82,7 @@ module Gitlab
         end
 
         def open_file(path, name)
-          ::UploadedFile.new(path, name || File.basename(path), 'application/octet-stream')
+          ::UploadedFile.new(path, filename: name || File.basename(path), content_type: 'application/octet-stream')
         end
       end
 
diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb
index c26656704d7e9279948425fb1ff18b34afae1d7d..7f63e39b3aacdcb5862e37b21ecf4a38a9f0e87b 100644
--- a/lib/gitlab/middleware/read_only.rb
+++ b/lib/gitlab/middleware/read_only.rb
@@ -1,90 +1,19 @@
 module Gitlab
   module Middleware
     class ReadOnly
-      DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze
-      APPLICATION_JSON = 'application/json'.freeze
       API_VERSIONS = (3..4)
 
+      def self.internal_routes
+        @internal_routes ||=
+          API_VERSIONS.map { |version| "api/v#{version}/internal" }
+      end
+
       def initialize(app)
         @app = app
-        @whitelisted = internal_routes
       end
 
       def call(env)
-        @env = env
-        @route_hash = nil
-
-        if disallowed_request? && Gitlab::Database.read_only?
-          Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation')
-          error_message = 'You cannot do writing operations on a read-only GitLab instance'
-
-          if json_request?
-            return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]]
-          else
-            rack_flash.alert = error_message
-            rack_session['flash'] = rack_flash.to_session_value
-
-            return [301, { 'Location' => last_visited_url }, []]
-          end
-        end
-
-        @app.call(env)
-      end
-
-      private
-
-      def internal_routes
-        API_VERSIONS.flat_map { |version| "api/v#{version}/internal" }
-      end
-
-      def disallowed_request?
-        DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && !whitelisted_routes
-      end
-
-      def json_request?
-        request.media_type == APPLICATION_JSON
-      end
-
-      def rack_flash
-        @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session)
-      end
-
-      def rack_session
-        @env['rack.session']
-      end
-
-      def request
-        @env['rack.request'] ||= Rack::Request.new(@env)
-      end
-
-      def last_visited_url
-        @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url
-      end
-
-      def route_hash
-        @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {}
-      end
-
-      def whitelisted_routes
-        grack_route || @whitelisted.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route
-      end
-
-      def sidekiq_route
-        request.path.start_with?('/admin/sidekiq')
-      end
-
-      def grack_route
-        # Calling route_hash may be expensive. Only do it if we think there's a possible match
-        return false unless request.path.end_with?('.git/git-upload-pack')
-
-        route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack'
-      end
-
-      def lfs_route
-        # Calling route_hash may be expensive. Only do it if we think there's a possible match
-        return false unless request.path.end_with?('/info/lfs/objects/batch')
-
-        route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch'
+        ::Gitlab::Middleware::ReadOnly::Controller.new(@app, env).call
       end
     end
   end
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..45b644e6510b50f72bace49e97db16f3afd843c0
--- /dev/null
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -0,0 +1,86 @@
+module Gitlab
+  module Middleware
+    class ReadOnly
+      class Controller
+        DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze
+        APPLICATION_JSON = 'application/json'.freeze
+        ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'.freeze
+
+        def initialize(app, env)
+          @app = app
+          @env = env
+        end
+
+        def call
+          if disallowed_request? && Gitlab::Database.read_only?
+            Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation')
+
+            if json_request?
+              return [403, { 'Content-Type' => APPLICATION_JSON }, [{ 'message' => ERROR_MESSAGE }.to_json]]
+            else
+              rack_flash.alert = ERROR_MESSAGE
+              rack_session['flash'] = rack_flash.to_session_value
+
+              return [301, { 'Location' => last_visited_url }, []]
+            end
+          end
+
+          @app.call(@env)
+        end
+
+        private
+
+        def disallowed_request?
+          DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) &&
+            !whitelisted_routes
+        end
+
+        def json_request?
+          request.media_type == APPLICATION_JSON
+        end
+
+        def rack_flash
+          @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session)
+        end
+
+        def rack_session
+          @env['rack.session']
+        end
+
+        def request
+          @env['rack.request'] ||= Rack::Request.new(@env)
+        end
+
+        def last_visited_url
+          @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url
+        end
+
+        def route_hash
+          @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {}
+        end
+
+        def whitelisted_routes
+          grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route
+        end
+
+        def sidekiq_route
+          request.path.start_with?('/admin/sidekiq')
+        end
+
+        def grack_route
+          # Calling route_hash may be expensive. Only do it if we think there's a possible match
+          return false unless request.path.end_with?('.git/git-upload-pack')
+
+          route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack'
+        end
+
+        def lfs_route
+          # Calling route_hash may be expensive. Only do it if we think there's a possible match
+          return false unless request.path.end_with?('/info/lfs/objects/batch')
+
+          route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/middleware/release_env.rb b/lib/gitlab/middleware/release_env.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bfe8e113b5eee57856206d2f77a9bd22a8a2f742
--- /dev/null
+++ b/lib/gitlab/middleware/release_env.rb
@@ -0,0 +1,14 @@
+module Gitlab # rubocop:disable Naming/FileName
+  module Middleware
+    # Some of middleware would hold env for no good reason even after the
+    # request had already been processed, and we could not garbage collect
+    # them due to this. Put this middleware as the first middleware so that
+    # it would clear the env after the request is done, allowing GC gets a
+    # chance to release memory for the last request.
+    ReleaseEnv = Struct.new(:app) do
+      def call(env)
+        app.call(env).tap { env.clear }
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..35ed3a5ac058803d398920691211b82d6e3e19b6
--- /dev/null
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -0,0 +1,75 @@
+module Gitlab
+  class OmniauthInitializer
+    def initialize(devise_config)
+      @devise_config = devise_config
+    end
+
+    def execute(providers)
+      providers.each do |provider|
+        add_provider(provider['name'].to_sym, *arguments_for(provider))
+      end
+    end
+
+    private
+
+    def add_provider(*args)
+      @devise_config.omniauth(*args)
+    end
+
+    def arguments_for(provider)
+      provider_arguments = []
+
+      %w[app_id app_secret].each do |argument|
+        provider_arguments << provider[argument] if provider[argument]
+      end
+
+      case provider['args']
+      when Array
+        # An Array from the configuration will be expanded.
+        provider_arguments.concat provider['args']
+      when Hash
+        hash_arguments = provider['args'].merge(provider_defaults(provider))
+
+        # A Hash from the configuration will be passed as is.
+        provider_arguments << hash_arguments.symbolize_keys
+      end
+
+      provider_arguments
+    end
+
+    def provider_defaults(provider)
+      case provider['name']
+      when 'cas3'
+        { on_single_sign_out: cas3_signout_handler }
+      when 'authentiq'
+        { remote_sign_out_handler: authentiq_signout_handler }
+      when 'shibboleth'
+        { fail_with_empty_uid: true }
+      else
+        {}
+      end
+    end
+
+    def cas3_signout_handler
+      lambda do |request|
+        ticket = request.params[:session_index]
+        raise "Service Ticket not found." unless Gitlab::Auth::OAuth::Session.valid?(:cas3, ticket)
+
+        Gitlab::Auth::OAuth::Session.destroy(:cas3, ticket)
+        true
+      end
+    end
+
+    def authentiq_signout_handler
+      lambda do |request|
+        authentiq_session = request.params['sid']
+        if Gitlab::Auth::OAuth::Session.valid?(:authentiq, authentiq_session)
+          Gitlab::Auth::OAuth::Session.destroy(:authentiq, authentiq_session)
+          true
+        else
+          false
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
index 1d9a5d1a20a7c91d7fd5283bb6f4d7001f3cbb17..d09bce642b0953f06413da755e18e3efe110d647 100644
--- a/lib/gitlab/optimistic_locking.rb
+++ b/lib/gitlab/optimistic_locking.rb
@@ -3,18 +3,15 @@ module Gitlab
     module_function
 
     def retry_lock(subject, retries = 100, &block)
-      loop do
-        begin
-          ActiveRecord::Base.transaction do
-            return yield(subject)
-          end
-        rescue ActiveRecord::StaleObjectError
-          retries -= 1
-          raise unless retries >= 0
-
-          subject.reload
-        end
+      ActiveRecord::Base.transaction do
+        yield(subject)
       end
+    rescue ActiveRecord::StaleObjectError
+      retries -= 1
+      raise unless retries >= 0
+
+      subject.reload
+      retry
     end
 
     alias_method :retry_optimistic_lock, :retry_lock
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index 6c2b20360748f4605d184c09e88cffb231d60398..92a308a12dc9a7bfc23e004338da1caa0173f3ba 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -5,6 +5,7 @@ module Gitlab
 
     def self.enabled?(user = nil)
       return true if Rails.env.development?
+      return true if user&.admin?
       return false unless user && allowed_group_id
 
       allowed_user_ids.include?(user.id)
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 98a168b43bb2da808dae3115fd00320d4137ea50..18540e64d4c62edf5e7308fde495359799a4e5ae 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -92,8 +92,8 @@ module Gitlab
 
             if type && time
               @load_times_by_model ||= {}
-              @load_times_by_model[type] ||= 0
-              @load_times_by_model[type] += time.to_f
+              @load_times_by_model[type] ||= []
+              @load_times_by_model[type] << time.to_f
             end
 
             super
@@ -135,8 +135,12 @@ module Gitlab
     def self.log_load_times_by_model(logger)
       return unless logger.respond_to?(:load_times_by_model)
 
-      logger.load_times_by_model.to_a.sort_by(&:last).reverse.each do |(model, time)|
-        logger.info("#{model} total: #{time.round(2)}ms")
+      summarised_load_times = logger.load_times_by_model.to_a.map do |(model, times)|
+        [model, times.count, times.sum]
+      end
+
+      summarised_load_times.sort_by(&:last).reverse.each do |(model, query_count, time)|
+        logger.info("#{model} total (#{query_count}): #{time.round(2)}ms")
       end
     end
   end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index cf0935dbd9aba52135fd0b24af4165a03061844f..390efda326a7db4d7b0c0988fb826803a355c33d 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -29,8 +29,18 @@ module Gitlab
       @blobs_count ||= blobs.count
     end
 
-    def notes_count
-      @notes_count ||= notes.count
+    def limited_notes_count
+      return @limited_notes_count if defined?(@limited_notes_count)
+
+      types = %w(issue merge_request commit snippet)
+      @limited_notes_count = 0
+
+      types.each do |type|
+        @limited_notes_count += notes_finder(type).limit(count_limit).count
+        break if @limited_notes_count >= count_limit
+      end
+
+      @limited_notes_count
     end
 
     def wiki_blobs_count
@@ -48,7 +58,7 @@ module Gitlab
       data = ""
       startline = 0
 
-      result.strip.each_line.each_with_index do |line, index|
+      result.each_line.each_with_index do |line, index|
         prefix ||= line.match(/^(?<ref>[^:]*):(?<filename>.*)\x00(?<startline>\d+)\x00/)&.tap do |matches|
           ref = matches[:ref]
           filename = matches[:filename]
@@ -72,11 +82,12 @@ module Gitlab
     end
 
     def single_commit_result?
-      commits_count == 1 && total_result_count == 1
-    end
+      return false if commits_count != 1
 
-    def total_result_count
-      issues_count + merge_requests_count + milestones_count + notes_count + blobs_count + wiki_blobs_count + commits_count
+      counts = %i(limited_milestones_count limited_notes_count
+                  limited_merge_requests_count limited_issues_count
+                  blobs_count wiki_blobs_count)
+      counts.all? { |count_method| public_send(count_method).zero? } # rubocop:disable GitlabSecurity/PublicSend
     end
 
     private
@@ -106,7 +117,11 @@ module Gitlab
     end
 
     def notes
-      @notes ||= NotesFinder.new(project, @current_user, search: query).execute.user.order('updated_at DESC')
+      @notes ||= notes_finder(nil)
+    end
+
+    def notes_finder(type)
+      NotesFinder.new(project, @current_user, search: query, target_type: type).execute.user.order('updated_at DESC')
     end
 
     def commits
diff --git a/lib/gitlab/project_transfer.rb b/lib/gitlab/project_transfer.rb
index 1bba0b78e2ff6ef21cbe4a5a7c551d5e52abe49b..690c38737c0c455e78fd6476f461b9956326a108 100644
--- a/lib/gitlab/project_transfer.rb
+++ b/lib/gitlab/project_transfer.rb
@@ -1,13 +1,19 @@
 module Gitlab
+  # This class is used to move local, unhashed files owned by projects to their new location
   class ProjectTransfer
-    def move_project(project_path, namespace_path_was, namespace_path)
-      new_namespace_folder = File.join(root_dir, namespace_path)
-      FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
-      from = File.join(root_dir, namespace_path_was, project_path)
-      to = File.join(root_dir, namespace_path, project_path)
+    # nil parent_path (or parent_path_was) represents a root namespace
+    def move_namespace(path, parent_path_was, parent_path)
+      parent_path_was ||= ''
+      parent_path ||= ''
+      new_parent_folder = File.join(root_dir, parent_path)
+      FileUtils.mkdir_p(new_parent_folder)
+      from = File.join(root_dir, parent_path_was, path)
+      to = File.join(root_dir, parent_path, path)
       move(from, to, "")
     end
 
+    alias_method :move_project, :move_namespace
+
     def rename_project(path_was, path, namespace_path)
       base_dir = File.join(root_dir, namespace_path)
       move(path_was, path, base_dir)
diff --git a/lib/gitlab/prometheus/additional_metrics_parser.rb b/lib/gitlab/prometheus/additional_metrics_parser.rb
index cb95daf22602b68b3aea27aeab7aabe0508cee06..bb1172f82a192fa0c6409ee38c90b8dbc8ec2ecf 100644
--- a/lib/gitlab/prometheus/additional_metrics_parser.rb
+++ b/lib/gitlab/prometheus/additional_metrics_parser.rb
@@ -1,10 +1,12 @@
 module Gitlab
   module Prometheus
     module AdditionalMetricsParser
+      CONFIG_ROOT = 'config/prometheus'.freeze
+      MUTEX = Mutex.new
       extend self
 
-      def load_groups_from_yaml
-        additional_metrics_raw.map(&method(:group_from_entry))
+      def load_groups_from_yaml(file_name = 'additional_metrics.yml')
+        yaml_metrics_raw(file_name).map(&method(:group_from_entry))
       end
 
       private
@@ -22,13 +24,20 @@ module Gitlab
         MetricGroup.new(entry).tap(&method(:validate!))
       end
 
-      def additional_metrics_raw
-        load_yaml_file&.map(&:deep_symbolize_keys).freeze
+      def yaml_metrics_raw(file_name)
+        load_yaml_file(file_name)&.map(&:deep_symbolize_keys).freeze
       end
 
-      def load_yaml_file
-        @loaded_yaml_file ||= YAML.load_file(Rails.root.join('config/prometheus/additional_metrics.yml'))
+      # rubocop:disable Gitlab/ModuleWithInstanceVariables
+      def load_yaml_file(file_name)
+        return YAML.load_file(Rails.root.join(CONFIG_ROOT, file_name)) if Rails.env.development?
+
+        MUTEX.synchronize do
+          @loaded_yaml_cache ||= {}
+          @loaded_yaml_cache[file_name] ||= YAML.load_file(Rails.root.join(CONFIG_ROOT, file_name))
+        end
       end
+      # rubocop:enable Gitlab/ModuleWithInstanceVariables
     end
   end
 end
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
index 972ab75d1d5c5c1beb0a5da89e8de548d5a09c55..e677ec84cd4f5837e7f959fdfae8c7ae1f5cc2ea 100644
--- a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
+++ b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
@@ -4,7 +4,7 @@ module Gitlab
       class AdditionalMetricsDeploymentQuery < BaseQuery
         include QueryAdditionalMetrics
 
-        def query(environment_id, deployment_id)
+        def query(deployment_id)
           Deployment.find_by(id: deployment_id).try do |deployment|
             query_metrics(
               deployment.project,
diff --git a/lib/gitlab/prometheus/queries/base_query.rb b/lib/gitlab/prometheus/queries/base_query.rb
index c60828165bd9bcd09d3319530a006fb31ed3ad7b..29cab6e9c15e28f463c8d795e89b465bcc345677 100644
--- a/lib/gitlab/prometheus/queries/base_query.rb
+++ b/lib/gitlab/prometheus/queries/base_query.rb
@@ -20,6 +20,10 @@ module Gitlab
         def query(*args)
           raise NotImplementedError
         end
+
+        def self.transform_reactive_result(result)
+          result
+        end
       end
     end
   end
diff --git a/lib/gitlab/prometheus/queries/deployment_query.rb b/lib/gitlab/prometheus/queries/deployment_query.rb
index 6e6da593178ecf5730d18846dce4b7c9cbcae742..c2626581897a593849911c6bc1cd49c6f20c3f04 100644
--- a/lib/gitlab/prometheus/queries/deployment_query.rb
+++ b/lib/gitlab/prometheus/queries/deployment_query.rb
@@ -2,7 +2,7 @@ module Gitlab
   module Prometheus
     module Queries
       class DeploymentQuery < BaseQuery
-        def query(environment_id, deployment_id)
+        def query(deployment_id)
           Deployment.find_by(id: deployment_id).try do |deployment|
             environment_slug = deployment.environment.slug
 
@@ -25,6 +25,11 @@ module Gitlab
             }
           end
         end
+
+        def self.transform_reactive_result(result)
+          result[:metrics] = result.delete :data
+          result
+        end
       end
     end
   end
diff --git a/lib/gitlab/prometheus/queries/environment_query.rb b/lib/gitlab/prometheus/queries/environment_query.rb
index 1d17d3cfd56c676d2505219c6f7fc9e39ad61304..b62910c8de675fa6d271e60d3f308e802506fcb7 100644
--- a/lib/gitlab/prometheus/queries/environment_query.rb
+++ b/lib/gitlab/prometheus/queries/environment_query.rb
@@ -19,6 +19,11 @@ module Gitlab
             }
           end
         end
+
+        def self.transform_reactive_result(result)
+          result[:metrics] = result.delete :data
+          result
+        end
       end
     end
   end
diff --git a/lib/gitlab/prometheus/queries/matched_metric_query.rb b/lib/gitlab/prometheus/queries/matched_metric_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d920e9a749fed706f64fe7300002c65d78945d8f
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/matched_metric_query.rb
@@ -0,0 +1,80 @@
+module Gitlab
+  module Prometheus
+    module Queries
+      class MatchedMetricQuery < 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.common_metrics)
+          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/matched_metrics_query.rb b/lib/gitlab/prometheus/queries/matched_metrics_query.rb
deleted file mode 100644
index 5710ad47c1a33b1601d07c968b71ea120ee02f66..0000000000000000000000000000000000000000
--- a/lib/gitlab/prometheus/queries/matched_metrics_query.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-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.common_metrics)
-          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
index 0c280dc9a3c5fd68640e25a64c2a9515f5931b9f..f5879de1e942baf04d85076d428513e9a71d8ae6 100644
--- a/lib/gitlab/prometheus/queries/query_additional_metrics.rb
+++ b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
@@ -3,9 +3,16 @@ module Gitlab
     module Queries
       module QueryAdditionalMetrics
         def query_metrics(project, query_context)
+          matched_metrics(project).map(&query_group(query_context))
+            .select(&method(:group_with_any_metrics))
+        end
+
+        protected
+
+        def query_group(query_context)
           query_processor = method(:process_query).curry[query_context]
 
-          groups = matched_metrics(project).map do |group|
+          lambda do |group|
             metrics = group.metrics.map do |metric|
               {
                 title: metric.title,
@@ -21,8 +28,6 @@ module Gitlab
               metrics: metrics.select(&method(:metric_with_any_queries))
             }
           end
-
-          groups.select(&method(:group_with_any_metrics))
         end
 
         private
@@ -72,12 +77,17 @@ module Gitlab
         end
 
         def common_query_context(environment, timeframe_start:, timeframe_end:)
-          {
-            timeframe_start: timeframe_start,
-            timeframe_end: timeframe_end,
+          base_query_context(timeframe_start, timeframe_end).merge({
             ci_environment_slug: environment.slug,
-            kube_namespace: environment.project.deployment_platform&.actual_namespace || '',
+            kube_namespace: environment.deployment_platform&.actual_namespace || '',
             environment_filter: %{container_name!="POD",environment="#{environment.slug}"}
+          })
+        end
+
+        def base_query_context(timeframe_start, timeframe_end)
+          {
+            timeframe_start: timeframe_start,
+            timeframe_end: timeframe_end
           }
         end
       end
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index 659021c9ac92a2e79b39bc3c79f82812440ca542..b66253a10e0e39c2abe44841556d777073b57b22 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -57,7 +57,11 @@ module Gitlab
     rescue OpenSSL::SSL::SSLError
       raise PrometheusClient::Error, "#{rest_client.url} contains invalid SSL data"
     rescue RestClient::ExceptionWithResponse => ex
-      handle_exception_response(ex.response)
+      if ex.response
+        handle_exception_response(ex.response)
+      else
+        raise PrometheusClient::Error, "Network connection error"
+      end
     rescue RestClient::Exception
       raise PrometheusClient::Error, "Network connection error"
     end
diff --git a/lib/gitlab/proxy_http_connection_adapter.rb b/lib/gitlab/proxy_http_connection_adapter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d682289b632cd4ff121ef1fd40a0d9e058306b22
--- /dev/null
+++ b/lib/gitlab/proxy_http_connection_adapter.rb
@@ -0,0 +1,34 @@
+# This class is part of the Gitlab::HTTP wrapper. Depending on the value
+# of the global setting allow_local_requests_from_hooks_and_services this adapter
+# will allow/block connection to internal IPs and/or urls.
+#
+# This functionality can be overriden by providing the setting the option
+# allow_local_requests = true in the request. For example:
+# Gitlab::HTTP.get('http://www.gitlab.com', allow_local_requests: true)
+#
+# This option will take precedence over the global setting.
+module Gitlab
+  class ProxyHTTPConnectionAdapter < HTTParty::ConnectionAdapter
+    def connection
+      unless allow_local_requests?
+        begin
+          Gitlab::UrlBlocker.validate!(uri, allow_local_network: false)
+        rescue Gitlab::UrlBlocker::BlockedUrlError => e
+          raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
+        end
+      end
+
+      super
+    end
+
+    private
+
+    def allow_local_requests?
+      options.fetch(:allow_local_requests, allow_settings_local_requests?)
+    end
+
+    def allow_settings_local_requests?
+      Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
+    end
+  end
+end
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 79265cf952d7ebb8efa22019d5915fcb3f5633f1..1fa2a19b0af1d269be81040759ad06ba48cf5fc6 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -21,11 +21,11 @@ module Gitlab
       result = repo_path
 
       storage = Gitlab.config.repositories.storages.values.find do |params|
-        repo_path.start_with?(params['path'])
+        repo_path.start_with?(params.legacy_disk_path)
       end
 
       if storage
-        result = result.sub(storage['path'], '')
+        result = result.sub(storage.legacy_disk_path, '')
       elsif fail_on_not_found
         raise NotFoundError.new("No known storage path matches #{repo_path.inspect}")
       end
diff --git a/lib/gitlab/repository_cache.rb b/lib/gitlab/repository_cache.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b1bf3ca4143b94020696cf5c819a5fcda7d91215
--- /dev/null
+++ b/lib/gitlab/repository_cache.rb
@@ -0,0 +1,33 @@
+# Interface to the Redis-backed cache store
+module Gitlab
+  class RepositoryCache
+    attr_reader :repository, :namespace, :backend
+
+    def initialize(repository, extra_namespace: nil, backend: Rails.cache)
+      @repository = repository
+      @namespace = "#{repository.full_path}:#{repository.project.id}"
+      @namespace += ":#{extra_namespace}" if extra_namespace
+      @backend = backend
+    end
+
+    def cache_key(type)
+      "#{type}:#{namespace}"
+    end
+
+    def expire(key)
+      backend.delete(cache_key(key))
+    end
+
+    def fetch(key, &block)
+      backend.fetch(cache_key(key), &block)
+    end
+
+    def exist?(key)
+      backend.exist?(cache_key(key))
+    end
+
+    def read(key)
+      backend.read(cache_key(key))
+    end
+  end
+end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7f64a8c9e46268d62da175e39e1876a632150cf8
--- /dev/null
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -0,0 +1,84 @@
+module Gitlab
+  module RepositoryCacheAdapter
+    extend ActiveSupport::Concern
+
+    class_methods do
+      # Wraps around the given method and caches its output in Redis and an instance
+      # variable.
+      #
+      # This only works for methods that do not take any arguments.
+      def cache_method(name, fallback: nil, memoize_only: false)
+        original = :"_uncached_#{name}"
+
+        alias_method(original, name)
+
+        define_method(name) do
+          cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
+            __send__(original) # rubocop:disable GitlabSecurity/PublicSend
+          end
+        end
+      end
+    end
+
+    # RepositoryCache to be used. Should be overridden by the including class
+    def cache
+      raise NotImplementedError
+    end
+
+    # Caches the supplied block both in a cache and in an instance variable.
+    #
+    # The cache key and instance variable are named the same way as the value of
+    # the `key` argument.
+    #
+    # This method will return `nil` if the corresponding instance variable is also
+    # set to `nil`. This ensures we don't keep yielding the block when it returns
+    # `nil`.
+    #
+    # key - The name of the key to cache the data in.
+    # fallback - A value to fall back to in the event of a Git error.
+    def cache_method_output(key, fallback: nil, memoize_only: false, &block)
+      ivar = cache_instance_variable_name(key)
+
+      if instance_variable_defined?(ivar)
+        instance_variable_get(ivar)
+      else
+        # If the repository doesn't exist and a fallback was specified we return
+        # that value inmediately. This saves us Rugged/gRPC invocations.
+        return fallback unless fallback.nil? || cache.repository.exists?
+
+        begin
+          value =
+            if memoize_only
+              yield
+            else
+              cache.fetch(key, &block)
+            end
+
+          instance_variable_set(ivar, value)
+        rescue Gitlab::Git::Repository::NoRepository
+          # Even if the above `#exists?` check passes these errors might still
+          # occur (for example because of a non-existing HEAD). We want to
+          # gracefully handle this and not cache anything
+          fallback
+        end
+      end
+    end
+
+    # Expires the caches of a specific set of methods
+    def expire_method_caches(methods)
+      methods.each do |key|
+        cache.expire(key)
+
+        ivar = cache_instance_variable_name(key)
+
+        remove_instance_variable(ivar) if instance_variable_defined?(ivar)
+      end
+    end
+
+    private
+
+    def cache_instance_variable_name(key)
+      :"@#{key.to_s.tr('?!', '')}"
+    end
+  end
+end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 781783f4d9797066c0eae0f67af47bb798b2fc7c..1e45d074e0a2efc0efc7341ab207c2573a4ed4b8 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -7,8 +7,8 @@ module Gitlab
 
       def initialize(opts = {})
         @id = opts.fetch(:id, nil)
-        @filename = opts.fetch(:filename, nil)
-        @basename = opts.fetch(:basename, nil)
+        @filename = encode_utf8(opts.fetch(:filename, nil))
+        @basename = encode_utf8(opts.fetch(:basename, nil))
         @ref = opts.fetch(:ref, nil)
         @startline = opts.fetch(:startline, nil)
         @data = encode_utf8(opts.fetch(:data, nil))
@@ -62,22 +62,6 @@ module Gitlab
       without_count ? collection.without_count : collection
     end
 
-    def projects_count
-      @projects_count ||= projects.count
-    end
-
-    def issues_count
-      @issues_count ||= issues.count
-    end
-
-    def merge_requests_count
-      @merge_requests_count ||= merge_requests.count
-    end
-
-    def milestones_count
-      @milestones_count ||= milestones.count
-    end
-
     def limited_projects_count
       @limited_projects_count ||= projects.limit(count_limit).count
     end
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 4a22fc80f75bd08240d7a1128b8a7a20554de116..6381e94c1d215d065cad1e25970aaaf6c9a68eaa 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -18,6 +18,25 @@ module Gitlab
       end
     end
 
+    # This can be used for investigating exceptions that can be recovered from in
+    # code. The exception will still be raised in development and test
+    # environments.
+    #
+    # That way we can track down these exceptions with as much information as we
+    # need to resolve them.
+    #
+    # Provide an issue URL for follow up.
+    def self.track_exception(exception, issue_url: nil, extra: {})
+      if enabled?
+        extra[:issue_url] = issue_url if issue_url
+        context # Make sure we've set everything we know in the context
+
+        Raven.capture_exception(exception, extra: extra)
+      end
+
+      raise exception if should_raise?
+    end
+
     def self.program_context
       if Sidekiq.server?
         'sidekiq'
@@ -25,5 +44,9 @@ module Gitlab
         'rails'
       end
     end
+
+    def self.should_raise?
+      Rails.env.development? || Rails.env.test?
+    end
   end
 end
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 07d7c91cb5d80246045cc4749e6420da220a1298..e5c02dd8ecc9c5f05ce2d41ad5986fc1f83970f7 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -24,7 +24,7 @@ module Gitlab
             address = val['gitaly_address']
           end
 
-          storages << { name: key, path: val['path'] }
+          storages << { name: key, path: val.legacy_disk_path }
         end
 
         if Rails.env.test?
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 4ba44e0feefc5098dca69ad97ffac2166d721aae..ac4ac537a8ac14fceac4c675f2ef3d0de2c168ea 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -69,35 +69,36 @@ module Gitlab
     # name - project disk path
     #
     # Ex.
-    #   add_repository("/path/to/storage", "gitlab/gitlab-ci")
+    #   create_repository("/path/to/storage", "gitlab/gitlab-ci")
     #
-    def add_repository(storage, name)
+    def create_repository(storage, name)
       relative_path = name.dup
       relative_path << '.git' unless relative_path.end_with?('.git')
 
-      gitaly_migrate(:create_repository) do |is_enabled|
+      gitaly_migrate(:create_repository,
+                     status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
         if is_enabled
           repository = Gitlab::Git::Repository.new(storage, relative_path, '')
           repository.gitaly_repository_client.create_repository
           true
         else
-          repo_path = File.join(Gitlab.config.repositories.storages[storage]['path'], relative_path)
+          repo_path = File.join(Gitlab.config.repositories.storages[storage].legacy_disk_path, relative_path)
           Gitlab::Git::Repository.create(repo_path, bare: true, symlink_hooks_to: gitlab_shell_hooks_path)
         end
       end
-    rescue => err
+    rescue => err # Once the Rugged codes gets removes this can be improved
       Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}")
       false
     end
 
     # Import repository
     #
-    # storage - project's storage path
+    # storage - project's storage name
     # name - project disk path
     # url - URL to import from
     #
     # Ex.
-    #   import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://gitlab.com/gitlab-org/gitlab-test.git")
+    #   import_repository("nfs-file06", "gitlab/gitlab-ci", "https://gitlab.com/gitlab-org/gitlab-test.git")
     #
     # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/874
     def import_repository(storage, name, url)
@@ -125,13 +126,12 @@ module Gitlab
     # Ex.
     #   fetch_remote(my_repo, "upstream")
     #
-    def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false)
+    def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false, prune: true)
       gitaly_migrate(:fetch_remote) do |is_enabled|
         if is_enabled
-          repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, timeout: git_timeout)
+          repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, timeout: git_timeout, prune: prune)
         else
-          storage_path = Gitlab.config.repositories.storages[repository.storage]["path"]
-          local_fetch_remote(storage_path, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
+          local_fetch_remote(repository.storage, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, prune: prune)
         end
       end
     end
@@ -155,13 +155,13 @@ module Gitlab
     end
 
     # Fork repository to new path
-    # forked_from_storage - forked-from project's storage path
-    # forked_from_disk_path - project disk path
-    # forked_to_storage - forked-to project's storage path
-    # forked_to_disk_path - forked project disk path
+    # forked_from_storage - forked-from project's storage name
+    # forked_from_disk_path - project disk relative path
+    # forked_to_storage - forked-to project's storage name
+    # forked_to_disk_path - forked project disk relative path
     #
     # Ex.
-    #  fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "new-namespace/gitlab-ci")
+    #  fork_repository("nfs-file06", "gitlab/gitlab-ci", "nfs-file07", "new-namespace/gitlab-ci")
     #
     # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/817
     def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path)
@@ -340,7 +340,7 @@ module Gitlab
         if enabled
           gitaly_namespace_client(storage).rename(old_name, new_name)
         else
-          return false if exists?(storage, new_name) || !exists?(storage, old_name)
+          break false if exists?(storage, new_name) || !exists?(storage, old_name)
 
           FileUtils.mv(full_path(storage, old_name), full_path(storage, new_name))
         end
@@ -419,17 +419,17 @@ module Gitlab
 
     private
 
-    def gitlab_projects(shard_path, disk_path)
+    def gitlab_projects(shard_name, disk_path)
       Gitlab::Git::GitlabProjects.new(
-        shard_path,
+        shard_name,
         disk_path,
         global_hooks_path: Gitlab.config.gitlab_shell.hooks_path,
         logger: Rails.logger
       )
     end
 
-    def local_fetch_remote(storage_path, repository_relative_path, remote, ssh_auth: nil, forced: false, no_tags: false)
-      vars = { force: forced, tags: !no_tags }
+    def local_fetch_remote(storage_name, repository_relative_path, remote, ssh_auth: nil, forced: false, no_tags: false, prune: true)
+      vars = { force: forced, tags: !no_tags, prune: prune }
 
       if ssh_auth&.ssh_import?
         if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present?
@@ -441,7 +441,7 @@ module Gitlab
         end
       end
 
-      cmd = gitlab_projects(storage_path, repository_relative_path)
+      cmd = gitlab_projects(storage_name, repository_relative_path)
 
       success = cmd.fetch_remote(remote, git_timeout, vars)
 
@@ -477,7 +477,7 @@ module Gitlab
 
     def gitaly_namespace_client(storage_path)
       storage, _value = Gitlab.config.repositories.storages.find do |storage, value|
-        value['path'] == storage_path
+        value.legacy_disk_path == storage_path
       end
 
       Gitlab::GitalyClient::NamespaceService.new(storage)
@@ -487,8 +487,8 @@ module Gitlab
       Gitlab.config.gitlab_shell.git_timeout
     end
 
-    def gitaly_migrate(method, &block)
-      Gitlab::GitalyClient.migrate(method, &block)
+    def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block)
+      Gitlab::GitalyClient.migrate(method, status: status, &block)
     rescue GRPC::NotFound, GRPC::BadStatus => e
       # Old Popen code returns [Error, output] to the caller, so we
       # need to do the same here...
diff --git a/lib/gitlab/sidekiq_logging/json_formatter.rb b/lib/gitlab/sidekiq_logging/json_formatter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..98f8222fd035c2f2e84dfe3e2f4ca538521d407e
--- /dev/null
+++ b/lib/gitlab/sidekiq_logging/json_formatter.rb
@@ -0,0 +1,21 @@
+module Gitlab
+  module SidekiqLogging
+    class JSONFormatter
+      def call(severity, timestamp, progname, data)
+        output = {
+          severity: severity,
+          time: timestamp.utc.iso8601(3)
+        }
+
+        case data
+        when String
+          output[:message] = data
+        when Hash
+          output.merge!(data)
+        end
+
+        output.to_json + "\n"
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9a89ae70b9867befc5d54eb017513f8baf8de010
--- /dev/null
+++ b/lib/gitlab/sidekiq_logging/structured_logger.rb
@@ -0,0 +1,96 @@
+module Gitlab
+  module SidekiqLogging
+    class StructuredLogger
+      START_TIMESTAMP_FIELDS = %w[created_at enqueued_at].freeze
+      DONE_TIMESTAMP_FIELDS = %w[started_at retried_at failed_at completed_at].freeze
+
+      def call(job, queue)
+        started_at = current_time
+        base_payload = parse_job(job)
+
+        Sidekiq.logger.info log_job_start(started_at, base_payload)
+
+        yield
+
+        Sidekiq.logger.info log_job_done(started_at, base_payload)
+      rescue => job_exception
+        Sidekiq.logger.warn log_job_done(started_at, base_payload, job_exception)
+
+        raise
+      end
+
+      private
+
+      def base_message(payload)
+        "#{payload['class']} JID-#{payload['jid']}"
+      end
+
+      def log_job_start(started_at, payload)
+        payload['message'] = "#{base_message(payload)}: start"
+        payload['job_status'] = 'start'
+
+        payload
+      end
+
+      def log_job_done(started_at, payload, job_exception = nil)
+        payload = payload.dup
+        payload['duration'] = elapsed(started_at)
+        payload['completed_at'] = Time.now.utc
+
+        message = base_message(payload)
+
+        if job_exception
+          payload['message'] = "#{message}: fail: #{payload['duration']} sec"
+          payload['job_status'] = 'fail'
+          payload['error_message'] = job_exception.message
+          payload['error'] = job_exception.class
+          payload['error_backtrace'] = backtrace_cleaner.clean(job_exception.backtrace)
+        else
+          payload['message'] = "#{message}: done: #{payload['duration']} sec"
+          payload['job_status'] = 'done'
+        end
+
+        convert_to_iso8601(payload, DONE_TIMESTAMP_FIELDS)
+
+        payload
+      end
+
+      def parse_job(job)
+        job = job.dup
+
+        # Add process id params
+        job['pid'] = ::Process.pid
+
+        job.delete('args') unless ENV['SIDEKIQ_LOG_ARGUMENTS']
+
+        convert_to_iso8601(job, START_TIMESTAMP_FIELDS)
+
+        job
+      end
+
+      def convert_to_iso8601(payload, keys)
+        keys.each do |key|
+          payload[key] = format_time(payload[key]) if payload[key]
+        end
+      end
+
+      def elapsed(start)
+        (current_time - start).round(3)
+      end
+
+      def current_time
+        Gitlab::Metrics::System.monotonic_time
+      end
+
+      def backtrace_cleaner
+        @backtrace_cleaner ||= ActiveSupport::BacktraceCleaner.new
+      end
+
+      def format_time(timestamp)
+        return timestamp if timestamp.is_a?(String)
+
+        Time.at(timestamp).utc.iso8601(3)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/sidekiq_middleware/shutdown.rb b/lib/gitlab/sidekiq_middleware/shutdown.rb
index c2b8d6de66e752733bf3663ef75396f0d4747162..b232ac4da332f625f3c63216d9285e478f2949ad 100644
--- a/lib/gitlab/sidekiq_middleware/shutdown.rb
+++ b/lib/gitlab/sidekiq_middleware/shutdown.rb
@@ -25,7 +25,7 @@ module Gitlab
       # can be only one shutdown thread in the process.
       def self.create_shutdown_thread
         mu_synchronize do
-          return unless @shutdown_thread.nil?
+          break unless @shutdown_thread.nil?
 
           @shutdown_thread = Thread.new { yield }
         end
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 85aaa6b0eba32b9d2a808cec0e2ad6e6f715b438..bb778f37096db2b9df9f3a1cdc85836655371556 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -5,6 +5,7 @@ module Gitlab
         Gitlab::SlashCommands::IssueShow,
         Gitlab::SlashCommands::IssueNew,
         Gitlab::SlashCommands::IssueSearch,
+        Gitlab::SlashCommands::IssueMove,
         Gitlab::SlashCommands::Deploy
       ].freeze
 
diff --git a/lib/gitlab/slash_commands/issue_move.rb b/lib/gitlab/slash_commands/issue_move.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3985e63598354fb638f8d8982ee1d6f7b304f4ee
--- /dev/null
+++ b/lib/gitlab/slash_commands/issue_move.rb
@@ -0,0 +1,45 @@
+module Gitlab
+  module SlashCommands
+    class IssueMove < IssueCommand
+      def self.match(text)
+        %r{
+          \A                                 # the beginning of a string
+          issue\s+move\s+                    # the command
+          \#?(?<iid>\d+)\s+                  # the issue id, may preceded by hash sign
+          (to\s+)?                           # aid the command to be much more human-ly
+          (?<project_path>[^\s]+)            # named group for id of dest. project
+        }x.match(text)
+      end
+
+      def self.help_message
+        'issue move <issue_id> (to)? <project_path>'
+      end
+
+      def self.allowed?(project, user)
+        can?(user, :admin_issue, project)
+      end
+
+      def execute(match)
+        old_issue = find_by_iid(match[:iid])
+        target_project = Project.find_by_full_path(match[:project_path])
+
+        unless current_user.can?(:read_project, target_project) && old_issue
+          return Gitlab::SlashCommands::Presenters::Access.new.not_found
+        end
+
+        new_issue = Issues::MoveService.new(project, current_user)
+                      .execute(old_issue, target_project)
+
+        presenter(new_issue).present(old_issue)
+      rescue Issues::MoveService::MoveError => e
+        presenter(old_issue).display_move_error(e.message)
+      end
+
+      private
+
+      def presenter(issue)
+        Gitlab::SlashCommands::Presenters::IssueMove.new(issue)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/slash_commands/presenters/issue_move.rb b/lib/gitlab/slash_commands/presenters/issue_move.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03921729941b85c1482119fc927fd1b49b087737
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/issue_move.rb
@@ -0,0 +1,53 @@
+# coding: utf-8
+module Gitlab
+  module SlashCommands
+    module Presenters
+      class IssueMove < Presenters::Base
+        include Presenters::IssueBase
+
+        def present(old_issue)
+          in_channel_response(moved_issue(old_issue))
+        end
+
+        def display_move_error(error)
+          message = header_with_list("The action was not successful, because:", [error])
+
+          ephemeral_response(text: message)
+        end
+
+        private
+
+        def moved_issue(old_issue)
+          {
+            attachments: [
+              {
+                title:        "#{@resource.title} 路 #{@resource.to_reference}",
+                title_link:   resource_url,
+                author_name:  author.name,
+                author_icon:  author.avatar_url,
+                fallback:     "Issue #{@resource.to_reference}: #{@resource.title}",
+                pretext:      pretext(old_issue),
+                color:        color(@resource),
+                fields:       fields,
+                mrkdwn_in: [
+                  :title,
+                  :pretext,
+                  :text,
+                  :fields
+                ]
+              }
+            ]
+          }
+        end
+
+        def pretext(old_issue)
+          "Moved issue *#{issue_link(old_issue)}* to *#{issue_link(@resource)}*"
+        end
+
+        def issue_link(issue)
+          "[#{issue.to_reference}](#{project_issue_url(issue.project, issue)})"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/slash_commands/presenters/issue_new.rb b/lib/gitlab/slash_commands/presenters/issue_new.rb
index 86490a39cc183e96789822ea97d97cd8d0d8a6dc..5964bfe996079fe6b8ee6a6499408a2e173a42ad 100644
--- a/lib/gitlab/slash_commands/presenters/issue_new.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_new.rb
@@ -38,7 +38,7 @@ module Gitlab
         end
 
         def project_link
-          "[#{project.name_with_namespace}](#{project.web_url})"
+          "[#{project.full_name}](#{project.web_url})"
         end
 
         def author_profile_link
diff --git a/lib/gitlab/slash_commands/presenters/issue_show.rb b/lib/gitlab/slash_commands/presenters/issue_show.rb
index c99316df66701ee51cdfa4db4ea1df61c4dd764d..562f15f403cd014fab5dc8bbd1914b15081f0189 100644
--- a/lib/gitlab/slash_commands/presenters/issue_show.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_show.rb
@@ -53,7 +53,7 @@ module Gitlab
         end
 
         def pretext
-          "Issue *#{@resource.to_reference}* from #{project.name_with_namespace}"
+          "Issue *#{@resource.to_reference}* from #{project.full_name}"
         end
       end
     end
diff --git a/lib/gitlab/slash_commands/result.rb b/lib/gitlab/slash_commands/result.rb
index 7021b4b01b2b20254fdd7902a1a5cae151872a5c..3669dedf0fe9f8c2fcfcf64c82d359bd1ee3c0b6 100644
--- a/lib/gitlab/slash_commands/result.rb
+++ b/lib/gitlab/slash_commands/result.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module Gitlab # rubocop:disable Naming/FileName
   module SlashCommands
     Result = Struct.new(:type, :message)
   end
diff --git a/lib/gitlab/string_placeholder_replacer.rb b/lib/gitlab/string_placeholder_replacer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9a2219b7d778d0549b51d0451de052dd9d41834c
--- /dev/null
+++ b/lib/gitlab/string_placeholder_replacer.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  class StringPlaceholderReplacer
+    # This method accepts the following paras
+    # - string: the string to be analyzed
+    # - placeholder_regex: i.e. /%{project_path|project_id|default_branch|commit_sha}/
+    # - block: this block will be called with each placeholder found in the string using
+    # the placeholder regex. If the result of the block is nil, the original
+    # placeholder will be returned.
+
+    def self.replace_string_placeholders(string, placeholder_regex = nil, &block)
+      return string if string.blank? || placeholder_regex.blank? || !block_given?
+
+      replace_placeholders(string, placeholder_regex, &block)
+    end
+
+    class << self
+      private
+
+      # If the result of the block is nil, then the placeholder is returned
+      def replace_placeholders(string, placeholder_regex, &block)
+        string.gsub(/%{(#{placeholder_regex})}/) do |arg|
+          yield($~[1]) || arg
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb
index f9faa13420619d20b330ea26a300da45c72ebefc..c6ad997a4d4a43a0170a8d5c19c515d6cfa280cc 100644
--- a/lib/gitlab/string_range_marker.rb
+++ b/lib/gitlab/string_range_marker.rb
@@ -14,7 +14,7 @@ module Gitlab
     end
 
     def mark(marker_ranges)
-      return rich_line unless marker_ranges
+      return rich_line unless marker_ranges&.any?
 
       if html_escaped
         rich_marker_ranges = []
diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb
index 7ebf1c0428ca2786435c73e98b0489a56b7f71e3..b19aa6dea358c72ec08bcf29ac97cafd72379293 100644
--- a/lib/gitlab/string_regex_marker.rb
+++ b/lib/gitlab/string_regex_marker.rb
@@ -1,13 +1,15 @@
 module Gitlab
   class StringRegexMarker < StringRangeMarker
     def mark(regex, group: 0, &block)
-      regex_match = raw_line.match(regex)
-      return rich_line unless regex_match
+      ranges = []
 
-      begin_index, end_index = regex_match.offset(group)
-      name_range = begin_index..(end_index - 1)
+      raw_line.scan(regex) do
+        begin_index, end_index = Regexp.last_match.offset(group)
 
-      super([name_range], &block)
+        ranges << (begin_index..(end_index - 1))
+      end
+
+      super(ranges, &block)
     end
   end
 end
diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb
index 34bee6fecbed36e6d40dd63dce7acf9ab206127f..42be301fd9b38ef4b3c62e2aa91f1e27a4a0e429 100644
--- a/lib/gitlab/task_helpers.rb
+++ b/lib/gitlab/task_helpers.rb
@@ -129,7 +129,7 @@ module Gitlab
 
     def all_repos
       Gitlab.config.repositories.storages.each_value do |repository_storage|
-        IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -type d -name *.git)) do |find|
+        IO.popen(%W(find #{repository_storage.legacy_disk_path} -mindepth 2 -type d -name *.git)) do |find|
           find.each_line do |path|
             yield path.chomp
           end
@@ -138,7 +138,7 @@ module Gitlab
     end
 
     def repository_storage_paths_args
-      Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
+      Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path }
     end
 
     def user_home
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 13150ddab678514ae9b4f64f0b0cb72ed1518234..db97f65bd546ccfcf23f2552480ddcf4be0da450 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -2,49 +2,84 @@ require 'resolv'
 
 module Gitlab
   class UrlBlocker
-    class << self
-      # Used to specify what hosts and port numbers should be prohibited for project
-      # imports.
-      VALID_PORTS = [22, 80, 443].freeze
-
-      def blocked_url?(url)
-        return false if url.nil?
+    BlockedUrlError = Class.new(StandardError)
 
-        blocked_ips = ["127.0.0.1", "::1", "0.0.0.0"]
-        blocked_ips.concat(Socket.ip_address_list.map(&:ip_address))
+    class << self
+      def validate!(url, allow_localhost: false, allow_local_network: true, valid_ports: [])
+        return true if url.nil?
 
         begin
           uri = Addressable::URI.parse(url)
-          # Allow imports from the GitLab instance itself but only from the configured ports
-          return false if internal?(uri)
+        rescue Addressable::URI::InvalidURIError
+          raise BlockedUrlError, "URI is invalid"
+        end
 
-          return true if blocked_port?(uri.port)
-          return true if blocked_user_or_hostname?(uri.user)
-          return true if blocked_user_or_hostname?(uri.hostname)
+        # Allow imports from the GitLab instance itself but only from the configured ports
+        return true if internal?(uri)
 
-          server_ips = Addrinfo.getaddrinfo(uri.hostname, 80, nil, :STREAM).map(&:ip_address)
-          return true if (blocked_ips & server_ips).any?
-        rescue Addressable::URI::InvalidURIError
-          return true
+        port = uri.port || uri.default_port
+        validate_port!(port, valid_ports) if valid_ports.any?
+        validate_user!(uri.user)
+        validate_hostname!(uri.hostname)
+
+        begin
+          addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM)
         rescue SocketError
-          return false
+          return true
         end
 
+        validate_localhost!(addrs_info) unless allow_localhost
+        validate_local_network!(addrs_info) unless allow_local_network
+
+        true
+      end
+
+      def blocked_url?(*args)
+        validate!(*args)
+
         false
+      rescue BlockedUrlError
+        true
       end
 
       private
 
-      def blocked_port?(port)
-        return false if port.blank?
+      def validate_port!(port, valid_ports)
+        return if port.blank?
+        # Only ports under 1024 are restricted
+        return if port >= 1024
+        return if valid_ports.include?(port)
+
+        raise BlockedUrlError, "Only allowed ports are #{valid_ports.join(', ')}, and any over 1024"
+      end
+
+      def validate_user!(value)
+        return if value.blank?
+        return if value =~ /\A\p{Alnum}/
+
+        raise BlockedUrlError, "Username needs to start with an alphanumeric character"
+      end
+
+      def validate_hostname!(value)
+        return if value.blank?
+        return if value =~ /\A\p{Alnum}/
+
+        raise BlockedUrlError, "Hostname needs to start with an alphanumeric character"
+      end
+
+      def validate_localhost!(addrs_info)
+        local_ips = ["127.0.0.1", "::1", "0.0.0.0"]
+        local_ips.concat(Socket.ip_address_list.map(&:ip_address))
+
+        return if (local_ips & addrs_info.map(&:ip_address)).empty?
 
-        port < 1024 && !VALID_PORTS.include?(port)
+        raise BlockedUrlError, "Requests to localhost are not allowed"
       end
 
-      def blocked_user_or_hostname?(value)
-        return false if value.blank?
+      def validate_local_network!(addrs_info)
+        return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? }
 
-        value !~ /\A\p{Alnum}/
+        raise BlockedUrlError, "Requests to the local network are not allowed"
       end
 
       def internal?(uri)
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 37d3512990edd509c18cd02792acbc1648b708a1..8c0a4d55ea2f5f7abdf0a981f0fefe831dc6e94b 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -30,6 +30,7 @@ module Gitlab
         usage_data
       end
 
+      # rubocop:disable Metrics/AbcSize
       def system_usage_data
         {
           counts: {
@@ -50,6 +51,12 @@ module Gitlab
             clusters: ::Clusters::Cluster.count,
             clusters_enabled: ::Clusters::Cluster.enabled.count,
             clusters_disabled: ::Clusters::Cluster.disabled.count,
+            clusters_platforms_gke: ::Clusters::Cluster.gcp_installed.enabled.count,
+            clusters_platforms_user: ::Clusters::Cluster.user_provided.enabled.count,
+            clusters_applications_helm: ::Clusters::Applications::Helm.installed.count,
+            clusters_applications_ingress: ::Clusters::Applications::Ingress.installed.count,
+            clusters_applications_prometheus: ::Clusters::Applications::Prometheus.installed.count,
+            clusters_applications_runner: ::Clusters::Applications::Runner.installed.count,
             in_review_folder: ::Environment.in_review_folder.count,
             groups: Group.count,
             issues: Issue.count,
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 91b8bb2a83f4b7a1e4f383170f1bceb19147c6c9..69952cbb47c1d84041e86498ba795b2ffe0277e4 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -51,7 +51,7 @@ module Gitlab
       return false unless can_access_git?
 
       if protected?(ProtectedBranch, project, ref)
-        user.can?(:delete_protected_branch, project)
+        user.can?(:push_to_delete_protected_branch, project)
       else
         user.can?(:push_code, project)
       end
@@ -63,13 +63,12 @@ module Gitlab
 
     request_cache def can_push_to_branch?(ref)
       return false unless can_access_git?
+      return false unless user.can?(:push_code, project) || project.branch_allows_maintainer_push?(user, ref)
 
       if protected?(ProtectedBranch, project, ref)
-        return true if project.user_can_push_to_empty_repo?(user)
-
-        protected_branch_accessible_to?(ref, action: :push)
+        project.user_can_push_to_empty_repo?(user) || protected_branch_accessible_to?(ref, action: :push)
       else
-        user.can?(:push_code, project)
+        true
       end
     end
 
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index fa22f0e37b2e6051bbe8d91820d80f22675b8b6b..aeda66763e8f803fe4da3013f1a921b7425ac274 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -27,6 +27,11 @@ module Gitlab
         .gsub(/(\A-+|-+\z)/, '')
     end
 
+    # Converts newlines into HTML line break elements
+    def nlbr(str)
+      ActionView::Base.full_sanitizer.sanitize(str, tags: []).gsub(/\r?\n/, '<br>').html_safe
+    end
+
     def remove_line_breaks(str)
       str.gsub(/\r?\n/, '')
     end
@@ -67,5 +72,17 @@ module Gitlab
 
       nil
     end
+
+    def bytes_to_megabytes(bytes)
+      bytes.to_f / Numeric::MEGABYTE
+    end
+
+    # Used in EE
+    # Accepts either an Array or a String and returns an array
+    def ensure_array_from_string(string_or_array)
+      return string_or_array if string_or_array.is_a?(Array)
+
+      string_or_array.split(',').map(&:strip)
+    end
   end
 end
diff --git a/lib/gitlab/verify/batch_verifier.rb b/lib/gitlab/verify/batch_verifier.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1ef369a4b676e93176cac52d44d82516b1f28d06
--- /dev/null
+++ b/lib/gitlab/verify/batch_verifier.rb
@@ -0,0 +1,64 @@
+module Gitlab
+  module Verify
+    class BatchVerifier
+      attr_reader :batch_size, :start, :finish
+
+      def initialize(batch_size:, start: nil, finish: nil)
+        @batch_size = batch_size
+        @start = start
+        @finish = finish
+      end
+
+      # Yields a Range of IDs and a Hash of failed verifications (object => error)
+      def run_batches(&blk)
+        relation.in_batches(of: batch_size, start: start, finish: finish) do |relation| # rubocop: disable Cop/InBatches
+          range = relation.first.id..relation.last.id
+          failures = run_batch(relation)
+
+          yield(range, failures)
+        end
+      end
+
+      def name
+        raise NotImplementedError.new
+      end
+
+      def describe(_object)
+        raise NotImplementedError.new
+      end
+
+      private
+
+      def run_batch(relation)
+        relation.map { |upload| verify(upload) }.compact.to_h
+      end
+
+      def verify(object)
+        expected = expected_checksum(object)
+        actual = actual_checksum(object)
+
+        raise 'Checksum missing' unless expected.present?
+        raise 'Checksum mismatch' unless expected == actual
+
+        nil
+      rescue => err
+        [object, err]
+      end
+
+      # This should return an ActiveRecord::Relation suitable for calling #in_batches on
+      def relation
+        raise NotImplementedError.new
+      end
+
+      # The checksum we expect the object to have
+      def expected_checksum(_object)
+        raise NotImplementedError.new
+      end
+
+      # The freshly-recalculated checksum of the object
+      def actual_checksum(_object)
+        raise NotImplementedError.new
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/verify/job_artifacts.rb b/lib/gitlab/verify/job_artifacts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03500a610743088ba13590dde8b7624c81996f28
--- /dev/null
+++ b/lib/gitlab/verify/job_artifacts.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module Verify
+    class JobArtifacts < BatchVerifier
+      def name
+        'Job artifacts'
+      end
+
+      def describe(object)
+        "Job artifact: #{object.id}"
+      end
+
+      private
+
+      def relation
+        ::Ci::JobArtifact.all
+      end
+
+      def expected_checksum(artifact)
+        artifact.file_sha256
+      end
+
+      def actual_checksum(artifact)
+        Digest::SHA256.file(artifact.file.path).hexdigest
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/verify/lfs_objects.rb b/lib/gitlab/verify/lfs_objects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..970e2a7b7189d2d14f6329c79314ef9c09d50a56
--- /dev/null
+++ b/lib/gitlab/verify/lfs_objects.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module Verify
+    class LfsObjects < BatchVerifier
+      def name
+        'LFS objects'
+      end
+
+      def describe(object)
+        "LFS object: #{object.oid}"
+      end
+
+      private
+
+      def relation
+        LfsObject.with_files_stored_locally
+      end
+
+      def expected_checksum(lfs_object)
+        lfs_object.oid
+      end
+
+      def actual_checksum(lfs_object)
+        LfsObject.calculate_oid(lfs_object.file.path)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/verify/rake_task.rb b/lib/gitlab/verify/rake_task.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dd138e6b92b143a31124219568fa91db62e47081
--- /dev/null
+++ b/lib/gitlab/verify/rake_task.rb
@@ -0,0 +1,53 @@
+module Gitlab
+  module Verify
+    class RakeTask
+      def self.run!(verify_kls)
+        verifier = verify_kls.new(
+          batch_size: ENV.fetch('BATCH', 200).to_i,
+          start: ENV['ID_FROM'],
+          finish: ENV['ID_TO']
+        )
+
+        verbose = Gitlab::Utils.to_boolean(ENV['VERBOSE'])
+
+        new(verifier, verbose).run!
+      end
+
+      attr_reader :verifier, :output
+
+      def initialize(verifier, verbose)
+        @verifier = verifier
+        @verbose = verbose
+      end
+
+      def run!
+        say "Checking integrity of #{verifier.name}"
+
+        verifier.run_batches { |*args| run_batch(*args) }
+
+        say 'Done!'
+      end
+
+      def verbose?
+        !!@verbose
+      end
+
+      private
+
+      def say(text)
+        puts(text) # rubocop:disable Rails/Output
+      end
+
+      def run_batch(range, failures)
+        status_color = failures.empty? ? :green : :red
+        say "- #{range}: Failures: #{failures.count}".color(status_color)
+
+        return unless verbose?
+
+        failures.each do |object, error|
+          say "  - #{verifier.describe(object)}: #{error.inspect}".color(:red)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/verify/uploads.rb b/lib/gitlab/verify/uploads.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0ffa71a6d724d683eff044641c8e20f191a327c8
--- /dev/null
+++ b/lib/gitlab/verify/uploads.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module Verify
+    class Uploads < BatchVerifier
+      def name
+        'Uploads'
+      end
+
+      def describe(object)
+        "Upload: #{object.id}"
+      end
+
+      private
+
+      def relation
+        Upload.with_files_stored_locally
+      end
+
+      def expected_checksum(upload)
+        upload.checksum
+      end
+
+      def actual_checksum(upload)
+        Upload.hexdigest(upload.absolute_path)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb
index 841fb681435533a9d2290acd79d4486dbc65316e..36162faa1ebf72cfdfa903b0edd00999ac29eb4e 100644
--- a/lib/gitlab/view/presenter/base.rb
+++ b/lib/gitlab/view/presenter/base.rb
@@ -20,6 +20,10 @@ module Gitlab
           subject
         end
 
+        def present(**attributes)
+          self
+        end
+
         class_methods do
           def presenter?
             true
diff --git a/lib/gitlab/wiki/committer_with_hooks.rb b/lib/gitlab/wiki/committer_with_hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..19f0b3814fd48e998043274d197a33faef2edaf0
--- /dev/null
+++ b/lib/gitlab/wiki/committer_with_hooks.rb
@@ -0,0 +1,39 @@
+module Gitlab
+  module Wiki
+    class CommitterWithHooks < Gollum::Committer
+      attr_reader :gl_wiki
+
+      def initialize(gl_wiki, options = {})
+        @gl_wiki = gl_wiki
+        super(gl_wiki.gollum_wiki, options)
+      end
+
+      def commit
+        result = Gitlab::Git::OperationService.new(git_user, gl_wiki.repository).with_branch(
+          @wiki.ref,
+          start_branch_name: @wiki.ref
+        ) do |start_commit|
+          super(false)
+        end
+
+        result[:newrev]
+      rescue Gitlab::Git::HooksService::PreReceiveError => e
+        message = "Custom Hook failed: #{e.message}"
+        raise Gitlab::Git::Wiki::OperationError, message
+      end
+
+      private
+
+      def git_user
+        @git_user ||= Gitlab::Git::User.new(@options[:username],
+                                            @options[:name],
+                                            @options[:email],
+                                            gitlab_id)
+      end
+
+      def gitlab_id
+        Gitlab::GlId.gl_id_from_id_value(@options[:user_id])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 823df67ea39e81b6d56cab731a625a6329ed83e0..153cb2a8bb1475a407495d3873de1ab61948aa7d 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -10,6 +10,7 @@ module Gitlab
     INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'.freeze
     INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze
     NOTIFICATION_CHANNEL = 'workhorse:notifications'.freeze
+    ALLOWED_GIT_HTTP_ACTIONS = %w[git_receive_pack git_upload_pack info_refs].freeze
 
     # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
     # bytes https://tools.ietf.org/html/rfc4868#section-2.6
@@ -17,56 +18,26 @@ module Gitlab
 
     class << self
       def git_http_ok(repository, is_wiki, user, action, show_all_refs: false)
+        raise "Unsupported action: #{action}" unless ALLOWED_GIT_HTTP_ACTIONS.include?(action.to_s)
+
         project = repository.project
-        repo_path = repository.path_to_repo
-        params = {
+
+        {
           GL_ID: Gitlab::GlId.gl_id(user),
           GL_REPOSITORY: Gitlab::GlRepository.gl_repository(project, is_wiki),
           GL_USERNAME: user&.username,
-          RepoPath: repo_path,
-          ShowAllRefs: show_all_refs
-        }
-        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
-                          when 'git_receive_pack'
-                            Gitlab::GitalyClient.feature_enabled?(
-                              :post_receive_pack,
-                              status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
-                            )
-                          when 'git_upload_pack'
-                            true
-                          when 'info_refs'
-                            true
-                          else
-                            raise "Unsupported action: #{action}"
-                          end
-
-        if feature_enabled
-          params[:GitalyServer] = server
-        end
-
-        params
-      end
-
-      def lfs_upload_ok(oid, size)
-        {
-          StoreLFSPath: LfsObjectUploader.workhorse_upload_path,
-          LfsOid: oid,
-          LfsSize: size
+          ShowAllRefs: show_all_refs,
+          Repository: repository.gitaly_repository.to_h,
+          RepoPath: 'ignored but not allowed to be empty in gitlab-workhorse',
+          GitalyServer: {
+            address: Gitlab::GitalyClient.address(project.repository_storage),
+            token: Gitlab::GitalyClient.token(project.repository_storage)
+          }
         }
       end
 
-      def artifact_upload_ok
-        { TempPath: JobArtifactUploader.workhorse_upload_path }
-      end
-
       def send_git_blob(repository, blob)
-        params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_raw_show)
+        params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_raw_show, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT)
                    {
                      'GitalyServer' => gitaly_server_hash(repository),
                      'GetBlobRequest' => {
@@ -88,13 +59,13 @@ module Gitlab
         ]
       end
 
-      def send_git_archive(repository, ref:, format:)
+      def send_git_archive(repository, ref:, format:, append_sha:)
         format ||= 'tar.gz'
         format.downcase!
-        params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
+        params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha)
         raise "Repository or ref not found" if params.empty?
 
-        if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive)
+        if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT)
           params.merge!(
             'GitalyServer' => gitaly_server_hash(repository),
             'GitalyRepository' => repository.gitaly_repository.to_h
@@ -111,7 +82,7 @@ module Gitlab
       end
 
       def send_git_diff(repository, diff_refs)
-        params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_diff)
+        params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_diff, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT)
                    {
                      'GitalyServer' => gitaly_server_hash(repository),
                      'RawDiffRequest' => Gitaly::RawDiffRequest.new(
@@ -129,7 +100,7 @@ module Gitlab
       end
 
       def send_git_patch(repository, diff_refs)
-        params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_patch)
+        params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_patch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT)
                    {
                      'GitalyServer' => gitaly_server_hash(repository),
                      'RawPatchRequest' => Gitaly::RawPatchRequest.new(
diff --git a/lib/google_api/auth.rb b/lib/google_api/auth.rb
index 99a82c849e032d15a40e8bfff0de631d573e3aaa..1aeaa387a499263176a9a4809d69cd148a133b96 100644
--- a/lib/google_api/auth.rb
+++ b/lib/google_api/auth.rb
@@ -32,7 +32,7 @@ module GoogleApi
     private
 
     def config
-      Gitlab.config.omniauth.providers.find { |provider| provider.name == "google_oauth2" }
+      Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
     end
 
     def client
diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb
index 4f776330e80e15aa413e133d4898e3e759f1dbdc..adbed20f15217648eaa027bc9c8bff12f397cb6e 100644
--- a/lib/haml_lint/inline_javascript.rb
+++ b/lib/haml_lint/inline_javascript.rb
@@ -1,4 +1,4 @@
-unless Rails.env.production?
+unless Rails.env.production? # rubocop:disable Naming/FileName
   require 'haml_lint/haml_visitor'
   require 'haml_lint/linter'
   require 'haml_lint/linter_registry'
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index ef08bd46e17a5f13cbb93361d25e5ad251db1140..85f78e44f32ce8da18497065ef0a6fdf644d02c7 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -22,16 +22,14 @@ module Mattermost
   # going.
   class Session
     include Doorkeeper::Helpers::Controller
-    include HTTParty
 
     LEASE_TIMEOUT = 60
 
-    base_uri Settings.mattermost.host
-
-    attr_accessor :current_resource_owner, :token
+    attr_accessor :current_resource_owner, :token, :base_uri
 
     def initialize(current_user)
       @current_resource_owner = current_user
+      @base_uri = Settings.mattermost.host
     end
 
     def with_session
@@ -73,18 +71,32 @@ module Mattermost
 
     def get(path, options = {})
       handle_exceptions do
-        self.class.get(path, options.merge(headers: @headers))
+        Gitlab::HTTP.get(path, build_options(options))
       end
     end
 
     def post(path, options = {})
       handle_exceptions do
-        self.class.post(path, options.merge(headers: @headers))
+        Gitlab::HTTP.post(path, build_options(options))
+      end
+    end
+
+    def delete(path, options = {})
+      handle_exceptions do
+        Gitlab::HTTP.delete(path, build_options(options))
       end
     end
 
     private
 
+    def build_options(options)
+      options.tap do |hash|
+        hash[:headers] = @headers
+        hash[:allow_local_requests] = true
+        hash[:base_uri] = base_uri if base_uri.presence
+      end
+    end
+
     def create
       raise Mattermost::NoSessionError unless oauth_uri
       raise Mattermost::NoSessionError unless token_uri
@@ -159,14 +171,14 @@ module Mattermost
 
     def handle_exceptions
       yield
-    rescue HTTParty::Error => e
+    rescue Gitlab::HTTP::Error => e
       raise Mattermost::ConnectionError.new(e.message)
     rescue Errno::ECONNREFUSED => e
       raise Mattermost::ConnectionError.new(e.message)
     end
 
     def parse_cookie(response)
-      cookie_hash = CookieHash.new
+      cookie_hash = Gitlab::HTTP::CookieHash.new
       response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
       cookie_hash
     end
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
index b2511f3af1d8615908d8ad8d9cd07ee69f7fb7a4..75513a9ba044e0b54d1e1ce7cabb16a395a9a5f4 100644
--- a/lib/mattermost/team.rb
+++ b/lib/mattermost/team.rb
@@ -16,10 +16,9 @@ module Mattermost
     end
 
     # The deletion is done async, so the response is fast.
-    # On the mattermost side, this triggers an soft deletion first, after which
-    # the actuall data is removed
+    # On the mattermost side, this triggers an soft deletion
     def destroy(team_id:)
-      session_delete("/api/v4/teams/#{team_id}?permanent=true")
+      session_delete("/api/v4/teams/#{team_id}")
     end
   end
 end
diff --git a/lib/microsoft_teams/notifier.rb b/lib/microsoft_teams/notifier.rb
index 3bef68a1bcb2004e1de7ed7d4ba2087358ba89d7..c08d3e933a899dbba845ecb7e818b75a4da62aed 100644
--- a/lib/microsoft_teams/notifier.rb
+++ b/lib/microsoft_teams/notifier.rb
@@ -9,14 +9,15 @@ module MicrosoftTeams
       result = false
 
       begin
-        response = HTTParty.post(
+        response = Gitlab::HTTP.post(
           @webhook.to_str,
           headers: @header,
+          allow_local_requests: true,
           body: body(options)
         )
 
         result = true if response
-      rescue HTTParty::Error, StandardError => error
+      rescue Gitlab::HTTP::Error, StandardError => error
         Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}")
       end
 
diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb
index d519d8e86fa8ca8ef6815c9b51cc5380c8d7fda0..ab35f7a2258914f27f5302caf209d3ae0025e921 100644
--- a/lib/peek/views/gitaly.rb
+++ b/lib/peek/views/gitaly.rb
@@ -10,11 +10,29 @@ module Peek
       end
 
       def results
-        { duration: formatted_duration, calls: calls }
+        {
+          duration: formatted_duration,
+          calls: calls,
+          details: details
+        }
       end
 
       private
 
+      def details
+        ::Gitlab::GitalyClient.list_call_details
+          .values
+          .sort { |a, b| b[:duration] <=> a[:duration] }
+          .map(&method(:format_call_details))
+      end
+
+      def format_call_details(call)
+        pretty_request = call[:request]&.reject { |k, v| v.blank? }.to_h.pretty_inspect
+
+        call.merge(duration: (call[:duration] * 1000).round(3),
+                   request: pretty_request || {})
+      end
+
       def formatted_duration
         ms = duration * 1000
         if ms >= 1000
diff --git a/lib/peek/views/host.rb b/lib/peek/views/host.rb
new file mode 100644
index 0000000000000000000000000000000000000000..43c8a35c7ea00f2f223652c48f4630485c03491f
--- /dev/null
+++ b/lib/peek/views/host.rb
@@ -0,0 +1,9 @@
+module Peek
+  module Views
+    class Host < View
+      def results
+        { hostname: Gitlab::Environment.hostname }
+      end
+    end
+  end
+end
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
deleted file mode 100644
index 068a95790c0a61f2da7372f28bc89b18a0774d0a..0000000000000000000000000000000000000000
--- a/lib/repository_cache.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# Interface to the Redis-backed cache store used by the Repository model
-class RepositoryCache
-  attr_reader :namespace, :backend, :project_id
-
-  def initialize(namespace, project_id, backend = Rails.cache)
-    @namespace = namespace
-    @backend = backend
-    @project_id = project_id
-  end
-
-  def cache_key(type)
-    "#{type}:#{namespace}:#{project_id}"
-  end
-
-  def expire(key)
-    backend.delete(cache_key(key))
-  end
-
-  def fetch(key, &block)
-    backend.fetch(cache_key(key), &block)
-  end
-
-  def exist?(key)
-    backend.exist?(cache_key(key))
-  end
-
-  def read(key)
-    backend.read(cache_key(key))
-  end
-end
diff --git a/lib/rouge/plugins/common_mark.rb b/lib/rouge/plugins/common_mark.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8f9de061124b70bb11d63290a99c70473f49cad2
--- /dev/null
+++ b/lib/rouge/plugins/common_mark.rb
@@ -0,0 +1,20 @@
+# A rouge plugin for CommonMark markdown engine.
+# Used to highlight code generated by CommonMark.
+
+module Rouge
+  module Plugins
+    module CommonMark
+      def code_block(code, language)
+        lexer = Lexer.find_fancy(language, code) || Lexers::PlainText
+
+        formatter = rouge_formatter(lexer)
+        formatter.format(lexer.lex(code))
+      end
+
+      # override this method for custom formatting behavior
+      def rouge_formatter(lexer)
+        Formatters::HTMLLegacy.new(css_class: "highlight #{lexer.tag}")
+      end
+    end
+  end
+end
diff --git a/lib/rspec_flaky/config.rb b/lib/rspec_flaky/config.rb
index a17ae55910e3a6b8254e86e17e309baee143fc1e..06e96f969f130fedff9aa6d20674eb540877e19e 100644
--- a/lib/rspec_flaky/config.rb
+++ b/lib/rspec_flaky/config.rb
@@ -1,9 +1,7 @@
-require 'json'
-
 module RspecFlaky
   class Config
     def self.generate_report?
-      ENV['FLAKY_RSPEC_GENERATE_REPORT'] == 'true'
+      !!(ENV['FLAKY_RSPEC_GENERATE_REPORT'] =~ /1|true/)
     end
 
     def self.suite_flaky_examples_report_path
diff --git a/lib/rspec_flaky/flaky_examples_collection.rb b/lib/rspec_flaky/flaky_examples_collection.rb
index 973c95b02124f3a693d43163351e7c7e77b66947..dea23c325be9803a54f0fab450123ddd77479ada 100644
--- a/lib/rspec_flaky/flaky_examples_collection.rb
+++ b/lib/rspec_flaky/flaky_examples_collection.rb
@@ -1,11 +1,9 @@
-require 'json'
+require 'active_support/hash_with_indifferent_access'
+
+require_relative 'flaky_example'
 
 module RspecFlaky
   class FlakyExamplesCollection < SimpleDelegator
-    def self.from_json(json)
-      new(JSON.parse(json))
-    end
-
     def initialize(collection = {})
       unless collection.is_a?(Hash)
         raise ArgumentError, "`collection` must be a Hash, #{collection.class} given!"
@@ -22,7 +20,7 @@ module RspecFlaky
       super(Hash[collection_of_flaky_examples])
     end
 
-    def to_report
+    def to_h
       Hash[map { |uid, example| [uid, example.to_h] }].deep_symbolize_keys
     end
 
diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb
index 4a5bfec99679de4b375f2d5c151a3cc5685197da..5b5e4f7c7de8a726c49c66bed7de94b96eb23cca 100644
--- a/lib/rspec_flaky/listener.rb
+++ b/lib/rspec_flaky/listener.rb
@@ -1,5 +1,11 @@
 require 'json'
 
+require_relative 'config'
+require_relative 'example'
+require_relative 'flaky_example'
+require_relative 'flaky_examples_collection'
+require_relative 'report'
+
 module RspecFlaky
   class Listener
     # - suite_flaky_examples: contains all the currently tracked flacky example
@@ -9,7 +15,7 @@ module RspecFlaky
     attr_reader :suite_flaky_examples, :flaky_examples
 
     def initialize(suite_flaky_examples_json = nil)
-      @flaky_examples = FlakyExamplesCollection.new
+      @flaky_examples = RspecFlaky::FlakyExamplesCollection.new
       @suite_flaky_examples = init_suite_flaky_examples(suite_flaky_examples_json)
     end
 
@@ -18,47 +24,36 @@ module RspecFlaky
 
       return unless current_example.attempts > 1
 
-      flaky_example = suite_flaky_examples.fetch(current_example.uid) { FlakyExample.new(current_example) }
+      flaky_example = suite_flaky_examples.fetch(current_example.uid) { RspecFlaky::FlakyExample.new(current_example) }
       flaky_example.update_flakiness!(last_attempts_count: current_example.attempts)
 
       flaky_examples[current_example.uid] = flaky_example
     end
 
     def dump_summary(_)
-      write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path)
+      RspecFlaky::Report.new(flaky_examples).write(RspecFlaky::Config.flaky_examples_report_path)
+      # write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path)
 
       new_flaky_examples = flaky_examples - suite_flaky_examples
       if new_flaky_examples.any?
         Rails.logger.warn "\nNew flaky examples detected:\n"
-        Rails.logger.warn JSON.pretty_generate(new_flaky_examples.to_report)
+        Rails.logger.warn JSON.pretty_generate(new_flaky_examples.to_h)
 
-        write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path)
+        RspecFlaky::Report.new(new_flaky_examples).write(RspecFlaky::Config.new_flaky_examples_report_path)
+        # write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path)
       end
     end
 
-    def to_report(examples)
-      Hash[examples.map { |k, ex| [k, ex.to_h] }]
-    end
-
     private
 
     def init_suite_flaky_examples(suite_flaky_examples_json = nil)
-      unless suite_flaky_examples_json
+      if suite_flaky_examples_json
+        RspecFlaky::Report.load_json(suite_flaky_examples_json).flaky_examples
+      else
         return {} unless File.exist?(RspecFlaky::Config.suite_flaky_examples_report_path)
 
-        suite_flaky_examples_json = File.read(RspecFlaky::Config.suite_flaky_examples_report_path)
+        RspecFlaky::Report.load(RspecFlaky::Config.suite_flaky_examples_report_path).flaky_examples
       end
-
-      FlakyExamplesCollection.from_json(suite_flaky_examples_json)
-    end
-
-    def write_report_file(examples_collection, file_path)
-      return unless RspecFlaky::Config.generate_report?
-
-      report_path_dir = File.dirname(file_path)
-      FileUtils.mkdir_p(report_path_dir) unless Dir.exist?(report_path_dir)
-
-      File.write(file_path, JSON.pretty_generate(examples_collection.to_report))
     end
   end
 end
diff --git a/lib/rspec_flaky/report.rb b/lib/rspec_flaky/report.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a8730d3b7c71bc18703d29373054bd2a307085f0
--- /dev/null
+++ b/lib/rspec_flaky/report.rb
@@ -0,0 +1,54 @@
+require 'json'
+require 'time'
+
+require_relative 'config'
+require_relative 'flaky_examples_collection'
+
+module RspecFlaky
+  # This class is responsible for loading/saving JSON reports, and pruning
+  # outdated examples.
+  class Report < SimpleDelegator
+    OUTDATED_DAYS_THRESHOLD = 90
+
+    attr_reader :flaky_examples
+
+    def self.load(file_path)
+      load_json(File.read(file_path))
+    end
+
+    def self.load_json(json)
+      new(RspecFlaky::FlakyExamplesCollection.new(JSON.parse(json)))
+    end
+
+    def initialize(flaky_examples)
+      unless flaky_examples.is_a?(RspecFlaky::FlakyExamplesCollection)
+        raise ArgumentError, "`flaky_examples` must be a RspecFlaky::FlakyExamplesCollection, #{flaky_examples.class} given!"
+      end
+
+      @flaky_examples = flaky_examples
+      super(flaky_examples)
+    end
+
+    def write(file_path)
+      unless RspecFlaky::Config.generate_report?
+        puts "! Generating reports is disabled. To enable it, please set the `FLAKY_RSPEC_GENERATE_REPORT=1` !" # rubocop:disable Rails/Output
+        return
+      end
+
+      report_path_dir = File.dirname(file_path)
+      FileUtils.mkdir_p(report_path_dir) unless Dir.exist?(report_path_dir)
+
+      File.write(file_path, JSON.pretty_generate(flaky_examples.to_h))
+    end
+
+    def prune_outdated(days: OUTDATED_DAYS_THRESHOLD)
+      outdated_date_threshold = Time.now - (3600 * 24 * days)
+      updated_hash = flaky_examples.dup
+        .delete_if do |uid, hash|
+          hash[:last_flaky_at] && Time.parse(hash[:last_flaky_at]).to_i < outdated_date_threshold.to_i
+        end
+
+      self.class.new(RspecFlaky::FlakyExamplesCollection.new(updated_hash))
+    end
+  end
+end
diff --git a/lib/system_check/helpers.rb b/lib/system_check/helpers.rb
index 914ed794601c89d31b86ac89dc1ae17e3d2726bf..6227e461d24c5df6ce7d8cb6e0e814a9c3501976 100644
--- a/lib/system_check/helpers.rb
+++ b/lib/system_check/helpers.rb
@@ -50,7 +50,7 @@ module SystemCheck
       if should_sanitize?
         "#{project.namespace_id.to_s.color(:yellow)}/#{project.id.to_s.color(:yellow)} ... "
       else
-        "#{project.name_with_namespace.color(:yellow)} ... "
+        "#{project.full_name.color(:yellow)} ... "
       end
     end
 
diff --git a/lib/system_check/orphans/namespace_check.rb b/lib/system_check/orphans/namespace_check.rb
index b8446300f72d6005e6dc5044258d9bc257cc1257..b5f443abe06e74a21f626a6f893e478001a899df 100644
--- a/lib/system_check/orphans/namespace_check.rb
+++ b/lib/system_check/orphans/namespace_check.rb
@@ -6,8 +6,8 @@ module SystemCheck
       def multi_check
         Gitlab.config.repositories.storages.each do |storage_name, repository_storage|
           $stdout.puts
-          $stdout.puts "* Storage: #{storage_name} (#{repository_storage['path']})".color(:yellow)
-          toplevel_namespace_dirs = disk_namespaces(repository_storage['path'])
+          $stdout.puts "* Storage: #{storage_name} (#{repository_storage.legacy_disk_path})".color(:yellow)
+          toplevel_namespace_dirs = disk_namespaces(repository_storage.legacy_disk_path)
 
           orphans = (toplevel_namespace_dirs - existing_namespaces)
           print_orphans(orphans, storage_name)
diff --git a/lib/system_check/orphans/repository_check.rb b/lib/system_check/orphans/repository_check.rb
index 9b6b2429783ae37f20d594f0b2850e59648b9524..5ef0b93ad087340eecad9047feb99dea12d949ed 100644
--- a/lib/system_check/orphans/repository_check.rb
+++ b/lib/system_check/orphans/repository_check.rb
@@ -6,10 +6,12 @@ module SystemCheck
 
       def multi_check
         Gitlab.config.repositories.storages.each do |storage_name, repository_storage|
+          storage_path = repository_storage.legacy_disk_path
+
           $stdout.puts
-          $stdout.puts "* Storage: #{storage_name} (#{repository_storage['path']})".color(:yellow)
+          $stdout.puts "* Storage: #{storage_name} (#{storage_path})".color(:yellow)
 
-          repositories = disk_repositories(repository_storage['path'])
+          repositories = disk_repositories(storage_path)
           orphans = (repositories - fetch_repositories(storage_name))
 
           print_orphans(orphans, storage_name)
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 564aa1419521fb57069b506b4f70c6a2bebb917b..cb4d5abffbc3b1e1a6ed8a50a34b3b313b0d482d 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -6,17 +6,22 @@ namespace :cache do
     desc "GitLab | Clear redis cache"
     task redis: :environment do
       Gitlab::Redis::Cache.with do |redis|
-        cursor = REDIS_SCAN_START_STOP
-        loop do
-          cursor, keys = redis.scan(
-            cursor,
-            match: "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*",
-            count: REDIS_CLEAR_BATCH_SIZE
-          )
+        cache_key_pattern = %W[#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*
+                               projects/*/pipeline_status]
 
-          redis.del(*keys) if keys.any?
+        cache_key_pattern.each do |match|
+          cursor = REDIS_SCAN_START_STOP
+          loop do
+            cursor, keys = redis.scan(
+              cursor,
+              match: match,
+              count: REDIS_CLEAR_BATCH_SIZE
+            )
 
-          break if cursor == REDIS_SCAN_START_STOP
+            redis.del(*keys) if keys.any?
+
+            break if cursor == REDIS_SCAN_START_STOP
+          end
         end
       end
     end
diff --git a/lib/tasks/gitlab/artifacts/check.rake b/lib/tasks/gitlab/artifacts/check.rake
new file mode 100644
index 0000000000000000000000000000000000000000..a105261ed516e060475526ace14843c73e800f2b
--- /dev/null
+++ b/lib/tasks/gitlab/artifacts/check.rake
@@ -0,0 +1,8 @@
+namespace :gitlab do
+  namespace :artifacts do
+    desc 'GitLab | Artifacts | Check integrity of uploaded job artifacts'
+    task check: :environment do
+      Gitlab::Verify::RakeTask.run!(Gitlab::Verify::JobArtifacts)
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/artifacts/migrate.rake b/lib/tasks/gitlab/artifacts/migrate.rake
new file mode 100644
index 0000000000000000000000000000000000000000..bfca4bfb3f71289bd86e113263e1911144473679
--- /dev/null
+++ b/lib/tasks/gitlab/artifacts/migrate.rake
@@ -0,0 +1,25 @@
+require 'logger'
+require 'resolv-replace'
+
+desc "GitLab | Migrate files for artifacts to comply with new storage format"
+namespace :gitlab do
+  namespace :artifacts do
+    task migrate: :environment do
+      logger = Logger.new(STDOUT)
+      logger.info('Starting transfer of artifacts')
+
+      Ci::Build.joins(:project)
+        .with_artifacts_stored_locally
+        .find_each(batch_size: 10) do |build|
+        begin
+          build.artifacts_file.migrate!(ObjectStorage::Store::REMOTE)
+          build.artifacts_metadata.migrate!(ObjectStorage::Store::REMOTE)
+
+          logger.info("Transferred artifacts of #{build.id} of #{build.artifacts_size} to object storage")
+        rescue => e
+          logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
+        end
+      end
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 2403f57f05a4f3e8d7f779e3a77b289206c20cc8..abef8cd2bcc1073b4c27f4140db5a5b5ddb3d51f 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -61,7 +61,7 @@ namespace :gitlab do
       puts "Repo base directory exists?"
 
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        repo_base_path = repository_storage['path']
+        repo_base_path = repository_storage.legacy_disk_path
         print "#{name}... "
 
         if File.exist?(repo_base_path)
@@ -86,7 +86,7 @@ namespace :gitlab do
       puts "Repo storage directories are symlinks?"
 
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        repo_base_path = repository_storage['path']
+        repo_base_path = repository_storage.legacy_disk_path
         print "#{name}... "
 
         unless File.exist?(repo_base_path)
@@ -110,7 +110,7 @@ namespace :gitlab do
       puts "Repo paths access is drwxrws---?"
 
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        repo_base_path = repository_storage['path']
+        repo_base_path = repository_storage.legacy_disk_path
         print "#{name}... "
 
         unless File.exist?(repo_base_path)
@@ -140,7 +140,7 @@ namespace :gitlab do
       puts "Repo paths owned by #{gitlab_shell_ssh_user}:root, or #{gitlab_shell_ssh_user}:#{Gitlab.config.gitlab_shell.owner_group}?"
 
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        repo_base_path = repository_storage['path']
+        repo_base_path = repository_storage.legacy_disk_path
         print "#{name}... "
 
         unless File.exist?(repo_base_path)
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 2453079911d9de82fdc2135083ed2c32a0f356eb..d6d152854899c4b33edad624b794b46f5fb94e41 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -12,7 +12,7 @@ namespace :gitlab do
       namespaces  = Namespace.pluck(:path)
       namespaces << HASHED_REPOSITORY_NAME  # add so that it will be ignored
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        git_base_path = repository_storage['path']
+        git_base_path = repository_storage.legacy_disk_path
         all_dirs = Dir.glob(git_base_path + '/*')
 
         puts git_base_path.color(:yellow)
@@ -54,7 +54,7 @@ namespace :gitlab do
 
       move_suffix = "+orphaned+#{Time.now.to_i}"
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        repo_root = repository_storage['path']
+        repo_root = repository_storage.legacy_disk_path
         # Look for global repos (legacy, depth 1) and normal repos (depth 2)
         IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
           find.each_line do |path|
diff --git a/lib/tasks/gitlab/exclusive_lease.rake b/lib/tasks/gitlab/exclusive_lease.rake
new file mode 100644
index 0000000000000000000000000000000000000000..83722bf6d94b2df8f3417b2255ca00d49d935c40
--- /dev/null
+++ b/lib/tasks/gitlab/exclusive_lease.rake
@@ -0,0 +1,9 @@
+namespace :gitlab do
+  namespace :exclusive_lease do
+    desc 'GitLab | Clear existing exclusive leases for specified scope (default: *)'
+    task :clear, [:scope] => [:environment] do |_, args|
+      args[:scope].nil? ? Gitlab::ExclusiveLease.reset_all! : Gitlab::ExclusiveLease.reset_all!(args[:scope])
+      puts 'All exclusive lease entries were removed.'
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index 45e9a1a1c7243ae4b2b24e13e22af5a5e8cf1aaa..47ed522aec30fe4a9c6571ae6079bf6b2dc8dfcc 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -68,7 +68,7 @@ namespace :gitlab do
       puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}"
       puts "Repository storage paths:"
       Gitlab.config.repositories.storages.each do |name, repository_storage|
-        puts "- #{name}: \t#{repository_storage['path']}"
+        puts "- #{name}: \t#{repository_storage.legacy_disk_path}"
       end
       puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
       puts "Git:\t\t#{Gitlab.config.git.bin_path}"
diff --git a/lib/tasks/gitlab/lfs/check.rake b/lib/tasks/gitlab/lfs/check.rake
new file mode 100644
index 0000000000000000000000000000000000000000..869463d4e5dfea2a50ec0c4d335e415e0d9a1705
--- /dev/null
+++ b/lib/tasks/gitlab/lfs/check.rake
@@ -0,0 +1,8 @@
+namespace :gitlab do
+  namespace :lfs do
+    desc 'GitLab | LFS | Check integrity of uploaded LFS objects'
+    task check: :environment do
+      Gitlab::Verify::RakeTask.run!(Gitlab::Verify::LfsObjects)
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/lfs/migrate.rake b/lib/tasks/gitlab/lfs/migrate.rake
new file mode 100644
index 0000000000000000000000000000000000000000..a45e5ca91e0ca932e413e3187f049f7dba5763e1
--- /dev/null
+++ b/lib/tasks/gitlab/lfs/migrate.rake
@@ -0,0 +1,22 @@
+require 'logger'
+
+desc "GitLab | Migrate LFS objects to remote storage"
+namespace :gitlab do
+  namespace :lfs do
+    task migrate: :environment do
+      logger = Logger.new(STDOUT)
+      logger.info('Starting transfer of LFS files to object storage')
+
+      LfsObject.with_files_stored_locally
+        .find_each(batch_size: 10) do |lfs_object|
+          begin
+            lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+
+            logger.info("Transferred LFS object #{lfs_object.oid} of size #{lfs_object.size.to_i.bytes} to object storage")
+          rescue => e
+            logger.error("Failed to transfer LFS object #{lfs_object.oid} with error: #{e.message}")
+          end
+        end
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index 1d903c81358bab12c3e225f028499b86d79fe39f..f71e69987cb4a7215f4f0a2d0c699abb264c074e 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -1,9 +1,20 @@
 namespace :gitlab do
   desc "GitLab | Setup production application"
   task setup: :gitlab_environment do
+    check_gitaly_connection
     setup_db
   end
 
+  def check_gitaly_connection
+    Gitlab.config.repositories.storages.each do |name, _details|
+      Gitlab::GitalyClient::ServerService.new(name).info
+    end
+  rescue GRPC::Unavailable => ex
+    puts "Failed to connect to Gitaly...".color(:red)
+    puts "Error: #{ex}"
+    exit 1
+  end
+
   def setup_db
     warn_user_is_not_gitlab
 
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 844664b12d4f0ce67bfa3a8c17a38831a97ad504..4fcbbbf8c9db566baeedc425b880dff5c8acc7c2 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -69,7 +69,7 @@ namespace :gitlab do
         if File.exist?(path_to_repo)
           print '-'
         else
-          if Gitlab::Shell.new.add_repository(project.repository_storage,
+          if Gitlab::Shell.new.create_repository(project.repository_storage,
                                               project.disk_path)
             print '.'
           else
diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake
index 8ac73bc8ff25425d7d922eb40c5462df761c9b1d..6e8bd9078c8d8fab1be537ec3884de1a615a45ab 100644
--- a/lib/tasks/gitlab/storage.rake
+++ b/lib/tasks/gitlab/storage.rake
@@ -111,7 +111,7 @@ namespace :gitlab do
 
           puts "  - #{project.full_path} (id: #{project.id})".color(:red)
 
-          return if counter >= limit # rubocop:disable Lint/NonLocalExitFromIterator
+          return if counter >= limit # rubocop:disable Lint/NonLocalExitFromIterator, Cop/AvoidReturnFromBlocks
         end
       end
     end
@@ -132,7 +132,7 @@ namespace :gitlab do
 
           puts "  - #{upload.path} (id: #{upload.id})".color(:red)
 
-          return if counter >= limit # rubocop:disable Lint/NonLocalExitFromIterator
+          return if counter >= limit # rubocop:disable Lint/NonLocalExitFromIterator, Cop/AvoidReturnFromBlocks
         end
       end
     end
diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake
new file mode 100644
index 0000000000000000000000000000000000000000..fd2a4f2d11a460f07b6b16e5726c9eb3396d021a
--- /dev/null
+++ b/lib/tasks/gitlab/traces.rake
@@ -0,0 +1,24 @@
+require 'logger'
+require 'resolv-replace'
+
+desc "GitLab | Archive legacy traces to trace artifacts"
+namespace :gitlab do
+  namespace :traces do
+    task archive: :environment do
+      logger = Logger.new(STDOUT)
+      logger.info('Archiving legacy traces')
+
+      Ci::Build.finished
+        .where('NOT EXISTS (?)',
+          Ci::JobArtifact.select(1).trace.where('ci_builds.id = ci_job_artifacts.job_id'))
+        .order(id: :asc)
+        .find_in_batches(batch_size: 1000) do |jobs|
+        job_ids = jobs.map { |job| [job.id] }
+
+        ArchiveTraceWorker.bulk_perform_async(job_ids)
+
+        logger.info("Scheduled #{job_ids.count} jobs. From #{job_ids.min} to #{job_ids.max}")
+      end
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/two_factor.rake b/lib/tasks/gitlab/two_factor.rake
index 7728c485e8dbc0b3d52ff117bd34232c2cd2b919..6b22499a5c8d4798d14f69bdc0cf390c6c38a477 100644
--- a/lib/tasks/gitlab/two_factor.rake
+++ b/lib/tasks/gitlab/two_factor.rake
@@ -1,7 +1,7 @@
 namespace :gitlab do
   namespace :two_factor do
     desc "GitLab | Disable Two-factor authentication (2FA) for all users"
-    task disable_for_all_users: :environment do
+    task disable_for_all_users: :gitlab_environment do
       scope = User.with_two_factor
       count = scope.count
 
diff --git a/lib/tasks/gitlab/uploads.rake b/lib/tasks/gitlab/uploads.rake
deleted file mode 100644
index df31567ce648f1ec1249edc1d1ae67b0027d4b45..0000000000000000000000000000000000000000
--- a/lib/tasks/gitlab/uploads.rake
+++ /dev/null
@@ -1,44 +0,0 @@
-namespace :gitlab do
-  namespace :uploads do
-    desc 'GitLab | Uploads | Check integrity of uploaded files'
-    task check: :environment do
-      puts 'Checking integrity of uploaded files'
-
-      uploads_batches do |batch|
-        batch.each do |upload|
-          puts "- Checking file (#{upload.id}): #{upload.absolute_path}".color(:green)
-
-          if upload.exist?
-            check_checksum(upload)
-          else
-            puts "  * File does not exist on the file system".color(:red)
-          end
-        end
-      end
-
-      puts 'Done!'
-    end
-
-    def batch_size
-      ENV.fetch('BATCH', 200).to_i
-    end
-
-    def calculate_checksum(absolute_path)
-      Digest::SHA256.file(absolute_path).hexdigest
-    end
-
-    def check_checksum(upload)
-      checksum = calculate_checksum(upload.absolute_path)
-
-      if checksum != upload.checksum
-        puts "  * File checksum (#{checksum}) does not match the one in the database (#{upload.checksum})".color(:red)
-      end
-    end
-
-    def uploads_batches(&block)
-      Upload.all.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches
-        yield relation
-      end
-    end
-  end
-end
diff --git a/lib/tasks/gitlab/uploads/check.rake b/lib/tasks/gitlab/uploads/check.rake
new file mode 100644
index 0000000000000000000000000000000000000000..2be2ec7f9c9adea7a986c19f99e305947326e18d
--- /dev/null
+++ b/lib/tasks/gitlab/uploads/check.rake
@@ -0,0 +1,8 @@
+namespace :gitlab do
+  namespace :uploads do
+    desc 'GitLab | Uploads | Check integrity of uploaded files'
+    task check: :environment do
+      Gitlab::Verify::RakeTask.run!(Gitlab::Verify::Uploads)
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/uploads/migrate.rake b/lib/tasks/gitlab/uploads/migrate.rake
new file mode 100644
index 0000000000000000000000000000000000000000..78e18992a8e5845fdedeeb68fc2058d7c1fda91d
--- /dev/null
+++ b/lib/tasks/gitlab/uploads/migrate.rake
@@ -0,0 +1,34 @@
+namespace :gitlab do
+  namespace :uploads do
+    desc 'GitLab | Uploads | Migrate the uploaded files to object storage'
+    task :migrate, [:uploader_class, :model_class, :mounted_as] => :environment do |task, args|
+      batch_size     = ENV.fetch('BATCH', 200).to_i
+      @to_store      = ObjectStorage::Store::REMOTE
+      @mounted_as    = args.mounted_as&.gsub(':', '')&.to_sym
+      @uploader_class = args.uploader_class.constantize
+      @model_class    = args.model_class.constantize
+
+      uploads.each_batch(of: batch_size, &method(:enqueue_batch)) # rubocop: disable Cop/InBatches
+    end
+
+    def enqueue_batch(batch, index)
+      job = ObjectStorage::MigrateUploadsWorker.enqueue!(batch,
+                                                         @model_class,
+                                                         @mounted_as,
+                                                         @to_store)
+      puts "Enqueued job ##{index}: #{job}"
+    rescue ObjectStorage::MigrateUploadsWorker::SanityCheckError => e
+      # continue for the next batch
+      puts "Could not enqueue batch (#{batch.ids}) #{e.message}".color(:red)
+    end
+
+    def uploads
+      Upload.class_eval { include EachBatch } unless Upload < EachBatch
+
+      Upload
+        .where(store: [nil, ObjectStorage::Store::LOCAL],
+               uploader: @uploader_class.to_s,
+               model_type: @model_class.base_class.sti_name)
+    end
+  end
+end
diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake
index 5c0cc4990fcd52ae4e00e7da554abd9235c6c1f7..ad2d034b0b4d53e07aee4f223719714f835675d3 100644
--- a/lib/tasks/haml-lint.rake
+++ b/lib/tasks/haml-lint.rake
@@ -2,14 +2,5 @@ unless Rails.env.production?
   require 'haml_lint/rake_task'
   require 'haml_lint/inline_javascript'
 
-  # Workaround for warnings from parser/current
-  # TODO: Remove this after we update parser gem
-  task :haml_lint do
-    require 'parser'
-    def Parser.warn(*args)
-      puts(*args) # static-analysis ignores stdout if status is 0
-    end
-  end
-
   HamlLint::RakeTask.new
 end
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index 1c7a8a90f5cc5307c682553349856e1cc6e24d9c..af30ecb0e9b47208e94b8a9b73b8350af73b1f56 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -7,8 +7,8 @@ task setup_postgresql: :environment do
   require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
   require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
   require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb')
-  require Rails.root.join('db/migrate/20180113220114_rework_redirect_routes_indexes.rb')
   require Rails.root.join('db/migrate/20180215181245_users_name_lower_index.rb')
+  require Rails.root.join('db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb')
 
   NamespacesProjectsPathLowerIndexes.new.up
   AddUsersLowerUsernameEmailIndexes.new.up
@@ -17,6 +17,6 @@ task setup_postgresql: :environment do
   AddLowerPathIndexToRedirectRoutes.new.up
   IndexRedirectRoutesPathForLike.new.up
   AddIndexOnNamespacesLowerName.new.up
-  ReworkRedirectRoutesIndexes.new.up
   UsersNameLowerIndex.new.up
+  AddPathIndexToRedirectRoutes.new.up
 end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index 3e01f91d32c5fa6041a294adc6298b403dd506ee..b52af81fc160150f09efbdaa47b7aa2b8dea90a5 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -4,8 +4,3 @@ desc "GitLab | Run all tests"
 task :test do
   Rake::Task["gitlab:test"].invoke
 end
-
-unless Rails.env.production?
-  desc "GitLab | Run all tests on CI with simplecov"
-  task test_ci: [:rubocop, :brakeman, :karma, :spinach, :spec]
-end
diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb
index 4a3c40f88eb9bc9a29dcc7b21664334778cbc660..5dc85b2baead9dd14f129db1d2796f1fc2b367b7 100644
--- a/lib/uploaded_file.rb
+++ b/lib/uploaded_file.rb
@@ -1,8 +1,10 @@
 require "tempfile"
+require "tmpdir"
 require "fileutils"
 
-# Taken from: Rack::Test::UploadedFile
 class UploadedFile
+  InvalidPathError = Class.new(StandardError)
+
   # The filename, *not* including the path, of the "uploaded" file
   attr_reader :original_filename
 
@@ -12,14 +14,46 @@ class UploadedFile
   # The content type of the "uploaded" file
   attr_accessor :content_type
 
-  def initialize(path, filename, content_type = "text/plain")
-    raise "#{path} file does not exist" unless ::File.exist?(path)
+  attr_reader :remote_id
+  attr_reader :sha256
+
+  def initialize(path, filename: nil, content_type: "application/octet-stream", sha256: nil, remote_id: nil)
+    raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path)
 
     @content_type = content_type
     @original_filename = filename || ::File.basename(path)
+    @content_type = content_type
+    @sha256 = sha256
+    @remote_id = remote_id
     @tempfile = File.new(path, 'rb')
   end
 
+  def self.from_params(params, field, upload_path)
+    unless params["#{field}.path"]
+      raise InvalidPathError, "file is invalid" if params["#{field}.remote_id"]
+
+      return
+    end
+
+    file_path = File.realpath(params["#{field}.path"])
+
+    unless self.allowed_path?(file_path, [upload_path, Dir.tmpdir].compact)
+      raise InvalidPathError, "insecure path used '#{file_path}'"
+    end
+
+    UploadedFile.new(file_path,
+      filename: params["#{field}.name"],
+      content_type: params["#{field}.type"] || 'application/octet-stream',
+      sha256: params["#{field}.sha256"],
+      remote_id: params["#{field}.remote_id"])
+  end
+
+  def self.allowed_path?(file_path, paths)
+    paths.any? do |path|
+      File.exist?(path) && file_path.start_with?(File.realpath(path))
+    end
+  end
+
   def path
     @tempfile.path
   end
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index badd665c08c89f6722c7e20926ec448947bef529..a170014844f2b80bfe61216fb45d2baeb516c103 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 03:58-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:34-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Bulgarian\n"
 "Language: bg_BG\n"
@@ -29,6 +29,11 @@ msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -44,11 +49,19 @@ msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s 锌芯写邪胁邪薪械 斜械褕械 锌褉芯锌褍褋薪邪褌芯, 蟹邪 写邪 薪械 褋械 薪邪褌芯胁邪褉胁邪 褋懈褋褌械屑邪褌邪."
 msgstr[1] "%s 锌芯写邪胁邪薪懈褟 斜褟褏邪 锌褉芯锌褍褋薪邪褌懈, 蟹邪 写邪 薪械 褋械 薪邪褌芯胁邪褉胁邪 褋懈褋褌械屑邪褌邪."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -57,6 +70,12 @@ msgid_plural "%{count} participants"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -66,6 +85,9 @@ msgstr ""
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr ""
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] ""
@@ -94,15 +116,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "袧邪斜芯褉 芯褌 谐褉邪褎懈泻懈 芯褌薪芯褋薪芯 薪械锌褉械泻褗褋薪邪褌邪褌邪 懈薪褌械谐褉邪褑懈褟"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "袨褌薪芯褋薪芯 邪胁褌芯屑邪褌懈褔薪芯褌芯 胁薪械写褉褟胁邪薪械"
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -112,6 +149,9 @@ msgstr ""
 msgid "Account"
 msgstr ""
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "袗泻褌懈胁薪芯"
 
@@ -130,9 +170,15 @@ msgstr "袛芯斜邪胁褟薪械 薪邪 褉褗泻芯胁芯写褋褌胁芯 蟹邪 褋褗褌褉褍写薪懈褔械褋
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "袛芯斜邪胁褟薪械 薪邪 谢懈褑械薪蟹"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "袛芯斜邪胁褟薪械 薪邪 薪芯胁邪 锌邪锌泻邪"
 
@@ -151,12 +197,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -169,9 +248,33 @@ msgstr ""
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -181,6 +284,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -190,12 +299,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -208,12 +341,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr ""
 
@@ -232,21 +374,24 @@ msgstr "袗褉褏懈胁懈褉邪薪 锌褉芯械泻褌! 啸褉邪薪懈谢懈褖械褌芯 械 褋邪屑芯 蟹邪
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "袧邪懈褋褌懈薪邪 谢懈 懈褋泻邪褌械 写邪 懈蟹褌褉懈械褌械 褌芯蟹懈 锌谢邪薪 蟹邪 褋褏械屑邪?"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr ""
-
 msgid "Are you sure you want to reset registration token?"
 msgstr ""
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr ""
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr ""
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -259,6 +404,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -280,6 +434,12 @@ msgstr ""
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -304,7 +464,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -316,6 +482,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -370,13 +545,10 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "袣谢芯薪"
-msgstr[1] "袣谢芯薪懈"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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}"
@@ -399,6 +571,15 @@ msgstr "袩褉械胁泻谢褞褔胁邪薪械 薪邪 泻谢芯薪邪"
 msgid "Branches"
 msgstr "袣谢芯薪懈"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -444,12 +625,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -495,30 +703,45 @@ msgstr "袩褉械谐谢械写 薪邪 褎邪泄谢芯胁械褌械"
 msgid "Browse files"
 msgstr "袪邪蟹谐谢械卸写邪薪械 薪邪 褎邪泄谢芯胁械褌械"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "芯褌"
 
 msgid "CI / CD"
 msgstr ""
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr "袨褌泻邪蟹"
 
-msgid "Cancel edit"
+msgid "Cannot be merged automatically"
 msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "袠蟹斜懈褉邪薪械 胁 泻谢芯薪邪"
 
@@ -573,6 +796,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -669,9 +898,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -723,6 +964,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -768,12 +1012,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -792,6 +1045,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -822,10 +1078,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -888,6 +1144,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -915,6 +1174,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -960,6 +1222,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr ""
 
@@ -968,6 +1236,11 @@ msgid_plural "Commits"
 msgstr[0] "袩芯写邪胁邪薪械"
 msgstr[1] "袩芯写邪胁邪薪懈褟"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "Commit Message"
 msgstr ""
 
@@ -980,6 +1253,9 @@ msgstr "小褗芯斜褖械薪懈械 蟹邪 锌芯写邪胁邪薪械褌芯"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "袩芯写邪胁邪薪械"
 
@@ -1025,6 +1301,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1040,9 +1322,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1088,6 +1406,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "袪褗泻芯胁芯写褋褌胁芯 蟹邪 褋褗褌褉褍写薪懈褔械褋褌胁芯"
 
@@ -1121,6 +1445,9 @@ msgstr "袣芯锌懈褉邪薪械 薪邪 邪写褉械褋邪 胁 斜褍褎械褉邪 蟹邪 芯斜屑械薪"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "袣芯锌懈褉邪薪械 薪邪 懈写械薪褌懈褎懈泻邪褌芯褉邪 薪邪 锌芯写邪胁邪薪械褌芯 胁 斜褍褎械褉邪 蟹邪 芯斜屑械薪"
 
@@ -1133,14 +1460,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "小褗蟹写邪胁邪薪械 薪邪 薪芯胁邪 锌邪锌泻邪"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "小褗蟹写邪泄褌械 褋懈 谢懈褔械薪 卸械褌芯薪 蟹邪 写芯褋褌褗锌 胁 邪泻邪褍薪褌邪 褋懈, 蟹邪 写邪 屑芯卸械褌械 写邪 懈蟹褌械谐谢褟褌械 懈 懈蟹锌褉邪褖邪褌械 锌褉芯屑械薪懈 褔褉械蟹 %{protocol}."
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "小褗蟹写邪胁邪薪械 薪邪 锌邪锌泻邪"
 
-msgid "Create empty bare repository"
-msgstr "小褗蟹写邪胁邪薪械 薪邪 锌褉邪蟹薪芯 褏褉邪薪懈谢懈褖械"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1148,12 +1484,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "小褗蟹写邪胁邪薪械 薪邪 蟹邪褟胁泻邪 蟹邪 褋谢懈胁邪薪械"
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1169,6 +1511,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "小褗蟹写邪胁邪薪械 薪邪 薪芯胁鈥�"
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "袪邪蟹泻谢芯薪褟胁邪薪械"
 
@@ -1178,6 +1523,12 @@ msgstr "袝褌懈泻械褌"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "褋懈 褋褗蟹写邪写械褌械 谢懈褔械薪 卸械褌芯薪 蟹邪 写芯褋褌褗锌"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1196,6 +1547,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "袗薪邪谢懈蟹 薪邪 褑懈泻谢懈褌械"
 
@@ -1232,6 +1586,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "袟邪写邪泄褌械 锌芯褌褉械斜懈褌械谢褋泻懈 褕邪斜谢芯薪, 懈蟹锌芯谢蟹胁邪泄泻懈 褋懈薪褌邪泻褋懈褋邪 薪邪 鈥濩ron鈥�"
 
@@ -1264,7 +1621,7 @@ msgstr "袠屑械 薪邪 锌邪锌泻邪褌邪"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
+msgid "Discard draft"
 msgstr ""
 
 msgid "Discover GitLab Geo."
@@ -1276,9 +1633,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "袛邪 薪械 褋械 锌芯泻邪蟹胁邪 锌芯胁械褔械"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "小胁邪谢褟薪械"
 
@@ -1306,9 +1669,15 @@ msgstr "袨斜懈泻薪芯胁械薪 褎邪泄谢 褋 褉邪蟹谢懈泻懈"
 msgid "DownloadSource|Download"
 msgstr "小胁邪谢褟薪械"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "袪械写邪泻褌懈褉邪薪械"
 
@@ -1318,12 +1687,54 @@ msgstr "袪械写邪泻褌懈褉邪薪械 薪邪 锌谢邪薪邪 %{id} 蟹邪 褋褏械屑邪"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1378,9 +1789,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1447,12 +1870,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "小芯斜褋褌胁械薪懈泻褗褌 薪械 屑芯卸械 写邪 斜褗写械 锌褉芯屑械薪械薪"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "袩谢邪薪褗褌 蟹邪 褋褏械屑邪 薪械 屑芯卸械 写邪 斜褗写械 锌褉械屑邪褏薪邪褌"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1468,6 +1924,12 @@ msgstr ""
 msgid "Files"
 msgstr "肖邪泄谢芯胁械"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "肖懈谢褌褉懈褉邪薪械 锌芯 褋褗芯斜褖械薪懈械"
 
@@ -1477,12 +1939,21 @@ msgstr "孝褗褉褋械薪械 锌芯 锌褗褌"
 msgid "Find file"
 msgstr "孝褗褉褋械薪械 薪邪 褎邪泄谢"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "袩褗褉胁芯"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "懈蟹锌褉邪褖邪薪械 薪邪 锌褉芯屑械薪懈 芯褌"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "袪邪蟹泻谢芯薪械薪懈械"
@@ -1494,15 +1965,24 @@ msgstr "袪邪蟹泻谢芯薪械薪懈械 薪邪"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr "袨褌 褋褗蟹写邪胁邪薪械褌芯 薪邪 锌褉芯斜谢械屑邪 写芯 胁薪械写褉褟胁邪薪械褌芯 胁 泻褉邪泄薪邪褌邪 胁械褉褋懈褟"
 
 msgid "From merge request merge until deploy to production"
 msgstr "袨褌 锌褉懈谢邪谐邪薪械褌芯 薪邪 蟹邪褟胁泻邪褌邪 蟹邪 褋谢懈胁邪薪械 写芯 胁薪械写褉褟胁邪薪械褌芯 胁 泻褉邪泄薪邪褌邪 胁械褉褋懈褟"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1512,12 +1992,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1563,21 +2049,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1590,9 +2103,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1623,6 +2148,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1632,12 +2160,30 @@ msgstr ""
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr ""
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "袣褗屑 袙邪褕械褌芯 褉邪蟹泻谢芯薪械薪懈械"
 
@@ -1650,6 +2196,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1686,9 +2250,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1719,6 +2280,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr ""
 
@@ -1737,6 +2301,15 @@ msgstr ""
 msgid "HealthCheck|Unhealthy"
 msgstr ""
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1748,9 +2321,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr "袨褋胁械卸邪胁邪薪械褌芯 蟹邪锌芯褔薪邪 褍褋锌械褕薪芯"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "袙薪邪褋褟薪械 薪邪 褏褉邪薪懈谢懈褖械"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1760,6 +2363,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr ""
 
@@ -1771,6 +2377,9 @@ msgstr[1] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1810,6 +2419,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1822,6 +2434,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1840,6 +2455,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1849,12 +2467,30 @@ msgstr "袠蟹泻谢褞褔械薪芯"
 msgid "LFSStatus|Enabled"
 msgstr "袙泻谢褞褔械薪芯"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "袩芯褋谢械写薪懈褟 %d 写械薪"
@@ -1887,6 +2523,12 @@ msgstr ""
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "袧邪褍褔械褌械 锌芯胁械褔械 胁"
 
@@ -1905,6 +2547,12 @@ msgstr "袧邪锌褍褋泻邪薪械 薪邪 锌褉芯械泻褌邪"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1914,7 +2562,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1923,15 +2571,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1953,7 +2616,7 @@ msgstr "袦械写懈邪薪邪"
 msgid "Members"
 msgstr ""
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1974,6 +2637,81 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1989,12 +2727,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "写芯斜邪胁懈褌械 SSH 泻谢褞褔"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr ""
 
@@ -2066,6 +2825,9 @@ msgstr ""
 msgid "New tag"
 msgstr "袧芯胁 械褌懈泻械褌"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2084,15 +2846,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "袧褟屑邪 褏褉邪薪懈谢懈褖械"
 
 msgid "No schedules"
 msgstr "袧褟屑邪 锌谢邪薪芯胁械"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr ""
 
@@ -2102,12 +2864,33 @@ msgstr ""
 msgid "Not available"
 msgstr "袧械 械 薪邪谢懈褔薪芯"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "袧褟屑邪 写芯褋褌邪褌褗褔薪芯 写邪薪薪懈"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "小褗斜懈褌懈褟 蟹邪 懈蟹胁械褋褌褟胁邪薪械"
 
@@ -2192,6 +2975,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr "肖懈谢褌褗褉"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2210,12 +2999,21 @@ msgstr ""
 msgid "Options"
 msgstr "袨锌褑懈懈"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr "小芯斜褋褌胁械薪懈泻"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2228,9 +3026,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "小褏械屑邪"
 
@@ -2312,9 +3122,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "胁褋懈褔泻懈"
 
@@ -2327,6 +3182,9 @@ msgstr "褋 械褌邪锌"
 msgid "Pipeline|with stages"
 msgstr "褋 械褌邪锌懈"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2336,6 +3194,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2348,6 +3212,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr ""
 
@@ -2387,6 +3254,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2411,9 +3281,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr ""
 
@@ -2510,37 +3377,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2558,6 +3476,12 @@ msgstr ""
 msgid "Push events"
 msgstr ""
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2570,6 +3494,9 @@ msgstr "袩褉芯褔械褌械褌械 锌芯胁械褔械"
 msgid "Readme"
 msgstr "袩褉芯褔械褌懈袦械"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "袣谢芯薪懈"
 
@@ -2603,6 +3530,9 @@ msgstr "小胁褗褉蟹邪薪懈 蟹邪褟胁泻懈 蟹邪 褋谢懈胁邪薪械"
 msgid "Related Merged Requests"
 msgstr "小胁褗褉蟹邪薪懈 锌褉懈谢芯卸械薪懈 蟹邪褟胁泻懈 蟹邪 褋谢懈胁邪薪械"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "袧邪锌芯屑薪褟薪械 锌芯-泻褗褋薪芯"
 
@@ -2618,9 +3548,24 @@ msgstr "袩褉械屑邪褏胁邪薪械 薪邪 锌褉芯械泻褌邪"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "袟邪褟胁泻邪 蟹邪 写芯褋褌褗锌"
 
@@ -2633,6 +3578,12 @@ msgstr ""
 msgid "Reset runners registration token"
 msgstr ""
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2644,6 +3595,36 @@ msgstr "袨褌屑褟薪邪 薪邪 褌芯胁邪 锌芯写邪胁邪薪械"
 msgid "Revert this merge request"
 msgstr "袨褌屑褟薪邪 薪邪 褌邪蟹懈 蟹邪褟胁泻邪 蟹邪 褋谢懈胁邪薪械"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2659,6 +3640,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "小褗蟹写邪胁邪薪械 薪邪 薪芯胁 锌谢邪薪 蟹邪 褋褏械屑邪"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2668,6 +3652,9 @@ msgstr "袩谢邪薪懈褉邪薪械 薪邪 褋褏械屑懈褌械"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "孝褗褉褋械褌械 胁 泻谢芯薪懈褌械 懈 械褌懈泻械褌懈褌械"
 
@@ -2689,12 +3676,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "袠蟹斜械褉械褌械 褎芯褉屑邪褌邪 薪邪 邪褉褏懈胁邪"
 
 msgid "Select a timezone"
 msgstr "袠蟹斜械褉械褌械 褔邪褋芯胁邪 蟹芯薪邪"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2707,6 +3700,9 @@ msgstr "袠蟹斜械褉械褌械 褑械谢械胁懈 泻谢芯薪"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2719,22 +3715,46 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "袟邪写邪泄褌械 锌邪褉芯谢邪 薪邪 邪泻邪褍薪褌邪 褋懈, 蟹邪 写邪 屑芯卸械褌械 写邪 懈蟹褌械谐谢褟褌械 懈 懈蟹锌褉邪褖邪褌械 锌褉芯屑械薪懈 褔褉械蟹 %{protocol}."
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "袧邪褋褌褉芯泄泻邪 薪邪 鈥濳oding鈥�"
 
-msgid "Set up auto deploy"
-msgstr "袧邪褋褌褉芯泄泻邪 薪邪 邪胁褌. 胁薪械写褉褟胁邪薪械"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "蟹邪写邪写械褌械 锌邪褉芯谢邪"
 
-msgid "Settings"
+msgid "Settings"
+msgstr ""
+
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
 msgstr ""
 
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
@@ -2746,6 +3766,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2769,6 +3792,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2778,13 +3813,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2898,6 +3933,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "袠蟹褏芯写械薪 泻芯写"
 
@@ -2907,12 +3945,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr "袟胁械蟹写邪"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2922,6 +3969,15 @@ msgstr "小褗蟹写邪泄褌械 %{new_merge_request} 褋 褌械蟹懈 锌褉芯屑械薪懈"
 msgid "Start the Runner!"
 msgstr ""
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2934,13 +3990,19 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr "袩褉械屑懈薪邪胁邪薪械 泻褗屑 泻谢芯薪/械褌懈泻械褌"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "袝褌懈泻械褌"
-msgstr[1] "袝褌懈泻械褌懈"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "Tags"
 msgstr "袝褌懈泻械褌懈"
@@ -3017,6 +4079,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "笑械谢械胁懈 泻谢芯薪"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3032,15 +4097,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "袙褉褗蟹泻邪褌邪 薪邪 褉邪蟹泻谢芯薪械薪懈械 斜械褕械 锌褉械屑邪褏薪邪褌邪."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "袝褌邪锌褗褌 薪邪 锌褉芯斜谢械屑懈褌械 锌芯泻邪蟹胁邪 泻芯谢泻芯 械 胁褉械屑械褌芯 芯褌 褋褗蟹写邪胁邪薪械褌芯 薪邪 锌褉芯斜谢械屑 写芯 芯锌褉械写械谢褟薪械褌芯 薪邪 褑械谢械胁懈 械褌邪锌 薪邪 锌褉芯械泻褌邪 蟹邪 薪械谐芯, 懈谢懈 写芯 写芯斜邪胁褟薪械褌芯 屑褍 胁 褋锌懈褋褗泻 薪邪 写褗褋泻邪褌邪 蟹邪 锌褉芯斜谢械屑懈. 袟邪锌芯褔薪械褌械 写邪 写芯斜邪胁褟褌械 锌褉芯斜谢械屑懈, 蟹邪 写邪 胁懈写懈褌械 写邪薪薪懈褌械 蟹邪 褌芯蟹懈 械褌邪锌."
 
@@ -3053,12 +4127,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "袝褌邪锌褗褌 薪邪 懈蟹写邪胁邪薪械 锌芯泻邪蟹胁邪 芯斜褖芯褌芯 胁褉械屑械, 泻芯械褌芯 械 薪褍卸薪芯 芯褌 褋褗蟹写邪胁邪薪械褌芯 薪邪 锌褉芯斜谢械屑 写芯 胁薪械写褉褟胁邪薪械褌芯 薪邪 泻芯写邪 胁 泻褉邪泄薪邪褌邪 胁械褉褋懈褟. 袛邪薪薪懈褌械 褖械 斜褗写邪褌 写芯斜邪胁械薪懈 邪胁褌芯屑邪褌懈褔薪芯 褋谢械写 泻邪褌芯 蟹邪胁褗褉褕懈褌械 械写懈薪 锌褗谢械薪 褑懈泻褗谢 懈 锌褉械胁褗褉薪械褌械 锌褗褉胁邪褌邪 褋懈 懈写械褟 胁 褉械邪谢薪芯褋褌."
 
@@ -3071,9 +4151,18 @@ msgstr "袙褋械泻懈 屑芯卸械 写邪 懈屑邪 写芯褋褌褗锌 写芯 锌褉芯械泻褌邪, 斜械蟹
 msgid "The repository for this project does not exist."
 msgstr "啸褉邪薪懈谢懈褖械褌芯 蟹邪 褌芯蟹懈 锌褉芯械泻褌 薪械 褋褗褖械褋褌胁褍胁邪."
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 "袝褌邪锌褗褌 薪邪 锌芯写谐芯褌芯胁泻邪 蟹邪 懈蟹写邪胁邪薪械 锌芯泻邪蟹胁邪 胁褉械屑械褌芯 屑械卸写褍 锌褉懈谢邪谐邪薪械褌芯 薪邪 蟹邪褟胁泻邪褌邪 蟹邪 褋谢懈胁邪薪械 懈 胁薪械写褉褟胁邪薪械褌芯 薪邪 泻芯写邪 胁 褋褉械写邪褌邪 薪邪 褉邪斜芯褌械褖邪褌邪 泻褉邪泄薪邪 胁械褉褋懈褟. 袛邪薪薪懈褌械 褖械 斜褗写邪褌 写芯斜邪胁械薪懈 邪胁褌芯屑邪褌懈褔薪芯 褋谢械写 泻邪褌芯 薪邪锌褉邪胁懈褌械 锌褗褉胁芯褌芯 褋懈 胁薪械写褉褟胁邪薪械 胁 泻褉邪泄薪邪褌邪 胁械褉褋懈褟."
 
@@ -3104,6 +4193,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr ""
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3167,12 +4259,18 @@ msgstr "孝芯胁邪 芯蟹薪邪褔邪胁邪, 褔械 薪褟屑邪 写邪 屑芯卸械褌械 写邪 懈蟹锌褉
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3185,6 +4283,12 @@ msgstr "袙褉械屑械 锌褉械写懈 褉邪斜芯褌邪褌邪 锌芯 锌褉芯斜谢械屑 写邪 蟹邪锌芯
 msgid "Time between merge request creation and merge/close"
 msgstr "袙褉械屑械 屑械卸写褍 褋褗蟹写邪胁邪薪械 薪邪 蟹邪褟胁泻邪 蟹邪 褋谢懈胁邪薪械 懈 锌褉懈谢邪谐邪薪械褌芯/芯褌褏胁褗褉谢褟薪械褌芯 褲"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3336,9 +4440,45 @@ msgstr[1] "屑懈薪"
 msgid "Time|s"
 msgstr "褋械泻"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3354,19 +4494,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "袨斜褖芯 胁褉械屑械"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "袨斜褖芯 胁褉械屑械 蟹邪 褌械褋褌胁邪薪械 薪邪 胁褋懈褔泻懈 锌芯写邪胁邪薪懈褟/褋谢懈胁邪薪懈褟"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3378,22 +4515,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3429,6 +4560,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "褖褉邪泻薪械褌械 蟹邪 泻邪褔胁邪薪械"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3438,21 +4575,51 @@ msgstr ""
 msgid "Use your global notification setting"
 msgstr "袠蟹锌芯谢蟹胁邪薪械 薪邪 谐谢芯斜邪谢薪邪褌邪 袙懈 薪邪褋褌褉芯泄泻邪 蟹邪 懈蟹胁械褋褌懈褟褌邪"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "袩褉械谐谢械写 薪邪 芯褌胁芯褉械薪邪褌邪 蟹邪褟胁泻邪 蟹邪 褋谢懈胁邪薪械"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "袙褗褌褉械褕械薪"
 
@@ -3477,12 +4644,21 @@ msgstr "袧褟屑邪 写芯褋褌邪褌褗褔薪芯 写邪薪薪懈 蟹邪 褌芯蟹懈 械褌邪锌."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3597,22 +4773,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "袨褌褌械谐谢褟薪械 薪邪 蟹邪褟胁泻邪褌邪 蟹邪 写芯褋褌褗锌"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "袧邪 锌褗褌 褋褌械 写邪 锌褉械屑邪褏薪械褌械 鈥�%{group_name}鈥�. 袗泻芯 褟 锌褉械屑邪褏薪械褌械, 谐褉褍锌邪褌邪 袧袝 屑芯卸械 写邪 斜褗写械 胁褗蟹褋褌邪薪芯胁械薪邪! 袧袗袠小孝袠袧袗 谢懈 懈褋泻邪褌械 褌芯胁邪?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "袧邪 锌褗褌 褋褌械 写邪 锌褉械屑邪褏薪械褌械 鈥�%{project_name_with_namespace}鈥�. 袗泻芯 谐芯 锌褉械屑邪褏薪械褌械, 褌芯泄 袧袝 屑芯卸械 写邪 斜褗写械 胁褗蟹褋褌邪薪芯胁械薪!袧袗袠小孝袠袧袗 谢懈 懈褋泻邪褌械 褌芯胁邪?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "袧邪 锌褗褌 褋褌械 写邪 锌褉械屑邪褏薪械褌械 胁褉褗蟹泻邪褌邪 薪邪 褉邪蟹泻谢芯薪械薪懈械褌芯 泻褗屑 芯褉懈谐懈薪邪谢薪懈褟 锌褉芯械泻褌, 鈥�%{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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3630,12 +4818,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "袧械 屑芯卸械褌械 写邪 褋褗蟹写邪胁邪褌械 锌芯胁械褔械 锌褉芯械泻褌懈"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "孝褉褟斜胁邪 写邪 褋械 胁锌懈褕械褌械, 蟹邪 写邪 芯褌斜械谢械卸懈褌械 锌褉芯械泻褌 褋褗褋 蟹胁械蟹写邪"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "袧褍卸写邪械褌械 褋械 芯褌 褉邪蟹褉械褕械薪懈械."
 
@@ -3666,9 +4866,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3681,6 +4902,14 @@ msgstr "袙邪褕械褌芯 懈屑械"
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3690,13 +4919,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3708,7 +4955,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3723,25 +4970,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3749,14 +5011,84 @@ msgid_plural "days"
 msgstr[0] "写械薪"
 msgstr[1] "写薪懈"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3781,15 +5113,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3823,6 +5167,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3871,12 +5218,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3906,6 +5259,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3915,6 +5271,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3924,3 +5283,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index 4a0ca1e7efbef10d3af1c6266c1aaddbc5138a74..5a5cf1a19a3f84eaee2c78e476385b4219effd50 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:00-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:37-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: German\n"
 "Language: de_DE\n"
@@ -29,6 +29,11 @@ msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -44,11 +49,19 @@ msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s zus盲tzlicher Commit wurde ausgelassen um Leistungsprobleme zu verhindern."
 msgstr[1] "%s zus盲tzliche Commits wurden ausgelassen um Leistungsprobleme zu verhindern."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -57,6 +70,12 @@ msgid_plural "%{count} participants"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -66,6 +85,9 @@ msgstr "%{number_of_failures} von %{maximum_failures} Fehlschl盲gen. GitLab wird
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} von %{maximum_failures} Fehlschl盲gen. GitLab wird es nicht weiter versuchen. Setze die Speicherinformation nach Behebung des Problems zur眉ck."
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}: fehlgeschlagener Speicherzugriff auf Host:"
@@ -94,15 +116,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "Eine Sammlung von Graphen bez眉glich kontinuierlicher Integration"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "脺ber automatische Bereitstellung "
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -112,6 +149,9 @@ msgstr "Zugriff auf fehlerhafte Speicher wurde vor眉bergehend deaktiviert, um di
 msgid "Account"
 msgstr "Konto"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "Aktiv"
 
@@ -130,9 +170,15 @@ msgstr "Mitarbeitsanleitung hinzuf眉gen"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "Lizenz hinzuf眉gen"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "Erstelle eine neues Verzeichnis"
 
@@ -151,12 +197,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -169,9 +248,33 @@ msgstr "Alle"
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -181,6 +284,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -190,12 +299,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -208,12 +341,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr ""
 
@@ -232,21 +374,24 @@ msgstr "Archiviertes Projekt! Repository ist nicht 盲nderbar."
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "Bist Du sicher, dass Du diesen Pipeline-Zeitplan l枚schen m枚chtest?"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "Bist Du sicher, dass Du alle 脛nderungen zur眉cksetzen willst?"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "Bist Du sicher, dass Du den Registrierungstoken zur眉cksetzen willst?"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "Bist Du sicher, dass Du den System眉berwachungstoken zur眉cksetzen willst?"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr "Bist Du sicher?"
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -259,6 +404,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -280,6 +434,12 @@ msgstr ""
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -304,7 +464,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -316,6 +482,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -370,13 +545,10 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "Zweig"
-msgstr[1] "Zweige"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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 "Branch <strong>%{branch_name}</strong> wurde erstellt. Um die automatische Bereitstellung einzurichten, w盲hle eine GitLab CI Yaml Vorlage und committe Deine 脛nderungen. %{link_to_autodeploy_doc}"
@@ -399,6 +571,15 @@ msgstr "Branch wechseln"
 msgid "Branches"
 msgstr ""
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -444,12 +625,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -495,30 +703,45 @@ msgstr "Dateien durchsuchen"
 msgid "Browse files"
 msgstr "Dateien durchsuchen"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "von"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr "Abbrechen"
 
-msgid "Cancel edit"
-msgstr "Bearbeitung abbrechen"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "In dem Branch w盲hlen"
 
@@ -573,6 +796,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -669,9 +898,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -723,6 +964,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -768,12 +1012,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -792,6 +1045,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -822,10 +1078,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -888,6 +1144,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -915,6 +1174,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -960,6 +1222,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr "Kommentare"
 
@@ -968,6 +1236,11 @@ msgid_plural "Commits"
 msgstr[0] "Commit"
 msgstr[1] "Commits"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "Commit Message"
 msgstr ""
 
@@ -980,6 +1253,9 @@ msgstr "Commit Nachricht"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "Commit"
 
@@ -1025,6 +1301,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1040,9 +1322,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1088,6 +1406,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "Mitarbeitsanleitung"
 
@@ -1121,6 +1445,9 @@ msgstr "Kopiere URL in die Zwischenablage"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "Kopiere Commit SHA in die Zwischenablage"
 
@@ -1133,14 +1460,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "Erstelle neues Verzeichnis"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "Erstelle einen pers枚nlichen Zugriffstoken in Deinem Konto um mittels %{protocol} zu 眉bertragen (push) oder abzurufen (pull)."
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "Erstelle Verzeichnis"
 
-msgid "Create empty bare repository"
-msgstr "Erstelle leeres Repository"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1148,12 +1484,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "Erstelle Merge Request"
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1169,6 +1511,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "Erstelle neues..."
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "Ableger"
 
@@ -1178,6 +1523,12 @@ msgstr "Tag "
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "Erstelle einen pers枚nlichen Zugriffstoken"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1196,6 +1547,9 @@ msgstr "Individuelle Benachrichtigungsereignisse"
 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 "Individuelle Benachrichtigungsstufen sind identisch mit den Beteiligungsstufen. Mit individuellen Benachrichtigungsstufen erh盲ltst Du ebenfalls Mitteilungen f眉r ausgew盲hlte Ereignisse. F眉r weitere Informationen lies %{notification_link}. "
 
+msgid "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "Arbeitsablaufsanalysen"
 
@@ -1232,6 +1586,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "Erstelle ein individuelles Muster mittels Cron Syntax"
 
@@ -1264,8 +1621,8 @@ msgstr "Verzeichnisname"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
-msgstr "脛nderungen verwerfen"
+msgid "Discard draft"
+msgstr ""
 
 msgid "Discover GitLab Geo."
 msgstr ""
@@ -1276,10 +1633,16 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "Nicht erneut anzeigen"
 
-msgid "Download"
+msgid "Done"
+msgstr ""
+
+msgid "Download"
 msgstr "Herunterladen"
 
 msgid "Download tar"
@@ -1306,9 +1669,15 @@ msgstr "Unterschiede"
 msgid "DownloadSource|Download"
 msgstr "Herunterladen"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "Bearbeiten"
 
@@ -1318,12 +1687,54 @@ msgstr "Pipeline Zeitplan bearbeiten %{id}"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr "E-Mails"
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1378,9 +1789,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1447,12 +1870,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "Wechsel des Besitzers fehlgeschlagen"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "Entfernung der Pipelineplanung fehlgeschlagen"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1468,6 +1924,12 @@ msgstr ""
 msgid "Files"
 msgstr "Dateien"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "Filter nach Commit Nachricht"
 
@@ -1477,12 +1939,21 @@ msgstr "Finde 眉ber den Pfad"
 msgid "Find file"
 msgstr "Finde Datei"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "Erster"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "眉bertragen von"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "Ableger"
@@ -1494,15 +1965,24 @@ msgstr "Ableger von"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr "Von der Ticketbeschreibung bis zur Bereitstellung"
 
 msgid "From merge request merge until deploy to production"
 msgstr "Vom Umsetzen des Merge Request bis zur Bereitstellung auf dem Produktivsystem"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1512,12 +1992,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1563,21 +2049,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1590,9 +2103,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1623,6 +2148,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1632,12 +2160,30 @@ msgstr "Informationen 眉ber den Speicherzustand von Gitlab wurden zur眉ckgesetzt
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr "GitLab Runner Bereich"
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "Gehe zu Deinem Ableger"
 
@@ -1650,6 +2196,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1686,9 +2250,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1719,6 +2280,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "Systemzustand"
 
@@ -1737,6 +2301,15 @@ msgstr "Keine Probleme erkannt"
 msgid "HealthCheck|Unhealthy"
 msgstr "Problematisch"
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1748,9 +2321,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr "Aufr盲umen erfolgreich gestartet"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "Repository importieren"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1760,6 +2363,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "Installiere einen Runner der mit GitLab CI kompatibel ist"
 
@@ -1771,6 +2377,9 @@ msgstr[1] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1810,6 +2419,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1822,6 +2434,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1840,6 +2455,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1849,12 +2467,30 @@ msgstr "Deaktiviert"
 msgid "LFSStatus|Enabled"
 msgstr "Aktiviert"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "Letzten %d Tag"
@@ -1887,6 +2523,12 @@ msgstr "am"
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "Erfahre mehr in den"
 
@@ -1905,6 +2547,12 @@ msgstr "Verlasse das Projekt"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1914,7 +2562,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1923,15 +2571,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1953,7 +2616,7 @@ msgstr "Median"
 msgid "Members"
 msgstr "Mitglieder"
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1974,6 +2637,81 @@ msgstr ""
 msgid "Messages"
 msgstr "Nachrichten"
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1989,12 +2727,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "einen SSH Schl眉ssel hinzuf眉gst"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr "脺berwachung"
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr "hier"
 
@@ -2066,6 +2825,9 @@ msgstr ""
 msgid "New tag"
 msgstr "Neuer Tag"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2084,15 +2846,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "Kein Repository"
 
 msgid "No schedules"
 msgstr "Keine Zeitpl盲ne"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr ""
 
@@ -2102,12 +2864,33 @@ msgstr ""
 msgid "Not available"
 msgstr "Nicht verf眉gbar"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "Nicht gen眉gend Daten"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "Benachrichtigungsereignisse"
 
@@ -2192,6 +2975,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr "Filter"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2210,12 +2999,21 @@ msgstr ""
 msgid "Options"
 msgstr "Optionen"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "脺bersicht"
 
 msgid "Owner"
 msgstr "Besitzer"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2228,9 +3026,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "Passwort"
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr ""
 
@@ -2312,9 +3122,54 @@ msgstr "Pipelines des letzten Jahres"
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "Alle"
 
@@ -2327,6 +3182,9 @@ msgstr "mit Stage"
 msgid "Pipeline|with stages"
 msgstr "mit Stages"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2336,6 +3194,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2348,6 +3212,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr "Profil"
 
@@ -2387,6 +3254,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2411,9 +3281,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr "Projektdetails"
 
@@ -2510,37 +3377,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2558,6 +3476,12 @@ msgstr ""
 msgid "Push events"
 msgstr "脺bertragungsereignisse"
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2570,6 +3494,9 @@ msgstr "Mehr lesen"
 msgid "Readme"
 msgstr "Lies mich"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "Branches"
 
@@ -2603,6 +3530,9 @@ msgstr "Zugeh枚rige Merge Requests"
 msgid "Related Merged Requests"
 msgstr "Zugeh枚rige umgesetzte Merge Requests"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "Sp盲ter erinnern"
 
@@ -2618,9 +3548,24 @@ msgstr "Projekt entfernen"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "Anfrage auf Zugriff"
 
@@ -2633,6 +3578,12 @@ msgstr "Zugriffstoken f眉r Systemzustand zur眉cksetzen"
 msgid "Reset runners registration token"
 msgstr "Registrierungstoken f眉r Runner zur眉cksetzen"
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2644,6 +3595,36 @@ msgstr "Commit zur眉cksetzen"
 msgid "Revert this merge request"
 msgstr "Merge Request zur眉cksetzen"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "SSH-Schl眉ssel"
 
@@ -2659,6 +3640,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "Plane eine neue Pipeline"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2668,6 +3652,9 @@ msgstr "Pipelines planen"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "Suche nach Branches und Tags"
 
@@ -2689,12 +3676,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "Archivierungsformat ausw盲hlen"
 
 msgid "Select a timezone"
 msgstr "Zeitzone ausw盲hlen"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2707,6 +3700,9 @@ msgstr "Zielbranch ausw盲hlen"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2719,17 +3715,35 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "Lege ein Passwort f眉r dein Konto fest, um mittels %{protocol} zu 眉bertragen (push) oder abzurufen (pull)."
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "Koding einrichten"
 
-msgid "Set up auto deploy"
-msgstr "Automatische Bereitstellung einrichten"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "ein Passwort festlegst"
@@ -2737,6 +3751,12 @@ msgstr "ein Passwort festlegst"
 msgid "Settings"
 msgstr "Einstellungen"
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2746,6 +3766,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2769,6 +3792,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2778,13 +3813,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2898,6 +3933,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "Quellcode"
 
@@ -2907,12 +3945,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr "Spam-Protokolle"
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "Lege die folgende URL w盲hrend des Runner Setups fest:"
 
 msgid "StarProject|Star"
 msgstr "Favorisieren"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2922,6 +3969,15 @@ msgstr "Beginne einen %{new_merge_request} mit diesen 脛nderungen"
 msgid "Start the Runner!"
 msgstr "Starte den Runner!"
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2934,11 +3990,17 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr "Zu Branch/Tag wechseln"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -3017,6 +4079,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "Zielbranch"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3032,15 +4097,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "Die Entwicklungsphase stellt die Zeit vom ersten Commit bis zum Erstellen eines Merge Requests dar. Sobald Du Deinen ersten Merge Request anlegst, werden dessen Daten automatisch erg盲nzt."
 
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "Ereignisse, die f眉r diese Phase ausgewertet wurden."
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "Die Beziehung des Ablegers wurde entfernt."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "Die Ticketphase stellt die Zeit vom Anlegen eines Tickets bis zum Zuweisen eines Meilensteins oder Hinzuf眉gen zur Aufgabentafel dar. Erstelle einen Ticket, damit dessen Daten hier erscheinen."
 
@@ -3053,12 +4127,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
 msgid "The phase of the development lifecycle."
 msgstr "Die Phase des Entwicklungslebenszyklus."
 
 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 "Die Planungsphase stellt die Zeit von der vorherigen Phase bis zum 脺bertragen des ersten Commits dar. Sobald Du den ersten Commit 眉bertr盲gst, werden dessen Daten hier erscheinen."
 
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "Die Produktionsphase stellt die Gesamtzeit vom Anlegen eines Tickets bis zur Bereitstellung des Codes auf dem Produktivsystem dar.  Sobald Du den vollst盲ndigen Entwicklungszyklus, von einer Idee bis zur Fertigstellung, durchlaufen hast, erscheinen die zugeh枚rigen Daten hier."
 
@@ -3071,9 +4151,18 @@ msgstr "Auf das Projekt kann ohne Authentifizierung zugegriffen werden."
 msgid "The repository for this project does not exist."
 msgstr "Das Repository f眉r das Projekt existiert nicht."
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "Die 脺berpr眉fungsphase stellt die Zeit vom Anlegen eines Merge Requests bis dessen Umsetzung dar. Sobald Du Deinen ersten Merge Request abschlie脽t, werden dessen Daten hier automatisch angezeigt."
 
+msgid "The roadmap shows the progress of your epics along a timeline"
+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 "Die Staging-Phase stellt die Zeit zwischen der Umsetzung  eines Merge Requests und der Bereitstellung des Codes auf dem Produktivsystem dar. Sobald Du das erste Mal auf das Produktivsystem ausgeliefert hast, werden dessen Daten hier automatisch angezeigt."
 
@@ -3104,6 +4193,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr "Es gibt ein Problem beim Zugriff auf den Gitspeicher:"
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3167,12 +4259,18 @@ msgstr "Dies bedeutet, dass Du keinen Code 眉bertragen kannst, bevor Du kein lee
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3185,6 +4283,12 @@ msgstr "Zeit bis die Implementierung f眉r ein Ticket beginnt"
 msgid "Time between merge request creation and merge/close"
 msgstr "Zeit zwischen einem Merge Request und dessen Umsetzung / Schlie脽ung"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3336,9 +4440,45 @@ msgstr[1] "Min."
 msgid "Time|s"
 msgstr "Sek."
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3354,19 +4494,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "Gesamtzeit"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "Gesamte Testzeit f眉r alle Commits/Merges"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3378,22 +4515,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3429,6 +4560,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "Zum Upload klicken"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3438,21 +4575,51 @@ msgstr "Benutze den folgenden Registrierungstoken w盲hrend des Setups:"
 msgid "Use your global notification setting"
 msgstr "Benutze Deine globalen Benachrichtigungseinstellungen"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "Zeige offene Merge Requests."
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "Intern"
 
@@ -3477,12 +4644,21 @@ msgstr "Es liegen nicht gen眉gend Daten vor, um diese Phase anzuzeigen."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr "Wiki"
 
@@ -3597,22 +4773,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "Zugriffsanfrage widerrufen"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "Du bist dabei %{group_name} zu entfernen. Entfernte Gruppen k枚nnen NICHT wiederhergestellt werden! Bist Du dir WIRKLICH sicher?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "Du bist dabei %{project_name_with_namespace} zu entfernen. Entfernte Projekte k枚nnen NICHT wiederhergestellt werden! Bist Du dir WIRKLICH sicher?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "Du bist dabei, die Beziehung des Ablegers zum Ursprungsprojekt %{forked_from_project}, zu entfernen. Bist Du dir WIRKLICH sicher?"
 
-msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
-msgstr "Du bist dabei %{project_name_with_namespace} einem andere Besitzer zu 眉bergeben. Bist Du dir WIRKLICH sicher?"
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3630,12 +4818,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "Du hast die Projektbegrenzung erreicht."
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "Du musst angemeldet sein, um ein Projekt zu favorisieren."
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "Du brauchst eine Genehmigung."
 
@@ -3666,9 +4866,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3681,6 +4902,14 @@ msgstr "Dein Name"
 msgid "Your projects"
 msgstr "Deine Projekte"
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3690,13 +4919,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3708,7 +4955,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3723,25 +4970,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3749,14 +5011,84 @@ msgid_plural "days"
 msgstr[0] "Tag"
 msgstr[1] "Tage"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3781,15 +5113,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3823,6 +5167,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3871,12 +5218,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3906,6 +5259,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3915,6 +5271,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3924,3 +5283,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 7d4648c55f190d820b12368c6f065068e8f4dab7..009ad31d28bf9bdcf28c2f1cc3c70f06c2a5a714 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 03:59-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:38-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Esperanto\n"
 "Language: eo_UY\n"
@@ -29,6 +29,11 @@ msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -44,11 +49,19 @@ msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s enmetado estis transsaltita, por ne tro艥ar臐i la sistemon."
 msgstr[1] "%s enmetadoj estis transsaltitaj, por ne tro艥ar臐i la sistemon."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -57,6 +70,12 @@ msgid_plural "%{count} participants"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -66,6 +85,9 @@ msgstr ""
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr ""
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] ""
@@ -94,15 +116,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "Aro da diagramoj pri la seninterrompa integrado"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "Pri la a怒tomata disponigado"
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -112,6 +149,9 @@ msgstr ""
 msgid "Account"
 msgstr ""
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "Aktiva"
 
@@ -130,9 +170,15 @@ msgstr "Aldoni gvidliniojn por kontribuado"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "Aldoni rajtigilon"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "Aldoni novan dosierujon"
 
@@ -151,12 +197,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -169,9 +248,33 @@ msgstr ""
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -181,6 +284,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -190,12 +299,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -208,12 +341,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr ""
 
@@ -232,21 +374,24 @@ 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 "Are you sure you want to discard your changes?"
-msgstr ""
-
 msgid "Are you sure you want to reset registration token?"
 msgstr ""
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr ""
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr ""
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -259,6 +404,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -280,6 +434,12 @@ msgstr ""
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -304,7 +464,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -316,6 +482,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -370,13 +545,10 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "Bran膲o"
-msgstr[1] "Bran膲oj"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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 "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}"
@@ -399,6 +571,15 @@ msgstr "Iri al bran膲o"
 msgid "Branches"
 msgstr "Bran膲oj"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -444,12 +625,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -495,30 +703,45 @@ msgstr "Foliumi dosierojn"
 msgid "Browse files"
 msgstr "Elekti dosierojn"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "de"
 
 msgid "CI / CD"
 msgstr ""
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr "Nuligi"
 
-msgid "Cancel edit"
+msgid "Cannot be merged automatically"
 msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "Elekti en bran膲on"
 
@@ -573,6 +796,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -669,9 +898,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -723,6 +964,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -768,12 +1012,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -792,6 +1045,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -822,10 +1078,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -888,6 +1144,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -915,6 +1174,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -960,6 +1222,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr ""
 
@@ -968,6 +1236,11 @@ msgid_plural "Commits"
 msgstr[0] "Enmetado"
 msgstr[1] "Enmetadoj"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "Commit Message"
 msgstr ""
 
@@ -980,6 +1253,9 @@ msgstr "Mesa臐o pri la enmetado"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "Enmeti"
 
@@ -1025,6 +1301,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1040,9 +1322,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1088,6 +1406,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "Gvidlinioj por kontribuado"
 
@@ -1121,6 +1445,9 @@ msgstr "Kopii la adreson en la kopibufron"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "Kopii la identigilon de la enmetado"
 
@@ -1133,14 +1460,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "Krei novan dosierujon"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "Kreu propran atingo牡etonon en via konto por ebligi al vi eltiri kaj alpu艥i per %{protocol}."
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "Krei dosierujon"
 
-msgid "Create empty bare repository"
-msgstr "Krei malplenan deponejon"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1148,12 +1484,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "Krei peton pri kunfando"
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1169,6 +1511,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "Krei novan鈥�"
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "Disbran膲igi"
 
@@ -1178,6 +1523,12 @@ msgstr "Etikedo"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "kreos propran atingo牡etonon"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1196,6 +1547,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "Cikla analizo"
 
@@ -1232,6 +1586,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "Difini propran 艥ablonon, uzante la sintakson de Cron"
 
@@ -1264,7 +1621,7 @@ msgstr "Nomo de dosierujo"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
+msgid "Discard draft"
 msgstr ""
 
 msgid "Discover GitLab Geo."
@@ -1276,9 +1633,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "Ne montru denove"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "El艥uti"
 
@@ -1306,9 +1669,15 @@ msgstr "Normala dosiero kun diferencoj"
 msgid "DownloadSource|Download"
 msgstr "El艥uti"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "Redakti"
 
@@ -1318,12 +1687,54 @@ msgstr "Redakti 膲enstablan planon %{id}"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1378,9 +1789,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1447,12 +1870,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "Ne eblas 艥an臐i la posedanton"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "Ne eblas forigi la 膲enstablan planon"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1468,6 +1924,12 @@ msgstr ""
 msgid "Files"
 msgstr "Dosieroj"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "Filtri per mesa臐o"
 
@@ -1477,12 +1939,21 @@ msgstr "Trovi per dosierindiko"
 msgid "Find file"
 msgstr "Trovi dosieron"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "Unue"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "alpu艥ita de"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "Disbran膲igo"
@@ -1494,15 +1965,24 @@ msgstr "Disbran膲igita el"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 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 "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1512,12 +1992,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1563,21 +2049,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1590,9 +2103,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1623,6 +2148,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1632,12 +2160,30 @@ msgstr ""
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr ""
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "Al via disbran膲igo"
 
@@ -1650,6 +2196,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1686,9 +2250,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1719,6 +2280,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr ""
 
@@ -1737,6 +2301,15 @@ msgstr ""
 msgid "HealthCheck|Unhealthy"
 msgstr ""
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1748,9 +2321,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr "La refre艥igo komenci臐is sukcese"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "Enporti deponejon"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1760,6 +2363,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr ""
 
@@ -1771,6 +2377,9 @@ msgstr[1] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1810,6 +2419,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1822,6 +2434,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1840,6 +2455,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1849,12 +2467,30 @@ msgstr "Mal艥altita"
 msgid "LFSStatus|Enabled"
 msgstr "艤altita"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "La lasta %d tago"
@@ -1887,6 +2523,12 @@ msgstr ""
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "Lernu pli en la"
 
@@ -1905,6 +2547,12 @@ msgstr "Forlasi la projekton"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1914,7 +2562,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1923,15 +2571,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1953,7 +2616,7 @@ msgstr "Mediano"
 msgid "Members"
 msgstr ""
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1974,6 +2637,81 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1989,12 +2727,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "aldonos SSH-艥losilon"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr ""
 
@@ -2066,6 +2825,9 @@ msgstr ""
 msgid "New tag"
 msgstr "Nova etikedo"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2084,15 +2846,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "Ne estas deponejo"
 
 msgid "No schedules"
 msgstr "Ne estas planoj"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr ""
 
@@ -2102,12 +2864,33 @@ msgstr ""
 msgid "Not available"
 msgstr "Ne disponebla"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "Ne estas sufi膲e da datenoj"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "Sciigaj eventoj"
 
@@ -2192,6 +2975,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr "Filtrilo"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2210,12 +2999,21 @@ msgstr ""
 msgid "Options"
 msgstr "Opcioj"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr "Posedanto"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2228,9 +3026,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "膱enstablo"
 
@@ -2312,9 +3122,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "膲iuj"
 
@@ -2327,6 +3182,9 @@ msgstr "kun etapo"
 msgid "Pipeline|with stages"
 msgstr "kun etapoj"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2336,6 +3194,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2348,6 +3212,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr ""
 
@@ -2387,6 +3254,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2411,9 +3281,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr ""
 
@@ -2510,37 +3377,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2558,6 +3476,12 @@ msgstr ""
 msgid "Push events"
 msgstr ""
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2570,6 +3494,9 @@ msgstr "Legu pli"
 msgid "Readme"
 msgstr "LeguMin"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "Bran膲oj"
 
@@ -2603,6 +3530,9 @@ msgstr "Rilataj petoj pri kunfando"
 msgid "Related Merged Requests"
 msgstr "Rilataj aplikitaj petoj pri kunfando"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "Rememorigu denove"
 
@@ -2618,9 +3548,24 @@ msgstr "Forigi la projekton"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "Peti atingeblon"
 
@@ -2633,6 +3578,12 @@ msgstr ""
 msgid "Reset runners registration token"
 msgstr ""
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2644,6 +3595,36 @@ msgstr "Malfari 膲i tiun enmetadon"
 msgid "Revert this merge request"
 msgstr "Malfari 膲i tiun peton pri kunfando"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2659,6 +3640,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "Plani novan 膲enstablon"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2668,6 +3652,9 @@ msgstr "Planado de la 膲enstabloj"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "Ser膲u bran膲on a怒 etikedon"
 
@@ -2689,12 +3676,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "Elektu formaton de arkivo"
 
 msgid "Select a timezone"
 msgstr "Elektu horzonon"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2707,6 +3700,9 @@ msgstr "Elektu celan bran膲on"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2719,22 +3715,46 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 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 default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "Agordi 鈥濳oding鈥�"
 
-msgid "Set up auto deploy"
-msgstr "Agordi a怒tomatan disponigadon"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "kreos pasvorton"
 
-msgid "Settings"
+msgid "Settings"
+msgstr ""
+
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
 msgstr ""
 
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
@@ -2746,6 +3766,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2769,6 +3792,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2778,13 +3813,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2898,6 +3933,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "Kodo"
 
@@ -2907,12 +3945,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr "Steligi"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2922,6 +3969,15 @@ msgstr "Kreu %{new_merge_request} kun 膲i tiuj 艥an臐oj"
 msgid "Start the Runner!"
 msgstr ""
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2934,13 +3990,19 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr "Iri al bran膲o/etikedo"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "Etikedo"
-msgstr[1] "Etikedoj"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "Tags"
 msgstr "Etikedoj"
@@ -3017,6 +4079,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "Cela bran膲o"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3032,15 +4097,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "La rilato de disbran膲igo estis forigita."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "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."
 
@@ -3053,12 +4127,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
 msgid "The phase of the development lifecycle."
 msgstr "La etapo de la disvolva ciklo."
 
 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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "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."
 
@@ -3071,9 +4151,18 @@ 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 repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "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 roadmap shows the progress of your epics along a timeline"
+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 "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."
 
@@ -3104,6 +4193,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr ""
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3167,12 +4259,18 @@ msgstr "膱i tiu signifas, ke vi ne povos alpu艥i kodon, anta怒 ol vi kreos malpl
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3185,6 +4283,12 @@ 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 between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3336,9 +4440,45 @@ msgstr[1] "min"
 msgid "Time|s"
 msgstr "s"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3354,19 +4494,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "Totala tempo"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "Totala tempo por la testado de 膲iuj enmetadoj/kunfandoj"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3378,22 +4515,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3429,6 +4560,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "alklaku por al艥uti"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3438,21 +4575,51 @@ msgstr ""
 msgid "Use your global notification setting"
 msgstr "Uzi vian 臐eneralan agordon pri la sciigoj"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "Vidi la malfermitan peton pri kunfando"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "Interna"
 
@@ -3477,12 +4644,21 @@ msgstr "Ne estas sufi膲e da datenoj por montri 膲i tiun etapon."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3597,22 +4773,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "Nuligi la peton pri atingeblo"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "Vi forigos 鈥�%{group_name}鈥�. Oni NE POVAS malfari la forigon de grupo! 膱u vi estas ABSOLUTE certa?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "Vi forigos 鈥�%{project_name_with_namespace}鈥�. Oni NE POVAS malfari la forigon de projekto! 膱u vi estas ABSOLUTE certa?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "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 estas transigonta 鈥�%{project_name_with_namespace}鈥� al alia posedanto. 膱u vi estas ABSOLUTE certa?"
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3630,12 +4818,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "Vi ne povas krei pliajn projektojn"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "Oni devas ensaluti por steligi projekton"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "VI bezonas permeson."
 
@@ -3666,9 +4866,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3681,6 +4902,14 @@ msgstr "Via nomo"
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3690,13 +4919,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3708,7 +4955,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3723,25 +4970,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3749,14 +5011,84 @@ msgid_plural "days"
 msgstr[0] "tago"
 msgstr[1] "tagoj"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3781,15 +5113,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3823,6 +5167,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3871,12 +5218,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3906,6 +5259,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3915,6 +5271,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3924,3 +5283,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 7d28a7064d34931ae171341c8e3af2330f2dfdad..e2af97f1b833c5976669eb8c0a7b77970330862b 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:01-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:35-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Spanish\n"
 "Language: es_ES\n"
@@ -29,6 +29,11 @@ msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -44,11 +49,19 @@ msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s cambio adicional ha sido omitido para evitar problemas de rendimiento."
 msgstr[1] "%s cambios adicionales han sido omitidos para evitar problemas de rendimiento."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -57,6 +70,12 @@ msgid_plural "%{count} participants"
 msgstr[0] "%{count} participante"
 msgstr[1] "%{count} participantes"
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr "%{number_commits_behind} commits detr谩s de %{default_branch}, %{number_commits_ahead} commits por delante"
 
@@ -66,6 +85,9 @@ msgstr "%{number_of_failures} de %{maximum_failures} intentos fallidos. GitLab p
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} de %{maximum_failures} intentos fallidos. GitLab no reintentar谩 autom谩ticamente. Debe reinicializar la informaci贸n de almacenamiento cuando el problema sea solventado."
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}: intento de acceso fallido al almacenamiento en host:"
@@ -94,15 +116,30 @@ msgstr "隆1ra contribuci贸n!"
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "Una colecci贸n de gr谩ficos sobre Integraci贸n Continua"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "Acerca del auto despliegue"
 
 msgid "Abuse Reports"
 msgstr "Reportes de abuso"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "Tokens de acceso"
 
@@ -112,6 +149,9 @@ msgstr ""
 msgid "Account"
 msgstr "Cuenta"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "Activo"
 
@@ -130,9 +170,15 @@ msgstr "Agregar gu铆a de contribuci贸n"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr "A帽adir Webhooks Grupales y Gitlab Enterprise Edition."
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "Agregar Licencia"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "Agregar nuevo directorio"
 
@@ -140,47 +186,110 @@ msgid "Add todo"
 msgstr ""
 
 msgid "AdminArea|Stop all jobs"
-msgstr ""
+msgstr "Detener todos los trabajos"
 
 msgid "AdminArea|Stop all jobs?"
-msgstr ""
+msgstr "Detener todos los trabajos?"
 
 msgid "AdminArea|Stop jobs"
-msgstr ""
+msgstr "Detener trabajos"
 
 msgid "AdminArea|Stopping jobs failed"
-msgstr ""
+msgstr "Error al detener trabajos"
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr "P谩gina de estado"
 
-msgid "Advanced"
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
 msgstr ""
 
+msgid "Advanced"
+msgstr "Avanzado"
+
 msgid "Advanced settings"
 msgstr "Configuraci贸n avanzada"
 
 msgid "All"
-msgstr ""
+msgstr "Todos"
 
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
-msgid "An error occurred previewing the blob"
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
 msgstr ""
 
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr "Ha ocurrido un error visualizando el blob"
+
 msgid "An error occurred when toggling the notification subscription"
 msgstr "Se produjo un error al activar/desactivar la suscripci贸n de notificaci贸n"
 
 msgid "An error occurred when updating the issue weight"
 msgstr "Se produjo un error al actualizar el peso de la incidencia"
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -190,12 +299,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr "Se produjo un error al obtener datos de la barra lateral"
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -208,12 +341,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr "Se produjo un error. Por favor int茅ntelo de nuevo."
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr "Apariencia"
 
@@ -232,36 +374,48 @@ 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 "Are you sure you want to discard your changes?"
-msgstr "驴Est谩 seguro que desea descartar sus cambios?"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "驴Est谩 seguro que desea reinicializar el token de registro?"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "驴Est谩 seguro que desea reinicializar el token de Verificaci贸n de Estado?"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr "驴Est谩s seguro?"
 
 msgid "Artifacts"
 msgstr "Artefactos"
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
 msgid "Assign labels"
-msgstr ""
+msgstr "Asignar etiquetas"
 
 msgid "Assign milestone"
-msgstr ""
+msgstr "Asignar milestone"
 
 msgid "Assign to"
+msgstr "Asignar a"
+
+msgid "Assigned Issues"
 msgstr ""
 
-msgid "Assignee"
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
 msgstr ""
 
+msgid "Assignee"
+msgstr "Asignado a"
+
 msgid "Attach a file by drag &amp; drop or %{upload_link}"
 msgstr "Adjunte un archivo arrastrando &amp; soltando o %{upload_link}"
 
@@ -275,9 +429,15 @@ msgid "Authentication Log"
 msgstr "Registro de Autenticaci贸n"
 
 msgid "Author"
-msgstr "Autor"
+msgstr ""
 
 msgid "Authors: %{authors}"
+msgstr "Autores: %{authors}"
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
 msgstr ""
 
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
@@ -287,24 +447,30 @@ msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} t
 msgstr ""
 
 msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
-msgstr ""
+msgstr "Tanto las Auto Review Apps como Auto Deploy necesitan un dominio para funcionar correctamente."
 
 msgid "AutoDevOps|Auto DevOps (Beta)"
-msgstr ""
+msgstr "Auto DevOps (Beta)"
 
 msgid "AutoDevOps|Auto DevOps documentation"
-msgstr ""
+msgstr "Documentaci贸n de Auto DevOps"
 
 msgid "AutoDevOps|Enable in settings"
-msgstr ""
+msgstr "Habilitar en la configuraci贸n"
 
 msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
-msgstr ""
+msgstr "Autom谩ticamente construir谩n, probar谩n y desplegar谩n su aplicaci贸n con base en la configuraci贸n predefinida de CI/CD."
 
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr "M谩s informaci贸n en %{link_to_documentation}"
+
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -314,81 +480,87 @@ msgid "Avatar will be removed. Are you sure?"
 msgstr ""
 
 msgid "Average per day: %{average}"
+msgstr "Promedio por d铆a: %{average}"
+
+msgid "Background Color"
 msgstr ""
 
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr "Iniciar con el commit seleccionado"
+
 msgid "Billing"
 msgstr "Facturaci贸n"
 
 msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
-msgstr ""
+msgstr "%{group_name} est谩 actualmente en el plan %{plan_link}."
 
 msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
-msgstr ""
+msgstr "Aumentar o disminuir autom谩ticamente las caracter铆sticas de algunos Planes no est谩 disponible actualmente."
 
 msgid "BillingPlans|Current plan"
-msgstr ""
+msgstr "Plan actual"
 
 msgid "BillingPlans|Customer Support"
-msgstr ""
+msgstr "Atenci贸n al cliente"
 
 msgid "BillingPlans|Downgrade"
 msgstr ""
 
 msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
-msgstr ""
+msgstr "Obtenga m谩s informaci贸n sobre cada plan al leer nuestro %{faq_link}."
 
 msgid "BillingPlans|Manage plan"
-msgstr ""
+msgstr "Gestionar plan"
 
 msgid "BillingPlans|Please contact %{customer_support_link} in that case."
-msgstr ""
+msgstr "Por favor contacte a %{customer_support_link} en ese caso."
 
 msgid "BillingPlans|See all %{plan_name} features"
-msgstr ""
+msgstr "Ver todas la funcionalidades del plan %{plan_name}"
 
 msgid "BillingPlans|This group uses the plan associated with its parent group."
 msgstr ""
 
 msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
-msgstr ""
+msgstr "Para gestionar el plan para este grupo, visite la secci贸n de facturaci贸n de %{parent_billing_page_link}."
 
 msgid "BillingPlans|Upgrade"
 msgstr ""
 
 msgid "BillingPlans|You are currently on the %{plan_link} plan."
-msgstr ""
+msgstr "Actualmente est谩s en el plan %{plan_link}."
 
 msgid "BillingPlans|frequently asked questions"
-msgstr ""
+msgstr "preguntas frecuentes"
 
 msgid "BillingPlans|monthly"
-msgstr ""
+msgstr "mensual"
 
 msgid "BillingPlans|paid annually at %{price_per_year}"
-msgstr ""
+msgstr "%{price_per_year} pagados anualmente"
 
 msgid "BillingPlans|per user"
-msgstr ""
-
-msgid "Begin with the selected commit"
-msgstr ""
+msgstr "por usuario"
 
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "Rama"
-msgstr[1] "Ramas"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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 "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 "Branch has changed"
-msgstr ""
+msgstr "La rama ha cambiado"
 
 msgid "Branch is already taken"
-msgstr ""
+msgstr "La rama ya existe"
 
 msgid "Branch name"
-msgstr ""
+msgstr "Nombre de la rama"
 
 msgid "BranchSwitcherPlaceholder|Search branches"
 msgstr "Buscar ramas"
@@ -399,35 +571,44 @@ msgstr "Cambiar rama"
 msgid "Branches"
 msgstr "Ramas"
 
-msgid "Branches|Cant find HEAD commit for this branch"
+msgid "Branches|Active"
 msgstr ""
 
-msgid "Branches|Compare"
+msgid "Branches|Active branches"
 msgstr ""
 
-msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgid "Branches|All"
 msgstr ""
 
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr "No puedo encontrar el commit HEAD para esta rama"
+
+msgid "Branches|Compare"
+msgstr "Comparar"
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr "Borrar todas las ramas que se fusionen con '%{default_branch}'"
+
 msgid "Branches|Delete branch"
-msgstr ""
+msgstr "Eliminar la rama"
 
 msgid "Branches|Delete merged branches"
-msgstr ""
+msgstr "Borrar ramas fusionadas"
 
 msgid "Branches|Delete protected branch"
-msgstr ""
+msgstr "Borrar rama protegida"
 
 msgid "Branches|Delete protected branch '%{branch_name}'?"
-msgstr ""
+msgstr "驴Borrar rama protegida '%{branch_name}'?"
 
 msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
-msgstr ""
+msgstr "El borrado de la rama '%{branch_name}' no podr谩 deshacerse. 驴Est谩 seguro?"
 
 msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
-msgstr ""
+msgstr "El borrado de las ramas fusionadas no podr谩 deshacerse. 驴Est谩 usted seguro?"
 
 msgid "Branches|Filter by branch name"
-msgstr ""
+msgstr "Filtrar por nombre de rama"
 
 msgid "Branches|Merged into %{default_branch}"
 msgstr ""
@@ -444,12 +625,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -495,13 +703,22 @@ msgstr "Examinar archivos"
 msgid "Browse files"
 msgstr "Examinar archivos"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "por"
 
 msgid "CI / CD"
+msgstr "CI / CD"
+
+msgid "CI/CD"
 msgstr ""
 
 msgid "CI/CD configuration"
+msgstr "Configuraci贸n de CI/CD"
+
+msgid "CI/CD for external repo"
 msgstr ""
 
 msgid "CICD|Jobs"
@@ -510,13 +727,19 @@ msgstr ""
 msgid "Cancel"
 msgstr "Cancelar"
 
-msgid "Cancel edit"
+msgid "Cannot be merged automatically"
 msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
+msgstr "Cambiar peso"
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
 msgstr ""
 
 msgid "ChangeTypeActionLabel|Pick into branch"
@@ -550,10 +773,10 @@ msgid "Check interval"
 msgstr ""
 
 msgid "Checking %{text} availability鈥�"
-msgstr ""
+msgstr "Comprobando disponibilidad de %{text}..."
 
 msgid "Checking branch availability..."
-msgstr ""
+msgstr "Verificando disponibilidad de la rama..."
 
 msgid "Cherry-pick this commit"
 msgstr "Escoger este cambio"
@@ -568,14 +791,20 @@ msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to s
 msgstr ""
 
 msgid "Choose file..."
-msgstr ""
+msgstr "Elegir archivo..."
 
 msgid "Choose which groups you wish to synchronize to this secondary node."
+msgstr "Elija qu茅 grupos desea sincronizar a este nodo secundario."
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
 msgstr ""
 
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose which repositories you want to import."
 msgstr ""
 
+msgid "Choose which shards you wish to synchronize to this secondary node."
+msgstr "Elija qu茅 fragmentos desea sincronizar a este nodo secundario."
+
 msgid "CiStatusLabel|canceled"
 msgstr "cancelado"
 
@@ -669,17 +898,29 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
+msgstr "Haga clic para expandir el texto"
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
 msgstr ""
 
 msgid "Clone repository"
 msgstr ""
 
 msgid "Close"
-msgstr ""
+msgstr "Cerrar"
 
 msgid "Closed"
-msgstr ""
+msgstr "Cerrado"
 
 msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
 msgstr ""
@@ -721,16 +962,19 @@ msgid "ClusterIntegration|Copy API URL"
 msgstr ""
 
 msgid "ClusterIntegration|Copy CA Certificate"
+msgstr "Copiar Certificado CA"
+
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
 msgstr ""
 
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
 msgid "ClusterIntegration|Copy Token"
-msgstr ""
+msgstr "Copiar Token"
 
 msgid "ClusterIntegration|Create Kubernetes cluster"
-msgstr ""
+msgstr "Crear cluster de Kubernetes"
 
 msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
 msgstr ""
@@ -739,10 +983,10 @@ msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes E
 msgstr ""
 
 msgid "ClusterIntegration|Create on GKE"
-msgstr ""
+msgstr "Crear en GKE"
 
 msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
-msgstr ""
+msgstr "Ingrese los detalles para un cluster Kubernetes existente"
 
 msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
 msgstr ""
@@ -751,49 +995,61 @@ msgid "ClusterIntegration|Environment scope"
 msgstr ""
 
 msgid "ClusterIntegration|GitLab Integration"
-msgstr ""
+msgstr "Integraci贸n GitLab"
 
 msgid "ClusterIntegration|GitLab Runner"
-msgstr ""
+msgstr "GitLab Runner"
 
 msgid "ClusterIntegration|Google Cloud Platform project ID"
-msgstr ""
+msgstr "ID del proyecto de Google Cloud Platform"
 
 msgid "ClusterIntegration|Google Kubernetes Engine"
-msgstr ""
+msgstr "Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Google Kubernetes Engine project"
-msgstr ""
+msgstr "Proyecto Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Helm Tiller"
+msgstr "Helm Tiller"
+
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
 msgstr ""
 
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
+msgstr "Instalar"
+
+msgid "ClusterIntegration|Install Prometheus"
 msgstr ""
 
 msgid "ClusterIntegration|Installed"
-msgstr ""
+msgstr "Instalado"
 
 msgid "ClusterIntegration|Installing"
-msgstr ""
+msgstr "Instalando"
 
 msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
 msgstr ""
 
 msgid "ClusterIntegration|Integration status"
-msgstr ""
+msgstr "Estado de integraci贸n"
 
 msgid "ClusterIntegration|Kubernetes cluster"
-msgstr ""
+msgstr "cluster de Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr "Detalles del cluster de Kubernetes"
+
+msgid "ClusterIntegration|Kubernetes cluster health"
 msgstr ""
 
 msgid "ClusterIntegration|Kubernetes cluster integration"
-msgstr ""
+msgstr "Integraci贸n de cluster de Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
 msgstr ""
@@ -808,7 +1064,7 @@ msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernet
 msgstr ""
 
 msgid "ClusterIntegration|Kubernetes cluster name"
-msgstr ""
+msgstr "Nombre de cluster de Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
 msgstr ""
@@ -820,25 +1076,25 @@ msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications
 msgstr ""
 
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
-msgstr ""
-
-msgid "ClusterIntegration|Learn more about Kubernetes"
-msgstr ""
+msgstr "Conozca m谩s sobre %{link_to_documentation}"
 
 msgid "ClusterIntegration|Learn more about environments"
+msgstr "Conozca m谩s sobre los entornos"
+
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
-msgstr ""
+msgstr "Tipo de m谩quina"
 
 msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
 msgstr ""
 
 msgid "ClusterIntegration|Manage"
-msgstr ""
+msgstr "Administrar"
 
 msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
-msgstr ""
+msgstr "Administre su cluster de Kubernetes visitando %{link_gke}"
 
 msgid "ClusterIntegration|More information"
 msgstr ""
@@ -847,19 +1103,19 @@ msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab E
 msgstr ""
 
 msgid "ClusterIntegration|Note:"
-msgstr ""
+msgstr "Nota:"
 
 msgid "ClusterIntegration|Number of nodes"
-msgstr ""
+msgstr "N煤mero de nodos"
 
 msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
 msgstr ""
 
 msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
-msgstr ""
+msgstr "Aseg煤rese de que su cuenta de Google cumpla con los siguientes requisitos:"
 
 msgid "ClusterIntegration|Project ID"
-msgstr ""
+msgstr "ID de Proyecto"
 
 msgid "ClusterIntegration|Project namespace"
 msgstr ""
@@ -877,42 +1133,48 @@ msgid "ClusterIntegration|Remove Kubernetes cluster integration"
 msgstr ""
 
 msgid "ClusterIntegration|Remove integration"
-msgstr ""
+msgstr "Eliminar integraci贸n"
 
 msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
 msgstr ""
 
 msgid "ClusterIntegration|Request to begin installing failed"
-msgstr ""
+msgstr "Fall贸 la solicitud para iniciar la instalaci贸n"
 
 msgid "ClusterIntegration|Save changes"
+msgstr "Guardar cambios"
+
+msgid "ClusterIntegration|Security"
 msgstr ""
 
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
 msgid "ClusterIntegration|See machine types"
-msgstr ""
+msgstr "Ver tipos de m谩quina"
 
 msgid "ClusterIntegration|See your projects"
-msgstr ""
+msgstr "Ver tus proyectos"
 
 msgid "ClusterIntegration|See zones"
-msgstr ""
+msgstr "Ver zonas"
 
 msgid "ClusterIntegration|Service token"
 msgstr ""
 
 msgid "ClusterIntegration|Show"
-msgstr ""
+msgstr "Mostrar"
 
 msgid "ClusterIntegration|Something went wrong on our end."
-msgstr ""
+msgstr "Algo sali贸 mal de nuestro lado."
 
 msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
 msgstr ""
 
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr "Algo sali贸 mal durante la instalaci贸n de %{title}"
+
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
 msgstr ""
 
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -925,51 +1187,62 @@ msgid "ClusterIntegration|Toggle Kubernetes cluster"
 msgstr ""
 
 msgid "ClusterIntegration|Token"
-msgstr ""
+msgstr "Token"
 
 msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
 msgstr ""
 
 msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
-msgstr ""
+msgstr "Su cuenta debe tener %{link_to_kubernetes_engine}"
 
 msgid "ClusterIntegration|Zone"
-msgstr ""
+msgstr "Zona"
 
 msgid "ClusterIntegration|access to Google Kubernetes Engine"
-msgstr ""
+msgstr "acceso a Google Kubernetes Engine"
 
 msgid "ClusterIntegration|check the pricing here"
 msgstr ""
 
 msgid "ClusterIntegration|documentation"
-msgstr ""
+msgstr "documentaci贸n"
 
 msgid "ClusterIntegration|help page"
-msgstr ""
+msgstr "p谩gina de ayuda"
 
 msgid "ClusterIntegration|installing applications"
-msgstr ""
+msgstr "Instalando aplicaciones"
 
 msgid "ClusterIntegration|meets the requirements"
-msgstr ""
+msgstr "cumple con los requisitos"
 
 msgid "ClusterIntegration|properly configured"
 msgstr ""
 
 msgid "Collapse"
+msgstr "Contraer"
+
+msgid "Comment and resolve discussion"
 msgstr ""
 
-msgid "Comments"
+msgid "Comment and unresolve discussion"
 msgstr ""
 
+msgid "Comments"
+msgstr "Comentarios"
+
 msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "Cambio"
 msgstr[1] "Cambios"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "Commit Message"
-msgstr ""
+msgstr "Mensaje del commit"
 
 msgid "Commit duration in minutes for last 30 commits"
 msgstr "Duraci贸n de los cambios en minutos para los 煤ltimos 30"
@@ -980,6 +1253,9 @@ msgstr "Mensaje del cambio"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "Cambio"
 
@@ -1025,6 +1301,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1040,54 +1322,96 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
+msgstr "Confidencialidad"
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
 msgstr ""
 
 msgid "Container Registry"
 msgstr ""
 
 msgid "ContainerRegistry|Created"
-msgstr ""
+msgstr "Creado"
 
 msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
 msgstr ""
 
 msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
-msgstr ""
+msgstr "Gitlab soporta hasta 3 niveles para nombres de im谩genes. Los siguientes ejemplos de im谩genes son v谩lidos para tu proyecto:"
 
 msgid "ContainerRegistry|How to use the Container Registry"
 msgstr ""
 
 msgid "ContainerRegistry|Learn more about"
-msgstr ""
+msgstr "Conozca m谩s sobre"
 
 msgid "ContainerRegistry|No tags in Container Registry for this container image."
-msgstr ""
+msgstr "No hay etiquetas en el Container Registry para esta imagen de contenedor."
 
 msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
-msgstr ""
+msgstr "Una vez que inicies sesi贸n, eres libre de crear y cargar una imagen de contenedor utilizando los comandos comunes %{build} y %{push}"
 
 msgid "ContainerRegistry|Remove repository"
-msgstr ""
+msgstr "Borrar repositorio"
 
 msgid "ContainerRegistry|Remove tag"
-msgstr ""
+msgstr "Borrar etiqueta"
 
 msgid "ContainerRegistry|Size"
-msgstr ""
+msgstr "Tama帽o"
 
 msgid "ContainerRegistry|Tag"
-msgstr ""
+msgstr "Etiqueta"
 
 msgid "ContainerRegistry|Tag ID"
-msgstr ""
+msgstr "Etiqueta ID"
 
 msgid "ContainerRegistry|Use different image names"
-msgstr ""
+msgstr "Usar nombres de imagen diferentes"
 
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "Gu铆a de contribuci贸n"
 
@@ -1113,7 +1437,7 @@ msgid "Control the maximum concurrency of repository backfill for this secondary
 msgstr ""
 
 msgid "Copy SSH public key to clipboard"
-msgstr ""
+msgstr "Copiar la clave p煤blica SSH al portapapeles"
 
 msgid "Copy URL to clipboard"
 msgstr "Copiar URL al portapapeles"
@@ -1121,6 +1445,9 @@ msgstr "Copiar URL al portapapeles"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "Copiar SHA del cambio al portapapeles"
 
@@ -1128,24 +1455,36 @@ msgid "Copy reference to clipboard"
 msgstr ""
 
 msgid "Create"
-msgstr ""
+msgstr "Crear"
 
 msgid "Create New Directory"
 msgstr "Crear Nuevo Directorio"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "Crear un token de acceso personal en tu cuenta para actualizar o enviar a trav茅s de %{protocol}."
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "Crear directorio"
 
-msgid "Create empty bare repository"
-msgstr "Crear repositorio vac铆o"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
-msgstr ""
+msgstr "Crear epic"
 
 msgid "Create file"
+msgstr "Crear archivo"
+
+msgid "Create group label"
 msgstr ""
 
 msgid "Create lists from labels. Issues with that label appear in that list."
@@ -1154,21 +1493,27 @@ msgstr ""
 msgid "Create merge request"
 msgstr "Crear solicitud de fusi贸n"
 
-msgid "Create new branch"
+msgid "Create merge request and branch"
 msgstr ""
 
+msgid "Create new branch"
+msgstr "Crear una nueva rama"
+
 msgid "Create new directory"
-msgstr ""
+msgstr "Crear nuevo directorio"
 
 msgid "Create new file"
-msgstr ""
+msgstr "Crear nuevo archivo"
 
 msgid "Create new label"
-msgstr ""
+msgstr "Crear nueva etiqueta"
 
 msgid "Create new..."
 msgstr "Crear nuevo..."
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "Bifurcar"
 
@@ -1178,9 +1523,15 @@ msgstr "Etiqueta"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "crear un token de acceso personal"
 
-msgid "Creating epic"
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
 msgstr ""
 
+msgid "Creating epic"
+msgstr "Creando epic"
+
 msgid "Cron Timezone"
 msgstr "Zona horaria del Cron"
 
@@ -1188,7 +1539,7 @@ msgid "Cron syntax"
 msgstr "Sintaxis de Cron"
 
 msgid "Current node"
-msgstr ""
+msgstr "Nodo actual"
 
 msgid "Custom notification events"
 msgstr "Eventos de notificaciones personalizadas"
@@ -1196,6 +1547,9 @@ msgstr "Eventos de notificaciones personalizadas"
 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 "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}."
 
+msgid "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr ""
 
@@ -1221,15 +1575,18 @@ msgid "CycleAnalyticsStage|Test"
 msgstr "Pruebas"
 
 msgid "DashboardProjects|All"
-msgstr ""
+msgstr "Todos"
 
 msgid "DashboardProjects|Personal"
-msgstr ""
+msgstr "Personales"
 
 msgid "Dec"
-msgstr ""
+msgstr "Dic"
 
 msgid "December"
+msgstr "Diciembre"
+
+msgid "Default classification label"
 msgstr ""
 
 msgid "Define a custom pattern with cron syntax"
@@ -1253,7 +1610,7 @@ msgid "Description templates allow you to define context-specific templates for
 msgstr ""
 
 msgid "Details"
-msgstr ""
+msgstr "Detalles"
 
 msgid "Diffs|No file name available"
 msgstr ""
@@ -1262,9 +1619,9 @@ msgid "Directory name"
 msgstr "Nombre del directorio"
 
 msgid "Disable"
-msgstr ""
+msgstr "Deshabilitar"
 
-msgid "Discard changes"
+msgid "Discard draft"
 msgstr ""
 
 msgid "Discover GitLab Geo."
@@ -1276,9 +1633,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "No mostrar de nuevo"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "Descargar"
 
@@ -1306,9 +1669,15 @@ msgstr "Diferencias en texto plano"
 msgid "DownloadSource|Download"
 msgstr "Descargar"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "Editar"
 
@@ -1318,10 +1687,52 @@ msgstr "Editar Programaci贸n del Pipeline %{id}"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
+msgstr "Habilitar"
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
 msgstr ""
 
 msgid "Environments|An error occurred while fetching the environments."
@@ -1367,23 +1778,35 @@ msgid "Environments|Show all"
 msgstr ""
 
 msgid "Environments|Updated"
-msgstr ""
+msgstr "Actualizado"
 
 msgid "Environments|You don't have any environments right now."
-msgstr ""
+msgstr "No tiene ning煤n entorno en este momento."
 
 msgid "Epic will be removed! Are you sure?"
 msgstr ""
 
 msgid "Epics"
+msgstr "Epics"
+
+msgid "Epics Roadmap"
 msgstr ""
 
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
-msgid "Error creating epic"
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
 msgstr ""
 
+msgid "Error creating epic"
+msgstr "Error creando epic"
+
 msgid "Error fetching contributors data."
 msgstr ""
 
@@ -1400,7 +1823,7 @@ msgid "Error fetching usage ping data."
 msgstr ""
 
 msgid "Error occurred when toggling the notification subscription"
-msgstr ""
+msgstr "Se produjo un error al activar/desactivar la suscripci贸n de notificaci贸n"
 
 msgid "Error saving label update."
 msgstr ""
@@ -1415,7 +1838,7 @@ msgid "EventFilterBy|Filter by all"
 msgstr ""
 
 msgid "EventFilterBy|Filter by comments"
-msgstr ""
+msgstr "Filtrar por comentarios"
 
 msgid "EventFilterBy|Filter by issue events"
 msgstr ""
@@ -1427,7 +1850,7 @@ msgid "EventFilterBy|Filter by push events"
 msgstr ""
 
 msgid "EventFilterBy|Filter by team"
-msgstr ""
+msgstr "Filtrar por equipo"
 
 msgid "Every day (at 4:00am)"
 msgstr "Todos los d铆as (a las 4:00 am)"
@@ -1439,35 +1862,74 @@ msgid "Every week (Sundays at 4:00am)"
 msgstr "Todas las semanas (domingos a las 4:00 am)"
 
 msgid "Expand"
-msgstr ""
+msgstr "Expandir"
 
 msgid "Explore projects"
-msgstr ""
+msgstr "Explorar proyectos"
 
 msgid "Explore public groups"
+msgstr "Explorar grupos p煤blicos"
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
 msgstr ""
 
 msgid "Failed to change the owner"
 msgstr "Error al cambiar el propietario"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "Error al eliminar la programaci贸n del pipeline"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
 msgid "February"
-msgstr ""
+msgstr "Febrero"
 
 msgid "Fields on this page are now uneditable, you can configure"
 msgstr ""
 
 msgid "File name"
-msgstr ""
+msgstr "Nombre de archivo"
 
 msgid "Files"
 msgstr "Archivos"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "Filtrar por mensaje del cambio"
 
@@ -1477,12 +1939,21 @@ msgstr "Buscar por ruta"
 msgid "Find file"
 msgstr "Buscar archivo"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "Primer"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "enviado por"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "Bifurcaci贸n"
@@ -1494,7 +1965,13 @@ msgstr "Bifurcado de"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
+msgstr "Formato"
+
+msgid "From %{provider_title}"
 msgstr ""
 
 msgid "From issue creation until deploy to production"
@@ -1503,19 +1980,28 @@ msgstr "Desde la creaci贸n de la incidencia hasta el despliegue a producci贸n"
 msgid "From merge request merge until deploy to production"
 msgstr "Desde la integraci贸n de la solicitud de fusi贸n hasta el despliegue a producci贸n"
 
-msgid "GPG Keys"
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
 msgstr ""
 
+msgid "GPG Keys"
+msgstr "Llaves GPG"
+
 msgid "Generate a default set of labels"
 msgstr ""
 
 msgid "Geo Nodes"
 msgstr ""
 
-msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
 msgstr ""
 
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr "El nodo est谩 fallando o da帽ado."
+
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr "El nodo est谩 lento, sobrecargado o se esta recuperando de una interrupci贸n."
+
+msgid "GeoNodes|Checksummed"
 msgstr ""
 
 msgid "GeoNodes|Database replication lag:"
@@ -1563,21 +2049,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1590,9 +2103,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1603,7 +2128,7 @@ msgid "Geo|All projects"
 msgstr ""
 
 msgid "Geo|File sync capacity"
-msgstr ""
+msgstr "Capacidad de sincronizaci贸n de archivos"
 
 msgid "Geo|Groups to synchronize"
 msgstr ""
@@ -1615,14 +2140,17 @@ msgid "Geo|Projects in certain storage shards"
 msgstr ""
 
 msgid "Geo|Repository sync capacity"
-msgstr ""
+msgstr "Capacidad de sincronizaci贸n del Repositorio"
 
 msgid "Geo|Select groups to replicate."
-msgstr ""
+msgstr "Seleccionar grupos a replicar."
 
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1632,12 +2160,30 @@ msgstr ""
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
+msgstr "Secci贸n GitLab Runner"
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
 msgstr ""
 
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "Ir a tu bifurcaci贸n"
 
@@ -1645,14 +2191,32 @@ msgid "GoToYourFork|Fork"
 msgstr "Bifurcaci贸n"
 
 msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
-msgstr ""
+msgstr "La autenticaci贸n de Google no se encuentra %{link_to_documentation}. Preg煤ntale a tu administrador de GitLab si quieres usar este servicio."
 
 msgid "Got it!"
 msgstr ""
 
-msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr "Prevenir que se comparta un proyecto de %{group} con otros grupos"
+
 msgid "GroupSettings|Share with group lock"
 msgstr ""
 
@@ -1681,44 +2245,44 @@ msgid "GroupsEmptyState|If you organize your projects under a group, it works li
 msgstr ""
 
 msgid "GroupsEmptyState|No groups found"
-msgstr ""
+msgstr "No se encuentran grupos"
 
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
-msgstr ""
+msgstr "Crear un proyecto en este grupo."
 
 msgid "GroupsTree|Create a subgroup in this group."
-msgstr ""
+msgstr "Crear un sub-grupo en este grupo."
 
 msgid "GroupsTree|Edit group"
-msgstr ""
+msgstr "Editar grupo"
 
 msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
-msgstr ""
+msgstr "No pudiste dejar el grupo. Por favor, aseg煤rate que no seas el 煤nico propietario."
 
 msgid "GroupsTree|Filter by name..."
-msgstr ""
+msgstr "Filtrar por nombre..."
 
 msgid "GroupsTree|Leave this group"
-msgstr ""
+msgstr "Dejar este grupo"
 
 msgid "GroupsTree|Loading groups"
-msgstr ""
+msgstr "Cargando grupos"
 
 msgid "GroupsTree|Sorry, no groups matched your search"
-msgstr ""
+msgstr "Lo sentimos, no existen grupos que coincidan con su b煤squeda"
 
 msgid "GroupsTree|Sorry, no groups or projects matched your search"
-msgstr ""
+msgstr "Lo sentimos, no existen grupos ni proyectos que coincidan con su b煤squeda"
 
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr ""
 
@@ -1726,15 +2290,24 @@ msgid "Health information can be retrieved from the following endpoints. More in
 msgstr ""
 
 msgid "HealthCheck|Access token is"
-msgstr ""
+msgstr "Token de Acceso es"
 
 msgid "HealthCheck|Healthy"
-msgstr ""
+msgstr "Saludable"
 
 msgid "HealthCheck|No Health Problems Detected"
 msgstr ""
 
 msgid "HealthCheck|Unhealthy"
+msgstr "Poco Saludable"
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
 msgstr ""
 
 msgid "Hide value"
@@ -1743,42 +2316,78 @@ msgstr[0] ""
 msgstr[1] ""
 
 msgid "History"
-msgstr ""
+msgstr "Historial"
 
 msgid "Housekeeping successfully started"
 msgstr "Servicio de limpieza iniciado con 茅xito"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "Importar repositorio"
 
-msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgid "ImportButtons|Connect repositories from"
 msgstr ""
 
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr "Mejore los tableros de Incidencias con GitLab Enterprise Edition."
+
 msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
-msgstr ""
+msgstr "Mejore la gesti贸n de incidencias con Peso de Incidencias y GitLab Enterprise Edition."
 
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
-msgid "Install a Runner compatible with GitLab CI"
+msgid "Install Runner on Kubernetes"
 msgstr ""
 
+msgid "Install a Runner compatible with GitLab CI"
+msgstr "Instala un Runner compatible con GitLab CI"
+
 msgid "Instance"
 msgid_plural "Instances"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Instancia"
+msgstr[1] "Instancias"
 
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
 msgid "Internal - The group and any internal projects can be viewed by any logged in user."
-msgstr ""
+msgstr "Interno - cualquier usuario que haya iniciado sesi贸n puede ver el grupo y cualquier proyecto interno."
 
 msgid "Internal - The project can be accessed by any logged in user."
-msgstr ""
+msgstr "Interno - cualquier usuario haya iniciado sesi贸n puede acceder a este proyecto."
 
 msgid "Interval Pattern"
 msgstr "Patr贸n de intervalo"
@@ -1787,16 +2396,16 @@ msgid "Introducing Cycle Analytics"
 msgstr "Introducci贸n a Cycle Analytics"
 
 msgid "Issue board focus mode"
-msgstr ""
+msgstr "Modo enfocado del Tablero de Incidencias"
 
 msgid "Issue events"
-msgstr ""
+msgstr "Eventos de incidencia"
 
 msgid "IssueBoards|Board"
-msgstr ""
+msgstr "Tablero"
 
 msgid "IssueBoards|Boards"
-msgstr ""
+msgstr "Tableros"
 
 msgid "Issues"
 msgstr ""
@@ -1808,6 +2417,9 @@ msgid "Jan"
 msgstr ""
 
 msgid "January"
+msgstr "Enero"
+
+msgid "Jobs"
 msgstr ""
 
 msgid "Jul"
@@ -1820,6 +2432,9 @@ msgid "Jun"
 msgstr ""
 
 msgid "June"
+msgstr "Junio"
+
+msgid "Koding"
 msgstr ""
 
 msgid "Kubernetes"
@@ -1840,6 +2455,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1849,12 +2467,30 @@ msgstr "Deshabilitado"
 msgid "LFSStatus|Enabled"
 msgstr "Habilitado"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "脷ltimo %d d铆a"
@@ -1870,13 +2506,13 @@ msgid "Last edited %{date}"
 msgstr ""
 
 msgid "Last edited by %{name}"
-msgstr ""
+msgstr "脷ltima edici贸n por %{name}"
 
 msgid "Last update"
-msgstr ""
+msgstr "脷ltima actualizaci贸n"
 
 msgid "Last updated"
-msgstr ""
+msgstr "脷ltima actualizaci贸n"
 
 msgid "LastPushEvent|You pushed to"
 msgstr ""
@@ -1885,6 +2521,12 @@ msgid "LastPushEvent|at"
 msgstr ""
 
 msgid "Learn more"
+msgstr "Conozca m谩s"
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
 msgstr ""
 
 msgid "Learn more in the"
@@ -1903,40 +2545,61 @@ msgid "Leave project"
 msgstr "Abandonar proyecto"
 
 msgid "License"
+msgstr "Licencia"
+
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
 msgstr ""
 
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
 msgid "Lock"
-msgstr ""
+msgstr "Bloquear"
 
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
-msgstr ""
+msgstr "Bloqueado"
 
 msgid "Locked Files"
+msgstr "Archivos Bloqueados"
+
+msgid "Locks give the ability to lock specific file or folder."
 msgstr ""
 
 msgid "Login"
-msgstr ""
+msgstr "Iniciar sesi贸n"
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
 msgid "March"
-msgstr ""
+msgstr "Marzo"
 
 msgid "Mark done"
 msgstr ""
@@ -1951,9 +2614,9 @@ msgid "Median"
 msgstr "Mediana"
 
 msgid "Members"
-msgstr ""
+msgstr "Miembros"
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1962,16 +2625,91 @@ msgstr ""
 msgid "Merge events"
 msgstr ""
 
-msgid "Merge request"
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Messages"
+msgstr "Mensajes"
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
 msgstr ""
 
-msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgid "Metrics|e.g. Throughput"
 msgstr ""
 
-msgid "Merged"
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
 msgstr ""
 
-msgid "Messages"
+msgid "Metrics|e.g. req/sec"
 msgstr ""
 
 msgid "Milestone"
@@ -1989,12 +2727,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "agregar una clave SSH"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr ""
 
@@ -2028,19 +2787,19 @@ msgid "New branch"
 msgstr "Nueva rama"
 
 msgid "New branch unavailable"
-msgstr ""
+msgstr "Nueva rama no disponible"
 
 msgid "New directory"
 msgstr "Nuevo directorio"
 
 msgid "New epic"
-msgstr ""
+msgstr "Nuevo epic"
 
 msgid "New file"
 msgstr "Nuevo archivo"
 
 msgid "New group"
-msgstr ""
+msgstr "Nuevo grupo"
 
 msgid "New issue"
 msgstr "Nueva incidencia"
@@ -2052,7 +2811,7 @@ msgid "New merge request"
 msgstr "Nueva solicitud de fusi贸n"
 
 msgid "New project"
-msgstr ""
+msgstr "Nuevo proyecto"
 
 msgid "New schedule"
 msgstr "Nueva programaci贸n"
@@ -2061,11 +2820,14 @@ msgid "New snippet"
 msgstr "Nuevo fragmento de c贸digo"
 
 msgid "New subgroup"
-msgstr ""
+msgstr "Nuevo sub-grupo"
 
 msgid "New tag"
 msgstr "Nueva etiqueta"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2084,17 +2846,17 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "No hay repositorio"
 
 msgid "No schedules"
 msgstr "No hay programaciones"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
-msgstr ""
+msgstr "Ninguno"
 
 msgid "Not allowed to merge"
 msgstr ""
@@ -2102,12 +2864,33 @@ msgstr ""
 msgid "Not available"
 msgstr "No disponible"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "No hay suficientes datos"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "Eventos de notificaci贸n"
 
@@ -2163,7 +2946,7 @@ msgid "NotificationLevel|Watch"
 msgstr "Vigilancia"
 
 msgid "Notifications"
-msgstr ""
+msgstr "Notificaciones"
 
 msgid "Notifications off"
 msgstr ""
@@ -2175,7 +2958,7 @@ msgid "Nov"
 msgstr ""
 
 msgid "November"
-msgstr ""
+msgstr "Noviembre"
 
 msgid "Number of access attempts"
 msgstr ""
@@ -2187,35 +2970,50 @@ msgid "Oct"
 msgstr ""
 
 msgid "October"
-msgstr ""
+msgstr "Octubre"
 
 msgid "OfSearchInADropdown|Filter"
 msgstr "Filtrar"
 
-msgid "Only project members can comment."
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
 msgstr ""
 
+msgid "Only project members can comment."
+msgstr "S贸lo los miembros de proyecto pueden comentar."
+
 msgid "Open"
 msgstr ""
 
 msgid "Opened"
-msgstr ""
+msgstr "Abierto"
 
 msgid "OpenedNDaysAgo|Opened"
 msgstr "Abierto"
 
 msgid "Opens in a new window"
-msgstr ""
+msgstr "Abre en una nueva ventana"
 
 msgid "Options"
 msgstr "Opciones"
 
-msgid "Overview"
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
 msgstr ""
 
+msgid "Overview"
+msgstr "Resumen"
+
 msgid "Owner"
 msgstr "Propietario"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2228,12 +3026,24 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
+msgstr "Contrase帽a"
+
+msgid "Pending"
 msgstr ""
 
-msgid "Pipeline"
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
 msgstr ""
 
+msgid "Pipeline"
+msgstr "Pipeline"
+
 msgid "Pipeline Health"
 msgstr "Estado del Pipeline"
 
@@ -2295,7 +3105,7 @@ msgid "PipelineSheduleIntervalPattern|Custom"
 msgstr "Personalizado"
 
 msgid "Pipelines"
-msgstr ""
+msgstr "Pipelines"
 
 msgid "Pipelines charts"
 msgstr "Gr谩ficos de los pipelines"
@@ -2312,9 +3122,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "todos"
 
@@ -2327,6 +3182,9 @@ msgstr "con etapa"
 msgid "Pipeline|with stages"
 msgstr "con etapas"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2336,20 +3194,29 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
-msgid "Preferences"
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
 msgstr ""
 
+msgid "Preferences"
+msgstr "Preferencias"
+
 msgid "Primary"
 msgstr ""
 
 msgid "Private - Project access must be granted explicitly to each user."
-msgstr ""
+msgstr "Privado - El acceso al proyecto debe ser concedido expl铆citamente para cada usuario."
 
 msgid "Private - The group and its projects can only be viewed by members."
+msgstr "Privado - El grupo y sus proyectos s贸lo pueden ser vistos por sus miembros."
+
+msgid "Private projects can be created in your personal namespace with:"
 msgstr ""
 
 msgid "Profile"
-msgstr ""
+msgstr "Perfil"
 
 msgid "Profiles|Account scheduled for removal."
 msgstr ""
@@ -2367,31 +3234,34 @@ msgid "Profiles|Deleting an account has the following effects:"
 msgstr ""
 
 msgid "Profiles|Invalid password"
-msgstr ""
+msgstr "Contrase帽a inv谩lida"
 
 msgid "Profiles|Invalid username"
-msgstr ""
+msgstr "Nombre de usuario inv谩lido"
 
 msgid "Profiles|Type your %{confirmationValue} to confirm:"
-msgstr ""
+msgstr "Escribe tu %{confirmationValue} para confirmar:"
 
 msgid "Profiles|You don't have access to delete this user."
-msgstr ""
+msgstr "No tienes acceso para eliminar este usuario."
 
 msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
-msgstr ""
+msgstr "Debes transferir o eliminar estos grupos antes de que puedas eliminar tu cuenta."
 
 msgid "Profiles|Your account is currently an owner in these groups:"
-msgstr ""
+msgstr "Actualmente su cuenta es propietaria de estos grupos:"
 
 msgid "Profiles|your account"
+msgstr "tu cuenta"
+
+msgid "Profiling - Performance bar"
 msgstr ""
 
 msgid "Programming languages used in this repository"
 msgstr ""
 
 msgid "Project '%{project_name}' is in the process of being deleted."
-msgstr ""
+msgstr "Proyecto '%{project_name}' est谩 en proceso de ser eliminado."
 
 msgid "Project '%{project_name}' queued for deletion."
 msgstr "Proyecto 鈥�%{project_name}鈥� en cola para eliminaci贸n."
@@ -2411,11 +3281,8 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
-msgstr ""
+msgstr "Detalles del Proyecto"
 
 msgid "Project export could not be deleted."
 msgstr "No se pudo eliminar la exportaci贸n del proyecto."
@@ -2430,7 +3297,7 @@ msgid "Project export started. A download link will be sent by email."
 msgstr "Se inici贸 la exportaci贸n del proyecto. Se enviar谩 un enlace de descarga por correo electr贸nico."
 
 msgid "ProjectActivityRSS|Subscribe"
-msgstr ""
+msgstr "Suscribirse"
 
 msgid "ProjectCreationLevel|Allowed to create projects"
 msgstr ""
@@ -2469,98 +3336,155 @@ msgid "ProjectNetworkGraph|Graph"
 msgstr "Historial gr谩fico"
 
 msgid "ProjectSettings|Contact an admin to change this setting."
-msgstr ""
+msgstr "P贸ngase en contacto con un administrador para cambiar esta opci贸n."
 
 msgid "ProjectSettings|Only signed commits can be pushed to this repository."
-msgstr ""
+msgstr "Solo se pueden enviar commits firmados a este repositorio."
 
 msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
-msgstr ""
+msgstr "Esta configuraci贸n se aplica a nivel del servidor y puede ser redefinida por un administrador."
 
 msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
-msgstr ""
+msgstr "Esta configuraci贸n se aplica a nivel del servidor pero se ha redefinido para este proyecto."
 
 msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
-msgstr ""
+msgstr "Esta configuraci贸n se aplicar谩 a todos los proyectos a menos que sea redefinida por un administrador."
 
 msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
 msgstr ""
 
 msgid "Projects"
-msgstr ""
+msgstr "Proyectos"
 
 msgid "ProjectsDropdown|Frequently visited"
 msgstr ""
 
 msgid "ProjectsDropdown|Loading projects"
-msgstr ""
+msgstr "Cargando proyectos"
 
 msgid "ProjectsDropdown|Projects you visit often will appear here"
 msgstr ""
 
 msgid "ProjectsDropdown|Search your projects"
-msgstr ""
+msgstr "Busca en tus proyectos"
 
 msgid "ProjectsDropdown|Something went wrong on our end."
-msgstr ""
+msgstr "Algo sali贸 mal por nuestra parte."
 
 msgid "ProjectsDropdown|Sorry, no projects matched your search"
-msgstr ""
+msgstr "Lo sentimos, no hay proyectos que coincidan con su b煤squeda"
 
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr "Esta funci贸n requiere que el navegador soporte localStorage"
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
 msgstr ""
 
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
+msgstr "De manera predeterminada, Prometheus escucha en 'http://localhost:9090'. No se recomienda cambiar la direcci贸n y el puerto predeterminados, ya que esto podr铆a afectar o entrar en conflicto con otros servicios que se ejecutan en el servidor de GitLab."
+
+msgid "PrometheusService|Common metrics"
 msgstr ""
 
-msgid "PrometheusService|Finding and configuring metrics..."
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Custom metrics"
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr "Encontrar y configurar m茅tricas..."
+
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Manage clusters"
 msgstr ""
 
-msgid "PrometheusService|More information"
+msgid "PrometheusService|Manual configuration"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|Metrics"
+msgstr "M茅tricas"
+
+msgid "PrometheusService|Missing environment variable"
+msgstr "Falta la variable de entorno"
+
+msgid "PrometheusService|More information"
+msgstr "M谩s informaci贸n"
+
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr "URL Base de Prometheus, como http://prometheus.example.com/"
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
 msgstr ""
 
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
 msgstr ""
 
 msgid "Public - The group and any public projects can be viewed without any authentication."
-msgstr ""
+msgstr "P煤blico - El grupo y cualquier proyecto p煤blico puede ser visto sin autenticaci贸n."
 
 msgid "Public - The project can be accessed without any authentication."
-msgstr ""
+msgstr "P煤blico - El proyecto puede ser accedido sin ninguna autenticaci贸n."
 
 msgid "Push Rules"
-msgstr ""
+msgstr "Reglas Push"
 
 msgid "Push events"
+msgstr "Eventos Push"
+
+msgid "Push project from command line"
 msgstr ""
 
-msgid "PushRule|Committer restriction"
+msgid "Push to create a project"
 msgstr ""
 
+msgid "PushRule|Committer restriction"
+msgstr "Restricci贸n de Committer"
+
 msgid "Quick actions can be used in the issues description and comment boxes."
 msgstr ""
 
@@ -2570,6 +3494,9 @@ msgstr "Leer m谩s"
 msgid "Readme"
 msgstr "L茅eme"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "Ramas"
 
@@ -2583,7 +3510,7 @@ msgid "Register / Sign In"
 msgstr ""
 
 msgid "Registry"
-msgstr ""
+msgstr "Registro"
 
 msgid "Related Commits"
 msgstr "Cambios Relacionados"
@@ -2603,6 +3530,9 @@ msgstr "Solicitudes de fusi贸n Relacionadas"
 msgid "Related Merged Requests"
 msgstr "Solicitudes de fusi贸n Relacionadas"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "Recordar despu茅s"
 
@@ -2618,7 +3548,22 @@ msgstr "Eliminar proyecto"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
+msgstr "Repositorio"
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
 msgstr ""
 
 msgid "Request Access"
@@ -2631,6 +3576,12 @@ msgid "Reset health check access token"
 msgstr ""
 
 msgid "Reset runners registration token"
+msgstr "Reinicializar el token de registro del runner"
+
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
 msgstr ""
 
 msgid "Reveal value"
@@ -2644,12 +3595,42 @@ msgstr "Revertir este cambio"
 msgid "Revert this merge request"
 msgstr "Revertir esta solicitud de fusi贸n"
 
-msgid "SSH Keys"
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
 msgstr ""
 
-msgid "Save changes"
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
 msgstr ""
 
+msgid "SSH Keys"
+msgstr "Llaves SSH"
+
+msgid "Save changes"
+msgstr "Guardar los cambios"
+
 msgid "Save pipeline schedule"
 msgstr "Guardar programaci贸n del pipeline"
 
@@ -2659,6 +3640,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "Programar un nuevo pipeline"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2668,6 +3652,9 @@ msgstr "Programaci贸n de Pipelines"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "Buscar ramas y etiquetas"
 
@@ -2681,20 +3668,26 @@ msgid "Search users"
 msgstr ""
 
 msgid "Seconds before reseting failure information"
-msgstr ""
+msgstr "Segundos antes de reinicializar informaci贸n de fallas"
 
 msgid "Seconds to wait for a storage access attempt"
-msgstr ""
+msgstr "Segundos a esperar para intentar acceder al almacenamiento"
 
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "Seleccionar formato de archivo"
 
 msgid "Select a timezone"
 msgstr "Selecciona una zona horaria"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2707,34 +3700,61 @@ msgstr "Selecciona una rama de destino"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
 msgid "September"
-msgstr ""
+msgstr "Septiembre"
 
 msgid "Server version"
 msgstr ""
 
 msgid "Service Templates"
+msgstr "Plantillas de Servicio"
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
 msgstr ""
 
 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 default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "Configurar Koding"
 
-msgid "Set up auto deploy"
-msgstr "Configurar auto despliegue"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "establecer una contrase帽a"
 
 msgid "Settings"
+msgstr "Configuraci贸n"
+
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
 msgstr ""
 
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
@@ -2746,9 +3766,12 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
-msgid "Show parent pages"
+msgid "Show command"
 msgstr ""
 
+msgid "Show parent pages"
+msgstr "Mostrar p谩ginas padre"
+
 msgid "Show parent subgroups"
 msgstr ""
 
@@ -2757,16 +3780,28 @@ msgid_plural "Showing %d events"
 msgstr[0] "Mostrando %d evento"
 msgstr[1] "Mostrando %d eventos"
 
-msgid "Sidebar|Change weight"
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|No"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign-in restrictions"
 msgstr ""
 
-msgid "Sidebar|No"
+msgid "Sign-up restrictions"
 msgstr ""
 
-msgid "Sidebar|None"
+msgid "Size and domain settings for static websites"
 msgstr ""
 
-msgid "Sidebar|Weight"
+msgid "Slack application"
 msgstr ""
 
 msgid "Snippets"
@@ -2778,13 +3813,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2797,7 +3832,7 @@ msgid "Something went wrong. Please try again."
 msgstr ""
 
 msgid "Sort by"
-msgstr ""
+msgstr "Ordenar por"
 
 msgid "SortOptions|Access level, ascending"
 msgstr ""
@@ -2896,6 +3931,9 @@ msgid "SortOptions|Weight"
 msgstr ""
 
 msgid "Source"
+msgstr "Fuente"
+
+msgid "Source (branch or tag)"
 msgstr ""
 
 msgid "Source code"
@@ -2907,40 +3945,64 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr "Destacar"
 
-msgid "Starred projects"
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
 msgstr ""
 
+msgid "Starred projects"
+msgstr "Proyectos favoritos"
+
 msgid "Start a %{new_merge_request} with these changes"
 msgstr "Iniciar una %{new_merge_request} con estos cambios"
 
 msgid "Start the Runner!"
 msgstr ""
 
-msgid "Stopped"
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
 msgstr ""
 
+msgid "Status"
+msgstr ""
+
+msgid "Stopped"
+msgstr "Detenido"
+
 msgid "Storage"
 msgstr ""
 
 msgid "Subgroups"
-msgstr ""
+msgstr "Sub-grupos"
 
 msgid "Switch branch/tag"
 msgstr "Cambiar rama/etiqueta"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
+msgstr "Hooks de sistema"
+
+msgid "System header and footer:"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "Etiqueta"
-msgstr[1] "Etiquetas"
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "Tags"
 msgstr "Etiquetas"
@@ -3017,11 +4079,14 @@ msgstr ""
 msgid "Target Branch"
 msgstr "Rama de destino"
 
-msgid "Team"
+msgid "Target branch"
 msgstr ""
 
+msgid "Team"
+msgstr "Equipo"
+
 msgid "Thanks! Don't show me this again"
-msgstr ""
+msgstr "Gracias! No mostrar esto nuevamente"
 
 msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
 msgstr ""
@@ -3032,15 +4097,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "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."
 
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "La colecci贸n de eventos agregados a los datos recopilados para esa etapa."
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "La relaci贸n con la bifurcaci贸n se ha eliminado."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "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."
 
@@ -3053,12 +4127,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
 msgid "The phase of the development lifecycle."
 msgstr "La etapa del ciclo de vida de desarrollo."
 
 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."
 
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "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."
 
@@ -3071,9 +4151,18 @@ msgstr "El proyecto puede accederse sin ninguna autenticaci贸n."
 msgid "The repository for this project does not exist."
 msgstr "El repositorio para este proyecto no existe."
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "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."
 
+msgid "The roadmap shows the progress of your epics along a timeline"
+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 "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."
 
@@ -3104,6 +4193,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr ""
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3167,12 +4259,18 @@ msgstr "Esto significa que no puede enviar c贸digo hasta que cree un repositorio
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3185,6 +4283,12 @@ msgstr "Tiempo antes de que empieze la implementaci贸n de una incidencia"
 msgid "Time between merge request creation and merge/close"
 msgstr "Tiempo entre la creaci贸n de la solicitud de fusi贸n y la integraci贸n o cierre de 茅sta"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3336,7 +4440,43 @@ msgstr[1] "mins"
 msgid "Time|s"
 msgstr "s"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
+msgstr "T铆tulo"
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
 msgstr ""
 
 msgid "Todo"
@@ -3354,19 +4494,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "Tiempo Total"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "Tiempo total de pruebas para todos los cambios o integraciones"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3378,22 +4515,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
-msgstr ""
-
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
+msgstr "Desbloquear"
 
 msgid "Unlocked"
+msgstr "Desbloqueado"
+
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3429,6 +4560,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "Hacer clic para subir"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3438,10 +4575,34 @@ msgstr ""
 msgid "Use your global notification setting"
 msgstr "Utiliza tu configuraci贸n de notificaci贸n global"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
+msgstr "Ver archivo @ "
+
+msgid "View group labels"
 msgstr ""
 
 msgid "View labels"
@@ -3450,7 +4611,13 @@ msgstr ""
 msgid "View open merge request"
 msgstr "Ver solicitud de fusi贸n abierta"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
+msgstr "Ver archivo reemplazado @ "
+
+msgid "Visibility and access controls"
 msgstr ""
 
 msgid "VisibilityLevel|Internal"
@@ -3477,14 +4644,23 @@ msgstr "No hay suficientes datos para mostrar en esta etapa."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
+msgstr "Peso"
+
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
 msgstr ""
 
 msgid "Wiki"
-msgstr ""
+msgstr "Wiki"
 
 msgid "WikiClone|Clone your wiki"
 msgstr ""
@@ -3597,22 +4773,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "Retirar Solicitud de Acceso"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "Va a eliminar %{group_name}. 隆El grupo eliminado NO puede ser restaurado! 驴Est谩s TOTALMENTE seguro?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "Va a eliminar %{project_name_with_namespace}. 隆El proyecto eliminado NO puede ser restaurado! 驴Est谩s TOTALMENTE seguro?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3630,12 +4818,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "Has alcanzado el l铆mite de tu proyecto"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "Debes iniciar sesi贸n para destacar un proyecto"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "Necesitas permisos."
 
@@ -3666,28 +4866,63 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr "La informaci贸n del cluster de Kubernetes en esta p谩gina a煤n se puede editar, pero se recomienda desactivarla y modificar"
+
+msgid "Your Projects (default)"
 msgstr ""
 
-msgid "Your comment will not be visible to the public."
+msgid "Your Projects' Activity"
 msgstr ""
 
-msgid "Your groups"
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
 msgstr ""
 
+msgid "Your comment will not be visible to the public."
+msgstr "Tus comentarios no ser谩n visibles al p煤blico."
+
+msgid "Your groups"
+msgstr "Tus grupos"
+
 msgid "Your name"
 msgstr "Tu nombre"
 
 msgid "Your projects"
+msgstr "Tus proyectos"
+
+msgid "among other things"
 msgstr ""
 
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "assign yourself"
 msgstr ""
 
 msgid "branch name"
-msgstr ""
+msgstr "nombre de la rama"
 
 msgid "by"
+msgstr "por"
+
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|Code quality"
@@ -3696,7 +4931,19 @@ msgstr ""
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3708,7 +4955,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3723,25 +4970,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3749,14 +5011,84 @@ msgid_plural "days"
 msgstr[0] "d铆a"
 msgstr[1] "d铆as"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3781,15 +5113,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3823,6 +5167,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3871,12 +5218,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3901,9 +5254,12 @@ msgstr[0] "padre"
 msgstr[1] "padres"
 
 msgid "password"
-msgstr ""
+msgstr "contrase帽a"
 
 msgid "personal access token"
+msgstr "token de acceso personal"
+
+msgid "private key does not match certificate."
 msgstr ""
 
 msgid "remove due date"
@@ -3915,12 +5271,18 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
-msgid "to help your contributors communicate effectively!"
+msgid "this document"
 msgstr ""
 
+msgid "to help your contributors communicate effectively!"
+msgstr "para ayudar a sus colaboradores a comunicarse de manera efectiva!"
+
 msgid "username"
-msgstr ""
+msgstr "usuario"
 
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po
new file mode 100644
index 0000000000000000000000000000000000000000..ed955f68381e017a6157d171527c318e1ecce4ae
--- /dev/null
+++ b/locale/fil_PH/gitlab.po
@@ -0,0 +1,5288 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:34-0400\n"
+"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
+"Language-Team: Filipino\n"
+"Language: fil_PH\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: crowdin.com\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Language: fil\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+
+msgid " and"
+msgstr ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{storage_name}: failed storage access attempt on host:"
+msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "(checkout the %{link} for information on how to install it)."
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "2FA enabled"
+msgstr ""
+
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account and limit settings"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add Changelog"
+msgstr ""
+
+msgid "Add Contribution guide"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add License"
+msgstr ""
+
+msgid "Add Readme"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add todo"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminHealthPageLink|health page"
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced settings"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
+msgid "An error occurred while rendering KaTeX"
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "Any Label"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archived project! Repository is read-only"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
+msgid "Assignee"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps (Beta)"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Downgrade"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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 "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project master or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn鈥檛 been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You鈥檙e about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Certificate fingerprint"
+msgstr ""
+
+msgid "Change Weight"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changelog"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "Check interval"
+msgstr ""
+
+msgid "Checking %{text} availability鈥�"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Choose File ..."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose file..."
+msgstr ""
+
+msgid "Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
+msgid "Choose which shards you wish to synchronize to this secondary node."
+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 "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occured while saving variables"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "CircuitBreakerApiLink|circuitbreaker api"
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add an existing Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
+msgstr ""
+
+msgid "ClusterIntegration|Applications"
+msgstr ""
+
+msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Token"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
+msgstr ""
+
+msgid "ClusterIntegration|Create on GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project ID"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
+msgid "ClusterIntegration|Install"
+msgstr ""
+
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Installed"
+msgstr ""
+
+msgid "ClusterIntegration|Installing"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Integration status"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about environments"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
+msgstr ""
+
+msgid "ClusterIntegration|More information"
+msgstr ""
+
+msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
+msgstr ""
+
+msgid "ClusterIntegration|Note:"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Project ID"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Security"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|See machine types"
+msgstr ""
+
+msgid "ClusterIntegration|See your projects"
+msgstr ""
+
+msgid "ClusterIntegration|See zones"
+msgstr ""
+
+msgid "ClusterIntegration|Service token"
+msgstr ""
+
+msgid "ClusterIntegration|Show"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes Cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Token"
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|check the pricing here"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|help page"
+msgstr ""
+
+msgid "ClusterIntegration|installing applications"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|properly configured"
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|Commit: %{commitText}"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Created"
+msgstr ""
+
+msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
+msgstr ""
+
+msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
+msgstr ""
+
+msgid "ContainerRegistry|How to use the Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Learn more about"
+msgstr ""
+
+msgid "ContainerRegistry|No tags in Container Registry for this container image."
+msgstr ""
+
+msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgstr ""
+
+msgid "ContainerRegistry|Size"
+msgstr ""
+
+msgid "ContainerRegistry|Tag"
+msgstr ""
+
+msgid "ContainerRegistry|Tag ID"
+msgstr ""
+
+msgid "ContainerRegistry|Use different image names"
+msgstr ""
+
+msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution guide"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
+msgstr ""
+
+msgid "ContributorsPage|Building repository graph."
+msgstr ""
+
+msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Control the maximum concurrency of repository backfill for this secondary node"
+msgstr ""
+
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy branch name to clipboard"
+msgstr ""
+
+msgid "Copy command to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Copy reference to clipboard"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "CreateNewFork|Fork"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Current node"
+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 "Customize colors"
+msgstr ""
+
+msgid "Cycle Analytics"
+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 "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discover GitLab Geo."
+msgstr ""
+
+msgid "Dismiss Cycle Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+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 "Downvotes"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|Open"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now."
+msgstr ""
+
+msgid "Epic will be removed! Are you sure?"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error fetching contributors data."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching usage ping data."
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error updating status for all todos."
+msgstr ""
+
+msgid "Error updating todo status."
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+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 "Expand"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fields on this page are now uneditable, you can configure"
+msgstr ""
+
+msgid "File name"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "From %{provider_title}"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Database replication lag:"
+msgstr ""
+
+msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version:"
+msgstr ""
+
+msgid "GeoNodes|Health status:"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor:"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary:"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|Local Attachments:"
+msgstr ""
+
+msgid "GeoNodes|Local LFS objects:"
+msgstr ""
+
+msgid "GeoNodes|Local job artifacts:"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Out of sync"
+msgstr ""
+
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL:"
+msgstr ""
+
+msgid "GeoNodes|Replication slots:"
+msgstr ""
+
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
+msgid "GeoNodes|Repositories:"
+msgstr ""
+
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Selective"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config:"
+msgstr ""
+
+msgid "GeoNodes|Sync settings:"
+msgstr ""
+
+msgid "GeoNodes|Synced"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
+msgid "GeoNodes|Wikis:"
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|File sync capacity"
+msgstr ""
+
+msgid "Geo|Groups to synchronize"
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Repository sync capacity"
+msgstr ""
+
+msgid "Geo|Select groups to replicate."
+msgstr ""
+
+msgid "Geo|Shards to synchronize"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git storage health information has been reset"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
+msgid "GitLab Runner section"
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "GoToYourFork|Fork"
+msgstr ""
+
+msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Share with group lock"
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Filter by name..."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|Sorry, no groups or projects matched your search"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header message"
+msgstr ""
+
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "History"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Install a Runner compatible with GitLab CI"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Issue board focus mode"
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Koding"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration was not removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes configured"
+msgstr ""
+
+msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark done"
+msgstr ""
+
+msgid "Maximum git storage failures"
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Kubernetes Cluster"
+msgstr ""
+
+msgid "New Kubernetes cluster"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "No Label"
+msgstr ""
+
+msgid "No assignee"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No labels created yet."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not allowed to merge"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+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 "Notifications"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "Number of access attempts"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pagination|Last 禄"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|芦 First"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline quota"
+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 "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project avatar in repository: %{link}"
+msgstr ""
+
+msgid "Project details"
+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 "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Masters"
+msgstr ""
+
+msgid "ProjectCreationLevel|Masters"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+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 "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
+msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Readme"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "RefSwitcher|Branches"
+msgstr ""
+
+msgid "RefSwitcher|Tags"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Registry"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Reset git storage health information"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Seconds before reseting failure information"
+msgstr ""
+
+msgid "Seconds to wait for a storage access attempt"
+msgstr ""
+
+msgid "Secret variables"
+msgstr ""
+
+msgid "Security report"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select assignee"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Selective synchronization"
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up Koding"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|No"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while fetching Dependency Scanning."
+msgstr ""
+
+msgid "Something went wrong while fetching SAST."
+msgstr ""
+
+msgid "Something went wrong while fetching the projects."
+msgstr ""
+
+msgid "Something went wrong while fetching the registry list."
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Milestone"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag."
+msgstr ""
+
+msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here..."
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The number of attempts GitLab will make to access a storage."
+msgstr ""
+
+msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 ""
+
+msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset."
+msgstr ""
+
+msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised."
+msgstr ""
+
+msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
+msgstr ""
+
+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 ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no merge requests to show"
+msgstr ""
+
+msgid "There are problems accessing Git storage: "
+msgstr ""
+
+msgid "There was an error loading results"
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "This board\\'s scope is reduced"
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This is a confidential issue."
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is confidential and locked."
+msgstr ""
+
+msgid "This issue is locked."
+msgstr ""
+
+msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+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 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|in a while"
+msgstr ""
+
+msgid "Timeago|less than a minute ago"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
+msgid "Todo"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unresolve discussion"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to activate Issue weight."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload new avatar"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyPageError|You are not allowed to create wiki pages"
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
+msgstr ""
+
+msgid "WikiNewPagePlaceholder|how-to-setup"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiNewPageTitle|New Wiki Page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{page_title}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{page_title}"
+msgstr ""
+
+msgid "WikiPage|Page slug"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here..."
+msgstr ""
+
+msgid "Wiki|Create Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|Empty page"
+msgstr ""
+
+msgid "Wiki|More Pages"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Write a commit message..."
+msgstr ""
+
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
+msgid "You must sign in to star a project"
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+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 "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your comment will not be visible to the public."
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "assign yourself"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|DAST detected no alerts by analyzing the review app"
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Instances"
+msgstr ""
+
+msgid "ciReport|Learn more about whitelisting"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|SAST detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|SAST:container no vulnerabilities were found"
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Show complete code vulnerabilities report"
+msgstr ""
+
+msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
+msgstr ""
+
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove Source Branch"
+msgstr ""
+
+msgid "mrWidget|Remove source branch"
+msgstr ""
+
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The source branch has been removed"
+msgstr ""
+
+msgid "mrWidget|The source branch is being removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will be removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be removed"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|Web IDE"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|You can remove source branch now"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "password"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index d176e67937f3aaa4cbee632c2f19cb6893a42fae..3f30c5c4b87ef5c19133b7eb8e179a9c0f27d334 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 03:59-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:38-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: French\n"
 "Language: fr_FR\n"
@@ -17,7 +17,7 @@ msgstr ""
 "X-Crowdin-File: /master/locale/gitlab.pot\n"
 
 msgid " and"
-msgstr ""
+msgstr " et"
 
 msgid "%d commit"
 msgid_plural "%d commits"
@@ -26,13 +26,18 @@ msgstr[1] "%d commits"
 
 msgid "%d commit behind"
 msgid_plural "%d commits behind"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d commit de retard"
+msgstr[1] "%d commits de retard"
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] "%d exportateur"
+msgstr[1] "%d exportateurs"
 
 msgid "%d issue"
 msgid_plural "%d issues"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d ticket"
+msgstr[1] "%d tickets"
 
 msgid "%d layer"
 msgid_plural "%d layers"
@@ -41,24 +46,38 @@ msgstr[1] "%d couches"
 
 msgid "%d merge request"
 msgid_plural "%d merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d demande de fusion"
+msgstr[1] "%d demandes de fusion"
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] "%d m茅trique"
+msgstr[1] "%d m茅triques"
 
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s validation suppl茅mentaire a 茅t茅 masqu茅e afin d'茅viter de cr茅er de probl猫mes de performances."
-msgstr[1] "%s validations suppl茅mentaires ont 茅t茅 masqu茅es afin d'茅viter de cr茅er de probl猫mes de performances."
+msgstr[1] "%s commits suppl茅mentaires ont 茅t茅 masqu茅s afin d'茅viter un probl猫me de performance."
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr "%{actionText} et %{openOrClose} %{noteable}"
 
 msgid "%{commit_author_link} authored %{commit_timeago}"
-msgstr ""
+msgstr "%{commit_author_link} a cr茅茅 %{commit_timeago}"
 
 msgid "%{count} participant"
 msgid_plural "%{count} participants"
 msgstr[0] "%{count} participant鈥"
 msgstr[1] "%{count} participant鈥鈥"
 
+msgid "%{loadingIcon} Started"
+msgstr "%{loadingIcon} D茅marr茅"
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr "%{lock_path} est verrouill茅 par l鈥檜tilisateur GitLab %{lock_user_id}"
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
-msgstr "%{number_commits_behind} validations de retard sur %{default_branch}, %{number_commits_ahead} validations d'avance"
+msgstr "%{number_commits_behind} commits de retard sur %{default_branch}, %{number_commits_ahead} commits d'avance"
 
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
 msgstr "%{number_of_failures} sur %{maximum_failures} tentative(s). GitLab va vous permettre d'acc茅der 脿 la prochaine tentative."
@@ -66,6 +85,9 @@ msgstr "%{number_of_failures} sur %{maximum_failures} tentative(s). GitLab va vo
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} 茅checs sur %{maximum_failures}. GitLab ne va plus r茅essayer automatiquement. R茅initialisez les informations de stockage lorsque le probl猫me est r茅solu."
 
+msgid "%{openOrClose} %{noteable}"
+msgstr "%{openOrClose} %{noteable}"
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}聽: la tentative d鈥檃cc猫s au stockage a 茅chou茅e sur l鈥檋么te :"
@@ -94,15 +116,30 @@ msgstr "1猫re contribution !"
 msgid "2FA enabled"
 msgstr "2FA activ茅"
 
+msgid "<strong>Removes</strong> source branch"
+msgstr "<strong>Supprime</strong> la branche source"
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "Un ensemble de graphiques concernant l鈥橧nt茅gration Continue (CI)"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr "Une nouvelle branche sera cr茅茅e dans votre fourche et une nouvelle demande de fusion sera lanc茅e."
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr "Un projet est l鈥檈ndroit o霉 vous h茅bergez vos fichiers (d茅p么t), planifiez votre travail (tickets) et publiez votre documentation (wiki), %{among_other_things_link}."
+
+msgid "A user with write access to the source branch selected this option"
+msgstr "Un鈥 utilisateur鈥ice avec un acc猫s en 茅criture 脿 la branche source a s茅lectionn茅 cette option"
+
 msgid "About auto deploy"
 msgstr "脌 propos de l'auto-d茅ploiement"
 
 msgid "Abuse Reports"
 msgstr "Rapports d鈥檃bus"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "Jetons d'Acc猫s"
 
@@ -112,6 +149,9 @@ msgstr "L'acc猫s aux stockages d茅faillants a 茅t茅 temporairement d茅sactiv茅 p
 msgid "Account"
 msgstr "Compte"
 
+msgid "Account and limit settings"
+msgstr "Param猫tres de compte et de limitation"
+
 msgid "Active"
 msgstr "Actif"
 
@@ -130,35 +170,74 @@ msgstr "Ajouter un guide de contribution"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr "Ajouter des Webhooks de groupe et GitLab Enterprise Edition."
 
+msgid "Add Kubernetes cluster"
+msgstr "Ajouter un cluster Kubernetes"
+
 msgid "Add License"
 msgstr "Ajouter une licence"
 
+msgid "Add Readme"
+msgstr "Ajouter un fichier Readme"
+
 msgid "Add new directory"
 msgstr "Ajouter un nouveau dossier"
 
 msgid "Add todo"
-msgstr ""
+msgstr "Ajouter 脿 la liste 脿 faire"
 
 msgid "AdminArea|Stop all jobs"
-msgstr ""
+msgstr "Arr锚tez tous les travaux"
 
 msgid "AdminArea|Stop all jobs?"
-msgstr ""
+msgstr "Arr锚tez tous les travaux?"
 
 msgid "AdminArea|Stop jobs"
-msgstr ""
+msgstr "Arr锚tez les travaux"
 
 msgid "AdminArea|Stopping jobs failed"
-msgstr ""
+msgstr "L鈥檃rr锚t des travaux a 茅chou茅"
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
-msgstr ""
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr "Vous 锚tes sur le point d鈥檃rr锚ter tous les travaux. Tous les travaux en cours seront interrompus."
 
 msgid "AdminHealthPageLink|health page"
 msgstr "脡tat des services"
 
+msgid "AdminProjects|Delete"
+msgstr "Supprimer"
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr "Supprimer le projet %{projectName} ?"
+
+msgid "AdminProjects|Delete project"
+msgstr "Supprimer le projet"
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr "Sp茅cifiez un domaine 脿 utiliser par d茅faut pour les 茅tapes Auto Review Apps et Auto Deploy de chaque projet."
+
+msgid "AdminUsers|Block user"
+msgstr "Bloquer l鈥檜tilisateur鈥ice"
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr "Supprimer l鈥檜tilisateur鈥ice %{username} et ses contributions ?"
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr "Supprimer l鈥檜tilisateur鈥ice %{username} ?"
+
+msgid "AdminUsers|Delete user"
+msgstr "Supprimer un鈥 utilisateur鈥ice"
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr "Supprimer l鈥檜tilisateur鈥ice et ses contributions"
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr "Pour confirmer, veuillez saisir %{projectName}"
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr "Pour confirmer, veuillez saisir %{username}"
+
 msgid "Advanced"
-msgstr ""
+msgstr "Avanc茅"
 
 msgid "Advanced settings"
 msgstr "Param猫tres avanc茅s"
@@ -167,52 +246,115 @@ msgid "All"
 msgstr "Tous"
 
 msgid "All changes are committed"
+msgstr "Toutes les modifications sont valid茅es"
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr "Toutes les fonctionnalit茅s sont activ茅es pour les projets vierges, 脿 partir de mod猫les ou lors de l鈥檌mportation, mais vous pouvez les d茅sactiver ult茅rieurement dans les param猫tres du projet."
+
+msgid "Allow edits from maintainers."
+msgstr "Autoriser les modifications par les mainteneur鈥e鈥."
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
 msgstr ""
 
 msgid "Allows you to add and manage Kubernetes clusters."
+msgstr "Vous permet d鈥檃jouter et de g茅rer des clusters Kubernetes."
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
 msgstr ""
 
-msgid "An error occurred previewing the blob"
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
 msgstr ""
 
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr "Alternativement, vous pouvez utiliser un %{personal_access_token_link}. Lorsque vous cr茅ez votre jeton d鈥檃cc猫s, vous devrez s茅lectionner le champ <code>repo</code>, afin que nous puissions afficher une liste de vos d茅p么ts publics et priv茅s qui sont disponibles pour se connecter."
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr "Alternativement, vous pouvez utiliser un %{personal_access_token_link}. Lorsque vous cr茅ez votre jeton d鈥檃cc猫s, vous devrez s茅lectionner le champ <code>repo</code>, afin que nous puissions afficher une liste de vos d茅p么ts publics et priv茅s qui sont disponibles pour 锚tre import茅s."
+
+msgid "An error occurred previewing the blob"
+msgstr "Une erreur s鈥檈st produite lors de la pr茅visualisation du blob"
+
 msgid "An error occurred when toggling the notification subscription"
 msgstr "Une erreur s鈥檈st produite lors de l鈥檃ctivation/d茅sactivation de l鈥檃bonnement aux notifications"
 
 msgid "An error occurred when updating the issue weight"
 msgstr "Une erreur s'est produite lors de la mise 脿 jour du poids du ticket"
 
+msgid "An error occurred while adding approver"
+msgstr "Une erreur s鈥檈st produite lors de l鈥檃jout de l'approbateur鈥ice"
+
+msgid "An error occurred while detecting host keys"
+msgstr "Une erreur s鈥檈st produite lors de la d茅tection des cl茅s de l'h么te"
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la r茅vocation de la mise en avant de la fonctionnalit茅. Actualisez la page et essayez de la r茅voquer 脿 nouveau."
 
 msgid "An error occurred while fetching markdown preview"
-msgstr ""
+msgstr "Une erreur s'est produite lors de la pr茅visualisation markdown"
 
 msgid "An error occurred while fetching sidebar data"
 msgstr "Une erreur s'est produite lors de la r茅cup茅ration des donn茅es de la barre lat茅rale"
 
+msgid "An error occurred while fetching the pipeline."
+msgstr "Une erreur est survenue pendant la r茅cup茅ration du pipeline."
+
 msgid "An error occurred while getting projects"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la r茅cup茅ration des projets"
+
+msgid "An error occurred while importing project"
+msgstr "Une erreur s鈥檈st produite lors de l鈥檌mportation du projet"
+
+msgid "An error occurred while initializing path locks"
+msgstr "Une erreur s鈥檈st produite lors de l鈥檌nitialisation des verrous de chemin"
+
+msgid "An error occurred while loading commits"
+msgstr "Une erreur s鈥檈st produite lors du chargement des commits"
+
+msgid "An error occurred while loading diff"
+msgstr "Une erreur s鈥檈st produite lors du chargement du diff"
 
 msgid "An error occurred while loading filenames"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors du chargement des noms de fichiers"
+
+msgid "An error occurred while loading the file"
+msgstr "Une erreur s鈥檈st produite lors du chargement du fichier"
+
+msgid "An error occurred while making the request."
+msgstr "Une erreur s鈥檈st produite lors de la requ锚te."
+
+msgid "An error occurred while removing approver"
+msgstr "Une erreur s鈥檈st produite lors de la suppression de l鈥檃pprobateur鈥ice"
 
 msgid "An error occurred while rendering KaTeX"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors du rendu de KaTeX"
 
 msgid "An error occurred while rendering preview broadcast message"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la pr茅visualisation de la banni猫re"
 
 msgid "An error occurred while retrieving calendar activity"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la r茅cup茅ration de l鈥檃ctivit茅 du calendrier"
 
 msgid "An error occurred while retrieving diff"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la r茅cup茅ration du diff"
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr "Une erreur s鈥檈st produite lors de l鈥檈nregistrement du statut de remplacement LDAP. Veuillez r茅essayer."
+
+msgid "An error occurred while saving assignees"
+msgstr "Une erreur s鈥檈st produite lors de l鈥檈nregistrement des destinataires"
 
 msgid "An error occurred while validating username"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la validation du nom d鈥檜tilisateur鈥ice"
 
 msgid "An error occurred. Please try again."
-msgstr "Une erreur est survenue. Merci de r茅essayer."
+msgstr "Une erreur s鈥檈st produite. Veuillez r茅essayer."
+
+msgid "Any Label"
+msgstr "Tout label"
 
 msgid "Appearance"
 msgstr "Apparence"
@@ -232,35 +374,47 @@ msgstr "Projet archiv茅 ! Le d茅p么t est en lecture seule"
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "脢tes-vous s没r路e de vouloir supprimer ce pipeline programm茅聽?"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "脢tes-vous s没r路e de vouloir annuler vos modifications聽?"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "脢tes-vous s没r路e de vouloir r茅initialiser le jeton d鈥檌nscription聽?"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "脢tes-vous s没r de vouloir r茅initialiser le jeton de bilan de sant茅聽?"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr "脢tes-vous s没r鈥 de vouloir d茅verrouiller %{path_lock_path} ?"
+
 msgid "Are you sure?"
 msgstr "脢tes-vous certain ?"
 
 msgid "Artifacts"
 msgstr "Art茅facts"
 
-msgid "Assign custom color like #FF0000"
+msgid "Assertion consumer service URL"
 msgstr ""
 
+msgid "Assign custom color like #FF0000"
+msgstr "Attribuer une couleur personnalis茅e comme #FF0000"
+
 msgid "Assign labels"
-msgstr ""
+msgstr "Attribuer des labels"
 
 msgid "Assign milestone"
-msgstr ""
+msgstr "Attribuer un jalon"
 
 msgid "Assign to"
-msgstr ""
+msgstr "Assigner 脿"
+
+msgid "Assigned Issues"
+msgstr "Tickets assign茅s"
+
+msgid "Assigned Merge Requests"
+msgstr "Demandes de fusion assign茅es"
+
+msgid "Assigned to :name"
+msgstr "Assign茅鈥 脿 :name"
 
 msgid "Assignee"
-msgstr ""
+msgstr "Assign茅鈥"
 
 msgid "Attach a file by drag &amp; drop or %{upload_link}"
 msgstr "Attachez un fichier par glisser &amp; d茅poser ou %{upload_link}"
@@ -278,13 +432,19 @@ msgid "Author"
 msgstr "Auteur"
 
 msgid "Authors: %{authors}"
+msgstr "Auteur鈥鈥聽: %{authors}"
+
+msgid "Auto DevOps enabled"
+msgstr "Auto DevOps activ茅"
+
+msgid "Auto DevOps, runners and job artifacts"
 msgstr ""
 
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "Auto Review Apps et Auto Deploy ont besoin d鈥檜n %{kubernetes} qui fonctionne correctement."
 
 msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "Auto Review Apps et Auto Deploy ont besoin d鈥檜n nom de domaine et d鈥檜n %{kubernetes} qui fonctionne correctement."
 
 msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
 msgstr "Les applications de r茅vision automatique et de d茅ploiement automatique requi猫rent un nom de domaine pour fonctionner correctement."
@@ -304,18 +464,33 @@ msgstr "AutoDevOps vous permet de construire, tester et d茅ployer automatiquemen
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr "En savoir plus dans %{link_to_documentation}"
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
-msgstr "Vous pouvez activer %{link_to_settings} pour ce projet."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr "Vous pouvez automatiquement g茅n茅rer et tester votre application si vous %{link_to_auto_devops_settings} pour ce projet. Vous pouvez aussi la d茅ployer automatiquement, si vous %{link_to_add_kubernetes_cluster}."
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr "ajouter un cluster Kubernetes"
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr "activer Auto DevOps (B锚ta)"
 
 msgid "Available"
 msgstr "Disponible"
 
 msgid "Avatar will be removed. Are you sure?"
-msgstr ""
+msgstr "L鈥檃vatar sera supprim茅. 脢tes-vous s没r鈥 ?"
 
 msgid "Average per day: %{average}"
+msgstr "Moyenne par jour : %{average}"
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
 msgstr ""
 
+msgid "Begin with the selected commit"
+msgstr "Commencer avec le commit s茅lectionn茅"
+
 msgid "Billing"
 msgstr "Facturation"
 
@@ -370,13 +545,10 @@ msgstr "pay茅 annuellement pour %{price_per_year}"
 msgid "BillingPlans|per user"
 msgstr "par utilisateur"
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "Branche"
-msgstr[1] "Branches"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+msgstr[0] "Branche (%{branch_count})"
+msgstr[1] "Branches (%{branch_count})"
 
 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 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 l'int茅gration continue (CI) de GitLab, et validez les modifications. %{link_to_autodeploy_doc}"
@@ -399,8 +571,17 @@ msgstr "Changer de branche"
 msgid "Branches"
 msgstr "Branches"
 
+msgid "Branches|Active"
+msgstr "Active"
+
+msgid "Branches|Active branches"
+msgstr "Branches actives"
+
+msgid "Branches|All"
+msgstr "Toutes"
+
 msgid "Branches|Cant find HEAD commit for this branch"
-msgstr "Impossible de trouver la validation HEAD pour cette branche"
+msgstr "Impossible de trouver le commit HEAD pour cette branche"
 
 msgid "Branches|Compare"
 msgstr "Comparer"
@@ -444,12 +625,39 @@ msgstr "Une fois que vous aurez confirm茅 et cliqu茅 sur %{delete_protected_bran
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr "Seulement un ma卯tre ou un propri茅taire du projet peut supprimer une branche prot茅g茅e"
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
-msgstr "Les branches prot茅g茅es peuvent 锚tre g茅r茅es dans %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr "Vue d鈥檈nsemble"
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr "Les branches prot茅g茅es peuvent 锚tre g茅r茅es dans %{project_settings_link}."
+
+msgid "Branches|Show active branches"
+msgstr "Afficher les branches actives"
+
+msgid "Branches|Show all branches"
+msgstr "Afficher toutes les branches"
+
+msgid "Branches|Show more active branches"
+msgstr "Afficher plus de branches actives"
+
+msgid "Branches|Show more stale branches"
+msgstr "Afficher plus de branches p茅rim茅es"
+
+msgid "Branches|Show overview of the branches"
+msgstr "Afficher l鈥檃per莽u des branches"
+
+msgid "Branches|Show stale branches"
+msgstr "Afficher les branches p茅rim茅es"
 
 msgid "Branches|Sort by"
 msgstr "Trier par"
 
+msgid "Branches|Stale"
+msgstr "P茅rim茅e"
+
+msgid "Branches|Stale branches"
+msgstr "Branches p茅rim茅es"
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr "Cette branche ne peut pas 锚tre mise 脿 jour automatiquement car elle a d茅vi茅 par rapport 脿 son d茅p么t en amont."
 
@@ -495,14 +703,23 @@ msgstr "Parcourir les fichiers"
 msgid "Browse files"
 msgstr "Parcourir les fichiers"
 
+msgid "Business"
+msgstr "Entreprise"
+
 msgid "ByAuthor|by"
 msgstr "par"
 
 msgid "CI / CD"
 msgstr "Int茅gration continu / D茅ploiement continu"
 
+msgid "CI/CD"
+msgstr "CI/CD"
+
 msgid "CI/CD configuration"
-msgstr ""
+msgstr "Configuration CI/CD"
+
+msgid "CI/CD for external repo"
+msgstr "CI / CD pour d茅p么t externe"
 
 msgid "CICD|Jobs"
 msgstr "T芒ches"
@@ -510,15 +727,21 @@ msgstr "T芒ches"
 msgid "Cancel"
 msgstr "Annuler"
 
-msgid "Cancel edit"
-msgstr "Annuler modification"
+msgid "Cannot be merged automatically"
+msgstr "Ne peut 锚tre fusionn茅e automatiquement"
 
 msgid "Cannot modify managed Kubernetes cluster"
+msgstr "Impossible de modifier le cluster g茅r茅 par Kubernetes"
+
+msgid "Certificate fingerprint"
 msgstr ""
 
 msgid "Change Weight"
 msgstr "Changer le poids"
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "Picorer dans la branche"
 
@@ -532,13 +755,13 @@ msgid "ChangeTypeAction|Revert"
 msgstr "D茅faire"
 
 msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
-msgstr ""
+msgstr "Cela va cr茅er un nouveau commit afin de d茅faire les modifications existantes."
 
 msgid "Changelog"
 msgstr "Journal des modifications"
 
 msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
-msgstr ""
+msgstr "Les modifications sont affich茅es comme si la r茅vision <b>source</b> 茅tait fusionn茅e dans la r茅vision<b>cible</b>."
 
 msgid "Charts"
 msgstr "Statistiques"
@@ -547,7 +770,7 @@ msgid "Chat"
 msgstr "Chat"
 
 msgid "Check interval"
-msgstr ""
+msgstr "Intervalle de v茅rification"
 
 msgid "Checking %{text} availability鈥�"
 msgstr "V茅rification de la disponibilit茅 de %{text}鈥�"
@@ -556,25 +779,31 @@ msgid "Checking branch availability..."
 msgstr "V茅rification de la disponibilit茅 du nom de branche..."
 
 msgid "Cherry-pick this commit"
-msgstr "Picorer cette validation"
+msgstr "Picorer ce commit"
 
 msgid "Cherry-pick this merge request"
 msgstr "Picorer cette demande de fusion"
 
 msgid "Choose File ..."
-msgstr ""
+msgstr "Choisir le fichier鈥�"
 
 msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
-msgstr ""
+msgstr "Choisissez une branche / tag (par exemple %{master}) ou entrez un commit (par exemple %{sha}) pour voir ce qui a chang茅 ou pour cr茅er une demande de fusion."
 
 msgid "Choose file..."
-msgstr ""
+msgstr "Choisir le fichier鈥�"
 
 msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "Choisissez les groupes que vous souhaitez synchroniser avec ce n艙ud secondaire."
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr "Choisissez 脿 quels d茅p么ts vous voulez connecter et ex茅cuter des pipelines CI/CD."
+
+msgid "Choose which repositories you want to import."
+msgstr "Choisissez les d茅p么ts que vous voulez importer."
 
 msgid "Choose which shards you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "Choisissez les partitions que vous souhaitez synchroniser avec ce n艙ud secondaire."
 
 msgid "CiStatusLabel|canceled"
 msgstr "annul茅"
@@ -631,46 +860,58 @@ msgid "CiStatus|running"
 msgstr "en cours"
 
 msgid "CiVariables|Input variable key"
-msgstr ""
+msgstr "Nom de la variable"
 
 msgid "CiVariables|Input variable value"
-msgstr ""
+msgstr "Valeur de la variable"
 
 msgid "CiVariables|Remove variable row"
-msgstr ""
+msgstr "Supprimer cette variable"
 
 msgid "CiVariable|* (All environments)"
-msgstr ""
+msgstr "* (Tous les environnements)"
 
 msgid "CiVariable|All environments"
-msgstr ""
+msgstr "Tous les environnements"
 
 msgid "CiVariable|Create wildcard"
-msgstr ""
+msgstr "Cr茅er un caract猫re g茅n茅rique"
 
 msgid "CiVariable|Error occured while saving variables"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite pendant la sauvegarde des variables"
 
 msgid "CiVariable|New environment"
-msgstr ""
+msgstr "Nouvel environnement"
 
 msgid "CiVariable|Protected"
-msgstr ""
+msgstr "Prot茅g茅e"
 
 msgid "CiVariable|Search environments"
-msgstr ""
+msgstr "Chercher dans les environnements"
 
 msgid "CiVariable|Toggle protected"
-msgstr ""
+msgstr "Changer l鈥櫭﹖at de protection"
 
 msgid "CiVariable|Validation failed"
-msgstr ""
+msgstr "La validation a 茅chou茅"
 
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr "CircuitBreaker API"
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr "Cliquez sur le bouton ci-dessous pour lancer le processus d鈥檌nstallation en acc茅dant 脿 la page Kubernetes"
+
 msgid "Click to expand text"
-msgstr ""
+msgstr "Cliquez pour agrandir le texte"
+
+msgid "Client authentication certificate"
+msgstr "Certificat d鈥檃uthentification du client"
+
+msgid "Client authentication key"
+msgstr "Cl茅 d鈥檃uthentification du client"
+
+msgid "Client authentication key password"
+msgstr "Mot de passe de la cl茅 d鈥檃uthentification client"
 
 msgid "Clone repository"
 msgstr "Cloner le d茅p么t"
@@ -679,28 +920,28 @@ msgid "Close"
 msgstr "Fermer"
 
 msgid "Closed"
-msgstr ""
+msgstr "Ferm茅e"
 
 msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
-msgstr ""
+msgstr "%{appList} a 茅t茅 install茅 avec succ猫s sur votre cluster Kubernetes"
 
 msgid "ClusterIntegration|API URL"
 msgstr "URL de l'API"
 
 msgid "ClusterIntegration|Add Kubernetes cluster"
-msgstr ""
+msgstr "Ajouter un cluster Kubernetes"
 
 msgid "ClusterIntegration|Add an existing Kubernetes cluster"
-msgstr ""
+msgstr "Ajouter un cluster Kubernetes existant"
 
 msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
-msgstr ""
+msgstr "Options avanc茅es sur l鈥檌nt茅gration de ce cluster Kubernetes"
 
 msgid "ClusterIntegration|Applications"
 msgstr "Applications"
 
 msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "脢tes-vous s没r鈥 de vouloir supprimer l'int茅gration de ce cluster Kubernetes? Cela ne supprimera pas votre cluster Kubernetes."
 
 msgid "ClusterIntegration|CA Certificate"
 msgstr "Certificat d鈥榓utorit茅 de certification"
@@ -709,13 +950,13 @@ msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
 msgstr "Paquet de l鈥楢utorit茅 de certification (format PEM)"
 
 msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
-msgstr ""
+msgstr "Choisissez comment configurer l鈥檌nt茅gration de cluster Kubernetes"
 
 msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
-msgstr ""
+msgstr "Choisissez les environnements de votre projet qui utiliseront ce cluster Kubernetes."
 
 msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
-msgstr ""
+msgstr "Contr么lez l鈥檌nt茅gration de votre cluster Kubernetes avec GitLab"
 
 msgid "ClusterIntegration|Copy API URL"
 msgstr "Copier l鈥橴RL de l鈥橝PI"
@@ -723,20 +964,23 @@ msgstr "Copier l鈥橴RL de l鈥橝PI"
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr "Copier le certificat CA"
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr "Copier l鈥檃dresse IP entrante dans le presse-papiers"
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
-msgstr ""
+msgstr "Copier le nom du cluster Kubernetes"
 
 msgid "ClusterIntegration|Copy Token"
 msgstr "Copier le jeton"
 
 msgid "ClusterIntegration|Create Kubernetes cluster"
-msgstr ""
+msgstr "Cr茅er un cluster Kubernetes"
 
 msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "Cr茅er un cluster Kubernetes sur Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
-msgstr ""
+msgstr "Cr茅er un nouveau cluster Kubernetes sur Google Kubernetes Engine directement depuis GitLab"
 
 msgid "ClusterIntegration|Create on GKE"
 msgstr "Cr茅er sur GKE"
@@ -745,13 +989,13 @@ msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
 msgstr "Entrer les d茅tails pour le cluster Kubernetes existant"
 
 msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
-msgstr ""
+msgstr "Entrez les d茅tails de votre cluster Kubernetes"
 
 msgid "ClusterIntegration|Environment scope"
-msgstr ""
+msgstr "Port茅e de l鈥檈nvironnement"
 
 msgid "ClusterIntegration|GitLab Integration"
-msgstr ""
+msgstr "Int茅gration GitLab"
 
 msgid "ClusterIntegration|GitLab Runner"
 msgstr "脡x茅cuteur GitLab"
@@ -768,12 +1012,21 @@ msgstr "Projet Google Kubernetes Engine"
 msgid "ClusterIntegration|Helm Tiller"
 msgstr "Helm Tiller"
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr "Afin d鈥檃fficher l鈥櫭﹖at du cluster, nous devons mettre votre cluster 脿 disposition de Prometheus pour r茅cup茅rer les donn茅es n茅cessaires."
+
 msgid "ClusterIntegration|Ingress"
 msgstr "Ingress"
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr "Adresse IP entrante"
+
 msgid "ClusterIntegration|Install"
 msgstr "Installer"
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr "Installer Prometheus"
+
 msgid "ClusterIntegration|Installed"
 msgstr "Install茅"
 
@@ -781,70 +1034,73 @@ msgid "ClusterIntegration|Installing"
 msgstr "En cours d鈥檌nstallation"
 
 msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
-msgstr ""
+msgstr "Int茅grez l鈥檃utomatisation du cluster Kubernetes"
 
 msgid "ClusterIntegration|Integration status"
-msgstr ""
+msgstr "Statut d鈥檌nt茅gration"
 
 msgid "ClusterIntegration|Kubernetes cluster"
-msgstr ""
+msgstr "Cluster Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster details"
-msgstr ""
+msgstr "D茅tails du cluster Kubernetes"
+
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr "脡tat du cluster Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster integration"
-msgstr ""
+msgstr "Int茅gration d鈥檜n cluster Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
-msgstr ""
+msgstr "L鈥檌nt茅gration de cluster Kubernetes est d茅sactiv茅e pour ce projet."
 
 msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
-msgstr ""
+msgstr "L鈥檌nt茅gration de cluster Kubernetes est activ茅e pour ce projet."
 
 msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
-msgstr ""
+msgstr "L鈥檌nt茅gration de cluster Kubernetes est activ茅e pour ce projet. La d茅sactivation de cette int茅gration n鈥檃ffectera pas votre cluster Kubernetes, elle ne d茅sactivera que temporairement la connexion de GitLab."
 
 msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
-msgstr ""
+msgstr "Le cluster Kubernetes est en cours de cr茅ation sur Google Kubernetes Engine鈥�"
 
 msgid "ClusterIntegration|Kubernetes cluster name"
-msgstr ""
+msgstr "Nom du cluster Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
-msgstr ""
+msgstr "Le cluster Kubernetes a 茅t茅 cr茅茅 avec succ猫s sur Google Kubernetes Engine. Actualisez la page pour voir les d茅tails du cluster Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
-msgstr ""
+msgstr "Les clusters Kubernetes vous permettent d鈥檜tiliser des applications de r茅vision, de d茅ployer vos applications, d鈥檈x茅cuter vos pipelines et bien plus encore. %{link_to_help_page}"
 
 msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
-msgstr ""
+msgstr "Les clusters Kubernetes peuvent 锚tre utilis茅s pour d茅ployer des applications et fournir des applications de r茅vision pour ce projet"
 
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr "En savoir plus sur %{link_to_documentation}"
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
-msgstr ""
-
 msgid "ClusterIntegration|Learn more about environments"
-msgstr ""
+msgstr "En savoir plus sur les environnements"
+
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr "En savoir plus sur la configuration de la s茅curit茅"
 
 msgid "ClusterIntegration|Machine type"
 msgstr "Type de machine"
 
 msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
-msgstr ""
+msgstr "Assurez-vous que votre compte %{link_to_requirements} pour cr茅er des clusters Kubernetes"
 
 msgid "ClusterIntegration|Manage"
-msgstr ""
+msgstr "G茅rer"
 
 msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
-msgstr ""
+msgstr "G茅rez votre cluster Kubernetes en visitant %{link_gke}"
 
 msgid "ClusterIntegration|More information"
-msgstr ""
+msgstr "Plus d鈥檌nformations"
 
 msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
-msgstr ""
+msgstr "Plusieurs clusters Kubernetes sont disponibles dans GitLab Enterprise Edition Premium et Ultimate"
 
 msgid "ClusterIntegration|Note:"
 msgstr "Remarque :"
@@ -853,7 +1109,7 @@ msgid "ClusterIntegration|Number of nodes"
 msgstr "Nombre de n艙uds"
 
 msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
-msgstr ""
+msgstr "Veuillez entrer les informations d鈥檃cc猫s de votre cluster Kubernetes. Si vous avez besoin d'aide, vous pouvez lire notre %{link_to_help_page} sur Kubernetes"
 
 msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
 msgstr "Veuillez vous assurer que votre compte Google r茅pond aux exigences suivantes聽: "
@@ -868,19 +1124,19 @@ msgid "ClusterIntegration|Project namespace (optional, unique)"
 msgstr "Espace de noms du projet (facultatif, unique)"
 
 msgid "ClusterIntegration|Prometheus"
-msgstr ""
+msgstr "Prometheus"
 
 msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
-msgstr ""
+msgstr "Lisez notre %{link_to_help_page} sur l鈥檌nt茅gration d鈥檜n cluster Kubernetes."
 
 msgid "ClusterIntegration|Remove Kubernetes cluster integration"
-msgstr ""
+msgstr "Supprimer l鈥檌nt茅gration du cluster Kubernetes"
 
 msgid "ClusterIntegration|Remove integration"
 msgstr "Retirer l鈥檌nt茅gration"
 
 msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "Supprimer la configuration de ce cluster Kubernetes de ce projet. Cela ne supprimera pas votre cluster Kubernetes actuel."
 
 msgid "ClusterIntegration|Request to begin installing failed"
 msgstr "La demande de lancement d'installation a 茅chou茅"
@@ -888,8 +1144,11 @@ msgstr "La demande de lancement d'installation a 茅chou茅"
 msgid "ClusterIntegration|Save changes"
 msgstr "Enregistrer les modifications"
 
+msgid "ClusterIntegration|Security"
+msgstr "S茅curit茅"
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
-msgstr ""
+msgstr "Voir et modifier les d茅tails de votre cluster Kubernetes"
 
 msgid "ClusterIntegration|See machine types"
 msgstr "Voir les types de machine"
@@ -910,25 +1169,28 @@ msgid "ClusterIntegration|Something went wrong on our end."
 msgstr "Un probl猫me est survenu de notre c么t茅."
 
 msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la cr茅ation de votre cluster Kubernetes sur Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr "Une erreur s鈥檈st produite lors de l'installation de %{title}"
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr "La configuration par d茅faut du cluster permet d鈥檃cc茅der 脿 un large 茅ventail de fonctionnalit茅s n茅cessaires pour construire et d茅ployer, avec succ猫s, une application conteneuris茅e."
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
-msgstr ""
+msgstr "Ce compte doit disposer des autorisations pour cr茅er un cluster Kubernetes dans le %{link_to_container_project} sp茅cifi茅 ci-dessous"
 
 msgid "ClusterIntegration|Toggle Kubernetes Cluster"
-msgstr ""
+msgstr "Activer le cluster Kubernetes"
 
 msgid "ClusterIntegration|Toggle Kubernetes cluster"
-msgstr ""
+msgstr "Activer/d茅sactiver le cluster Kubernetes"
 
 msgid "ClusterIntegration|Token"
 msgstr "Jeton"
 
 msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
-msgstr ""
+msgstr "Avec un cluster Kubernetes associ茅 脿 ce projet, vous pouvez utiliser des applications de r茅vision, d茅ployer vos applications, ex茅cuter vos pipelines, et bien plus encore."
 
 msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
 msgstr "Votre compte doit disposer de %{link_to_kubernetes_engine}"
@@ -940,7 +1202,7 @@ msgid "ClusterIntegration|access to Google Kubernetes Engine"
 msgstr "Acc猫der 脿 Google Kubernetes Engine"
 
 msgid "ClusterIntegration|check the pricing here"
-msgstr ""
+msgstr "v茅rifiez le prix ici"
 
 msgid "ClusterIntegration|documentation"
 msgstr "documentation"
@@ -958,60 +1220,74 @@ msgid "ClusterIntegration|properly configured"
 msgstr "correctement configur茅"
 
 msgid "Collapse"
-msgstr ""
+msgstr "R茅duire"
+
+msgid "Comment and resolve discussion"
+msgstr "Commenter et r茅soudre la discussion"
+
+msgid "Comment and unresolve discussion"
+msgstr "Commenter et marquer la discussion comme non r茅solue"
 
 msgid "Comments"
 msgstr "Commentaires"
 
 msgid "Commit"
 msgid_plural "Commits"
-msgstr[0] "Validation"
-msgstr[1] "Validations"
+msgstr[0] "Commit"
+msgstr[1] "Commits"
+
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] "Commit (%{commit_count})"
+msgstr[1] "Commits (%{commit_count})"
 
 msgid "Commit Message"
-msgstr "Message de validation"
+msgstr "Message du commit"
 
 msgid "Commit duration in minutes for last 30 commits"
 msgstr "Dur茅e des 30 derniers pipelines en minutes"
 
 msgid "Commit message"
-msgstr "Message de validation"
+msgstr "Message de commit"
 
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
-msgstr ""
+msgstr "Statistiques des commits pour %{ref} %{start_time} - %{end_time}"
+
+msgid "Commit to %{branchName} branch"
+msgstr "Valider dans la branche %{branchName}"
 
 msgid "CommitBoxTitle|Commit"
-msgstr "Validation"
+msgstr "Commit"
 
 msgid "CommitMessage|Add %{file_name}"
 msgstr "Ajout de %{file_name}"
 
 msgid "Commits"
-msgstr "Validations"
+msgstr "Commits"
 
 msgid "Commits feed"
-msgstr "Flux de validations"
+msgstr "Flux des commits"
 
 msgid "Commits per day hour (UTC)"
-msgstr ""
+msgstr "Commits par heure du jour (UTC)"
 
 msgid "Commits per day of month"
-msgstr ""
+msgstr "Commits par jour du mois"
 
 msgid "Commits per weekday"
-msgstr ""
+msgstr "Commits par jour de la semaine"
 
 msgid "Commits|An error occurred while fetching merge requests data."
-msgstr ""
+msgstr "Une erreur s'est produite lors de la r茅cup茅ration des donn茅es de demandes de fusion."
 
 msgid "Commits|Commit: %{commitText}"
-msgstr ""
+msgstr "Commit : %{commitText}"
 
 msgid "Commits|History"
 msgstr "Historique"
 
 msgid "Commits|No related merge requests found"
-msgstr ""
+msgstr "Aucune demande de fusion associ茅e trouv茅e"
 
 msgid "Committed by"
 msgstr "Valid茅 par"
@@ -1020,29 +1296,71 @@ msgid "Compare"
 msgstr "Comparer"
 
 msgid "Compare Git revisions"
-msgstr ""
+msgstr "Comparer les r茅visions Git"
 
 msgid "Compare Revisions"
+msgstr "Comparer les r茅visions"
+
+msgid "Compare changes with the last commit"
+msgstr "Comparer les changements avec le dernier commit"
+
+msgid "Compare changes with the merge request target branch"
 msgstr ""
 
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
-msgstr ""
+msgstr "%{source_branch} et %{target_branch} sont identiques."
 
 msgid "CompareBranches|Compare"
-msgstr ""
+msgstr "Comparer"
 
 msgid "CompareBranches|Source"
-msgstr ""
+msgstr "Source"
 
 msgid "CompareBranches|Target"
-msgstr ""
+msgstr "Cible"
 
 msgid "CompareBranches|There isn't anything to compare."
-msgstr ""
+msgstr "Il n鈥檡 a rien 脿 comparer."
+
+msgid "Confidential"
+msgstr "Confidentiel"
 
 msgid "Confidentiality"
+msgstr "Confidentialit茅"
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
 msgstr ""
 
+msgid "Configure the way a user creates a new account."
+msgstr "Configurez la fa莽on dont un鈥 utilisateur鈥ice cr茅e un nouveau compte."
+
+msgid "Connect"
+msgstr "Connecter"
+
+msgid "Connect all repositories"
+msgstr "Connecter tous les d茅p么ts"
+
+msgid "Connect repositories from GitHub"
+msgstr "Se connecter 脿 des d茅p么ts 脿 partir de GitHub"
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr "Connectez vos d茅p么ts externes et les pipelines CI / CD s鈥檈x茅cuteront pour les nouveaux commits. Un projet GitLab sera cr茅茅 avec uniquement les fonctionnalit茅s CI / CD activ茅es."
+
+msgid "Connecting..."
+msgstr "Connexion en cours鈥�"
+
 msgid "Container Registry"
 msgstr "Registre de conteneur"
 
@@ -1088,6 +1406,12 @@ msgstr "Utilisez des noms d鈥檌mages diff茅rents"
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr "Avec le registre de conteneur Docker int茅gr茅 脿 GitLab, chaque projet peut avoir son propre espace pour stocker ses images Docker."
 
+msgid "Continuous Integration and Deployment"
+msgstr "Int茅gration et d茅ploiement continus"
+
+msgid "Contribution"
+msgstr "Contribution"
+
 msgid "Contribution guide"
 msgstr "Guide de contribution"
 
@@ -1095,13 +1419,13 @@ msgid "Contributors"
 msgstr "Contributeurs"
 
 msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
-msgstr ""
+msgstr "%{startDate} - %{endDate}"
 
 msgid "ContributorsPage|Building repository graph."
 msgstr "Construction du graphique du d茅p么t."
 
 msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
-msgstr "Validations sur %{branch_name}, 脿 l鈥檈xclusion des validations de fusion. Limit茅 脿 6 000 validations."
+msgstr "Commit sur %{branch_name}, 脿 l'exclusion des commits de fusion. Limit茅 脿 6 000 commits."
 
 msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
 msgstr "Veuillez patienter, cette page va 锚tre automatiquement actualis茅e."
@@ -1119,27 +1443,39 @@ msgid "Copy URL to clipboard"
 msgstr "Copier l'URL dans le presse-papier"
 
 msgid "Copy branch name to clipboard"
-msgstr ""
+msgstr "Copier le nom de la branche dans le presse-papiers"
+
+msgid "Copy command to clipboard"
+msgstr "Copier la commande dans le presse-papiers"
 
 msgid "Copy commit SHA to clipboard"
-msgstr "Copier le SHA de la validation"
+msgstr "Copier le SHA du commit"
 
 msgid "Copy reference to clipboard"
-msgstr ""
+msgstr "Copier la r茅f茅rence dans le presse-papiers"
 
 msgid "Create"
-msgstr ""
+msgstr "Cr茅er"
 
 msgid "Create New Directory"
 msgstr "Cr茅er un nouveau dossier"
 
+msgid "Create a new branch"
+msgstr "Cr茅er une nouvelle branche"
+
+msgid "Create a new branch and merge request"
+msgstr "Cr茅er une nouvelle branche et demande de fusion"
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "Cr茅er un jeton d鈥檃cc猫s personnel pour votre compte afin de r茅cup茅rer ou pousser par %{protocol}."
 
+msgid "Create branch"
+msgstr "Cr茅er une branche"
+
 msgid "Create directory"
 msgstr "Cr茅er un dossier"
 
-msgid "Create empty bare repository"
+msgid "Create empty repository"
 msgstr "Cr茅er un d茅p么t vide"
 
 msgid "Create epic"
@@ -1148,12 +1484,18 @@ msgstr "Cr茅er l'茅pop茅e"
 msgid "Create file"
 msgstr "Cr茅er un fichier"
 
+msgid "Create group label"
+msgstr "Cr茅er un label de groupe"
+
 msgid "Create lists from labels. Issues with that label appear in that list."
-msgstr ""
+msgstr "Cr茅er des listes 脿 partir de labels. Les tickets avec ce label apparaissent dans cette liste."
 
 msgid "Create merge request"
 msgstr "Cr茅er une demande de fusion"
 
+msgid "Create merge request and branch"
+msgstr "Cr茅er une demande de fusion et une branche"
+
 msgid "Create new branch"
 msgstr "Cr茅er une nouvelle branche"
 
@@ -1164,11 +1506,14 @@ msgid "Create new file"
 msgstr "Cr茅er un nouveau fichier"
 
 msgid "Create new label"
-msgstr ""
+msgstr "Cr茅er un nouveau label"
 
 msgid "Create new..."
 msgstr "Cr茅er nouveau..."
 
+msgid "Create project label"
+msgstr "Cr茅er un label de projet"
+
 msgid "CreateNewFork|Fork"
 msgstr "Fourcher"
 
@@ -1178,6 +1523,12 @@ msgstr "Tag"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "Cr茅er un jeton d'acc猫s personnel"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr "Cr茅e une nouvelle branche 脿 partir de %{branchName}"
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr "Cr茅e une nouvelle branche 脿 partir de %{branchName} et redirige pour cr茅er une nouvelle demande de fusion"
+
 msgid "Creating epic"
 msgstr "Cr茅ation de l'茅pop茅e en cours"
 
@@ -1188,7 +1539,7 @@ msgid "Cron syntax"
 msgstr "Syntaxe Cron"
 
 msgid "Current node"
-msgstr ""
+msgstr "N艙ud actuel"
 
 msgid "Custom notification events"
 msgstr "脡v茅nements de notification personnalis茅s"
@@ -1196,6 +1547,9 @@ msgstr "脡v茅nements de notification personnalis茅s"
 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 "Le niveau de notification Personnalis茅 est similaire au niveau Participation. Cependant, il permet 茅galement de recevoir des notifications pour des 茅v茅nements s茅lectionn茅s. Pour plus d鈥檌nformation, vous pouvez consulter %{notification_link}."
 
+msgid "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "Analyseur de cycle"
 
@@ -1232,6 +1586,9 @@ msgstr "D茅c."
 msgid "December"
 msgstr "D茅cembre"
 
+msgid "Default classification label"
+msgstr "Label de classement par d茅faut"
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "D茅finir un sch茅ma personnalis茅 avec une syntaxe Cron"
 
@@ -1256,19 +1613,19 @@ msgid "Details"
 msgstr "D茅tails"
 
 msgid "Diffs|No file name available"
-msgstr ""
+msgstr "Aucun nom de fichier disponible"
 
 msgid "Directory name"
 msgstr "Nom du dossier"
 
 msgid "Disable"
-msgstr ""
+msgstr "D茅sactiver"
 
-msgid "Discard changes"
-msgstr "Supprimer les modifications"
+msgid "Discard draft"
+msgstr "Supprimer le brouillon"
 
 msgid "Discover GitLab Geo."
-msgstr ""
+msgstr "D茅couvrez GitLab Geo."
 
 msgid "Dismiss Cycle Analytics introduction box"
 msgstr "Passer l鈥檌ntroduction Cycle Analytics"
@@ -1276,9 +1633,15 @@ msgstr "Passer l鈥檌ntroduction Cycle Analytics"
 msgid "Dismiss Merge Request promotion"
 msgstr "Rejeter la promotion de la demande de fusion"
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "Ne plus montrer"
 
+msgid "Done"
+msgstr "Fait"
+
 msgid "Download"
 msgstr "T茅l茅charger"
 
@@ -1306,7 +1669,13 @@ msgstr "Diff simple"
 msgid "DownloadSource|Download"
 msgstr "T茅l茅charger"
 
+msgid "Downvotes"
+msgstr "Votes n茅gatifs"
+
 msgid "Due date"
+msgstr "Date d鈥櫭ヽh茅ance"
+
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
 msgstr ""
 
 msgid "Edit"
@@ -1316,12 +1685,54 @@ msgid "Edit Pipeline Schedule %{id}"
 msgstr "脡diter le pipeline programm茅 %{id}"
 
 msgid "Edit files in the editor and commit changes here"
+msgstr "Modifier les fichiers dans l'茅diteur et valider les modifications ici"
+
+msgid "Editing"
+msgstr "En cours de modification"
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
 msgstr ""
 
 msgid "Emails"
 msgstr "Courriels"
 
 msgid "Enable"
+msgstr "Activer"
+
+msgid "Enable Auto DevOps"
+msgstr "Activer Auto DevOps"
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr "Activer et configurer les m茅triques InfluxDB."
+
+msgid "Enable and configure Prometheus metrics."
+msgstr "Activer et configurer les m茅triques Prometheus."
+
+msgid "Enable classification control using an external service"
+msgstr "Activer le contr么le de classification 脿 l鈥檃ide d鈥檜n service externe"
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
 msgstr ""
 
 msgid "Environments|An error occurred while fetching the environments."
@@ -1331,7 +1742,7 @@ msgid "Environments|An error occurred while making the request."
 msgstr "Une erreur s鈥檈st produite lors de la requ锚te."
 
 msgid "Environments|Commit"
-msgstr "Validation"
+msgstr "Commit"
 
 msgid "Environments|Deployment"
 msgstr "D茅ploiement"
@@ -1378,38 +1789,50 @@ msgstr "L鈥櫭﹑op茅e sera supprim茅e ! 脢tes-vous s没r鈥 ?"
 msgid "Epics"
 msgstr "脡pop茅es"
 
+msgid "Epics Roadmap"
+msgstr "Feuille de route des 茅pop茅es"
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr "Les 茅pop茅es vous permettent de g茅rer votre portefeuille de projets plus efficacement et avec moins d'effort"
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr "Erreur lors de la v茅rification des donn茅es de branche. Veuillez r茅essayer."
+
+msgid "Error committing changes. Please try again."
+msgstr "Erreur lors de la validation des modifications. Veuillez r茅essayer."
+
 msgid "Error creating epic"
 msgstr "Erreur lors de la cr茅ation de l鈥櫭﹑op茅e"
 
 msgid "Error fetching contributors data."
-msgstr ""
+msgstr "Erreur lors de l鈥檈xtraction des donn茅es des contributeur鈥ice鈥."
 
 msgid "Error fetching labels."
-msgstr ""
+msgstr "Erreur lors de la r茅cup茅ration des labels."
 
 msgid "Error fetching network graph."
-msgstr ""
+msgstr "Erreur lors de la r茅cup茅ration du graphique du r茅seau."
 
 msgid "Error fetching refs"
-msgstr ""
+msgstr "Erreur lors de la r茅cup茅ration des refs"
 
 msgid "Error fetching usage ping data."
-msgstr ""
+msgstr "Erreur lors de la r茅cup茅ration des donn茅es d鈥檜tilisation."
 
 msgid "Error occurred when toggling the notification subscription"
 msgstr "Une erreur s鈥檈st produite lors de l鈥檃ctivation/d茅sactivation de l鈥檃bonnement aux notifications"
 
 msgid "Error saving label update."
-msgstr ""
+msgstr "Erreur lors de la mise 脿 jour du label."
 
 msgid "Error updating status for all todos."
-msgstr ""
+msgstr "Erreur lors de la mise 脿 jour de l鈥櫭﹖at de la liste de t芒ches 脿 faire."
 
 msgid "Error updating todo status."
-msgstr ""
+msgstr "Erreur lors de la mise 脿 jour du statut de la t芒che 脿 faire."
 
 msgid "EventFilterBy|Filter by all"
 msgstr "Aucun filtre"
@@ -1439,7 +1862,7 @@ msgid "Every week (Sundays at 4:00am)"
 msgstr "Chaque semaine (dimanche 脿 4h00 du matin)"
 
 msgid "Expand"
-msgstr ""
+msgstr "Ouvrir"
 
 msgid "Explore projects"
 msgstr "Explorer les projets"
@@ -1447,12 +1870,45 @@ msgstr "Explorer les projets"
 msgid "Explore public groups"
 msgstr "Explorer les groupes publics"
 
+msgid "External Classification Policy Authorization"
+msgstr "Autorisation de politique de classification externe"
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr "L鈥檃utorisation externe a refus茅 l鈥檃cc猫s 脿 ce projet"
+
+msgid "External authorization request timeout"
+msgstr "Expiration de la demande d鈥檃utorisation externe"
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr "Label de classification"
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr "Label de classification"
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr "Quand aucun label de classification n鈥檈st d茅fini, le label par d茅faut `%{default_label}` sera utilis茅."
+
+msgid "Failed"
+msgstr "脡chec"
+
+msgid "Failed Jobs"
+msgstr "T芒ches ayant 茅chou茅"
+
 msgid "Failed to change the owner"
 msgstr "脡chec du changement de propri茅taire"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr "Impossible de supprimer le ticket du tableau, veuillez r茅essayer."
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "脡chec de la suppression du pipeline programm茅"
 
+msgid "Failed to update issues, please try again."
+msgstr "脡chec de la mise 脿 jour du ticket. Veuillez r茅essayer."
+
 msgid "Feb"
 msgstr "F茅vr."
 
@@ -1460,7 +1916,7 @@ msgid "February"
 msgstr "F茅vrier"
 
 msgid "Fields on this page are now uneditable, you can configure"
-msgstr ""
+msgstr "Les champs sur cette page sont d茅sormais non modifiables, vous pouvez configurer"
 
 msgid "File name"
 msgstr "Nom du fichier"
@@ -1468,8 +1924,14 @@ msgstr "Nom du fichier"
 msgid "Files"
 msgstr "Fichiers"
 
+msgid "Files (%{human_size})"
+msgstr "Fichiers (%{human_size})"
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
-msgstr "Filtrer par message de validation"
+msgstr "Filtrer par message de commit"
 
 msgid "Find by path"
 msgstr "Rechercher par chemin"
@@ -1477,12 +1939,21 @@ msgstr "Rechercher par chemin"
 msgid "Find file"
 msgstr "Rechercher un fichier"
 
+msgid "Finished"
+msgstr "Termin茅"
+
 msgid "FirstPushedBy|First"
 msgstr "En premier"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "pouss茅 par"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "Fourche"
@@ -1494,125 +1965,179 @@ msgstr "Fourch茅 depuis"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr "Fourch茅 depuis %{project_name} (supprim茅)"
 
+msgid "Forking in progress"
+msgstr "Fourchage en cours"
+
 msgid "Format"
 msgstr "Format"
 
+msgid "From %{provider_title}"
+msgstr "De %{provider_title}"
+
 msgid "From issue creation until deploy to production"
 msgstr "Depuis la cr茅ation du ticket jusqu'au d茅ploiement en production"
 
 msgid "From merge request merge until deploy to production"
 msgstr "Depuis la fusion de la demande de fusion jusqu'au d茅ploiement en production"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr "脌 partir de l鈥檃ffichage des d茅tails du cluster Kubernetes, installez un Ex茅cuteur 脿 partir de la liste des applications"
+
 msgid "GPG Keys"
 msgstr "Cl茅s GPG"
 
 msgid "Generate a default set of labels"
-msgstr ""
+msgstr "G茅n茅rer un ensemble de labels par d茅faut"
 
 msgid "Geo Nodes"
 msgstr "N艙uds Geo"
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr "Le n艙ud est d茅faillant ou cass茅."
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr "Le n艙ud est lent, surcharg茅, ou il vient juste de r茅cup茅rer apr猫s un probl猫me."
 
+msgid "GeoNodes|Checksummed"
+msgstr "V茅rifi茅 par somme de contr么le"
+
 msgid "GeoNodes|Database replication lag:"
-msgstr ""
+msgstr "Retard de r茅plication de la base de donn茅es :"
 
 msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
+msgstr "La d茅sactivation d鈥檜n n艙ud arr锚te le processus de synchronisation. 脢tes-vous s没r鈥?"
 
 msgid "GeoNodes|Does not match the primary storage configuration"
-msgstr ""
+msgstr "Ne correspond pas 脿 la configuration de stockage principale"
 
 msgid "GeoNodes|Failed"
-msgstr ""
+msgstr "脡chec"
 
 msgid "GeoNodes|Full"
-msgstr ""
+msgstr "Complet"
 
 msgid "GeoNodes|GitLab version does not match the primary node version"
-msgstr ""
+msgstr "La version de GitLab ne correspond pas 脿 la version du n艙ud principal"
 
 msgid "GeoNodes|GitLab version:"
-msgstr ""
+msgstr "Version de GitLab :"
 
 msgid "GeoNodes|Health status:"
-msgstr ""
+msgstr "脡tat :"
 
 msgid "GeoNodes|Last event ID processed by cursor:"
-msgstr ""
+msgstr "Dernier ID d鈥櫭﹙茅nement trait茅 par le curseur :"
 
 msgid "GeoNodes|Last event ID seen from primary:"
-msgstr ""
+msgstr "Dernier 茅v茅nement vu du n艙ud primaire :"
 
 msgid "GeoNodes|Loading nodes"
-msgstr ""
+msgstr "Chargement des n艙uds"
 
 msgid "GeoNodes|Local Attachments:"
-msgstr ""
+msgstr "Pi猫ces jointes locales :"
 
 msgid "GeoNodes|Local LFS objects:"
-msgstr ""
+msgstr "Objets LFS locaux :"
 
 msgid "GeoNodes|Local job artifacts:"
-msgstr ""
+msgstr "Artefacts de t芒ches locaux :"
 
 msgid "GeoNodes|New node"
-msgstr ""
+msgstr "Nouveau n艙ud"
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr "Le n艙ud d'Authentification a 茅t茅 r茅par茅 avec succ猫s."
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr "Le n艙ud a 茅t茅 supprim茅 avec succ猫s."
+
+msgid "GeoNodes|Not checksummed"
+msgstr "Non v茅rifi茅 par somme de contr么le"
 
 msgid "GeoNodes|Out of sync"
-msgstr ""
+msgstr "D茅synchronis茅"
+
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr "Supprimer un n艙ud arr锚te le processus de synchronisation. 脢tes-vous s没r鈥 ?"
 
 msgid "GeoNodes|Replication slot WAL:"
-msgstr ""
+msgstr "Emplacement de r茅plication WAL :"
 
 msgid "GeoNodes|Replication slots:"
-msgstr ""
+msgstr "Emplacements de r茅plication :"
+
+msgid "GeoNodes|Repositories checksummed:"
+msgstr "D茅p么ts avec une somme de contr么le calcul茅e:"
 
 msgid "GeoNodes|Repositories:"
-msgstr ""
+msgstr "D茅p么ts :"
+
+msgid "GeoNodes|Repository checksums verified:"
+msgstr "D茅p么ts avec une somme de contr么le v茅rifi茅e :"
 
 msgid "GeoNodes|Selective"
-msgstr ""
+msgstr "S茅lectif"
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr "Une erreur s鈥檈st produite lors du changement de statut du n艙ud"
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr "Une erreur s鈥檈st produite lors de la suppression du n艙ud"
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr "Une erreur s鈥檈st produite lors de la r茅paration du n艙ud"
 
 msgid "GeoNodes|Storage config:"
-msgstr ""
+msgstr "Configuration de stockage :"
 
 msgid "GeoNodes|Sync settings:"
-msgstr ""
+msgstr "Param猫tres de synchronisation :"
 
 msgid "GeoNodes|Synced"
-msgstr ""
+msgstr "Synchronis茅"
 
 msgid "GeoNodes|Unused slots"
-msgstr ""
+msgstr "Emplacements non utilis茅s"
+
+msgid "GeoNodes|Unverified"
+msgstr "Non v茅rifi茅"
 
 msgid "GeoNodes|Used slots"
-msgstr ""
+msgstr "Emplacements utilis茅s"
+
+msgid "GeoNodes|Verified"
+msgstr "V茅rifi茅"
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr "Wiki avec une somme de contr么le v茅rifi茅e :"
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr "Wiki avec une somme de contr么le calcul茅e :"
 
 msgid "GeoNodes|Wikis:"
-msgstr ""
+msgstr "Wikis :"
 
 msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
-msgstr ""
+msgstr "Vous avez configur茅 des n艙uds Geo en utilisant une connexion HTTP non s茅curis茅e. Nous recommandons l鈥檜tilisation de HTTPS."
 
 msgid "Geo|All projects"
-msgstr ""
+msgstr "Tous les projets"
 
 msgid "Geo|File sync capacity"
 msgstr "Capacit茅 de synchronisation de fichier"
 
 msgid "Geo|Groups to synchronize"
-msgstr ""
+msgstr "Groupes 脿 synchroniser"
 
 msgid "Geo|Projects in certain groups"
-msgstr ""
+msgstr "Projets dans certains groupes"
 
 msgid "Geo|Projects in certain storage shards"
-msgstr ""
+msgstr "Projets dans certains fragments de stockage"
 
 msgid "Geo|Repository sync capacity"
 msgstr "Capacit茅 de synchronisation du d茅p么t"
@@ -1621,23 +2146,44 @@ msgid "Geo|Select groups to replicate."
 msgstr "S茅lectionner les groupes 脿 r茅pliquer."
 
 msgid "Geo|Shards to synchronize"
-msgstr ""
+msgstr "Fragments 脿 synchroniser"
+
+msgid "Git repository URL"
+msgstr "URL du d茅p么t Git"
 
 msgid "Git revision"
-msgstr ""
+msgstr "R茅vision de Git"
 
 msgid "Git storage health information has been reset"
 msgstr "Les informations de sant茅 du stockage Git ont 茅t茅 r茅initialis茅es"
 
 msgid "Git version"
+msgstr "Version de Git"
+
+msgid "GitHub import"
+msgstr "Importation de GitHub"
+
+msgid "GitLab CI Linter has been moved"
+msgstr "GitLab CI Linter a 茅t茅 d茅plac茅"
+
+msgid "GitLab Geo"
 msgstr ""
 
 msgid "GitLab Runner section"
 msgstr "Section de l'Ex茅cuteur GitLab"
 
-msgid "Gitaly Servers"
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
 msgstr ""
 
+msgid "Gitaly Servers"
+msgstr "Serveurs Gitaly"
+
+msgid "Go back"
+msgstr "Retour en arri猫re"
+
 msgid "Go to your fork"
 msgstr "Aller 脿 votre fourche"
 
@@ -1648,7 +2194,25 @@ msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab ad
 msgstr "L鈥檃uthentification Google n鈥檈st pas %{link_to_documentation}. Demandez 脿 votre administrateur GitLab si vous souhaitez utiliser ce service."
 
 msgid "Got it!"
-msgstr ""
+msgstr "Compris !"
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr "Les 茅pop茅es vous permettent de g茅rer votre portefeuille de projets plus efficacement et avec moins d鈥檈ffort"
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr "脌 partir de %{dateWord}"
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr "Chargement de la feuille de route"
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr "Une erreur s鈥檈st produite lors de la r茅cup茅ration des 茅pop茅es"
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr "Pour afficher la feuille de route, ajoutez une date de d茅but ou de fin planifi茅e 脿 l'une de vos 茅pop茅es dans ce groupe ou ses sous-groupes. Seules les 茅pop茅es des 3 derniers mois et des 3 prochains mois sont affich茅es &ndash; de %{startDate} 脿 %{endDate}."
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr "Jusqu鈥檃u %{dateWord}"
 
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr "Emp锚cher le partage d'un projet de %{group} avec d'autres groupes"
@@ -1686,9 +2250,6 @@ msgstr "Aucun groupe trouv茅"
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr "Vous pouvez g茅rer les autorisations des membres de votre groupe et acc茅der 脿 chacun de ses projets."
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr "Cr茅ez un projet dans ce groupe."
 
@@ -1719,6 +2280,9 @@ msgstr "D茅sol茅, aucun groupe ni projet ne correspond 脿 vos crit猫res de reche
 msgid "Have your users email"
 msgstr "Lister les emails utilisateurs"
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "脡tat des services"
 
@@ -1737,10 +2301,19 @@ msgstr "Aucun probl猫me d茅tect茅"
 msgid "HealthCheck|Unhealthy"
 msgstr "En mauvaise sant茅"
 
+msgid "Help"
+msgstr "Aide"
+
+msgid "Help page"
+msgstr "Page d鈥檃ide"
+
+msgid "Help page text and support page url."
+msgstr "Texte de la page d鈥檃ide et URL de la page de support."
+
 msgid "Hide value"
 msgid_plural "Hide values"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Masquer la valeur"
+msgstr[1] "Masquer les valeurs"
 
 msgid "History"
 msgstr "Historique"
@@ -1748,9 +2321,39 @@ msgstr "Historique"
 msgid "Housekeeping successfully started"
 msgstr "Maintenance d茅marr茅e avec succ猫s"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr "Si activ茅, l鈥檃cc猫s aux projets sera valid茅 sur un service externe en utilisant leur 茅tiquette de classification."
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr "Si vous utilisez GitHub, vous verrez les statuts des pipelines sur GitHub pour vos commit et demandes de fusion. %{more_info_link}"
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr "Si vous avez d茅j脿 des fichiers, vous pouvez les pousser 脿 l鈥檃ide de la %{link_to_cli} ci-dessous."
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr "Si votre d茅p么t HTTP n鈥檈st pas accessible publiquement, ajoutez des informations d鈥檃uthentification 脿 l鈥橴RL: <code>https: //utilisateur:mot_de_passe@gitlab.company.com/group/project.git</code>."
+
+msgid "Import"
+msgstr "Importer"
+
+msgid "Import all repositories"
+msgstr "Importer tous les d茅p么ts"
+
+msgid "Import in progress"
+msgstr "Importation en cours"
+
+msgid "Import repositories from GitHub"
+msgstr "Importer des d茅p么ts 脿 partir de GitHub"
+
 msgid "Import repository"
 msgstr "Importer un d茅p么t"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr "Connecter des d茅p么ts 脿 partir de"
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr "Am茅liorer les tableaux de tickets avec Gitlab Entreprise Edition."
 
@@ -1760,6 +2363,9 @@ msgstr "Am茅liorer la gestion des tickets avec les poids de ticket et GitLab Ent
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr "Am茅liorer la recherche avec la Recherche Globale Avanc茅e et GitLab Enterprise Edition."
 
+msgid "Install Runner on Kubernetes"
+msgstr "Installez un Ex茅cuteur sur Kubernetes"
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "Installez un Ex茅cuteur compatible avec l'int茅gration continue de GitLab"
 
@@ -1769,10 +2375,13 @@ msgstr[0] "Instance"
 msgstr[1] "Instances"
 
 msgid "Instance does not support multiple Kubernetes clusters"
-msgstr ""
+msgstr "L鈥檌nstance ne prend pas en charge plusieurs clusters Kubernetes"
+
+msgid "Integrations"
+msgstr "Int茅grations"
 
 msgid "Interested parties can even contribute by pushing commits if they want to."
-msgstr ""
+msgstr "Les parties int茅ress茅es peuvent m锚me contribuer en poussant des commits si elles le souhaitent."
 
 msgid "Internal - The group and any internal projects can be viewed by any logged in user."
 msgstr "Interne - Le groupe ainsi que tous les projets internes sont accessibles pour n鈥檌mporte quel路le utilisa路teur路trice connect茅路e."
@@ -1802,7 +2411,7 @@ msgid "Issues"
 msgstr "Tickets"
 
 msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
-msgstr ""
+msgstr "Les tickets peuvent 锚tre des bugs, des t芒ches ou des id茅es 脿 discuter. De plus, les tickets sont consultables et filtrables."
 
 msgid "Jan"
 msgstr "Janv."
@@ -1810,6 +2419,9 @@ msgstr "Janv."
 msgid "January"
 msgstr "Janvier"
 
+msgid "Jobs"
+msgstr "T芒ches"
+
 msgid "Jul"
 msgstr "Juill."
 
@@ -1822,26 +2434,32 @@ msgstr "Juin"
 msgid "June"
 msgstr "Juin"
 
-msgid "Kubernetes"
+msgid "Koding"
 msgstr ""
 
+msgid "Kubernetes"
+msgstr "Kubernetes"
+
 msgid "Kubernetes Cluster"
-msgstr ""
+msgstr "Cluster Kubernetes"
 
 msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
-msgstr ""
+msgstr "Le temps de cr茅ation du cluster Kubernetes d茅passe le d茅lai d鈥檈xpiration ; %{timeout}"
 
 msgid "Kubernetes cluster integration was not removed."
-msgstr ""
+msgstr "L鈥檌nt茅gration du cluster Kubernetes n鈥檃 pas 茅t茅 supprim茅e."
 
 msgid "Kubernetes cluster integration was successfully removed."
-msgstr ""
+msgstr "L鈥檌nt茅gration du cluster Kubernetes a 茅t茅 supprim茅e avec succ猫s."
 
 msgid "Kubernetes cluster was successfully updated."
-msgstr ""
+msgstr "Le cluster Kubernetes a 茅t茅 mis 脿 jour avec succ猫s."
+
+msgid "Kubernetes configured"
+msgstr "Kubernetes configur茅"
 
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
-msgstr ""
+msgstr "L鈥檌nt茅gration du service Kubernetes est obsol猫te. %{deprecated_message_content} vos clusters Kubernetes en utilisant la nouvelle page <a href=\"%{url}\"/>Clusters Kubernetes</a>"
 
 msgid "LFSStatus|Disabled"
 msgstr "D茅sactiv茅"
@@ -1849,11 +2467,29 @@ msgstr "D茅sactiv茅"
 msgid "LFSStatus|Enabled"
 msgstr "Activ茅"
 
+msgid "Label"
+msgstr "Label"
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr "%{firstLabelName} +%{remainingLabelCount} de plus"
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr "%{labelsString} et %{remainingLabelCount} de plus"
+
 msgid "Labels"
 msgstr "Labels"
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr "Les labels peuvent 锚tre appliqu茅s 脿 %{features}. Des labels de groupe sont disponibles pour tout type de projet au sein du groupe."
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
-msgstr ""
+msgstr "Les labels peuvent 锚tre appliqu茅s aux tickets et aux demandes de fusion pour les classer."
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr "<span>Promouvoir le label</span> %{labelTitle} <span>en label de groupe ?</span>"
+
+msgid "Labels|Promote Label"
+msgstr "Promouvoir le label"
 
 msgid "Last %d day"
 msgid_plural "Last %d days"
@@ -1864,7 +2500,7 @@ msgid "Last Pipeline"
 msgstr "Dernier pipeline"
 
 msgid "Last commit"
-msgstr "Derni猫re validation"
+msgstr "Dernier commit"
 
 msgid "Last edited %{date}"
 msgstr "Derni猫re modification le %{date}"
@@ -1885,7 +2521,13 @@ msgid "LastPushEvent|at"
 msgstr "脿"
 
 msgid "Learn more"
-msgstr ""
+msgstr "En savoir plus"
+
+msgid "Learn more about Kubernetes"
+msgstr "En savoir plus sur Kubernetes"
+
+msgid "Learn more about protected branches"
+msgstr "En savoir plus sur les branches prot茅g茅es"
 
 msgid "Learn more in the"
 msgstr "En apprendre plus dans le"
@@ -1905,17 +2547,23 @@ msgstr "Quitter le projet"
 msgid "License"
 msgstr "Licence"
 
+msgid "List"
+msgstr "Liste"
+
+msgid "List your GitHub repositories"
+msgstr "Lister vos d茅p么ts GitHub"
+
 msgid "Loading the GitLab IDE..."
-msgstr ""
+msgstr "Chargement de l鈥橧DE GitLab鈥�"
 
 msgid "Lock"
 msgstr "Verrouiller"
 
 msgid "Lock %{issuableDisplayName}"
-msgstr ""
+msgstr "Verrouiller %{issuableDisplayName}"
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
-msgstr ""
+msgid "Lock not found"
+msgstr "Verrou non trouv茅"
 
 msgid "Locked"
 msgstr "Verrouill茅"
@@ -1923,13 +2571,28 @@ msgstr "Verrouill茅"
 msgid "Locked Files"
 msgstr "Fichiers verrouill茅s"
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr "Les verrous donnent la possibilit茅 de verrouiller un fichier ou un dossier sp茅cifique."
+
 msgid "Login"
 msgstr "Se connecter"
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
-msgstr ""
+msgstr "Rendez votre 茅quipe plus productive quel que soit son emplacement. GitLab Geo cr茅e des miroirs en lecture seule de votre instance GitLab afin que vous puissiez r茅duire le temps n茅cessaire pour cloner et r茅cup茅rer de gros d茅p么ts."
+
+msgid "Manage all notifications"
+msgstr "G茅rer toutes les notifications"
+
+msgid "Manage group labels"
+msgstr "G茅rer les labels de groupe"
 
 msgid "Manage labels"
+msgstr "G茅rer les labels"
+
+msgid "Manage project labels"
+msgstr "G茅rer les labels de projet"
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
 msgstr ""
 
 msgid "Mar"
@@ -1939,7 +2602,7 @@ msgid "March"
 msgstr "Mars"
 
 msgid "Mark done"
-msgstr ""
+msgstr "Marquer comme fait"
 
 msgid "Maximum git storage failures"
 msgstr "Nombre maximum d鈥櫭ヽhecs du stockage git"
@@ -1953,7 +2616,7 @@ msgstr "M茅dian"
 msgid "Members"
 msgstr "Membres"
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1966,49 +2629,145 @@ msgid "Merge request"
 msgstr "Demande de fusion"
 
 msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
-msgstr ""
+msgstr "Les demandes de fusion permettent de proposer les modifications que vous avez apport茅es 脿 un projet et de discuter de ces modifications avec les autres"
 
 msgid "Merged"
-msgstr ""
+msgstr "Fusionn茅e"
 
 msgid "Messages"
 msgstr "Messages"
 
+msgid "Metrics - Influx"
+msgstr "M茅triques - Influx"
+
+msgid "Metrics - Prometheus"
+msgstr "M茅triques - Prometheus"
+
+msgid "Metrics|Business"
+msgstr "Affaires"
+
+msgid "Metrics|Create metric"
+msgstr "Cr茅er une m茅trique"
+
+msgid "Metrics|Edit metric"
+msgstr "Modifier la m茅trique"
+
+msgid "Metrics|For grouping similar metrics"
+msgstr "Pour regrouper des m茅triques similaires"
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr "Etiquette de l鈥檃xe vertical du graphique. Habituellement, le type de l鈥檜nit茅 茅tant cartographi茅e. L鈥檃xe horizontal (axe X) repr茅sente toujours le temps."
+
+msgid "Metrics|Legend label (optional)"
+msgstr "L茅gende de l鈥櫭﹖iquette (facultatif)"
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr "Doit 锚tre une requ锚te PromQL valide."
+
+msgid "Metrics|Name"
+msgstr "Nom"
+
+msgid "Metrics|New metric"
+msgstr "Nouvelle m茅trique"
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr "Documentation de requ锚te Prometheus"
+
+msgid "Metrics|Query"
+msgstr "Requ锚te"
+
+msgid "Metrics|Response"
+msgstr "R茅ponse"
+
+msgid "Metrics|System"
+msgstr "Syst猫me"
+
+msgid "Metrics|Type"
+msgstr "Type"
+
+msgid "Metrics|Unit label"
+msgstr "脡tiquette de l鈥檜nit茅"
+
+msgid "Metrics|Used as a title for the chart"
+msgstr "Utilis茅 comme titre pour le graphique"
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr "Utilis茅 si la requ锚te renvoie une seule s茅rie. Si elle renvoie plusieurs s茅ries, leurs 茅tiquettes de l茅gende seront r茅cup茅r茅es 脿 partir de la r茅ponse."
+
+msgid "Metrics|Y-axis label"
+msgstr "脡tiquette de l鈥檃xe Y"
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr "Par exemple, requ锚tes HTTP"
+
+msgid "Metrics|e.g. Requests/second"
+msgstr "Par exemple, requ锚tes / seconde"
+
+msgid "Metrics|e.g. Throughput"
+msgstr "Par exemple, d茅bit"
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr "Par exemple, rate(http_requests_total[5m])"
+
+msgid "Metrics|e.g. req/sec"
+msgstr "Par exemple, req/sec"
+
 msgid "Milestone"
-msgstr ""
+msgstr "Jalon"
 
 msgid "Milestones|Delete milestone"
-msgstr ""
+msgstr "Supprimer le jalon"
 
 msgid "Milestones|Delete milestone %{milestoneTitle}?"
-msgstr ""
+msgstr "Supprimer le jalon %{milestoneTitle}聽?"
 
 msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
-msgstr ""
+msgstr "Impossible de supprimer le jalon %{milestoneTitle}"
 
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
-msgstr ""
+msgstr "Le jalon %{milestoneTitle} est introuvable"
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr "Promouvoir %{milestoneTitle} 脿 un jalon de groupe ?"
+
+msgid "Milestones|Promote Milestone"
+msgstr "Promouvoir le jalon"
+
+msgid "Milestones|This action cannot be reversed."
+msgstr "Cette action ne peut pas 锚tre annul茅e."
 
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "ajouter une cl茅 SSH"
 
+msgid "Modal|Cancel"
+msgstr "Annuler"
+
+msgid "Modal|Close"
+msgstr "Fermer"
+
 msgid "Monitoring"
 msgstr "Surveillance"
 
+msgid "More info"
+msgstr "En savoir plus"
+
+msgid "More information"
+msgstr "Plus d鈥檌nformations"
+
 msgid "More information is available|here"
 msgstr "ici"
 
 msgid "Move"
-msgstr ""
+msgstr "D茅placer"
 
 msgid "Move issue"
-msgstr ""
+msgstr "D茅placer le ticket"
 
 msgid "Multiple issue boards"
 msgstr "Multiple tableaux de tickets"
 
 msgid "Name new label"
-msgstr ""
+msgstr "Nommez le nouveau label"
 
 msgid "New Issue"
 msgid_plural "New Issues"
@@ -2016,10 +2775,10 @@ msgstr[0] "Nouveau ticket"
 msgstr[1] "Nouveaux tickets"
 
 msgid "New Kubernetes Cluster"
-msgstr ""
+msgstr "Nouveau cluster Kubernetes"
 
 msgid "New Kubernetes cluster"
-msgstr ""
+msgstr "Nouveau cluster Kubernetes"
 
 msgid "New Pipeline Schedule"
 msgstr "Nouveau pipeline programm茅"
@@ -2046,7 +2805,7 @@ msgid "New issue"
 msgstr "Nouveau ticket"
 
 msgid "New label"
-msgstr ""
+msgstr "Nouveau label"
 
 msgid "New merge request"
 msgstr "Nouvelle demande de fusion"
@@ -2066,23 +2825,29 @@ msgstr "Nouveau sous-groupe"
 msgid "New tag"
 msgstr "Nouveau tag"
 
+msgid "No Label"
+msgstr "Pas de label"
+
 msgid "No assignee"
-msgstr ""
+msgstr "Aucune personne assign茅e"
 
 msgid "No changes"
-msgstr ""
+msgstr "Aucun changement"
 
 msgid "No connection could be made to a Gitaly Server, please check your logs!"
-msgstr ""
+msgstr "Aucune connexion n鈥檃 pu 锚tre 茅tablie avec un serveur Gitaly, veuillez v茅rifier vos logs !"
 
 msgid "No due date"
-msgstr ""
+msgstr "Aucune date d鈥櫭ヽh茅ance"
 
 msgid "No estimate or time spent"
-msgstr ""
+msgstr "Aucune estimation ou temps pass茅"
 
 msgid "No file chosen"
-msgstr ""
+msgstr "Aucun fichier s茅lectionn茅"
+
+msgid "No labels created yet."
+msgstr "Aucun label cr茅茅 pour le moment."
 
 msgid "No repository"
 msgstr "Pas de d茅p么t"
@@ -2090,24 +2855,42 @@ msgstr "Pas de d茅p么t"
 msgid "No schedules"
 msgstr "Aucun programme"
 
-msgid "No time spent"
-msgstr "Pas de temps pass茅"
-
 msgid "None"
 msgstr "Aucun路e"
 
 msgid "Not allowed to merge"
-msgstr ""
+msgstr "Non autoris茅鈥 脿 fusionner"
 
 msgid "Not available"
 msgstr "Indisponible"
 
+msgid "Not available for private projects"
+msgstr "Non disponible pour les projets priv茅s"
+
+msgid "Not available for protected branches"
+msgstr "Non disponible pour les branches prot茅g茅es"
+
 msgid "Not confidential"
-msgstr ""
+msgstr "Pas confidentiel鈥e"
 
 msgid "Not enough data"
 msgstr "Donn茅es insuffisantes"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr "Notez que la branche principale est automatiquement prot茅g茅e. %{link_to_protected_branches}"
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr "Remarque鈥�: En tant qu鈥檃dministrateur鈥ice, vous pouvez configurer %{github_integration_link}, ce qui vous permettra de vous connecter via GitHub et de permettre la connexion de d茅p么ts sans g茅n茅rer de jeton d鈥檃cc猫s personnel."
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr "Remarque鈥�: En tant qu鈥檃dministrateur鈥ice, vous pouvez configurer %{github_integration_link}, ce qui vous permettra de vous connecter via GitHub et de permettre l鈥檌mportation de d茅p么ts sans g茅n茅rer de jeton d鈥檃cc猫s personnel."
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr "Remarque : Envisagez de demander 脿 votre administrateur鈥ice GitLab de configurer %{github_integration_link}, ce qui vous permettra de vous connecter via GitHub et de permettre la connexion des d茅p么ts sans g茅n茅rer de jeton d鈥檃cc猫s personnel."
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr "Remarque : Envisagez de demander 脿 votre administrateur鈥ice GitLab de configurer %{github_integration_link}, ce qui vous permettra de vous connecter via GitHub et d鈥檌mporter des d茅p么ts sans g茅n茅rer de jeton d鈥檃cc猫s personnel."
+
 msgid "Notification events"
 msgstr "脡v茅nement de notifications"
 
@@ -2166,10 +2949,10 @@ msgid "Notifications"
 msgstr "Notifications"
 
 msgid "Notifications off"
-msgstr ""
+msgstr "Notifications d茅sactiv茅es"
 
 msgid "Notifications on"
-msgstr ""
+msgstr "Notifications activ茅es"
 
 msgid "Nov"
 msgstr "Nov."
@@ -2181,7 +2964,7 @@ msgid "Number of access attempts"
 msgstr "Nombre de tentatives d'acc猫s"
 
 msgid "OK"
-msgstr ""
+msgstr "OK"
 
 msgid "Oct"
 msgstr "Oct."
@@ -2192,11 +2975,17 @@ msgstr "Octobre"
 msgid "OfSearchInADropdown|Filter"
 msgstr "Filtre"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr "Une fois import茅s, les d茅p么ts peuvent 锚tre mis en miroir via SSH. Lire plus %{ssh_link}"
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr "Seuls les membres du projet peuvent commenter."
 
 msgid "Open"
-msgstr ""
+msgstr "Ouvrir"
 
 msgid "Opened"
 msgstr "Ouvert"
@@ -2210,12 +2999,21 @@ msgstr "Ouvrir dans une nouvelle fen锚tre"
 msgid "Options"
 msgstr "Param猫tres"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr "Sinon, il est recommand茅 de commencer avec l鈥檜ne des options ci-dessous."
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "Vue d'ensemble"
 
 msgid "Owner"
 msgstr "Propri茅taire"
 
+msgid "Pages"
+msgstr "Pages"
+
 msgid "Pagination|Last 禄"
 msgstr "Derni猫re聽禄"
 
@@ -2228,9 +3026,21 @@ msgstr "Pr茅c茅dente"
 msgid "Pagination|芦 First"
 msgstr "芦 Premi猫re"
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "Mot de Passe"
 
+msgid "Pending"
+msgstr "En attente"
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr "Jeton d鈥檃cc猫s personnel"
+
 msgid "Pipeline"
 msgstr "Pipeline"
 
@@ -2310,10 +3120,55 @@ msgid "Pipelines for last year"
 msgstr "Pipelines de l鈥檃nn茅e derni猫re"
 
 msgid "Pipelines|Build with confidence"
-msgstr ""
+msgstr "Construire en toute confiance"
+
+msgid "Pipelines|CI Lint"
+msgstr "CI Lint"
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr "Vider les caches des Ex茅cuteurs"
 
 msgid "Pipelines|Get started with Pipelines"
-msgstr ""
+msgstr "Premiers pas avec les Pipelines"
+
+msgid "Pipelines|Loading Pipelines"
+msgstr "Chargement des pipelines"
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr "R茅initialisation r茅ussie du cache de projet."
+
+msgid "Pipelines|Run Pipeline"
+msgstr "脡x茅cuter un pipeline"
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr "Une erreur s鈥檈st produite lors du nettoyage du cache des ex茅cuteurs."
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr "Il n鈥檡 a actuellement pas de pipelines %{scope}."
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr "Il n鈥檡 a actuellement pas de pipelines."
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr "Ce projet n鈥檈st actuellement pas configur茅 pour ex茅cuter des pipelines."
+
+msgid "Pipeline|Retry pipeline"
+msgstr "Relancer le pipeline"
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr "Relancer le pipeline #%{pipelineId} ?"
+
+msgid "Pipeline|Stop pipeline"
+msgstr "Arr锚ter le pipeline"
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr "Arr锚ter le pipeline #%{pipelineId} ?"
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr "Vous 锚tes sur le point de relancer le pipeline %{pipelineId}."
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr "Vous 锚tes sur le point d鈥檃rr锚ter le pipeline %{pipelineId}."
 
 msgid "Pipeline|all"
 msgstr "Tous"
@@ -2327,20 +3182,29 @@ msgstr "avec l'茅tape"
 msgid "Pipeline|with stages"
 msgstr "avec les 茅tapes"
 
-msgid "Play"
+msgid "PlantUML"
 msgstr ""
 
+msgid "Play"
+msgstr "Lancer"
+
 msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
-msgstr ""
+msgstr "Merci d鈥�<a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">activer la facturation pour un de vos projets afin d鈥櫭猼re en mesure de cr茅er un cluster Kubernetes</a>, puis essayez 脿 nouveau."
 
 msgid "Please solve the reCAPTCHA"
 msgstr "Veuillez r茅soudre le reCAPTCHA"
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr "Veuillez patienter pendant que nous nous connectons 脿 votre d茅p么t. Actualisez 脿 volont茅."
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr "Veuillez patienter pendant que nous importons le d茅p么t pour vous. Actualisez 脿 volont茅."
+
 msgid "Preferences"
 msgstr "Pr茅f茅rences"
 
 msgid "Primary"
-msgstr ""
+msgstr "Principal"
 
 msgid "Private - Project access must be granted explicitly to each user."
 msgstr "Priv茅 - L鈥檃cc猫s au projet doit 锚tre autoris茅 explicitement pour chaque utilisa路teur路trice."
@@ -2348,6 +3212,9 @@ msgstr "Priv茅 - L鈥檃cc猫s au projet doit 锚tre autoris茅 explicitement pour ch
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr "Priv茅 - Le groupe ainsi que ses projets ne sont accessibles qu鈥櫭� ses membres."
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr "Des projets priv茅s peuvent 锚tre cr茅茅s dans votre espace de noms personnel avec :"
+
 msgid "Profile"
 msgstr "Profil"
 
@@ -2387,9 +3254,12 @@ msgstr "Votre compte est actuellement propri茅taire des groupes suivants聽:"
 msgid "Profiles|your account"
 msgstr "votre compte"
 
-msgid "Programming languages used in this repository"
+msgid "Profiling - Performance bar"
 msgstr ""
 
+msgid "Programming languages used in this repository"
+msgstr "Langages de programmation utilis茅s dans ce d茅p么t"
+
 msgid "Project '%{project_name}' is in the process of being deleted."
 msgstr "Le projet 鈥�%{project_name}鈥� est en train d鈥櫭猼re supprim茅."
 
@@ -2406,13 +3276,10 @@ msgid "Project access must be granted explicitly to each user."
 msgstr "L鈥檃cc猫s au projet doit 锚tre explicitement accord茅 脿 chaque utilisateur."
 
 msgid "Project avatar"
-msgstr ""
+msgstr "Avatar du projet"
 
 msgid "Project avatar in repository: %{link}"
-msgstr ""
-
-msgid "Project cache successfully reset."
-msgstr ""
+msgstr "Avatar du project dans le d茅p么t : %{link}"
 
 msgid "Project details"
 msgstr "D茅tails du projet"
@@ -2433,19 +3300,19 @@ msgid "ProjectActivityRSS|Subscribe"
 msgstr "S鈥檃bonner"
 
 msgid "ProjectCreationLevel|Allowed to create projects"
-msgstr ""
+msgstr "Autoris茅鈥 脿 cr茅er des projets"
 
 msgid "ProjectCreationLevel|Default project creation protection"
-msgstr ""
+msgstr "Protection de cr茅ation de projet par d茅faut"
 
 msgid "ProjectCreationLevel|Developers + Masters"
-msgstr ""
+msgstr "D茅veloppeur鈥use鈥 + Expert鈥鈥"
 
 msgid "ProjectCreationLevel|Masters"
-msgstr ""
+msgstr "Expert鈥鈥"
 
 msgid "ProjectCreationLevel|No one"
-msgstr ""
+msgstr "Personne"
 
 msgid "ProjectFeature|Disabled"
 msgstr "D茅sactiv茅"
@@ -2472,7 +3339,7 @@ msgid "ProjectSettings|Contact an admin to change this setting."
 msgstr "Contactez un administrateur pour modifier ce param猫tre."
 
 msgid "ProjectSettings|Only signed commits can be pushed to this repository."
-msgstr "Seules les validations sign茅es peuvent 锚tre pouss茅es sur ce d茅p么t."
+msgstr "Seules les commits sign茅s peuvent 锚tre pouss茅s sur ce d茅p么t."
 
 msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
 msgstr "Ce param猫tre est appliqu茅 au niveau du serveur et peut 锚tre modifi茅 par un administrateur."
@@ -2484,7 +3351,7 @@ msgid "ProjectSettings|This setting will be applied to all projects unless overr
 msgstr "Ce param猫tre s鈥檃ppliquera 脿 tous les projets 脿 moins qu鈥檜n鈥 administrat鈥ur鈥ice ne le modifie."
 
 msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
-msgstr "Les utilisateurs peuvent uniquement pousser sur ce d茅p么t des validations qui ont 茅t茅 valid茅es avec une de leurs adresses courriels v茅rifi茅es."
+msgstr "Les utilisateurs peuvent uniquement pousser sur ce d茅p么t des commits qui ont 茅t茅 valid茅s avec une de leurs adresses courriels v茅rifi茅es."
 
 msgid "Projects"
 msgstr "Projets"
@@ -2510,41 +3377,92 @@ msgstr "D茅sol茅, aucun projet ne correspond 脿 votre recherche"
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr "Cette fonctionnalit茅 requiert le support du localStorage par votre navigateur"
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr "%{exporters} avec %{metrics} ont 茅t茅 trouv茅s"
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr "<p class=\"text-tertiary\">Aucune <a href=\"%{docsUrl}\">m茅trique commune</a> trouv茅e</p>"
+
+msgid "PrometheusService|Active"
+msgstr "Actif"
+
+msgid "PrometheusService|Auto configuration"
+msgstr "Configuration automatique"
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr "D茅ployer et configurer automatiquement Prometheus sur vos clusters pour surveiller les environnements de vos projets"
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr "Par d茅faut, Prometheus 茅coute sur 'http://localhost:9090'. Il n鈥檈st pas recommand茅 de changer l鈥檃dresse et le port par d茅faut car cela pourrait affecter ou entrer en conflit avec d'autres services fonctionnant sur le serveur GitLab."
 
+msgid "PrometheusService|Common metrics"
+msgstr "M茅triques communes"
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr "Les m茅triques communes sont automatiquement surveill茅es en fonction d鈥檜ne biblioth猫que de m茅triques provenant d鈥檈xportateurs populaires."
+
+msgid "PrometheusService|Custom metrics"
+msgstr "M茅triques personnalis茅s"
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr "Recherche et configuration des m茅triques en cours鈥�"
 
+msgid "PrometheusService|Finding custom metrics..."
+msgstr "Recherche de m茅triques personnalis茅es鈥�"
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr "Installer Prometheus sur les clusters"
+
+msgid "PrometheusService|Manage clusters"
+msgstr "G茅rer les clusters"
+
+msgid "PrometheusService|Manual configuration"
+msgstr "Configuration manuelle"
+
 msgid "PrometheusService|Metrics"
 msgstr "M茅triques"
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
-msgstr "Les m茅triques sont automatiquement configur茅es et surveill茅es en fonction d鈥檜ne biblioth猫que de m茅triques provenant d鈥檈xportateurs populaires."
-
 msgid "PrometheusService|Missing environment variable"
 msgstr "Variable d鈥檈nvironnement manquante"
 
-msgid "PrometheusService|Monitored"
-msgstr "Surveill茅"
-
 msgid "PrometheusService|More information"
 msgstr "Plus d鈥檌nformations"
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr "Aucune m茅trique n鈥檈st surveill茅e. Pour d茅marrer la surveillance, d茅ployez sur un environnement."
+msgid "PrometheusService|New metric"
+msgstr "Nouvelle m茅trique"
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr "URL de base de l鈥橝PI Prometheus, comme http://prometheus.example.com/"
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr "Prometheus est g茅r茅 automatiquement sur vos clusters"
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr "Ces m茅triques ne seront surveill茅s qu鈥檃pr猫s votre premier d茅ploiement dans un environnement"
+
 msgid "PrometheusService|Time-series monitoring service"
-msgstr ""
+msgstr "Service de surveillance de s茅ries temporelles"
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr "Pour activer la configuration manuelle, d茅sinstallez Prometheus de vos clusters"
 
-msgid "PrometheusService|View environments"
-msgstr "Afficher les environnements"
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr "Pour activer l鈥檌nstallation de Prometheus sur vos clusters, d茅sactivez la configuration manuelle ci-dessous"
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr "En attente de votre premier d茅ploiement dans un environnement pour trouver des m茅triques communes"
+
+msgid "Promote"
+msgstr "Promouvoir"
+
+msgid "Promote to Group Label"
+msgstr "Promouvoir en label de groupe"
+
+msgid "Promote to Group Milestone"
+msgstr "Promouvoir en jalon de groupe"
 
 msgid "Protip:"
-msgstr ""
+msgstr "Astuce :"
 
 msgid "Public - The group and any public projects can be viewed without any authentication."
 msgstr "Public - Le groupe ainsi que n鈥檌mporte quel projet public est accessible sans authentification."
@@ -2558,11 +3476,17 @@ msgstr "R猫gles de pouss茅e"
 msgid "Push events"
 msgstr "脡v猫nements de pouss茅e"
 
+msgid "Push project from command line"
+msgstr "Pousser le projet en ligne de commande"
+
+msgid "Push to create a project"
+msgstr "Pousser pour cr茅er un projet"
+
 msgid "PushRule|Committer restriction"
 msgstr "Restriction du validateur"
 
 msgid "Quick actions can be used in the issues description and comment boxes."
-msgstr ""
+msgstr "Les actions rapides peuvent 锚tre utilis茅es dans la description des tickets et dans les zones de commentaire."
 
 msgid "Read more"
 msgstr "Lire plus"
@@ -2570,6 +3494,9 @@ msgstr "Lire plus"
 msgid "Readme"
 msgstr "LisezMoi"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "Branches"
 
@@ -2577,16 +3504,16 @@ msgid "RefSwitcher|Tags"
 msgstr "Tags"
 
 msgid "Reference:"
-msgstr ""
+msgstr "R茅f茅rence :"
 
 msgid "Register / Sign In"
-msgstr ""
+msgstr "Inscription / Connexion"
 
 msgid "Registry"
 msgstr "Registre"
 
 msgid "Related Commits"
-msgstr "Validations li茅es"
+msgstr "Commits li茅s"
 
 msgid "Related Deployed Jobs"
 msgstr "T芒ches de d茅ploiement li茅es"
@@ -2603,24 +3530,42 @@ msgstr "Demandes de fusion li茅es"
 msgid "Related Merged Requests"
 msgstr "Demandes fusionn茅es li茅es"
 
+msgid "Related merge requests"
+msgstr "Demandes de fusion li茅es"
+
 msgid "Remind later"
 msgstr "Me le rappeler ult茅rieurement"
 
 msgid "Remove"
-msgstr ""
+msgstr "Supprimer"
 
 msgid "Remove avatar"
-msgstr ""
+msgstr "Supprimer l鈥檃vatar"
 
 msgid "Remove project"
 msgstr "Supprimer le projet"
 
 msgid "Repair authentication"
-msgstr ""
+msgstr "R茅parer l鈥檃uthentification"
+
+msgid "Repo by URL"
+msgstr "D茅p么t par URL"
 
 msgid "Repository"
 msgstr "D茅p么t"
 
+msgid "Repository has no locks."
+msgstr "Le d茅p么t n鈥檃 pas de verrous."
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "Demander l'acc猫s"
 
@@ -2633,17 +3578,53 @@ msgstr "R茅initialiser le jeton d鈥檃cc猫s au bilan de sant茅"
 msgid "Reset runners registration token"
 msgstr "R茅initialiser le jeton d鈥檌nscription des ex茅cuteurs"
 
+msgid "Resolve discussion"
+msgstr "R茅soudre la discussion"
+
+msgid "Response"
+msgstr "R茅ponse"
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "R茅v茅ler la valeur"
+msgstr[1] "R茅v茅ler les valeurs"
 
 msgid "Revert this commit"
-msgstr "D茅faire cette validation"
+msgstr "D茅faire ce commit"
 
 msgid "Revert this merge request"
 msgstr "D茅faire cette demande de fusion"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr "Examiner"
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr "Feuille de route"
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr "Ex茅cuter des pipelines CI / CD pour les d茅p么ts externes"
+
+msgid "Runners"
+msgstr "Ex茅cuteurs"
+
+msgid "Running"
+msgstr "En cours"
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "Cl茅s SSH"
 
@@ -2654,11 +3635,14 @@ msgid "Save pipeline schedule"
 msgstr "Sauvegarder le pipeline programm茅"
 
 msgid "Save variables"
-msgstr ""
+msgstr "Enregistrer les variables"
 
 msgid "Schedule a new pipeline"
 msgstr "Programmer un nouveau pipeline"
 
+msgid "Scheduled"
+msgstr "Planifi茅"
+
 msgid "Schedules"
 msgstr "Programmes"
 
@@ -2668,17 +3652,20 @@ msgstr "Programmer des pipelines"
 msgid "Scoped issue boards"
 msgstr "Tableaux de tickets avec port茅e limit茅e"
 
+msgid "Search"
+msgstr "Rechercher"
+
 msgid "Search branches and tags"
 msgstr "Rechercher dans les branches et les 茅tiquettes"
 
 msgid "Search milestones"
-msgstr ""
+msgstr "Recherche de jalons"
 
 msgid "Search project"
-msgstr ""
+msgstr "Recherche de projets"
 
 msgid "Search users"
-msgstr ""
+msgstr "Recherche d鈥檜tilisateur鈥ice鈥"
 
 msgid "Seconds before reseting failure information"
 msgstr "Nombre de secondes avant de r茅initialiser les informations d鈥櫭ヽhec"
@@ -2687,7 +3674,10 @@ msgid "Seconds to wait for a storage access attempt"
 msgstr "Nombre de secondes d鈥檃ttente pour un essai d'acc猫s au stockage"
 
 msgid "Secret variables"
-msgstr ""
+msgstr "Variables secr猫tes"
+
+msgid "Security report"
+msgstr "Rapport de s茅curit茅"
 
 msgid "Select Archive Format"
 msgstr "S茅lectionnez le format de l'archive"
@@ -2695,17 +3685,23 @@ msgstr "S茅lectionnez le format de l'archive"
 msgid "Select a timezone"
 msgstr "S茅lectionnez un fuseau horaire"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr "S茅lectionnez un cluster Kubernetes existant ou cr茅ez-en un nouveau"
+
 msgid "Select assignee"
-msgstr ""
+msgstr "S茅lectionner une personne assign茅e"
 
 msgid "Select branch/tag"
-msgstr ""
+msgstr "S茅lectionnez une branche / un tag"
 
 msgid "Select target branch"
 msgstr "S茅lectionnez une branche cible"
 
 msgid "Selective synchronization"
-msgstr ""
+msgstr "Synchronisation s茅lective"
+
+msgid "Send email"
+msgstr "Envoyer un courriel"
 
 msgid "Sep"
 msgstr "Sept."
@@ -2714,22 +3710,40 @@ msgid "September"
 msgstr "Septembre"
 
 msgid "Server version"
-msgstr ""
+msgstr "Version du serveur"
 
 msgid "Service Templates"
 msgstr "Mod猫les de service"
 
+msgid "Service URL"
+msgstr "URL du service"
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr "Expiration de la session, limite des projets et taille des pi猫ces jointes."
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "D茅finissez un mot de passe pour votre compte pour pouvoir tirer ou pousser par %{protocol}."
 
-msgid "Set up CI/CD"
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr "D茅finir les valeurs par d茅faut et limiter les niveaux de visibilit茅. Configurer les sources d鈥檌mportation et le protocole d鈥檃cc猫s git."
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
 msgstr ""
 
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr "D茅finir les exigences pour la connexion d鈥檜n utilisateur. Activer l鈥檃uthentification obligatoire 脿 deux facteurs."
+
+msgid "Set up CI/CD"
+msgstr "Configurer CI/CD"
+
 msgid "Set up Koding"
 msgstr "Mettre en place Koding"
 
-msgid "Set up auto deploy"
-msgstr "Mettre en place l鈥檃uto-d茅ploiement"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "d茅finir un mot de passe"
@@ -2737,14 +3751,23 @@ msgstr "d茅finir un mot de passe"
 msgid "Settings"
 msgstr "Param猫tres"
 
-msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgid "Setup a specific Runner automatically"
+msgstr "Configurer un Ex茅cuteur sp茅cifique automatiquement"
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
 msgstr ""
 
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr "En r茅initialisant les minutes du pipeline pour cet espace de noms, les minutes actuellement utilis茅es seront remises 脿 z茅ro."
+
 msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
-msgstr ""
+msgstr "R茅initialiser les minutes du pipeline"
 
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
-msgstr ""
+msgstr "R茅initialiser les minutes de pipeline utilis茅es"
+
+msgid "Show command"
+msgstr "Afficher la commande"
 
 msgid "Show parent pages"
 msgstr "Afficher les pages parentes"
@@ -2769,23 +3792,35 @@ msgstr "Aucun"
 msgid "Sidebar|Weight"
 msgstr "Poids"
 
+msgid "Sign-in restrictions"
+msgstr "Restrictions de connexion"
+
+msgid "Sign-up restrictions"
+msgstr "Restrictions d鈥檌nscription"
+
+msgid "Size and domain settings for static websites"
+msgstr "Param猫tres de taille et de domaine pour les sites web statiques"
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr "Extraits de code"
 
 msgid "Something went wrong on our end"
-msgstr ""
+msgstr "Une erreur est survenue de notre c么t茅"
 
 msgid "Something went wrong on our end."
 msgstr "Une erreur est survenue de notre c么t茅."
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
-msgstr ""
+msgid "Something went wrong when toggling the button"
+msgstr "Une erreur s鈥檈st produite lors du basculement du bouton"
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
-msgstr "Quelque chose ne s鈥榚st pas bien pass茅 en essayant de changer l鈥櫭﹖at de verrouillage de cette ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
+msgstr "Une erreur s鈥檈st produite lors de la recherche des d茅pendances."
 
-msgid "Something went wrong when toggling the button"
-msgstr ""
+msgid "Something went wrong while fetching SAST."
+msgstr "Une erreur s鈥檈st produite lors de la r茅cup茅ration de la SAST."
 
 msgid "Something went wrong while fetching the projects."
 msgstr "Une erreur s'est produite lors de la r茅cup茅ration des projets."
@@ -2794,7 +3829,7 @@ msgid "Something went wrong while fetching the registry list."
 msgstr "Une erreur s'est produite lors de la r茅cup茅ration de la liste du registre."
 
 msgid "Something went wrong. Please try again."
-msgstr ""
+msgstr "Quelque chose s鈥檈st mal pass茅. Veuillez r茅essayer."
 
 msgid "Sort by"
 msgstr "Trier par"
@@ -2898,6 +3933,9 @@ msgstr "Poids"
 msgid "Source"
 msgstr "Source"
 
+msgid "Source (branch or tag)"
+msgstr "Source (branche ou tag)"
+
 msgid "Source code"
 msgstr "Code source"
 
@@ -2907,12 +3945,21 @@ msgstr "La source n鈥檈st pas disponible"
 msgid "Spam Logs"
 msgstr "Journaux des messages ind茅sirables"
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "Sp茅cifiez l鈥橴RL suivante lors de la configuration de l'Ex茅cuteur聽:"
 
 msgid "StarProject|Star"
 msgstr "Mettre en favori"
 
+msgid "Starred Projects"
+msgstr "Projets favoris"
+
+msgid "Starred Projects' Activity"
+msgstr "Activit茅 des projets favoris"
+
 msgid "Starred projects"
 msgstr "Projets favoris"
 
@@ -2922,11 +3969,20 @@ msgstr "Cr茅er une %{new_merge_request} avec ces changements"
 msgid "Start the Runner!"
 msgstr "D茅marrer l'Ex茅cuteur !"
 
+msgid "Started"
+msgstr "D茅marr茅"
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr "脡tat"
+
 msgid "Stopped"
 msgstr "Arr锚t茅"
 
 msgid "Storage"
-msgstr ""
+msgstr "Stockage"
 
 msgid "Subgroups"
 msgstr "Sous-groupes"
@@ -2934,25 +3990,31 @@ msgstr "Sous-groupes"
 msgid "Switch branch/tag"
 msgstr "Changer de branche / tag"
 
+msgid "System"
+msgstr "Syst猫me"
+
 msgid "System Hooks"
 msgstr "Crochets syst猫me"
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "脡tiquette"
-msgstr[1] "Tags"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] "Tag (%{tag_count})"
+msgstr[1] "Tags (%{tag_count})"
 
 msgid "Tags"
 msgstr "Tags"
 
 msgid "TagsPage|Browse commits"
-msgstr "Parcourir les validations"
+msgstr "Parcourir les commits"
 
 msgid "TagsPage|Browse files"
 msgstr "Parcourir les fichiers"
 
 msgid "TagsPage|Can't find HEAD commit for this tag"
-msgstr "Impossible de trouver la validation HEAD pour ce tag"
+msgstr "Impossible de trouver le commit HEAD pour ce tag"
 
 msgid "TagsPage|Cancel"
 msgstr "Annuler"
@@ -2970,7 +4032,7 @@ msgid "TagsPage|Edit release notes"
 msgstr "Modifier les notes de version"
 
 msgid "TagsPage|Existing branch name, tag, or commit SHA"
-msgstr "Nom de branche, tag, ou SHA de validation existant"
+msgstr "Nom de branche, tag, ou SHA de commit existant"
 
 msgid "TagsPage|Filter by tag name"
 msgstr "Filtrer par nom de tag"
@@ -3017,6 +4079,9 @@ msgstr "prot茅g茅"
 msgid "Target Branch"
 msgstr "Branche cible"
 
+msgid "Target branch"
+msgstr "Branche cible"
+
 msgid "Team"
 msgstr "脡quipe"
 
@@ -3027,25 +4092,34 @@ msgid "The Advanced Global Search in GitLab is a powerful search service that sa
 msgstr "La Recherche Globale Avanc茅e de Gitlab est un outils puissant qui vous fait gagner du temps. Au lieu de cr茅er du code similaire et perdre du temps, vous pouvez maintenant chercher dans le code d'autres 茅quipes pour vous aider sur votre projet."
 
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
-msgstr ""
+msgstr "Le suivi des tickets est l鈥檈ndroit o霉 ajouter des 茅l茅ments 脿 am茅liorer ou 脿 r茅soudre dans un projet"
 
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
-msgstr ""
+msgstr "Le suivi des tickets est l鈥檈ndroit o霉 ajouter des 茅l茅ments 脿 am茅liorer ou 脿 r茅soudre dans un projet. Vous pouvez vous inscrire ou vous connecter pour cr茅er des tickets pour ce projet."
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr "Le certificat X509 脿 utiliser lorsque le protocole TLS est requis pour communiquer avec le service d鈥檃utorisation externe. Si ce champ est vide, le certificat du serveur est utilis茅 lors de l鈥檃cc猫s via HTTPS."
 
 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 "L鈥櫭﹖ape 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."
+msgstr "L鈥櫭﹖ape de d茅veloppement montre le temps entre le premier commit 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."
 
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "L鈥檈nsemble d鈥櫭﹙猫nements ajout茅s aux donn茅es r茅cup茅r茅es pour cette 茅tape."
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr "La connexion expirera apr猫s %{timeout}. Pour les d茅p么ts qui prennent plus de temps, utilisez une combinaison cloner / pousser."
+
 msgid "The fork relationship has been removed."
 msgstr "La relation de fourche a 茅t茅 supprim茅e."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr "L鈥檌mportation expirera apr猫s %{timeout}. Pour les d茅p么ts qui prennent plus de temps, utilisez une combinaison clone / push."
+
 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 "L'茅tape des tickets montre le temps n茅cessaire entre la cr茅ation d'un ticket et son assignation 脿 un jalon, ou son ajout 脿 une liste d'un tableau de tickets. Commencez par cr茅er des tickets pour voir des donn茅es pour cette 茅tape."
 
 msgid "The maximum file size allowed is 200KB."
-msgstr ""
+msgstr "La taille de fichier maximale autoris茅e est de 200 Ko."
 
 msgid "The number of attempts GitLab will make to access a storage."
 msgstr "Le nombre de tentatives que GitLab va effectuer pour acc茅der au stockage."
@@ -3053,11 +4127,17 @@ msgstr "Le nombre de tentatives que GitLab va effectuer pour acc茅der au stockag
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr "Nombre d鈥櫭ヽhecs avant que GitLab n鈥檈mp锚che tout acc猫s au stockage. Ce nombre d鈥櫭ヽhecs peut 锚tre r茅initialis茅 dans l鈥檌nterface d鈥檃dministration聽: %{link_to_health_page} ou en suivant le %{api_documentation_link}."
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr "La phrase secr猫te est requise pour d茅chiffrer la cl茅 priv茅e. Ceci est facultatif et la valeur est crypt茅e 脿 l鈥檃rr锚t."
+
 msgid "The phase of the development lifecycle."
 msgstr "Les 茅tapes du cycle de d茅veloppement."
 
 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 "L鈥櫭﹖ape de planification montre le temps entre l鈥櫭﹖ape pr茅c茅dente et l鈥檈nvoi de votre premi猫re validation. Ce temps sera automatiquement ajout茅 quand vous pousserez votre premi猫re validation."
+msgstr "L鈥櫭﹖ape de planification montre le temps entre l鈥櫭﹖ape pr茅c茅dente et l鈥檈nvoi de votre premier commit. Ce temps sera automatiquement ajout茅 quand vous pousserez votre premier commit."
+
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+msgstr "La cl茅 priv茅e 脿 utiliser lorsqu鈥檜n certificat client est fourni. Cette valeur est crypt茅e 脿 l鈥檃rr锚t."
 
 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 "L鈥櫭﹖ape de mise en production montre le temps n茅cessaire entre la cr茅ation d鈥檜n ticket 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鈥檌d茅e jusqu鈥櫭� la mise en production."
@@ -3071,9 +4151,18 @@ msgstr "Votre projet peut 锚tre acc茅d茅 sans aucune authentification."
 msgid "The repository for this project does not exist."
 msgstr "Le d茅p么t pour ce projet n'existe pas."
 
+msgid "The repository for this project is empty"
+msgstr "Le d茅p么t de ce projet est vide"
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr "Le d茅p么t doit 锚tre accessible via <code>http://</code>, <code>https://</code> ou <code>git://</code>."
+
 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 "L鈥櫭﹖ape d鈥櫭﹙aluation 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."
 
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr "La feuille de route montre la progression de vos 茅pop茅es dans le temps"
+
 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 "L鈥櫭﹖ape de pr茅-production indique le temps entre la fusion de la DF et le d茅ploiement du code dans l鈥檈nvironnent de production. Les donn茅es seront automatiquement ajout茅es une fois que vous d茅ploierez en production pour la premi猫re fois."
 
@@ -3087,7 +4176,7 @@ msgid "The time in seconds GitLab will try to access storage. After this time a
 msgstr "Temps en secondes pendant lequel GitLab essaiera d鈥檃cc茅der au stockage. Apr猫s ce d茅lai, une erreur d鈥檈xpiration d鈥檃ttente sera d茅clench茅e."
 
 msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
-msgstr ""
+msgstr "Le temps en secondes entre les v茅rifications de stockage. GitLab ne d茅bute pas de nouvelle v茅rification si une v茅rification est d茅j脿 en cours."
 
 msgid "The time taken by each data entry gathered by that stage."
 msgstr "Le temps pris par chaque entr茅e r茅colt茅e durant cette 茅tape."
@@ -3096,37 +4185,40 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
 msgstr "La valeur situ茅e au point m茅dian d鈥檜ne 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."
 
 msgid "There are no issues to show"
-msgstr ""
+msgstr "Il n鈥檡 a aucun ticket 脿 afficher"
 
 msgid "There are no merge requests to show"
-msgstr ""
+msgstr "Il n鈥檡 a aucune demande de fusion 脿 afficher"
 
 msgid "There are problems accessing Git storage: "
 msgstr "Il y a des difficult茅s 脿 acc茅der aux donn茅es Git聽: "
 
+msgid "There was an error loading results"
+msgstr "Une erreur s鈥檈st produite lors du chargement des r茅sultats"
+
 msgid "There was an error loading users activity calendar."
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors du chargement du calendrier d鈥檃ctivit茅 des utilisateurs."
 
 msgid "There was an error saving your notification settings."
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de l鈥檈nregistrement de vos param猫tres de notification."
 
 msgid "There was an error subscribing to this label."
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de l鈥檃bonnement 脿 ce label."
 
 msgid "There was an error when reseting email token."
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la r茅initialisation du jeton de courriel."
 
 msgid "There was an error when subscribing to this label."
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de l鈥檃bonnement 脿 ce label."
 
 msgid "There was an error when unsubscribing from this label."
-msgstr ""
+msgstr "Une erreur s鈥檈st produite lors de la d茅sinscription 脿 ce label."
 
 msgid "This board\\'s scope is reduced"
 msgstr "La port茅e de ce tableau est limit茅e"
 
 msgid "This directory"
-msgstr ""
+msgstr "Ce r茅pertoire"
 
 msgid "This is a confidential issue."
 msgstr "Ce ticket est confidentiel."
@@ -3135,7 +4227,7 @@ msgid "This is the author's first Merge Request to this project."
 msgstr "C鈥檈st la premi猫re demande de fusion de cet auteur pour ce projet."
 
 msgid "This issue is confidential"
-msgstr ""
+msgstr "Ce ticket est confidentiel"
 
 msgid "This issue is confidential and locked."
 msgstr "Ce ticket est confidentiel et verrouill茅."
@@ -3144,22 +4236,22 @@ msgid "This issue is locked."
 msgstr "Ce ticket est verrouill茅."
 
 msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
-msgstr ""
+msgstr "Cette t芒che d茅pend de l鈥檜tilisateur鈥ice pour d茅clencher son processus. Elles sont souvent utilis茅es pour d茅ployer du code dans des environnements de production"
 
 msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
-msgstr ""
+msgstr "Cette t芒che d茅pend des t芒ches en amont qui doivent r茅ussir pour que celle-ci soit d茅clench茅e"
 
 msgid "This job has not been triggered yet"
-msgstr ""
+msgstr "Cette t芒che n鈥檃 pas encore 茅t茅 d茅clench茅e"
 
 msgid "This job has not started yet"
-msgstr ""
+msgstr "Cete t芒che n鈥檃 pas encore commenc茅e"
 
 msgid "This job is in pending state and is waiting to be picked by a runner"
-msgstr ""
+msgstr "Cette t芒che est en attente et attend d鈥櫭猼re choisie par un ex茅cuteur"
 
 msgid "This job requires a manual action"
-msgstr ""
+msgstr "Cette t芒che n茅cessite une action manuelle"
 
 msgid "This means you can not push code until you create an empty repository or import existing one."
 msgstr "Cela signifie que vous ne pouvez pas pousser du code tant que vous n鈥檃vez pas cr茅茅 un d茅p么t vide, ou que vous n鈥檃vez pas import茅 un d茅p么t existant."
@@ -3167,11 +4259,17 @@ msgstr "Cela signifie que vous ne pouvez pas pousser du code tant que vous n鈥檃
 msgid "This merge request is locked."
 msgstr "Cette demande de fusion est verrouill茅e."
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr "Cette page n鈥檈st pas disponible car vous n鈥櫭猼es pas autoris茅 脿 lire des informations dans plusieurs projets."
+
 msgid "This project"
-msgstr ""
+msgstr "Ce projet"
 
 msgid "This repository"
-msgstr ""
+msgstr "Ce d茅p么t"
+
+msgid "This will delete the custom metric, Are you sure?"
+msgstr "Cela supprimera la m茅trique personnalis茅e, 锚tes-vous s没r鈥 ?"
 
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr "Ces emails deviennent automatiquement des tickets (les commentaires 茅tant extrait de la conversation par email) r茅pertori茅s ici."
@@ -3185,20 +4283,26 @@ msgstr "Temps avant que la r茅solution du ticket ne d茅bute"
 msgid "Time between merge request creation and merge/close"
 msgstr "Temps entre la cr茅ation d'une demande de fusion et sa fusion/cl么ture"
 
-msgid "Time tracking"
+msgid "Time between updates and capacity settings."
 msgstr ""
 
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr "Temps en secondes o霉 GitLab attendra une r茅ponse du service externe. Si le service ne r茅pond pas 脿 temps, l鈥檃cc猫s sera refus茅."
+
+msgid "Time tracking"
+msgstr "Suivi du temps"
+
 msgid "Time until first merge request"
 msgstr "Temps jusqu鈥櫭� la premi猫re demande de fusion"
 
 msgid "TimeTrackingEstimated|Est"
-msgstr ""
+msgstr "Est"
 
 msgid "TimeTracking|Estimated:"
-msgstr ""
+msgstr "Estim茅 :"
 
 msgid "TimeTracking|Spent"
-msgstr ""
+msgstr "Pass茅"
 
 msgid "Timeago|%s days ago"
 msgstr "il y a %s jours"
@@ -3336,29 +4440,65 @@ msgstr[1] "mins"
 msgid "Time|s"
 msgstr "s"
 
+msgid "Tip:"
+msgstr "Astuce :"
+
 msgid "Title"
 msgstr "Titre"
 
-msgid "Todo"
+msgid "To GitLab"
+msgstr "Vers GitLab"
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr "Pour connecter les d茅p么ts GitHub, vous pouvez utiliser un %{personal_access_token_link}. Lorsque vous cr茅ez votre jeton d鈥檃cc猫s, vous devrez s茅lectionner le champ <code>repo</code>, afin que nous puissions afficher une liste de vos d茅p么ts publics et priv茅s qui sont disponibles pour se connecter."
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr "Pour connecter les d茅p么ts GitHub, vous devez d鈥檃bord autoriser GitLab 脿 acc茅der 脿 la liste de vos d茅p么ts GitHub :"
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr "Pour connecter un d茅p么t SVN, consultez %{svn_link}."
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr "Pour importer les d茅p么ts GitHub, vous pouvez utiliser un %{personal_access_token_link}. Lorsque vous cr茅ez votre jeton d鈥檃cc猫s, vous devrez s茅lectionner le champ <code>repo</code>, afin que nous puissions afficher une liste de vos d茅p么ts publics et priv茅s qui sont disponibles pour 锚tre import茅s."
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr "Pour importer des d茅p么ts GitHub, vous devez d鈥檃bord autoriser GitLab 脿 acc茅der 脿 la liste de vos d茅p么ts GitHub :"
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr "Pour importer un d茅p么t SVN, consultez %{svn_link}."
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr "Pour utiliser uniquement les fonctionnalit茅s CI / CD pour un d茅p么t externe, choisissez <strong>CI / CD pour le d茅p么t externe</strong>."
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
 msgstr ""
 
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr "Pour valider vos configurations GitLab CI, allez dans 'CI / CD 鈫� Pipelines' dans votre projet, et cliquez sur le bouton 'CI Lint'."
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr "Pour afficher la feuille de route, ajoutez une date de d茅but ou de fin planifi茅e 脿 l鈥檜ne de vos 茅pop茅es dans ce groupe ou ses sous-groupes. Seules les 茅pop茅es des 3 derniers mois et des 3 prochains mois sont affich茅es."
+
+msgid "Todo"
+msgstr "T芒che"
+
 msgid "Toggle sidebar"
-msgstr ""
+msgstr "Afficher/masquer la barre lat茅rale"
 
 msgid "ToggleButton|Toggle Status: OFF"
-msgstr ""
+msgstr "脡tat du commutateur : Inactif"
 
 msgid "ToggleButton|Toggle Status: ON"
-msgstr ""
+msgstr "脡tat du commutateur : Actif"
 
 msgid "Total Time"
 msgstr "Temps total"
 
-msgid "Total issue time spent"
-msgstr "Temps total pass茅 sur les tickets"
-
 msgid "Total test time for all commits/merges"
-msgstr "Temps total de test pour toutes les validations/fusions"
+msgstr "Temps total de test pour tous les commits/fusions"
+
+msgid "Total: %{total}"
+msgstr "Total : %{total}"
 
 msgid "Track activity with Contribution Analytics."
 msgstr "Suivre l鈥檃ctivit茅 avec Contribution Analytics."
@@ -3366,41 +4506,32 @@ msgstr "Suivre l鈥檃ctivit茅 avec Contribution Analytics."
 msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr "Suivez les groupes de tickets qui partagent un th猫me, entre projets et jalons"
 
-msgid "Total: %{total}"
-msgstr ""
-
 msgid "Track time with quick actions"
-msgstr ""
+msgstr "Suivre le temps estim茅/pass茅 avec les actions rapides"
 
 msgid "Trigger this manual action"
-msgstr ""
+msgstr "D茅clencher cette action manuelle"
 
 msgid "Turn on Service Desk"
 msgstr "Activer le Service Desk"
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
-msgstr ""
+msgstr "Inconnu"
 
 msgid "Unlock"
 msgstr "D茅verrouiller"
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
-
 msgid "Unlocked"
 msgstr "D茅verrouill茅"
 
+msgid "Unresolve discussion"
+msgstr "Marquer la discussion comme non r茅solue"
+
 msgid "Unstar"
 msgstr "Supprimer des favoris"
 
 msgid "Up to date"
-msgstr ""
+msgstr "脌 jour"
 
 msgid "Upgrade your plan to activate Advanced Global Search."
 msgstr "Mettez 脿 jour votre abonnement pour activer la Recherche Globale Avanc茅e."
@@ -3424,11 +4555,17 @@ msgid "Upload file"
 msgstr "T茅l茅verser un fichier"
 
 msgid "Upload new avatar"
-msgstr ""
+msgstr "Importer un nouvel avatar"
 
 msgid "UploadLink|click to upload"
 msgstr "Cliquez pour envoyer"
 
+msgid "Upvotes"
+msgstr "Votes positifs"
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr "Utilisez Service Desk pour int茅ragir avec vos utilisateurs (par exemple pour offrir un support client) par email directement dans GitLab"
 
@@ -3438,21 +4575,51 @@ msgstr "Utiliser le jeton d鈥檌nscription suivant pendant l鈥檌nstallation聽:"
 msgid "Use your global notification setting"
 msgstr "Utiliser vos param猫tres de notification globaux"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
+msgstr "Les variables sont appliqu茅es aux environnements via l鈥檈x茅cuteur. Elles peuvent 锚tre prot茅g茅es en les exposant uniquement 脿 des branches ou des tags prot茅g茅es. Vous pouvez utiliser des variables pour les mots de passe, les cl茅s secr猫tes ou tout ce que vous voulez."
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
 msgstr ""
 
+msgid "View and edit lines"
+msgstr "Afficher et modifier les lignes"
+
+msgid "View epics list"
+msgstr "Afficher la liste des 茅pop茅es"
+
 msgid "View file @ "
 msgstr "Voir le fichier @ "
 
+msgid "View group labels"
+msgstr "Afficher les labels de groupe"
+
 msgid "View labels"
-msgstr ""
+msgstr "Afficher les labels"
 
 msgid "View open merge request"
 msgstr "Afficher la demande de fusion"
 
+msgid "View project labels"
+msgstr "Afficher les labels de projet"
+
 msgid "View replaced file @ "
 msgstr "Voir le fichier remplac茅 @ "
 
+msgid "Visibility and access controls"
+msgstr "Contr么les de visibilit茅 et d鈥檃cc猫s"
+
 msgid "VisibilityLevel|Internal"
 msgstr "Interne"
 
@@ -3469,7 +4636,7 @@ msgid "Want to see the data? Please ask an administrator for access."
 msgstr "Vous voulez voir les donn茅es ? Merci de contacter un administrateur pour en obtenir l鈥檃cc猫s."
 
 msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
-msgstr ""
+msgstr "Nous n鈥檃vons pas pu v茅rifier que l鈥檜n de vos projets sur GCP est activ茅 pour la facturation. Veuillez r茅essayer."
 
 msgid "We don't have enough data to show this stage."
 msgstr "Nous n'avons pas suffisamment de donn茅es pour afficher cette 茅tape."
@@ -3477,12 +4644,21 @@ msgstr "Nous n'avons pas suffisamment de donn茅es pour afficher cette 茅tape."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr "Nous voulons 锚tre s没rs que c'est bien vous, merci de confirmer que vous n鈥櫭猼es pas un robot."
 
+msgid "Web IDE"
+msgstr "Web IDE"
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr "Les webhooks vous permettent d鈥檃ppeler une URL si, par exemple, du nouveau code est pouss茅 ou un nouveau ticket est cr茅茅. Vous pouvez configurer les webhooks pour 茅couter les 茅v茅nements sp茅cifiques comme des pouss茅es de code, des tickets ou des demandes de fusion. Les webhooks de groupes s鈥檃ppliqueront 脿 tous les projets dans un groupe, ce qui vous permet de normaliser la fonctionnalit茅 du webhook dans votre groupe entier."
 
 msgid "Weight"
 msgstr "Poids"
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr "Lorsque vous laissez l鈥橴RL vide, les 茅tiquettes de classification peuvent toujours 锚tre sp茅cifi茅es sans d茅sactiver les fonctionnalit茅s inter-projets ou effectuer des v茅rifications d鈥檃utorisation externes."
+
 msgid "Wiki"
 msgstr "Wiki"
 
@@ -3502,10 +4678,10 @@ msgid "WikiClone|Start Gollum and edit locally"
 msgstr "D茅marrer Gollum et modifier localement"
 
 msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
-msgstr ""
+msgstr "Astuce : Vous pouvez d茅placer cette page en ajoutant le chemin au d茅but du titre."
 
 msgid "WikiEdit|There is already a page with the same title in that path."
-msgstr ""
+msgstr "Il y a d茅j脿 une page avec le m锚me titre pour ce chemin."
 
 msgid "WikiEmptyPageError|You are not allowed to create wiki pages"
 msgstr "Vous n鈥櫭猼es pas autoris茅路e 脿 cr茅er des pages wiki"
@@ -3597,32 +4773,44 @@ msgstr "Avec Contribution Analytics vous pouvez avoir un aper莽u de l鈥檃ctivit
 msgid "Withdraw Access Request"
 msgstr "Retirer la demande d'acc猫s"
 
+msgid "Write a commit message..."
+msgstr "脡crire un message de commit鈥�"
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "Vous 锚tes sur le point de supprimer %{group_name}. Les groupes supprim茅s NE PEUVENT PAS 锚tre restaur茅s ! 脢tes vous ABSOLUMENT s没r路e ?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "Vous 锚tes sur le point de supprimer %{project_name_with_namespace}. Les projets supprim茅s NE PEUVENT PAS 锚tre restaur茅s ! 脢tes vous ABSOLUMENT s没r路e ?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vous 锚tes sur le point de supprimer %{project_full_name}. Les projets supprim茅s NE PEUVENT PAS 锚tre restaur茅s ! 脢tes-vous ABSOLUMENT s没r鈥 ?"
 
 msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
 msgstr "Vous allez supprimer la relation de fourche avec le projet source %{forked_from_project}. 脢tes-vous ABSOLUMENT s没r路e聽?"
 
-msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
-msgstr "Vous allez transf茅rer %{project_name_with_namespace} 脿 un nouveau propri茅taire. 脢tes vous ABSOLUMENT s没r路e ?"
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr "Vous allez transf茅rer %{project_full_name} 脿 un鈥 nouveau鈥lle propri茅taire. 脢tes-vous VRAIMENT s没r鈥 ?"
+
+msgid "You are on a read-only GitLab instance."
+msgstr "Vous 锚tes sur une instance GitLab en lecture seule."
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr "Vous 锚tes sur un n艙ud Geo secondaire (en lecture seule). Si vous voulez apporter des modifications, vous devez visiter le %{primary_node}."
+
+msgid "You can also create a project from the command line."
+msgstr "Vous pouvez 茅galement cr茅er un projet en ligne de commande."
 
 msgid "You can also star a label to make it a priority label."
-msgstr ""
+msgstr "Vous pouvez marquer un label comme important pour en faire un label prioritaire."
 
-msgid "You can move around the graph by using the arrow keys."
-msgstr ""
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr "Vous pouvez facilement installer un Ex茅cuteur sur un cluster Kubernetes. %{link_to_help_page}"
 
 msgid "You can move around the graph by using the arrow keys."
-msgstr ""
+msgstr "Vous pouvez vous d茅placer dans le graphique en utilisant les touches fl茅ch茅es."
 
 msgid "You can only add files when you are on a branch"
 msgstr "Vous ne pouvez ajouter de fichier que dans une branche"
 
 msgid "You can only edit files when you are on a branch"
-msgstr ""
+msgstr "Vous ne pouvez 茅diter de fichier que dans une branche"
 
 msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
 msgstr "Vous ne pouvez pas 茅crire sur une instance GitLab Geo secondaire en lecture-seule. Veuillez utiliser le %{link_to_primary_node} 脿 la place."
@@ -3630,12 +4818,24 @@ msgstr "Vous ne pouvez pas 茅crire sur une instance GitLab Geo secondaire en lec
 msgid "You cannot write to this read-only GitLab instance."
 msgstr "Vous ne pouvez pas 茅crire sur cette instance GitLab en lecture-seule."
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr "Vous ne disposez pas des autorisations appropri茅es pour remplacer les param猫tres de synchronisation du groupe LDAP."
+
+msgid "You have no permissions"
+msgstr "Vous n鈥檃vez pas les autorisations"
+
 msgid "You have reached your project limit"
 msgstr "Vous avez atteint votre limite de projet"
 
+msgid "You must have master access to force delete a lock"
+msgstr "Vous devez avoir un acc猫s Expert pour forcer la suppression d鈥檜n verrou"
+
 msgid "You must sign in to star a project"
 msgstr "Vous devez vous connecter pour mettre un projet en favori"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr "Vous avez besoin d鈥檜ne licence diff茅rente pour activer la fonctionnalit茅 FileLocks"
+
 msgid "You need permission."
 msgstr "Vous avez besoin d鈥檜ne autorisation."
 
@@ -3664,10 +4864,31 @@ msgid "You won't be able to pull or push project code via SSH until you add an S
 msgstr "Vous ne pourrez pas r茅cup茅rer ou pousser de code par SSH tant que vous n鈥檃urez pas ajout茅 de cl茅 SSH 脿 votre profil"
 
 msgid "You'll need to use different branch names to get a valid comparison."
-msgstr ""
+msgstr "Vous devrez utiliser diff茅rents noms de branches pour obtenir une comparaison valide."
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr "Vous recevez ce courriel en raison de votre compte sur %{host}. %{manage_notifications_link} &middot; %{help_link}"
+
+msgid "Your Groups"
+msgstr "Vos groupes"
 
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
-msgstr ""
+msgstr "Vos informations de cluster Kubernetes sur cette page sont toujours modifiables, mais il est conseill茅 de d茅sactiver et reconfigurer"
+
+msgid "Your Projects (default)"
+msgstr "Vos projets (d茅faut)"
+
+msgid "Your Projects' Activity"
+msgstr "Activit茅 de vos projets favoris"
+
+msgid "Your Todos"
+msgstr "Vos t芒ches 脿 faire"
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr "Vos modifications peuvent 锚tre valid茅es sur %{branch_name} car une demande de fusion est ouverte."
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr "Vos modifications ont 茅t茅 valid茅es. Commit %{commitId} %{commitStats}"
 
 msgid "Your comment will not be visible to the public."
 msgstr "Votre commentaire ne sera pas visible publiquement."
@@ -3681,8 +4902,16 @@ msgstr "Votre nom"
 msgid "Your projects"
 msgstr "Vos projets"
 
+msgid "among other things"
+msgstr "entre autres choses"
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] "et %d vuln茅rabilit茅 corrig茅e"
+msgstr[1] "et %d vuln茅rabilit茅s corrig茅es"
+
 msgid "assign yourself"
-msgstr ""
+msgstr "assignez vous"
 
 msgid "branch name"
 msgstr "nom de la branche"
@@ -3690,201 +4919,325 @@ msgstr "nom de la branche"
 msgid "by"
 msgstr "par"
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr "%{type} n鈥檃 d茅tect茅 aucune nouvelle vuln茅rabilit茅 de s茅curit茅"
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr "%{type} n鈥檃 d茅tect茅 aucune vuln茅rabilit茅 de s茅curit茅"
+
 msgid "ciReport|Code quality"
-msgstr ""
+msgstr "Qualit茅 du code"
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
-msgstr ""
+msgstr "DAST n鈥檃 d茅tect茅 aucune alerte en analysant l鈥檃pplication de revue"
 
-msgid "ciReport|Failed to load ${type} report"
-msgstr ""
+msgid "ciReport|Dependency scanning"
+msgstr "Analyse de d茅pendance"
+
+msgid "ciReport|Dependency scanning detected"
+msgstr "Analyse de d茅pendance d茅tect茅e"
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr "L鈥檃nalyse des d茅pendances n鈥檃 d茅tect茅 aucune nouvelle vuln茅rabilit茅 de s茅curit茅"
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr "L鈥檃nalyse des d茅pendances n鈥檃 d茅tect茅 aucune vuln茅rabilit茅 de s茅curit茅"
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr "Impossible de charger le rapport %{reportName}"
 
 msgid "ciReport|Fixed:"
-msgstr ""
+msgstr "R茅par茅 :"
 
 msgid "ciReport|Instances"
-msgstr ""
+msgstr "Instances"
 
 msgid "ciReport|Learn more about whitelisting"
-msgstr ""
+msgstr "En savoir plus sur la liste blanche"
 
-msgid "ciReport|Loading ${type} report"
-msgstr ""
+msgid "ciReport|Loading %{reportName} report"
+msgstr "Chargement du rapport %{reportName}"
 
 msgid "ciReport|No changes to code quality"
-msgstr ""
+msgstr "Aucun changement dans la qualit茅 du code"
 
 msgid "ciReport|No changes to performance metrics"
-msgstr ""
+msgstr "Aucun changement dans les indicateurs de performance"
 
 msgid "ciReport|Performance metrics"
-msgstr ""
+msgstr "Indicateurs de performance"
 
 msgid "ciReport|SAST"
-msgstr ""
+msgstr "SAST"
+
+msgid "ciReport|SAST detected"
+msgstr "SAST d茅tect茅e"
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr "SAST n鈥檃 d茅tect茅 aucune nouvelle vuln茅rabilit茅 de s茅curit茅"
 
 msgid "ciReport|SAST detected no security vulnerabilities"
-msgstr ""
+msgstr "SAST n鈥檃 d茅tect茅 aucune vuln茅rabilit茅 de s茅curit茅"
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
-msgstr ""
+msgstr "SAST:conteneur aucune vuln茅rabilit茅 n鈥檃 茅t茅 trouv茅e"
+
+msgid "ciReport|Security scanning"
+msgstr "Analyse de s茅curit茅"
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr "L鈥檃nalyse de s茅curit茅 n鈥檃 pas r茅ussi 脿 charger de r茅sultats"
 
 msgid "ciReport|Show complete code vulnerabilities report"
-msgstr ""
+msgstr "Afficher le rapport complet sur les vuln茅rabilit茅s du code"
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
-msgstr ""
+msgstr "Les vuln茅rabilit茅s non approuv茅es (en rouge) peuvent 锚tre marqu茅es comme approuv茅es. %{helpLink}"
 
-msgid "commit"
-msgstr "validation"
+msgid "ciReport|no vulnerabilities"
+msgstr "aucune vuln茅rabilit茅"
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
-msgstr ""
+msgid "command line instructions"
+msgstr "instructions en ligne de commande"
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
-msgstr ""
+msgid "connecting"
+msgstr "connexion en cours"
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr "impossible de lire la cl茅 priv茅e, la phrase secr猫te est-elle correcte ?"
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "jour"
 msgstr[1] "jours"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] "a d茅tect茅 %d vuln茅rabilit茅 corrig茅e"
+msgstr[1] "a d茅tect茅 %d vuln茅rabilit茅s corrig茅es"
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] "a d茅tect茅 %d nouvelle vuln茅rabilit茅"
+msgstr[1] "a d茅tect茅 %d nouvelle vuln茅rabilit茅"
+
+msgid "detected no vulnerabilities"
+msgstr "n鈥檃 d茅tect茅 aucune vuln茅rabilit茅"
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
-msgstr ""
+msgstr "%{slash_command} mettra 脿 jour la dur茅e estim茅e avec la derni猫re commande."
+
+msgid "here"
+msgstr "ici"
+
+msgid "importing"
+msgstr "importation en cours"
+
+msgid "in progress"
+msgstr "en cours"
+
+msgid "is invalid because there is downstream lock"
+msgstr "est invalide car il y a un verrou en aval"
+
+msgid "is invalid because there is upstream lock"
+msgstr "est invalide car il y a un verrou en amont"
+
+msgid "is not a valid X509 certificate."
+msgstr "n鈥檈st pas un certificat X509 valide."
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr "verrouill茅 par %{path_lock_user_name} %{created_at}"
 
 msgid "merge request"
 msgid_plural "merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "demande de fusion"
+msgstr[1] "demandes de fusion"
 
-msgid "mrWidget|Cancel automatic merge"
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr "Veuillez la restaurer ou utiliser une autre branche %{missingBranchName}"
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr "%{metricsLinkStart}L鈥檜sage m茅moire%{metricsLinkEnd} %{emphasisStart}a diminu茅%{emphasisEnd} de %{memoryFrom}MO 脿 %{memoryTo}MO"
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr "%{metricsLinkStart}L鈥檜sage m茅moire%{metricsLinkEnd} %{emphasisStart}a augment茅%{emphasisEnd} de %{memoryFrom}MO 脿 %{memoryTo}MO"
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr "%{metricsLinkStart}L鈥檜sage m茅moire%{metricsLinkEnd} %{emphasisStart}est rest茅 stable%{emphasisEnd} 脿 %{memoryFrom}MO"
+
+msgid "mrWidget|Add approval"
+msgstr "Ajouter une approbation"
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr "Autoriser les modifications par les mainteneurs"
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr "Une erreur est survenue lors de la suppression de votre approbation."
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr "Une erreur s鈥檈st produite lors de la r茅cup茅ration des donn茅es d鈥檃pprobation pour cette demande de fusion."
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr "Une erreur est survenue pendant l鈥檈nvoi de votre approbation."
+
+msgid "mrWidget|Approve"
+msgstr "Approuver"
+
+msgid "mrWidget|Approved"
 msgstr ""
 
+msgid "mrWidget|Approved by"
+msgstr "Approuv茅e par"
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr "Annuler la fusion automatique"
+
 msgid "mrWidget|Check out branch"
-msgstr ""
+msgstr "R茅cup茅rer la branche"
 
 msgid "mrWidget|Checking ability to merge automatically"
-msgstr ""
+msgstr "V茅rification de la possibilit茅 de fusion automatique"
 
 msgid "mrWidget|Cherry-pick"
-msgstr ""
+msgstr "Picorer"
 
 msgid "mrWidget|Cherry-pick this merge request in a new merge request"
-msgstr ""
+msgstr "Picorer cette demande de fusion dans une nouvelle demande de fusion"
 
 msgid "mrWidget|Closed"
-msgstr ""
+msgstr "Ferm茅e"
 
 msgid "mrWidget|Closed by"
-msgstr ""
+msgstr "Ferm茅e par"
 
 msgid "mrWidget|Closes"
-msgstr ""
+msgstr "R茅sout"
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr "Les statistiques de d茅ploiement ne sont pas disponibles pour le moment"
 
 msgid "mrWidget|Did not close"
-msgstr ""
+msgstr "N鈥檃 pas r茅solu"
 
 msgid "mrWidget|Email patches"
-msgstr ""
+msgstr "Patchs par courriel"
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr "Impossible de charger les statistiques de d茅ploiement"
 
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
-msgstr ""
+msgstr "Si la branche %{branch} existe dans votre d茅p么t local, vous pouvez fusionner cette demande de fusion manuellement 脿 l鈥檃ide de"
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr "Si la branche %{missingBranchName} existe dans votre d茅p么t local, vous pouvez fusionner cette demande de fusion manuellement en ligne de commande"
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr "mrWidget | Chargement des statistiques de d茅ploiement"
 
 msgid "mrWidget|Mentions"
-msgstr ""
+msgstr "Mentionne"
 
 msgid "mrWidget|Merge"
-msgstr ""
+msgstr "Fusionner"
 
 msgid "mrWidget|Merge failed."
-msgstr ""
+msgstr "La fusion a 茅chou茅."
 
 msgid "mrWidget|Merge locally"
-msgstr ""
+msgstr "Fusionner localement"
 
 msgid "mrWidget|Merged by"
-msgstr ""
+msgstr "Fusionn茅e par"
 
 msgid "mrWidget|Plain diff"
-msgstr ""
+msgstr "Diff simple"
 
 msgid "mrWidget|Refresh"
-msgstr ""
+msgstr "Actualiser"
 
 msgid "mrWidget|Refresh now"
-msgstr ""
+msgstr "Actualiser maintenant"
 
 msgid "mrWidget|Refreshing now"
-msgstr ""
+msgstr "Actualisation en cours"
 
 msgid "mrWidget|Remove Source Branch"
-msgstr ""
+msgstr "Supprimer la branche source"
 
 msgid "mrWidget|Remove source branch"
-msgstr ""
+msgstr "Supprimer la branche source"
+
+msgid "mrWidget|Remove your approval"
+msgstr "Supprimer votre approbation"
 
 msgid "mrWidget|Request to merge"
-msgstr ""
+msgstr "Demande de fusion de"
 
 msgid "mrWidget|Resolve conflicts"
-msgstr ""
+msgstr "R茅soudre les conflits"
 
 msgid "mrWidget|Revert"
-msgstr ""
+msgstr "D茅faire"
 
 msgid "mrWidget|Revert this merge request in a new merge request"
-msgstr ""
+msgstr "D茅faire cette demande de fusion dans une nouvelle demande de fusion"
 
 msgid "mrWidget|Set by"
-msgstr ""
+msgstr "Marqu茅 par"
 
 msgid "mrWidget|The changes were merged into"
-msgstr ""
+msgstr "Les modifications ont 茅t茅 fusionn茅es dans"
 
 msgid "mrWidget|The changes were not merged into"
-msgstr ""
+msgstr "Les modifications n鈥檕nt pas 茅t茅 fusionn茅es dans"
 
 msgid "mrWidget|The changes will be merged into"
-msgstr ""
+msgstr "Les modifications seront fusionn茅es dans"
 
 msgid "mrWidget|The source branch has been removed"
-msgstr ""
+msgstr "La branche source a 茅t茅 supprim茅e"
 
 msgid "mrWidget|The source branch is being removed"
-msgstr ""
+msgstr "La branche source est en cours de suppression"
 
 msgid "mrWidget|The source branch will be removed"
-msgstr ""
+msgstr "La branche source sera supprim茅e"
 
 msgid "mrWidget|The source branch will not be removed"
-msgstr ""
+msgstr "La branche source ne sera pas supprim茅e"
 
 msgid "mrWidget|There are merge conflicts"
-msgstr ""
+msgstr "Il y a des conflits de fusion"
 
 msgid "mrWidget|This merge request failed to be merged automatically"
-msgstr ""
+msgstr "Cette demande de fusion n鈥檃 pas pu 锚tre fusionn茅e automatiquement"
 
 msgid "mrWidget|This merge request is in the process of being merged"
-msgstr ""
+msgstr "Cette demande de fusion est en cours de fusion"
 
 msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr "Ce projet est archiv茅, l鈥檃cc猫s en 茅criture a 茅t茅 d茅sactiv茅"
+
+msgid "mrWidget|Web IDE"
 msgstr ""
 
 msgid "mrWidget|You can merge this merge request manually using the"
-msgstr ""
+msgstr "Vous pouvez fusionner cette demande de fusion manuellement 脿 l鈥檃ide de la"
 
 msgid "mrWidget|You can remove source branch now"
-msgstr ""
+msgstr "Vous pouvez maintenant supprimer la branche source"
+
+msgid "mrWidget|branch does not exist."
+msgstr "la branche n鈥檈xiste pas."
 
 msgid "mrWidget|command line"
-msgstr ""
+msgstr "ligne de commande"
 
 msgid "mrWidget|into"
-msgstr ""
+msgstr "dans"
 
 msgid "mrWidget|to be merged automatically when the pipeline succeeds"
-msgstr ""
+msgstr "pour 锚tre fusionn茅e automatiquement lorsque le pipeline r茅ussit"
 
 msgid "new merge request"
 msgstr "nouvelle demande de fusion"
@@ -3893,7 +5246,7 @@ msgid "notification emails"
 msgstr "courriels de notification"
 
 msgid "or"
-msgstr ""
+msgstr "ou"
 
 msgid "parent"
 msgid_plural "parents"
@@ -3906,14 +5259,20 @@ msgstr "mot de passe"
 msgid "personal access token"
 msgstr "jeton d鈥檃cc猫s personnel"
 
+msgid "private key does not match certificate."
+msgstr "cl茅 priv茅e ne correspond pas au certificat."
+
 msgid "remove due date"
-msgstr ""
+msgstr "supprimer la date d鈥櫭ヽh茅ance"
 
 msgid "source"
 msgstr "source"
 
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
-msgstr ""
+msgstr "%{slash_command} mettra 脿 jour la somme du temps pass茅."
+
+msgid "this document"
+msgstr "ce document"
 
 msgid "to help your contributors communicate effectively!"
 msgstr "pour aider vos contributeurs 脿 communiquer efficacement聽!"
@@ -3922,5 +5281,8 @@ msgid "username"
 msgstr "nom d鈥檜tilisateur"
 
 msgid "uses Kubernetes clusters to deploy your code!"
-msgstr ""
+msgstr "utilise les clusters Kubernetes pour d茅ployer votre code !"
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr "avec %{additions} ajouts, %{deletions} suppressions."
 
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c9b17f1e82651a776fdd264e75c06b75f6b96e13..0eec9793391a5cff0ad7b358b6d67236cc46163f 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: 2018-03-01 18:03+0100\n"
-"PO-Revision-Date: 2018-03-01 18:03+0100\n"
+"POT-Creation-Date: 2018-04-17 11:44+0200\n"
+"PO-Revision-Date: 2018-04-17 11:44+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
@@ -28,6 +28,11 @@ msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -43,6 +48,11 @@ msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] ""
@@ -59,6 +69,9 @@ msgid_plural "%{count} participants"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -99,15 +112,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr ""
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr ""
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -117,6 +145,9 @@ msgstr ""
 msgid "Account"
 msgstr ""
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr ""
 
@@ -201,9 +232,27 @@ msgstr ""
 msgid "All"
 msgstr ""
 
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -276,15 +325,12 @@ msgstr ""
 msgid "April"
 msgstr ""
 
-msgid "Archived project! Repository is read-only"
+msgid "Archived project! Repository and other project resources are read-only"
 msgstr ""
 
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr ""
 
-msgid "Are you sure you want to discard your changes?"
-msgstr ""
-
 msgid "Are you sure you want to reset registration token?"
 msgstr ""
 
@@ -309,6 +355,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -333,6 +388,9 @@ msgstr ""
 msgid "Auto DevOps enabled"
 msgstr ""
 
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -375,9 +433,84 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background jobs"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
 msgid "Begin with the selected commit"
 msgstr ""
 
+msgid "Blame"
+msgstr ""
+
 msgid "Branch (%{branch_count})"
 msgid_plural "Branches (%{branch_count})"
 msgstr[0] ""
@@ -404,6 +537,15 @@ msgstr ""
 msgid "Branches"
 msgstr ""
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -449,12 +591,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The default branch cannot be deleted"
 msgstr ""
 
@@ -506,12 +675,18 @@ msgstr ""
 msgid "Cancel"
 msgstr ""
 
-msgid "Cancel edit"
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Cannot be merged automatically"
 msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr ""
 
@@ -563,6 +738,9 @@ msgstr ""
 msgid "Choose file..."
 msgstr ""
 
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "CiStatusLabel|canceled"
 msgstr ""
 
@@ -647,6 +825,12 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
 msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
 msgstr ""
 
@@ -656,6 +840,9 @@ msgstr ""
 msgid "Clone repository"
 msgstr ""
 
+msgid "Close"
+msgstr ""
+
 msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
 msgstr ""
 
@@ -698,6 +885,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -713,7 +903,7 @@ msgstr ""
 msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
 msgstr ""
 
-msgid "ClusterIntegration|Create on GKE"
+msgid "ClusterIntegration|Create on Google Kubernetes Engine"
 msgstr ""
 
 msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
@@ -746,6 +936,9 @@ msgstr ""
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
@@ -800,6 +993,9 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr ""
+
 msgid "ClusterIntegration|Machine type"
 msgstr ""
 
@@ -857,6 +1053,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -884,6 +1083,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -957,6 +1159,9 @@ msgstr ""
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr ""
 
@@ -1002,6 +1207,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1017,9 +1228,36 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1065,6 +1303,15 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images."
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr ""
 
@@ -1104,6 +1351,12 @@ msgstr ""
 msgid "Create New Directory"
 msgstr ""
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr ""
 
@@ -1113,12 +1366,15 @@ msgstr ""
 msgid "Create directory"
 msgstr ""
 
-msgid "Create empty bare repository"
+msgid "Create empty repository"
 msgstr ""
 
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
@@ -1143,6 +1399,9 @@ msgstr ""
 msgid "Create new..."
 msgstr ""
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr ""
 
@@ -1152,6 +1411,12 @@ msgstr ""
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr ""
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Cron Timezone"
 msgstr ""
 
@@ -1214,6 +1479,78 @@ msgstr[1] ""
 msgid "Deploy Keys"
 msgstr ""
 
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Copy username to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow read-only access to your repository and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|This project has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke"
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
 msgid "Description"
 msgstr ""
 
@@ -1226,7 +1563,7 @@ msgstr ""
 msgid "Directory name"
 msgstr ""
 
-msgid "Discard changes"
+msgid "Discard draft"
 msgstr ""
 
 msgid "Dismiss Cycle Analytics introduction box"
@@ -1235,6 +1572,9 @@ msgstr ""
 msgid "Don't show again"
 msgstr ""
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr ""
 
@@ -1262,6 +1602,9 @@ msgstr ""
 msgid "DownloadSource|Download"
 msgstr ""
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
@@ -1271,12 +1614,42 @@ msgstr ""
 msgid "Edit Pipeline Schedule %{id}"
 msgstr ""
 
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
+msgid "Embed"
+msgstr ""
+
 msgid "Enable Auto DevOps"
 msgstr ""
 
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1325,6 +1698,15 @@ msgstr ""
 msgid "Environments|You don't have any environments right now."
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error fetching contributors data."
 msgstr ""
 
@@ -1385,6 +1767,9 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "Failed"
+msgstr ""
+
 msgid "Failed Jobs"
 msgstr ""
 
@@ -1427,6 +1812,9 @@ msgstr ""
 msgid "Find file"
 msgstr ""
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr ""
 
@@ -1444,9 +1832,15 @@ msgstr ""
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr ""
 
@@ -1462,6 +1856,9 @@ msgstr ""
 msgid "Generate a default set of labels"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1471,12 +1868,24 @@ msgstr ""
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr ""
 
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr ""
 
@@ -1525,9 +1934,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1573,6 +1979,15 @@ msgstr ""
 msgid "HealthCheck|Unhealthy"
 msgstr ""
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1587,6 +2002,21 @@ msgstr ""
 msgid "If you already have files you can push them using the %{link_to_cli} below."
 msgstr ""
 
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr ""
 
@@ -1632,6 +2062,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Job has been erased"
+msgstr ""
+
 msgid "Jobs"
 msgstr ""
 
@@ -1647,6 +2080,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1677,12 +2113,30 @@ msgstr ""
 msgid "LFSStatus|Enabled"
 msgstr ""
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] ""
@@ -1736,6 +2190,9 @@ msgstr ""
 msgid "Leave project"
 msgstr ""
 
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1745,18 +2202,24 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
-msgstr ""
-
 msgid "Locked"
 msgstr ""
 
 msgid "Login"
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1796,6 +2259,12 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1811,6 +2280,12 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr ""
 
@@ -1823,6 +2298,9 @@ msgstr ""
 msgid "Monitoring"
 msgstr ""
 
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr ""
 
@@ -1891,6 +2369,9 @@ msgstr ""
 msgid "No assignee"
 msgstr ""
 
+msgid "No changes"
+msgstr ""
+
 msgid "No connection could be made to a Gitaly Server, please check your logs!"
 msgstr ""
 
@@ -1903,6 +2384,9 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr ""
 
@@ -1918,13 +2402,25 @@ msgstr ""
 msgid "Not available"
 msgstr ""
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
-msgid "Not enough data"
+msgid "Not enough data"
+msgstr ""
+
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
 msgstr ""
 
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
 msgstr ""
 
 msgid "Notification events"
@@ -2008,6 +2504,9 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr ""
 
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2023,12 +2522,18 @@ msgstr ""
 msgid "Otherwise it is recommended you start with one of the options below."
 msgstr ""
 
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr ""
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2041,9 +2546,24 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Permalink"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr ""
 
@@ -2122,9 +2642,36 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
 msgid "Pipeline|Retry pipeline"
 msgstr ""
 
@@ -2155,15 +2702,24 @@ msgstr ""
 msgid "Pipeline|with stages"
 msgstr ""
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
 msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
 msgstr ""
 
+msgid "Please select at least one filter to see results"
+msgstr ""
+
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2182,6 +2738,12 @@ msgstr ""
 msgid "Profiles|Account scheduled for removal."
 msgstr ""
 
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
 msgid "Profiles|Delete Account"
 msgstr ""
 
@@ -2200,9 +2762,21 @@ msgstr ""
 msgid "Profiles|Invalid username"
 msgstr ""
 
+msgid "Profiles|Path"
+msgstr ""
+
 msgid "Profiles|Type your %{confirmationValue} to confirm:"
 msgstr ""
 
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
 msgid "Profiles|You don't have access to delete this user."
 msgstr ""
 
@@ -2215,6 +2789,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2230,6 +2807,9 @@ msgstr ""
 msgid "Project '%{project_name}' was successfully updated."
 msgstr ""
 
+msgid "Project Badges"
+msgstr ""
+
 msgid "Project access must be granted explicitly to each user."
 msgstr ""
 
@@ -2239,9 +2819,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr ""
 
@@ -2260,15 +2837,6 @@ msgstr ""
 msgid "ProjectActivityRSS|Subscribe"
 msgstr ""
 
-msgid "ProjectFeature|Disabled"
-msgstr ""
-
-msgid "ProjectFeature|Everyone with access"
-msgstr ""
-
-msgid "ProjectFeature|Only team members"
-msgstr ""
-
 msgid "ProjectFileTree|Name"
 msgstr ""
 
@@ -2305,6 +2873,15 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusDashboard|Time"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
 msgid "PrometheusService|Active"
 msgstr ""
 
@@ -2317,6 +2894,9 @@ msgstr ""
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
@@ -2338,15 +2918,9 @@ msgstr ""
 msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
-msgstr ""
-
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr ""
-
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
@@ -2362,7 +2936,19 @@ msgstr ""
 msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2386,12 +2972,18 @@ msgstr ""
 msgid "Quick actions can be used in the issues description and comment boxes."
 msgstr ""
 
+msgid "Raw"
+msgstr ""
+
 msgid "Read more"
 msgstr ""
 
 msgid "Readme"
 msgstr ""
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr ""
 
@@ -2422,6 +3014,9 @@ msgstr ""
 msgid "Related Merged Requests"
 msgstr ""
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr ""
 
@@ -2434,6 +3029,12 @@ msgstr ""
 msgid "Repository"
 msgstr ""
 
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr ""
 
@@ -2449,6 +3050,12 @@ msgstr ""
 msgid "Resolve discussion"
 msgstr ""
 
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2460,6 +3067,21 @@ msgstr ""
 msgid "Revert this merge request"
 msgstr ""
 
+msgid "Review"
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2475,12 +3097,18 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr ""
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
 msgid "Scheduling Pipelines"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr ""
 
@@ -2520,6 +3148,9 @@ msgstr ""
 msgid "Select target branch"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2532,9 +3163,24 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr ""
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
@@ -2550,6 +3196,9 @@ msgstr ""
 msgid "Setup a specific Runner automatically"
 msgstr ""
 
+msgid "Share"
+msgstr ""
+
 msgid "Show command"
 msgstr ""
 
@@ -2564,25 +3213,25 @@ msgid_plural "Showing %d events"
 msgstr[0] ""
 msgstr[1] ""
 
-msgid "Snippets"
+msgid "Sign-in restrictions"
 msgstr ""
 
-msgid "Something went wrong on our end"
+msgid "Sign-up restrictions"
 msgstr ""
 
-msgid "Something went wrong on our end."
+msgid "Size and domain settings for static websites"
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Snippets"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong on our end"
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2591,12 +3240,6 @@ msgstr ""
 msgid "Something went wrong while fetching the registry list."
 msgstr ""
 
-msgid "Something went wrong while reopening the %{issuable}. Please try again later"
-msgstr ""
-
-msgid "Something went wrong while resolving this discussion. Please try again."
-msgstr ""
-
 msgid "Something went wrong. Please try again."
 msgstr ""
 
@@ -2705,12 +3348,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr ""
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2720,6 +3372,15 @@ msgstr ""
 msgid "Start the Runner!"
 msgstr ""
 
+msgid "Started"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2815,6 +3476,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr ""
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -2833,6 +3497,9 @@ msgstr ""
 msgid "The fork relationship has been removed."
 msgstr ""
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 ""
 
@@ -2866,6 +3533,9 @@ msgstr ""
 msgid "The repository for this project is empty"
 msgstr ""
 
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 ""
 
@@ -2941,6 +3611,15 @@ msgstr ""
 msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
 msgstr ""
 
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
 msgid "This job has not been triggered yet"
 msgstr ""
 
@@ -2962,6 +3641,9 @@ msgstr ""
 msgid "This page is unavailable because you are not allowed to read information across multiple projects."
 msgstr ""
 
+msgid "This page will be removed in a future release."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
@@ -3131,6 +3813,21 @@ msgstr ""
 msgid "Tip:"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3158,15 +3855,9 @@ msgstr ""
 msgid "Trigger this manual action"
 msgstr ""
 
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
-
 msgid "Unlocked"
 msgstr ""
 
@@ -3176,6 +3867,9 @@ msgstr ""
 msgid "Unstar"
 msgstr ""
 
+msgid "Unverified"
+msgstr ""
+
 msgid "Up to date"
 msgstr ""
 
@@ -3191,27 +3885,63 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr ""
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
 msgid "Use the following registration token during setup:"
 msgstr ""
 
 msgid "Use your global notification setting"
 msgstr ""
 
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr ""
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr ""
 
@@ -3239,6 +3969,9 @@ msgstr ""
 msgid "Web IDE"
 msgstr ""
 
+msgid "Web terminal"
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3350,16 +4083,19 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr ""
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr ""
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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?"
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
 msgstr ""
 
 msgid "You are on a read-only GitLab instance."
@@ -3422,9 +4158,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3437,6 +4194,9 @@ msgstr ""
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3446,10 +4206,7 @@ msgstr ""
 msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
-msgstr ""
-
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "connecting"
 msgstr ""
 
 msgid "day"
@@ -3457,9 +4214,15 @@ msgid_plural "days"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "deploy token"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "importing"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
@@ -3468,6 +4231,18 @@ msgstr[1] ""
 msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
 msgstr ""
 
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3492,18 +4267,30 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
 msgstr ""
 
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3576,6 +4363,9 @@ msgstr ""
 msgid "mrWidget|There are merge conflicts"
 msgstr ""
 
+msgid "mrWidget|There are unresolved discussions. Please resolve these discussions"
+msgstr ""
+
 msgid "mrWidget|This merge request failed to be merged automatically"
 msgstr ""
 
@@ -3585,6 +4375,9 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
@@ -3632,8 +4425,14 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "username"
 msgstr ""
 
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po
new file mode 100644
index 0000000000000000000000000000000000000000..adb9746e85461bb8426bb9cc5240611d2902a577
--- /dev/null
+++ b/locale/id_ID/gitlab.po
@@ -0,0 +1,5257 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:39-0400\n"
+"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
+"Language-Team: Indonesian\n"
+"Language: id_ID\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.com\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Language: id\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+
+msgid " and"
+msgstr ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{storage_name}: failed storage access attempt on host:"
+msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
+msgstr[0] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "(checkout the %{link} for information on how to install it)."
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "2FA enabled"
+msgstr ""
+
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account and limit settings"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add Changelog"
+msgstr ""
+
+msgid "Add Contribution guide"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add License"
+msgstr ""
+
+msgid "Add Readme"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add todo"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminHealthPageLink|health page"
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced settings"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
+msgid "An error occurred while rendering KaTeX"
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "Any Label"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archived project! Repository is read-only"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
+msgid "Assignee"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps (Beta)"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Downgrade"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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 ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project master or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn鈥檛 been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You鈥檙e about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Certificate fingerprint"
+msgstr ""
+
+msgid "Change Weight"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changelog"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "Check interval"
+msgstr ""
+
+msgid "Checking %{text} availability鈥�"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Choose File ..."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose file..."
+msgstr ""
+
+msgid "Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
+msgid "Choose which shards you wish to synchronize to this secondary node."
+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 "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occured while saving variables"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "CircuitBreakerApiLink|circuitbreaker api"
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add an existing Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
+msgstr ""
+
+msgid "ClusterIntegration|Applications"
+msgstr ""
+
+msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Token"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
+msgstr ""
+
+msgid "ClusterIntegration|Create on GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project ID"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
+msgid "ClusterIntegration|Install"
+msgstr ""
+
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Installed"
+msgstr ""
+
+msgid "ClusterIntegration|Installing"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Integration status"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about environments"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
+msgstr ""
+
+msgid "ClusterIntegration|More information"
+msgstr ""
+
+msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
+msgstr ""
+
+msgid "ClusterIntegration|Note:"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Project ID"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Security"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|See machine types"
+msgstr ""
+
+msgid "ClusterIntegration|See your projects"
+msgstr ""
+
+msgid "ClusterIntegration|See zones"
+msgstr ""
+
+msgid "ClusterIntegration|Service token"
+msgstr ""
+
+msgid "ClusterIntegration|Show"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes Cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Token"
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|check the pricing here"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|help page"
+msgstr ""
+
+msgid "ClusterIntegration|installing applications"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|properly configured"
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|Commit: %{commitText}"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Created"
+msgstr ""
+
+msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
+msgstr ""
+
+msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
+msgstr ""
+
+msgid "ContainerRegistry|How to use the Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Learn more about"
+msgstr ""
+
+msgid "ContainerRegistry|No tags in Container Registry for this container image."
+msgstr ""
+
+msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgstr ""
+
+msgid "ContainerRegistry|Size"
+msgstr ""
+
+msgid "ContainerRegistry|Tag"
+msgstr ""
+
+msgid "ContainerRegistry|Tag ID"
+msgstr ""
+
+msgid "ContainerRegistry|Use different image names"
+msgstr ""
+
+msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution guide"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
+msgstr ""
+
+msgid "ContributorsPage|Building repository graph."
+msgstr ""
+
+msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Control the maximum concurrency of repository backfill for this secondary node"
+msgstr ""
+
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy branch name to clipboard"
+msgstr ""
+
+msgid "Copy command to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Copy reference to clipboard"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "CreateNewFork|Fork"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Current node"
+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 "Customize colors"
+msgstr ""
+
+msgid "Cycle Analytics"
+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 "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discover GitLab Geo."
+msgstr ""
+
+msgid "Dismiss Cycle Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+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 "Downvotes"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|Open"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now."
+msgstr ""
+
+msgid "Epic will be removed! Are you sure?"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error fetching contributors data."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching usage ping data."
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error updating status for all todos."
+msgstr ""
+
+msgid "Error updating todo status."
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+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 "Expand"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fields on this page are now uneditable, you can configure"
+msgstr ""
+
+msgid "File name"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "From %{provider_title}"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Database replication lag:"
+msgstr ""
+
+msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version:"
+msgstr ""
+
+msgid "GeoNodes|Health status:"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor:"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary:"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|Local Attachments:"
+msgstr ""
+
+msgid "GeoNodes|Local LFS objects:"
+msgstr ""
+
+msgid "GeoNodes|Local job artifacts:"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Out of sync"
+msgstr ""
+
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL:"
+msgstr ""
+
+msgid "GeoNodes|Replication slots:"
+msgstr ""
+
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
+msgid "GeoNodes|Repositories:"
+msgstr ""
+
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Selective"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config:"
+msgstr ""
+
+msgid "GeoNodes|Sync settings:"
+msgstr ""
+
+msgid "GeoNodes|Synced"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
+msgid "GeoNodes|Wikis:"
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|File sync capacity"
+msgstr ""
+
+msgid "Geo|Groups to synchronize"
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Repository sync capacity"
+msgstr ""
+
+msgid "Geo|Select groups to replicate."
+msgstr ""
+
+msgid "Geo|Shards to synchronize"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git storage health information has been reset"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
+msgid "GitLab Runner section"
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "GoToYourFork|Fork"
+msgstr ""
+
+msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Share with group lock"
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Filter by name..."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|Sorry, no groups or projects matched your search"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header message"
+msgstr ""
+
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+
+msgid "History"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Install a Runner compatible with GitLab CI"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Issue board focus mode"
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Koding"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration was not removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes configured"
+msgstr ""
+
+msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark done"
+msgstr ""
+
+msgid "Maximum git storage failures"
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+
+msgid "New Kubernetes Cluster"
+msgstr ""
+
+msgid "New Kubernetes cluster"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "No Label"
+msgstr ""
+
+msgid "No assignee"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No labels created yet."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not allowed to merge"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+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 "Notifications"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "Number of access attempts"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pagination|Last 禄"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|芦 First"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline quota"
+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 "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project avatar in repository: %{link}"
+msgstr ""
+
+msgid "Project details"
+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 "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Masters"
+msgstr ""
+
+msgid "ProjectCreationLevel|Masters"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+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 "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
+msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Readme"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "RefSwitcher|Branches"
+msgstr ""
+
+msgid "RefSwitcher|Tags"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Registry"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Reset git storage health information"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Seconds before reseting failure information"
+msgstr ""
+
+msgid "Seconds to wait for a storage access attempt"
+msgstr ""
+
+msgid "Secret variables"
+msgstr ""
+
+msgid "Security report"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select assignee"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Selective synchronization"
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up Koding"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|No"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while fetching Dependency Scanning."
+msgstr ""
+
+msgid "Something went wrong while fetching SAST."
+msgstr ""
+
+msgid "Something went wrong while fetching the projects."
+msgstr ""
+
+msgid "Something went wrong while fetching the registry list."
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Milestone"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag."
+msgstr ""
+
+msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here..."
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The number of attempts GitLab will make to access a storage."
+msgstr ""
+
+msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 ""
+
+msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset."
+msgstr ""
+
+msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised."
+msgstr ""
+
+msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
+msgstr ""
+
+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 ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no merge requests to show"
+msgstr ""
+
+msgid "There are problems accessing Git storage: "
+msgstr ""
+
+msgid "There was an error loading results"
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "This board\\'s scope is reduced"
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This is a confidential issue."
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is confidential and locked."
+msgstr ""
+
+msgid "This issue is locked."
+msgstr ""
+
+msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+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 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|in a while"
+msgstr ""
+
+msgid "Timeago|less than a minute ago"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
+msgid "Todo"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unresolve discussion"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to activate Issue weight."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload new avatar"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyPageError|You are not allowed to create wiki pages"
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
+msgstr ""
+
+msgid "WikiNewPagePlaceholder|how-to-setup"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiNewPageTitle|New Wiki Page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{page_title}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{page_title}"
+msgstr ""
+
+msgid "WikiPage|Page slug"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here..."
+msgstr ""
+
+msgid "Wiki|Create Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|Empty page"
+msgstr ""
+
+msgid "Wiki|More Pages"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Write a commit message..."
+msgstr ""
+
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
+msgid "You must sign in to star a project"
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+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 "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your comment will not be visible to the public."
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+
+msgid "assign yourself"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|DAST detected no alerts by analyzing the review app"
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Instances"
+msgstr ""
+
+msgid "ciReport|Learn more about whitelisting"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|SAST detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|SAST:container no vulnerabilities were found"
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Show complete code vulnerabilities report"
+msgstr ""
+
+msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
+msgstr ""
+
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove Source Branch"
+msgstr ""
+
+msgid "mrWidget|Remove source branch"
+msgstr ""
+
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The source branch has been removed"
+msgstr ""
+
+msgid "mrWidget|The source branch is being removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will be removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be removed"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|Web IDE"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|You can remove source branch now"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+
+msgid "password"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 62a6da1604abc5a982e72755f870cc33c60c6971..41d6a76be66c984a84c5a69e0473b05135e07c66 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:00-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:37-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Italian\n"
 "Language: it_IT\n"
@@ -29,6 +29,11 @@ msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -44,11 +49,19 @@ msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s commit aggiuntivo 猫 stato omesso per evitare degradi di prestazioni negli issues."
 msgstr[1] "%s commit aggiuntivi sono stati omessi per evitare degradi di prestazioni negli issues."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -57,6 +70,12 @@ msgid_plural "%{count} participants"
 msgstr[0] "%{count} partecipante"
 msgstr[1] "%{count} partecipanti"
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr "%{number_commits_behind} commits precedenti %{default_branch}, %{number_commits_ahead} commits avanti"
 
@@ -66,6 +85,9 @@ msgstr "%{number_of_failures} di %{maximum_failures} fallimenti. GitLab consenti
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} di %{maximum_failures} fallimenti. Gitlab non ritenter脿 automaticamente. Ripristina l'informazioni d'archiviazione quando il problema 猫 risolto."
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}: tentativo d'accesso all'archiviazione fallito da parte dell'host:"
@@ -94,15 +116,30 @@ msgstr "Primo contributo!"
 msgid "2FA enabled"
 msgstr "2FA abilitata"
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "Un insieme di grafici riguardo la Continuous Integration"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "Riguardo il rilascio automatico"
 
 msgid "Abuse Reports"
 msgstr "Segnalazioni di abuso"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "Token di accesso"
 
@@ -112,6 +149,9 @@ msgstr "L'accesso agli storages 猫 stato temporaneamente disabilitato per consen
 msgid "Account"
 msgstr "Account"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "Attivo"
 
@@ -130,9 +170,15 @@ msgstr "Aggiungi Guida per contribuire"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "Aggiungi Licenza"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "Aggiungi una directory (cartella)"
 
@@ -151,12 +197,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr "Pagina di stato"
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -169,9 +248,33 @@ msgstr "Tutto"
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -181,6 +284,12 @@ msgstr "Errore durante l'attivazione/disattivazione della sottoscrizione per l'i
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -190,12 +299,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr "Errore durante il recupero dei dati della barra laterale"
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -208,12 +341,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr "Si 猫 verificato un errore. Riprova."
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr "Aspetto"
 
@@ -232,21 +374,24 @@ 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 "Are you sure you want to discard your changes?"
-msgstr "Vuoi davvero ignorare le modifiche?"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "Sei sicuro di voler ripristinare il token di registrazione?"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "Confermi di voler resettare il token di controllo di stato?"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr "Sei sicuro?"
 
 msgid "Artifacts"
 msgstr "Artefatti"
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -259,6 +404,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -280,6 +434,12 @@ msgstr "Autore"
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -304,8 +464,14 @@ msgstr "Far脿 automaticamente le build, i test e i rilasci della tua applicazion
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr "Approfondisci: %{link_to_documentation}"
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
-msgstr "Puoi attivare %{link_to_settings} per questo progetto."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr ""
 
 msgid "Available"
 msgstr "Disponibile"
@@ -316,6 +482,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -370,11 +545,8 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -399,6 +571,15 @@ msgstr "Cambia branch"
 msgid "Branches"
 msgstr "Branch"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr "Impossibile trovare l'HEAD commit per questa branch"
 
@@ -444,12 +625,39 @@ msgstr "Una volta confermato e premuto %{delete_protected_branch} non sar脿 poss
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr "Solo gli Owner e i Master possono eliminare una branch protetta"
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
-msgstr "Le branch protette possono esser gestite in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
 
 msgid "Branches|Sort by"
 msgstr "Ordina per"
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -495,30 +703,45 @@ msgstr "Esplora Files"
 msgid "Browse files"
 msgstr "Guarda i files"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "per"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr "Jobs"
 
 msgid "Cancel"
 msgstr "Cancella"
 
-msgid "Cancel edit"
-msgstr "Annulla modifica"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "Preleva nella branch"
 
@@ -573,6 +796,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -669,9 +898,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr "api circuitbreaker"
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr "Clona repository"
 
@@ -723,6 +964,9 @@ msgstr "Copia URL API"
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr "Copia Certificato CA"
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -768,12 +1012,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr "Helm Tiller"
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr "Ingresso"
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr "Installa"
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr "Installato"
 
@@ -792,6 +1045,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -822,10 +1078,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -888,6 +1144,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -915,6 +1174,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -960,6 +1222,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr "Commenti"
 
@@ -968,6 +1236,11 @@ msgid_plural "Commits"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "Commit Message"
 msgstr "Messaggio di commit"
 
@@ -980,6 +1253,9 @@ msgstr "Messaggio del commit"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "Commit"
 
@@ -1025,6 +1301,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1040,9 +1322,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1088,6 +1406,12 @@ msgstr "Utilizza nomi d'immagine differenti"
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr "Con il Docker Container Registry integrato in Gitlab, ogni progetto pu貌 avere il suo spazio d'archiviazione sulle immagini Docker."
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "Guida per contribuire"
 
@@ -1121,6 +1445,9 @@ msgstr "Copia URL negli appunti"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "Copia l'SHA del commit negli appunti"
 
@@ -1133,14 +1460,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "Crea una nuova cartella"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "Creare un token di accesso sul tuo account per eseguire pull o push tramite %{protocol}"
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "Crea cartella"
 
-msgid "Create empty bare repository"
-msgstr "Crea una repository vuota"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1148,12 +1484,18 @@ msgstr ""
 msgid "Create file"
 msgstr "Crea file"
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "Crea una richiesta di merge"
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr "Crea un nuova branch"
 
@@ -1169,6 +1511,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "Crea nuovo..."
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "Fork"
 
@@ -1178,6 +1523,12 @@ msgstr "Tag"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "Crea token d'accesso personale"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1196,6 +1547,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "Statistiche Cicliche"
 
@@ -1232,6 +1586,9 @@ msgstr "Dic"
 msgid "December"
 msgstr "Dicembre"
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "Definisci un patter personalizzato mediante la sintassi cron"
 
@@ -1264,8 +1621,8 @@ msgstr "Nome cartella"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
-msgstr "Annulla modifiche"
+msgid "Discard draft"
+msgstr ""
 
 msgid "Discover GitLab Geo."
 msgstr ""
@@ -1276,10 +1633,16 @@ msgstr "Chiudi l'introduzione alle Analisi Cicliche"
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "Non mostrare pi霉"
 
-msgid "Download"
+msgid "Done"
+msgstr ""
+
+msgid "Download"
 msgstr "Scarica"
 
 msgid "Download tar"
@@ -1306,9 +1669,15 @@ msgstr "Differenze"
 msgid "DownloadSource|Download"
 msgstr "Scarica"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "Modifica"
 
@@ -1318,12 +1687,54 @@ msgstr "Cambia programmazione della pipeline %{id}"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr "E-mail"
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr "Errore durante il fetch degli ambienti."
 
@@ -1378,9 +1789,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1447,12 +1870,45 @@ msgstr "Esplora progetti"
 msgid "Explore public groups"
 msgstr "Esplora gruppi pubblici"
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "Impossibile cambiare owner"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "Impossibile rimuovere la pipeline pianificata"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr "Feb"
 
@@ -1468,6 +1924,12 @@ msgstr "Nome file"
 msgid "Files"
 msgstr "Files"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "Filtra per messaggio di commit"
 
@@ -1477,12 +1939,21 @@ msgstr "Trova in percorso"
 msgid "Find file"
 msgstr "Trova file"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "Primo"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "Push di"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "Fork"
@@ -1494,15 +1965,24 @@ msgstr "Fork da"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr "Fork da %{project_name} (eliminato)"
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr "Formato"
 
+msgid "From %{provider_title}"
+msgstr ""
+
 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 "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr "Chiavi GPG"
 
@@ -1512,12 +1992,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1563,21 +2049,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1590,9 +2103,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1623,6 +2148,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1632,12 +2160,30 @@ msgstr "Le informazioni sullo stato dell'archiviazione Git 猫 stata ripristinata
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr "Sezione Gitlab Runner"
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "Vai il tuo fork"
 
@@ -1650,6 +2196,24 @@ msgstr "L'autenticazione Google non 猫 %{link_to_documentation}. Richiedi al tuo
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr "Blocca la condivisione di un progetto di %{group} con altri gruppi"
 
@@ -1686,9 +2250,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1719,6 +2280,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "Verifica stato"
 
@@ -1737,6 +2301,15 @@ msgstr ""
 msgid "HealthCheck|Unhealthy"
 msgstr ""
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1748,9 +2321,39 @@ msgstr "Cronologia"
 msgid "Housekeeping successfully started"
 msgstr "Housekeeping iniziato con successo"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "Importa repository"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1760,6 +2363,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr ""
 
@@ -1771,6 +2377,9 @@ msgstr[1] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1810,6 +2419,9 @@ msgstr "Gen"
 msgid "January"
 msgstr "Gennaio"
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr "Lug"
 
@@ -1822,6 +2434,9 @@ msgstr "Giu"
 msgid "June"
 msgstr "Giugno"
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1840,6 +2455,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1849,12 +2467,30 @@ msgstr "Disabilitato"
 msgid "LFSStatus|Enabled"
 msgstr "Abilitato"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "L'ultimo %d giorno"
@@ -1887,6 +2523,12 @@ msgstr ""
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "Leggi di pi霉 su"
 
@@ -1905,6 +2547,12 @@ msgstr "Abbandona il progetto"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1914,7 +2562,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1923,15 +2571,30 @@ msgstr "Bloccato"
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr "Login"
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr "Mar"
 
@@ -1953,7 +2616,7 @@ msgstr "Mediano"
 msgid "Members"
 msgstr "Membri"
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1974,6 +2637,81 @@ msgstr ""
 msgid "Messages"
 msgstr "Messaggi"
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1989,12 +2727,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "aggiungi una chiave SSH"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr "Monitoraggio"
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr "Ulteriori informazioni sono disponibili | qui"
 
@@ -2066,6 +2825,9 @@ msgstr "Nuovo sottogruppo"
 msgid "New tag"
 msgstr "Nuovo tag"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2084,15 +2846,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "Nessuna Repository"
 
 msgid "No schedules"
 msgstr "Nessuna pianificazione"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr "Nessuno"
 
@@ -2102,12 +2864,33 @@ msgstr ""
 msgid "Not available"
 msgstr "Non disponibile"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "Dati insufficienti "
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "Notifica eventi"
 
@@ -2192,6 +2975,12 @@ msgstr "Ottobre"
 msgid "OfSearchInADropdown|Filter"
 msgstr "Filtra"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr "Solo i membri del progetto possono commentare."
 
@@ -2210,12 +2999,21 @@ msgstr "Si apre in una nuova finestra"
 msgid "Options"
 msgstr "Opzioni"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "Panoramica"
 
 msgid "Owner"
 msgstr "Proprietario"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr "Ultima 禄"
 
@@ -2228,9 +3026,21 @@ msgstr "Precedente"
 msgid "Pagination|芦 First"
 msgstr "芦 Prima"
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "Password"
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "Pipeline"
 
@@ -2312,9 +3122,54 @@ msgstr "Pipeline per l'ultimo anno"
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "tutto"
 
@@ -2327,6 +3182,9 @@ msgstr "con stadio"
 msgid "Pipeline|with stages"
 msgstr "con pi霉 stadi"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2336,6 +3194,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr "Preferenze"
 
@@ -2348,6 +3212,9 @@ msgstr "Privato - L'accesso al progetto deve essere fornito esplicitamente ad og
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr "Privato - Il gruppo e i suoi progetti possono essere visualizzati solo dai membri."
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr "Profilo"
 
@@ -2387,6 +3254,9 @@ msgstr "Il tuo account 猫 attualmente proprietario in questi gruppi:"
 msgid "Profiles|your account"
 msgstr "il tuo account"
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2411,9 +3281,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr "Dettagli del progetto"
 
@@ -2510,37 +3377,88 @@ msgstr "Siamo spiacenti, non ci sono progetti che corrispondono alla tua ricerca
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr "Questa feature richiede il supporto del localStorage del browser"
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr "Di default, Prometheus 猫 in ascolto su 鈥榟ttp://localhost:9090鈥�. Non 猫 consigliabile cambiare l'indirizzo e la porta di default in quanto ci貌 potrebbe influenzare o causare conflitto con altri servizi in esecuzione sul server GitLab."
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr "Ricerco e configuro le metriche..."
 
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
 msgid "PrometheusService|Metrics"
 msgstr "Metriche"
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
-msgstr "Le metriche sono configurate automaticamente e monitorate sulla base di una libreria di metriche di esportatori popolari."
-
 msgid "PrometheusService|Missing environment variable"
 msgstr "Variabile d'ambiente mancante"
 
-msgid "PrometheusService|Monitored"
-msgstr "Monitorato"
-
 msgid "PrometheusService|More information"
 msgstr "Ulteriori informazioni"
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr "Nessuna metrica 猫 stata monitorata. Per iniziare a monitorare, rilascia su un ambiente."
+msgid "PrometheusService|New metric"
+msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2558,6 +3476,12 @@ msgstr ""
 msgid "Push events"
 msgstr ""
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2570,6 +3494,9 @@ msgstr "Vedi altro"
 msgid "Readme"
 msgstr "Leggimi"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "Branches"
 
@@ -2603,6 +3530,9 @@ msgstr "Richieste di Merge Correlate"
 msgid "Related Merged Requests"
 msgstr "Richieste di Merge Completate Correlate"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "Ricordamelo pi霉 tardi"
 
@@ -2618,9 +3548,24 @@ msgstr "Rimuovi progetto"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "Richiedi accesso"
 
@@ -2633,6 +3578,12 @@ msgstr ""
 msgid "Reset runners registration token"
 msgstr ""
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2644,6 +3595,36 @@ msgstr "Ripristina questo commit"
 msgid "Revert this merge request"
 msgstr "Ripristina questa richiesta di merge"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "Chiavi SSH"
 
@@ -2659,6 +3640,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "Pianifica una nuova Pipeline"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2668,6 +3652,9 @@ msgstr "Pianificazione pipelines"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "Ricerca branches e tags"
 
@@ -2689,12 +3676,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "Seleziona formato d'archivio"
 
 msgid "Select a timezone"
 msgstr "Seleziona una timezone"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2707,6 +3700,9 @@ msgstr "Seleziona una branch di destinazione"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr "Set"
 
@@ -2719,17 +3715,35 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 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 default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "Configura Koding"
 
-msgid "Set up auto deploy"
-msgstr "Configura il rilascio automatico"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "imposta una password"
@@ -2737,6 +3751,12 @@ msgstr "imposta una password"
 msgid "Settings"
 msgstr "Impostazioni"
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2746,6 +3766,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2769,6 +3792,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr "Snippet"
 
@@ -2776,15 +3811,15 @@ msgid "Something went wrong on our end"
 msgstr ""
 
 msgid "Something went wrong on our end."
-msgstr "Si 猫 verificato un problema con il nostro server."
+msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2898,6 +3933,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "Codice Sorgente"
 
@@ -2907,12 +3945,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr "Star"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2922,6 +3969,15 @@ msgstr "inizia una %{new_merge_request} con queste modifiche"
 msgid "Start the Runner!"
 msgstr ""
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2934,11 +3990,17 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr "Cambia branch/tag"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -3017,6 +4079,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "Branch di destinazione"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3032,15 +4097,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "La relazione del fork 猫 stata rimossa"
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "Lo stadio di Issue mostra il tempo che impiega un issue ad esser correlato ad una Milestone, o ad esser aggiunto ad una tua Lavagna. Inizia la creazione di problemi per visualizzare i dati in questo stadio."
 
@@ -3053,12 +4127,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
 msgid "The phase of the development lifecycle."
 msgstr "Il ciclo vitale della fase di sviluppo."
 
 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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "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"
 
@@ -3071,9 +4151,18 @@ 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 repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "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 roadmap shows the progress of your epics along a timeline"
+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 "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"
 
@@ -3104,6 +4193,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr ""
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3167,12 +4259,18 @@ msgstr "Questo significa che non 猫 possibile effettuare push di codice fino a c
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3185,6 +4283,12 @@ 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 between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3336,7 +4440,43 @@ msgstr[1] "mins"
 msgid "Time|s"
 msgstr "s"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
+msgstr "Titolo"
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
 msgstr ""
 
 msgid "Todo"
@@ -3354,19 +4494,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "Tempo Totale"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "Tempo totale di test per tutti i commits/merges"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3378,22 +4515,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3429,6 +4560,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "clicca per caricare"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3438,21 +4575,51 @@ msgstr ""
 msgid "Use your global notification setting"
 msgstr "Usa le tue impostazioni globali "
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "Mostra la richieste di merge aperte"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "Interno"
 
@@ -3477,12 +4644,21 @@ msgstr "Non ci sono sufficienti dati da mostrare su questo stadio"
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3597,22 +4773,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "Ritira richiesta d'accesso"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "Stai per rimuovere il gruppo %{group_name}. I gruppi rimossi NON POSSONO esser ripristinati! Sei ASSOLUTAMENTE sicuro?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "Stai per rimuovere %{project_name_with_namespace}. I progetti rimossi NON POSSONO essere ripristinati! Sei assolutamente sicuro?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3630,12 +4818,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "Hai raggiunto il tuo limite di progetto"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "Devi accedere per porre una star al progetto"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "Necessiti del permesso."
 
@@ -3666,9 +4866,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3681,6 +4902,14 @@ msgstr "Il tuo nome"
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3690,13 +4919,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3708,7 +4955,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3723,25 +4970,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3749,14 +5011,84 @@ msgid_plural "days"
 msgstr[0] "giorno"
 msgstr[1] "giorni"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3781,15 +5113,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3823,6 +5167,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3871,12 +5218,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3906,6 +5259,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3915,6 +5271,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3924,3 +5283,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 31c4422c928993c54c45404514b286f91013d3e4..b526b0ba20296ad0cd0404311276411d34b25bc5 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:00-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:36-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Japanese\n"
 "Language: ja_JP\n"
@@ -27,6 +27,10 @@ msgid "%d commit behind"
 msgid_plural "%d commits behind"
 msgstr[0] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -39,10 +43,17 @@ msgid "%d merge request"
 msgid_plural "%d merge requests"
 msgstr[0] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "銉戙儠銈┿兗銉炪兂銈逛綆涓嬨倰閬裤亼銈嬨仧銈� %s 鍊嬨伄銈炽儫銉冦儓銈掔渷鐣ャ仐銇俱仐銇熴€�"
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -50,6 +61,12 @@ msgid "%{count} participant"
 msgid_plural "%{count} participants"
 msgstr[0] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -59,6 +76,9 @@ msgstr ""
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr ""
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] ""
@@ -85,15 +105,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "CI銇仱銇勩仸銇偘銉┿儠"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "鑷嫊銉囥儣銉偆銇仱銇勩仸"
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -103,6 +138,9 @@ msgstr ""
 msgid "Account"
 msgstr ""
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "鏈夊姽"
 
@@ -121,9 +159,15 @@ msgstr "璨㈢尞鑰呭悜銇戙偓銈ゃ儔銈掕拷鍔�"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "銉┿偆銈汇兂銈广倰杩藉姞"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "鏂拌銉囥偅銉偗銉堛儶銈掕拷鍔�"
 
@@ -142,12 +186,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -160,9 +237,33 @@ msgstr ""
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -172,6 +273,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -181,12 +288,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -199,12 +330,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr ""
 
@@ -223,21 +363,24 @@ msgstr "銈€兗銈偆銉栨笀銇裤儣銉偢銈с偗銉堬紒锛堛儸銉濄偢銉堛儶銉笺伅瑾伩
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "銇撱伄銉戙偆銉椼儵銈ゃ兂銈广偙銈搞儱銉笺儷銈掑墛闄ゃ仐銇俱仚銇嬶紵"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr ""
-
 msgid "Are you sure you want to reset registration token?"
 msgstr ""
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr ""
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr ""
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -250,6 +393,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -271,6 +423,12 @@ msgstr ""
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -295,7 +453,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -307,6 +471,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -361,12 +534,9 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "銉栥儵銉炽儊"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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> 銉栥儵銉炽儊銇屼綔鎴愩仌銈屻伨銇椼仧銆傝嚜鍕曘儑銉椼儹銈ゃ倰瑷畾銇欍倠銇伅銆丟itLab CI Yaml 銉嗐兂銉椼儸銉笺儓銈掗伕鎶炪仐銇︺€佸鏇淬倰銈炽儫銉冦儓銇椼仸銇忋仩銇曘亜銆� %{link_to_autodeploy_doc}"
@@ -389,6 +559,15 @@ msgstr "銉栥儵銉炽儊銈掑垏鏇�"
 msgid "Branches"
 msgstr "銉栥儵銉炽儊"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -434,12 +613,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -485,30 +691,45 @@ msgstr "銉曘偂銈ゃ儷銈掕〃绀�"
 msgid "Browse files"
 msgstr "銉曘偂銈ゃ儷銈掕〃绀�"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "浣滆€�"
 
 msgid "CI / CD"
 msgstr ""
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr "銈儯銉炽偦銉�"
 
-msgid "Cancel edit"
+msgid "Cannot be merged automatically"
 msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "銉斻儍銈厛銉栥儵銉炽儊:"
 
@@ -563,6 +784,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -659,9 +886,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -713,6 +952,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -758,12 +1000,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -782,6 +1033,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -812,10 +1066,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -878,6 +1132,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -905,6 +1162,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -950,6 +1210,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr ""
 
@@ -957,6 +1223,10 @@ msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "銈炽儫銉冦儓"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+
 msgid "Commit Message"
 msgstr ""
 
@@ -969,6 +1239,9 @@ msgstr "銈炽儫銉冦儓銉°儍銈汇兗銈�"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "銈炽儫銉冦儓"
 
@@ -1014,6 +1287,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1029,9 +1308,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1077,6 +1392,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "璨㈢尞鑰呭悜銇戙偓銈ゃ儔"
 
@@ -1110,6 +1431,9 @@ msgstr "銈儶銉冦儣銉溿兗銉夈伀URL銈掋偝銉斻兗"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "銈炽儫銉冦儓銇甋HA銈掋偗銉儍銉椼儨銉笺儔銇偝銉斻兗"
 
@@ -1122,14 +1446,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "鏂拌銉囥偅銉偗銉堛儶銈掍綔鎴�"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "%{protocol} 銇с儣銉冦偡銉ャ倓銉椼儷銇欍倠銇熴倎銇亗銇仧鍊嬩汉鐢ㄣ偄銈偦銈广儓銉笺偗銉炽倰浣滄垚"
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "銉囥偅銉偗銉堛儶銈掍綔鎴�"
 
-msgid "Create empty bare repository"
-msgstr "绌恒伄bare銉儩銈搞儓銉兗銈掍綔鎴�"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1137,12 +1470,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "銉炪兗銈搞儶銈偍銈广儓銈掍綔鎴�"
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1158,6 +1497,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "鏂拌浣滄垚"
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "銉曘偐銉笺偗"
 
@@ -1167,6 +1509,12 @@ msgstr "銈裤偘"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "鍊嬩汉鐢ㄣ偄銈偦銈广儓銉笺偗銉炽倰浣滄垚"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1185,6 +1533,9 @@ 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 "\"銈偣銈裤儬\" 銇€氱煡銉儥銉伄鍩烘湰銇� \"鍙傚姞\" 銇ㄥ悓銇樸仹銇欍€傘伨銇熴€併偒銈广偪銉犻€氱煡銇ō瀹氥仚銈嬨亾銇ㄣ仹閬告姙銇椼仧銈偣銈裤儬銈ゃ儥銉炽儓銇€氱煡銈掑彈銇戝彇銈嬨亾銇ㄣ倐銇с亶銇俱仚銆傘倐銇c仺瑭炽仐銇忕煡銈娿仧銇勫牬鍚堛伅 %{notification_link} 銈掕銇︺亸銇犮仌銇勩€�"
 
+msgid "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "銈点偆銈儷鍒嗘瀽"
 
@@ -1221,6 +1572,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "Cron 妲嬫枃銇с偒銈广偪銉犮仾銉戙偪銉笺兂銈掓寚瀹氥仚銈�"
 
@@ -1252,7 +1606,7 @@ msgstr "銉囥偅銉偗銉堛儶鍚�"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
+msgid "Discard draft"
 msgstr ""
 
 msgid "Discover GitLab Geo."
@@ -1264,9 +1618,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "娆″洖銇嬨倝琛ㄧず銇椼仾銇�"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "銉€銈︺兂銉兗銉�"
 
@@ -1294,9 +1654,15 @@ msgstr "銉椼儸銉笺兂宸垎"
 msgid "DownloadSource|Download"
 msgstr "銉€銈︺兂銉兗銉�"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "绶ㄩ泦"
 
@@ -1306,12 +1672,54 @@ msgstr "銉戙偆銉椼儵銈ゃ兂銈广偙銈搞儱銉笺儷 %{id} 銈掔法闆�"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1366,9 +1774,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1435,12 +1855,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "銈兗銉娿兗銈掑鏇淬仹銇嶃伨銇涖倱銇с仐銇�"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "銉戙偆銉椼儵銈ゃ兂銈广偙銈搞儱銉笺儷銈掑墛闄ゃ仹銇嶃伨銇涖倱銇с仐銇�"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1456,6 +1909,12 @@ msgstr ""
 msgid "Files"
 msgstr "銉曘偂銈ゃ儷"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "銈炽儫銉冦儓銉°儍銈汇兗銈搞仹绲炪倞杈笺伩"
 
@@ -1465,12 +1924,21 @@ msgstr "銉戙偣銇ф绱�"
 msgid "Find file"
 msgstr "銉曘偂銈ゃ儷銈掓绱�"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "鍒濆洖"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "銉椼儍銈枫儱銇椼仧浜�"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "銉曘偐銉笺偗"
@@ -1481,15 +1949,24 @@ msgstr "銉曘偐銉笺偗鍏�"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr "瑾查銇岀櫥閷层仌銈屻仸銇嬨倝銉椼儹銉€銈偡銉с兂銇儑銉椼儹銈ゃ仌銈屻倠銇俱仹"
 
 msgid "From merge request merge until deploy to production"
 msgstr "銉炪兗銈搞儶銈偍銈广儓銇屻優銉笺偢銇曘倢銇︺亱銈夈儣銉儉銈偡銉с兂銇儑銉椼儹銈ゃ仌銈屻倠銇俱仹"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1499,12 +1976,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1550,21 +2033,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1577,9 +2087,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1610,6 +2132,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1619,12 +2144,30 @@ msgstr ""
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr ""
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "鑷垎銇儠銈┿兗銈伕绉诲嫊"
 
@@ -1637,6 +2180,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1673,9 +2234,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1706,6 +2264,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr ""
 
@@ -1724,6 +2285,15 @@ msgstr ""
 msgid "HealthCheck|Unhealthy"
 msgstr ""
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1734,9 +2304,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr "銉忋偊銈广偔銉笺償銉炽偘銇甯搞伀璧峰嫊銇椼伨銇椼仧銆�"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "銉儩銈搞儓銉兗銈掋偆銉炽儩銉笺儓"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1746,6 +2346,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr ""
 
@@ -1756,6 +2359,9 @@ msgstr[0] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1795,6 +2401,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1807,6 +2416,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1825,6 +2437,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1834,12 +2449,30 @@ msgstr "鐒″姽"
 msgid "LFSStatus|Enabled"
 msgstr "鏈夊姽"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "閬庡幓%d鏃ラ枔"
@@ -1871,6 +2504,12 @@ msgstr ""
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "瑭炽仐銇忚銈嬶細"
 
@@ -1889,6 +2528,12 @@ msgstr "銉椼儹銈搞偋銈儓銈掗洟鑴�"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1898,7 +2543,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1907,15 +2552,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1937,7 +2597,7 @@ msgstr "涓ぎ鍊�"
 msgid "Members"
 msgstr ""
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1958,6 +2618,81 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1973,12 +2708,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "SSH 閸点倰杩藉姞"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr ""
 
@@ -2049,6 +2805,9 @@ msgstr ""
 msgid "New tag"
 msgstr "鏂拌銈裤偘"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2067,15 +2826,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "銉儩銈搞儓銉兗銇亗銈娿伨銇涖倱"
 
 msgid "No schedules"
 msgstr "銈广偙銈搞儱銉笺儷銇仐"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr ""
 
@@ -2085,12 +2844,33 @@ msgstr ""
 msgid "Not available"
 msgstr "鍒╃敤銇с亶銇俱仜銈�"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "銉囥兗銈夸笉瓒�"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "銈ゃ儥銉炽儓閫氱煡"
 
@@ -2175,6 +2955,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr "銉曘偅銉偪銉�"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2193,12 +2979,21 @@ msgstr ""
 msgid "Options"
 msgstr "銈儣銈枫儳銉�"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr "銈兗銉娿兗"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2211,9 +3006,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "銉戙偆銉椼儵銈ゃ兂"
 
@@ -2295,9 +3102,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "鍏ㄤ欢"
 
@@ -2310,6 +3162,9 @@ msgstr "銈广儐銉笺偢銇傘倞"
 msgid "Pipeline|with stages"
 msgstr "銈广儐銉笺偢銇傘倞"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2319,6 +3174,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2331,6 +3192,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr ""
 
@@ -2370,6 +3234,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2394,9 +3261,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr ""
 
@@ -2493,37 +3357,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2541,6 +3456,12 @@ msgstr ""
 msgid "Push events"
 msgstr ""
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2553,6 +3474,9 @@ msgstr "缍氥亶銈掕銈€"
 msgid "Readme"
 msgstr ""
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "銉栥儵銉炽儊"
 
@@ -2586,6 +3510,9 @@ msgstr "闁㈤€c仚銈嬨優銉笺偢銉偗銈ㄣ偣銉�"
 msgid "Related Merged Requests"
 msgstr "闁㈤€c仚銈嬨優銉笺偢銉偗銈ㄣ偣銉�"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "寰屻仹閫氱煡"
 
@@ -2601,9 +3528,24 @@ msgstr "銉椼儹銈搞偋銈儓銈掑墛闄�"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "銈€偗銈汇偣妯╅檺銈掋儶銈偍銈广儓銇欍倠"
 
@@ -2616,6 +3558,12 @@ msgstr ""
 msgid "Reset runners registration token"
 msgstr ""
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2626,6 +3574,36 @@ msgstr "銇撱伄銈炽儫銉冦儓銈掋儶銉愩兗銉�"
 msgid "Revert this merge request"
 msgstr "銇撱伄銉炪兗銈搞儶銈偍銈广儓銈掋儶銉愩兗銉�"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2641,6 +3619,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "鏂般仐銇勩儜銈ゃ儣銉┿偆銉炽伄銈广偙銈搞儱銉笺儷銈掍綔鎴�"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2650,6 +3631,9 @@ msgstr "銉戙偆銉椼儵銈ゃ兂銈广偙銈搞儱銉笺儶銉炽偘"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "銉栥儵銉炽儊銇俱仧銇偪銈般倰妞滅储"
 
@@ -2671,12 +3655,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "銈€兗銈偆銉栥伄銉曘偐銉笺優銉冦儓銈掗伕鎶�"
 
 msgid "Select a timezone"
 msgstr "銈裤偆銉犮偩銉笺兂銈掗伕鎶�"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2689,6 +3679,9 @@ msgstr "銈裤兗銈层儍銉堛儢銉┿兂銉併倰閬告姙"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2701,17 +3694,35 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "%{protocol} 銉椼儹銈炽儓銉祵鐢便仹銉椼儷銆併儣銉冦偡銉ャ仚銈嬨仧銈併伀銈€偒銈︺兂銉堛伄銉戙偣銉兗銉夈倰瑷畾銆�"
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "Koding 銈掕ō瀹�"
 
-msgid "Set up auto deploy"
-msgstr "鑷嫊銉囥儣銉偆銈掕ō瀹�"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "銉戙偣銉兗銉夈倰瑷畾"
@@ -2719,6 +3730,12 @@ msgstr "銉戙偣銉兗銉夈倰瑷畾"
 msgid "Settings"
 msgstr ""
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2728,6 +3745,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2750,6 +3770,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2759,13 +3791,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2879,6 +3911,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "銈姐兗銈广偝銉笺儔"
 
@@ -2888,12 +3923,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr "銈广偪銉笺倰浠樸亼銈�"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2903,6 +3947,15 @@ msgstr "銇撱伄澶夋洿銇� %{new_merge_request} 銈掍綔鎴愩仚銈�"
 msgid "Start the Runner!"
 msgstr ""
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2915,12 +3968,18 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr "銉栥儵銉炽儊銉汇偪銈板垏銈婃浛銇�"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "銈裤偘"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
 
 msgid "Tags"
 msgstr "銈裤偘"
@@ -2997,6 +4056,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "銈裤兗銈层儍銉堛儢銉┿兂銉�"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3012,15 +4074,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "銉曘偐銉笺偗銇儶銉兗銈枫儳銉炽亴鍓婇櫎銇曘倢銇俱仐銇熴€�"
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "瑾查銈广儐銉笺偢銇с伅銆佽椤屻亴鐧婚尣銇曘倢銇︺亱銈夈優銈ゃ儷銈广儓銉笺兂銇壊銈婂綋銇︺倝銈屻倠銇嬨€佽椤屻儨銉笺儔銇儶銈广儓銇拷鍔犮仌銈屻倠銇俱仹銇檪闁撱亴琛ㄧず銇曘倢銇俱仚銆傘亾銇儶銈广儓銇〃绀恒仚銈嬨伀銇椤屻倰鏈€鍒濄伀浣滄垚銇椼仸銇忋仩銇曘亜銆�"
 
@@ -3033,12 +4104,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "銉椼儹銉€銈偡銉с兂銈广儐銉笺偢銇с伅銆佽椤屻亴浣滄垚銇曘倢銇︺亱銈夈儣銉儉銈偡銉с兂銇搞儑銉椼儹銈ゃ仌銈屻倠銇俱仹銇檪闁撱亴琛ㄧず銇曘倢銇俱仚銆傘偄銈ゃ儑銈c偄銇檪鐐广亱銈夈儣銉儉銈偡銉с兂銇俱仹銇叏銈广儐銉笺偢銇屽畬浜嗐仐銇熴仺銇嶃伀鑷嫊鐨勩伀杩藉姞銇曘倢銇俱仚銆�"
 
@@ -3051,9 +4128,18 @@ msgstr "銉椼儹銈搞偋銈儓銇€併儹銈般偆銉炽仾銇椼伀瑾般仹銈傘偄銈偦銈广仹銇�
 msgid "The repository for this project does not exist."
 msgstr "銇撱伄銉椼儹銈搞偋銈儓銇儸銉濄偢銉堛儶銉笺伅銇傘倞銇俱仜銈撱€�"
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 "銈广儐銉笺偢銉炽偘銈广儐銉笺偢銇с伅銆併優銉笺偢銉偗銈ㄣ偣銉堛亴銉炪兗銈搞仌銈屻仸銇嬨倝銈炽兗銉夈亴銉椼儹銉€銈偡銉с兂鐠板銇儑銉椼儹銈ゃ仌銈屻倠銇俱仹銇檪闁撱亴琛ㄧず銇曘倢銇俱仚銆傘亾銇儑銉笺偪銇渶鍒濄伀銉椼儹銉€銈偡銉с兂銇儑銉椼儹銈ゃ仐銇熴仺銇嶃伀鑷嫊鐨勩伀杩藉姞銇曘倢銇俱仚銆�"
 
@@ -3084,6 +4170,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr ""
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3147,12 +4236,18 @@ msgstr "绌恒儸銉濄偢銉堛儶銉笺倰浣滄垚銇俱仧銇棦瀛樸儸銉濄偢銉堛儶銉笺倰銈ゃ兂
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3165,6 +4260,12 @@ msgstr "瑾查銇疅瑁呫亴闁嬪銇曘倢銈嬨伨銇с伄鏅傞枔"
 msgid "Time between merge request creation and merge/close"
 msgstr "銉炪兗銈搞儶銈偍銈广儓銇屼綔鎴愩仌銈屻仸銇嬨倝銉炪兗銈搞伨銇熴伅銈儹銉笺偤銇曘倢銈嬨伨銇с伄鏅傞枔"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3314,9 +4415,45 @@ msgstr[0] "鍒�"
 msgid "Time|s"
 msgstr "绉�"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3332,19 +4469,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "鍚堣▓鏅傞枔"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "銇欍伖銇︺伄銈炽儫銉冦儓/銉炪兗銈搞伄鍚堣▓銉嗐偣銉堟檪闁�"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3356,22 +4490,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3407,6 +4535,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "銈儶銉冦偗銇椼仸銈€儍銉椼儹銉笺儔"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3416,21 +4550,51 @@ msgstr ""
 msgid "Use your global notification setting"
 msgstr "鍏ㄤ綋閫氱煡瑷畾銈掑埄鐢�"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "銈兗銉椼兂銇優銉笺偢銉偗銈ㄣ偣銉堛倰琛ㄧず"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "鍐呴儴"
 
@@ -3455,12 +4619,21 @@ msgstr "銉囥兗銈夸笉瓒炽伄銇熴倎銆併亾銇偣銉嗐兗銈搞伄琛ㄧず銇仹銇嶃伨銇涖倱
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3575,22 +4748,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "銈€偗銈汇偣銉偗銈ㄣ偣銉堛倰鍙栥倞娑堛仚"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "%{group_name} 銈般儷銉笺儣銈掑墛闄ゃ仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆� 鍓婇櫎銇曘倢銇熴偘銉兗銉椼伅绲跺銇厓銇埢銇涖伨銇涖倱锛佹湰褰撱伀銈堛倣銇椼亜銇с仚銇嬶紵"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "%{project_name_with_namespace} 銉椼儹銈搞偋銈儓銈掑墛闄ゃ仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆傚墛闄ゃ仌銈屻仧銉椼儹銈搞偋銈儓銇刀瀵俱伀鍏冦伀銇埢銇涖伨銇涖倱锛佹湰褰撱伀銈堛倣銇椼亜銇с仚銇嬶紵"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "鍏冦伄銉椼儹銈搞偋銈儓 (%{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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3608,12 +4793,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "銉椼儹銈搞偋銈儓鏁般伄涓婇檺銇仈銇椼仸銇勩伨銇�"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "銉椼儹銈搞偋銈儓銇偣銈裤兗銈掋仱銇戙仧銇勫牬鍚堛伅銉偘銈ゃ兂銇椼仸銇忋仩銇曘亜"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "妯╅檺銇屽繀瑕併仹銇�"
 
@@ -3644,9 +4841,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3659,6 +4877,13 @@ msgstr "鍚嶅墠"
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3668,13 +4893,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3686,7 +4929,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3701,38 +4944,121 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "鏃�"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3757,15 +5083,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3799,6 +5137,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3847,12 +5188,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3881,6 +5228,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3890,6 +5240,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3899,3 +5252,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index 73909e6f6de313509f1a7135d5cd6a18047a3d85..91f68dfdee13e7e2decd3be6c04cf5aa519102e5 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:00-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:34-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Korean\n"
 "Language: ko_KR\n"
@@ -27,6 +27,10 @@ msgid "%d commit behind"
 msgid_plural "%d commits behind"
 msgstr[0] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -39,10 +43,17 @@ msgid "%d merge request"
 msgid_plural "%d merge requests"
 msgstr[0] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s 於旉皜 旎る皨鞚€ 靹彪姤 鞚挫妶毳� 氚╈頃橁赴 鞙勴暣 靸濍灥霅橃棃鞀惦媹雼�."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -50,6 +61,12 @@ msgid "%{count} participant"
 msgid_plural "%{count} participants"
 msgstr[0] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -59,6 +76,9 @@ msgstr "%{number_of_failures} / %{maximum_failures} 鞁ろ尐. GitLab 鞚€ 雼れ潓 
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} / %{maximum_failures} 鞁ろ尐. GitLab 鞚€ 鞛愲彊鞙茧 雼れ嫓 鞁滊弰頃橃 鞎婌姷雼堧嫟. 氍胳牅臧€ 頃搓舶霅橂┐ 鞝€鞛� 瓿店皠 鞝曤炒毳� 齑堦赴頇� 頃挫<靹胳殧. "
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] ""
@@ -85,15 +105,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "歆€靻嶌爜鞚� 韱淀暕鞐� 甏€頃� 攴鸽灅頂� 氇潓"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "鞛愲彊 氚绊彫 鞝曤炒"
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -103,6 +138,9 @@ msgstr "鞓る彊鞛戩鞚� 鞝€鞛リ车臧勳棎 雽€頃� 鞝戧芳鞚� 氤店惮 鞛戩梾鞚� 鞙勴暣
 msgid "Account"
 msgstr ""
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "頇滌劚"
 
@@ -121,9 +159,15 @@ msgstr "旮办棳 臧€鞚措摐 於旉皜"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "霛检澊靹犾姢 於旉皜"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "靸� 霐旊爥韱犽Μ 於旉皜"
 
@@ -142,12 +186,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -160,9 +237,33 @@ msgstr "鞝勳泊"
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -172,6 +273,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -181,12 +288,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -199,12 +330,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr ""
 
@@ -223,21 +363,24 @@ msgstr "頂勲鞝濏姼臧€ 氤搓磤霅橃棃鞀惦媹雼�! 鞝€鞛レ唽電� 鞚疥赴毵� 臧€電ロ暕
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "鞚� 韺岇澊頂勲澕鞚� 鞀れ紑欹挫潉 靷牅 頃橃嫓瓴犾姷雼堦箤?"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "氤€瓴� 雮挫毄鞚� 旆唽頃橃嫓瓴犾姷雼堦箤?"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "霌彪 韱犿伆鞚� 齑堦赴頇� 頃橃嫓瓴犾姷雼堦箤?"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "項姢 觳错伂 韱犿伆鞚� 齑堦赴頇� 頃橃嫓瓴犾姷雼堦箤?"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr "頇曥嫟頃╇媹旯�?"
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -250,6 +393,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -271,6 +423,12 @@ msgstr ""
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -295,7 +453,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -307,6 +471,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -361,12 +534,9 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "敫岆灉旃�"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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}"
@@ -389,6 +559,15 @@ msgstr "敫岆灉旃� 氤€瓴�"
 msgid "Branches"
 msgstr "敫岆灉旃�"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -434,12 +613,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -485,30 +691,45 @@ msgstr "韺岇澕 彀眷晞氤搓赴"
 msgid "Browse files"
 msgstr "韺岇澕 彀眷晞氤搓赴"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "鞛戩劚鞛�"
 
 msgid "CI / CD"
 msgstr ""
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr "旆唽"
 
-msgid "Cancel edit"
+msgid "Cannot be merged automatically"
 msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "敫岆灉旃橃棎靹� Pick"
 
@@ -563,6 +784,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -659,9 +886,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -713,6 +952,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -758,12 +1000,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -782,6 +1033,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -812,10 +1066,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -878,6 +1132,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -905,6 +1162,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -950,6 +1210,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr ""
 
@@ -957,6 +1223,10 @@ msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "旎る皨"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+
 msgid "Commit Message"
 msgstr ""
 
@@ -969,6 +1239,9 @@ msgstr "旎る皨 氅旍嫓歆€"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "旎る皨"
 
@@ -1014,6 +1287,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1029,9 +1308,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1077,6 +1392,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "旮办棳鞐� 雽€頃� 鞎堧偞"
 
@@ -1110,6 +1431,9 @@ msgstr "URL鞚� 韥措氤措摐鞐� 氤奠偓"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "旎る皨鞚� SHA毳� 韥措氤措摐搿� 氤奠偓頃╇媹雼�"
 
@@ -1122,14 +1446,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "靸� 霐旊爥韱犽Μ 毵岆摛旮�"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "%{protocol}鞚� (毳�) 韱淀暣 Pull 頃橁卑雮� Push 頃� 臧滌澑 鞎§劯鞀� 韱犿伆鞚� 毵岆摐鞁嫓鞓�."
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "霐旊爥韱犽Μ 毵岆摛旮�"
 
-msgid "Create empty bare repository"
-msgstr "牍� bare 鞝€鞛レ唽 毵岆摛旮�"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1137,12 +1470,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "毹胳 毽€橃姢韸� 毵岆摛旮�"
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1158,6 +1497,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "靸堧 毵岆摛旮� ..."
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "韽伂"
 
@@ -1167,6 +1509,12 @@ msgstr "韮滉犯"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "臧滌澑 鞎§劯鞀� 韱犿伆 毵岆摛旮�"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1185,6 +1533,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr ""
 
@@ -1221,6 +1572,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "cron 甑鞚� 靷毄頃橃棳 靷毄鞛� 鞝曥潣 韺劥 鞝曥潣"
 
@@ -1252,8 +1606,8 @@ msgstr "霐旊爥韱犽Μ 鞚措"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
-msgstr "氤€瓴� 雮挫毄 旆唽"
+msgid "Discard draft"
+msgstr ""
 
 msgid "Discover GitLab Geo."
 msgstr ""
@@ -1264,9 +1618,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "雼れ嫓 響滌嫓頃橃 鞎婌潓"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "雼れ毚搿滊摐"
 
@@ -1294,9 +1654,15 @@ msgstr "Plain Diff"
 msgid "DownloadSource|Download"
 msgstr "雼れ毚搿滊摐"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "韼胳"
 
@@ -1306,12 +1672,54 @@ msgstr "韺岇澊頂勲澕鞚� 鞀れ紑欷� 韼胳 %{id}"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1366,9 +1774,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1435,12 +1855,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "靻岇湢鞛愲ゼ 氤€瓴巾晿歆€ 氇豁枅鞀惦媹雼�"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "韺岇澊頂勲澕鞚� 鞀れ紑欷勳潉 鞝滉卑頃橃 氇豁枅鞀惦媹雼�."
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1456,6 +1909,12 @@ msgstr ""
 msgid "Files"
 msgstr "韺岇澕"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "旎る皨 氅旍嫓歆€搿� 頃勴劙"
 
@@ -1465,12 +1924,21 @@ msgstr "瓴诫搿� 彀娟赴"
 msgid "Find file"
 msgstr "韺岇澕 彀娟赴"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "觳橃潓"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "響胳嫓頃� 靷毄鞛�"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "韽伂"
@@ -1481,15 +1949,24 @@ msgstr "韽伂頃� 靷毄鞛�"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr "鞚挫妶 靸濎劚鞐愳劀 頂勲雿曥厴 氚绊彫旯岇"
 
 msgid "From merge request merge until deploy to production"
 msgstr "毹胳 毽€橃姢韸� 毹胳鞐愳劀 頂勲雿曥厴 頇橁步鞐� 氚绊彫旯岇"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1499,12 +1976,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1550,21 +2033,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1577,9 +2087,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1610,6 +2132,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1619,12 +2144,30 @@ msgstr "git storage 靸來儨 鞝曤炒臧€ 齑堦赴頇旊悩鞐堨姷雼堧嫟."
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr "GitLab Runner 靹轨厴"
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "雼轨嫚鞚� 韽伂搿� 鞚措彊頃橃劯鞖�"
 
@@ -1637,6 +2180,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1673,9 +2234,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1706,6 +2264,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "項姢 觳错伂"
 
@@ -1724,6 +2285,15 @@ msgstr " 項姢 氍胳牅臧€ 氚滉铂霅橃 鞎婌晿鞀惦媹雼�."
 msgid "HealthCheck|Unhealthy"
 msgstr "牍勳爼靸�"
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1734,9 +2304,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr "Housekeeping鞚� 靹标车鞝侅溂搿� 鞁滌瀾霅橃棃鞀惦媹雼�"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "鞝€鞛レ唽 臧€鞝� 鞓り赴"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1746,6 +2346,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "GitLab CI 鞕€ 順疙櫂霅橂姅 Runner 靹れ箻"
 
@@ -1756,6 +2359,9 @@ msgstr[0] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1795,6 +2401,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1807,6 +2416,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1825,6 +2437,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1834,12 +2449,30 @@ msgstr "Disabled"
 msgid "LFSStatus|Enabled"
 msgstr "Enabled"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "斓滉芳 %d 鞚�"
@@ -1871,6 +2504,12 @@ msgstr "at"
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "雿� 鞛愳劯頌� 鞎岇晞氤搓赴"
 
@@ -1889,6 +2528,12 @@ msgstr "頂勲鞝濏姼鞐愳劀 雮橁皜旮�"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1898,7 +2543,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1907,15 +2552,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1937,7 +2597,7 @@ msgstr "欷戩暀臧�"
 msgid "Members"
 msgstr ""
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1958,6 +2618,81 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1973,12 +2708,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "SSH 韨� 於旉皜"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr "鞐赴"
 
@@ -2049,6 +2805,9 @@ msgstr ""
 msgid "New tag"
 msgstr "靸� 韮滉犯 "
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2067,15 +2826,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "鞝€鞛レ唽 鞐嗢潓"
 
 msgid "No schedules"
 msgstr "鞚检爼 鞐嗢潓"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr ""
 
@@ -2085,12 +2844,33 @@ msgstr ""
 msgid "Not available"
 msgstr "靷毄頃� 靾� 鞐嗢潓"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "雿办澊韯瓣皜 於╇秳頃橃 鞎婌姷雼堧嫟."
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "鞎岆 鞚措菠韸�"
 
@@ -2175,6 +2955,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr "頃勴劙"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2193,12 +2979,21 @@ msgstr ""
 msgid "Options"
 msgstr "鞓奠厴 "
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr "靻岇湢鞛�"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2211,9 +3006,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "韺岇澊頂勲澕鞚�"
 
@@ -2295,9 +3102,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "氇憪"
 
@@ -2310,6 +3162,9 @@ msgstr "鞀ろ厡鞚挫"
 msgid "Pipeline|with stages"
 msgstr "鞀ろ厡鞚挫"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2319,6 +3174,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2331,6 +3192,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr ""
 
@@ -2370,6 +3234,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2394,9 +3261,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr "頂勲鞝濏姼 靸侅劯"
 
@@ -2493,37 +3357,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2541,6 +3456,12 @@ msgstr ""
 msgid "Push events"
 msgstr "響胳壃 鞚措菠韸�"
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2553,6 +3474,9 @@ msgstr "雿� 鞚疥赴"
 msgid "Readme"
 msgstr ""
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "敫岆灉旃�"
 
@@ -2586,6 +3510,9 @@ msgstr "甏€霠� 毹胳 毽€橃姢韸�"
 msgid "Related Merged Requests"
 msgstr "甏€霠� 毹胳 毽€橃姢韸�"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "雮橃鞐� 雼れ嫓 鞎岆"
 
@@ -2601,9 +3528,24 @@ msgstr "頂勲鞝濏姼 靷牅"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "鞎§劯鞀� 鞖旍箔"
 
@@ -2616,6 +3558,12 @@ msgstr "項姢 觳错伂 鞝戧芳 韱犿伆 齑堦赴頇�"
 msgid "Reset runners registration token"
 msgstr "runner 霌彪 韱犿伆 齑堦赴頇�"
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2626,6 +3574,36 @@ msgstr "鞚� 旎る皨 霅橂弻毽赴"
 msgid "Revert this merge request"
 msgstr "鞚� 毹胳 毽€橃姢韸� 霅橂弻毽赴"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2641,6 +3619,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "靸堧鞖� 韺岇澊頂勲澕鞚� 鞀れ紑欷� 鞛£赴"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2650,6 +3631,9 @@ msgstr "韺岇澊頂勲澕鞚� 鞀れ紑欷勲"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "敫岆灉旃� 氚� 韮滉犯 瓴€靸�"
 
@@ -2671,12 +3655,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "鞎勳勾鞚措笇 韽Х 靹犿儩"
 
 msgid "Select a timezone"
 msgstr "鞁滉皠雽€ 靹犿儩"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2689,6 +3679,9 @@ msgstr "雽€靸� 敫岆灉旃� 靹犿儩"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2701,17 +3694,35 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "%{protocol} 頂勲韱犾綔鞚� 韱淀暣 Pull 頃橁卑雮� Push頃橂牑氅� 瓿勳爼鞐� 韺姢鞗岆摐毳� 靹れ爼頃橃嫮鞁滌槫."
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "Koding 靹れ爼"
 
-msgid "Set up auto deploy"
-msgstr "鞛愲彊 氚绊彫 靹れ爼"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "韺姢鞗岆摐 靹れ爼"
@@ -2719,6 +3730,12 @@ msgstr "韺姢鞗岆摐 靹れ爼"
 msgid "Settings"
 msgstr ""
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2728,6 +3745,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2750,6 +3770,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2759,13 +3791,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2879,6 +3911,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "靻岇姢 旖旊摐"
 
@@ -2888,12 +3923,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "Runner 靹れ爼 欷� 雼れ潓 URL鞚� 歆€鞝曧晿靹胳殧."
 
 msgid "StarProject|Star"
 msgstr "氤勴憸"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2903,6 +3947,15 @@ msgstr "鞚� 氤€瓴� 靷暛鞙茧 %{new_merge_request} 鞚� 鞁滌瀾頃橃嫮鞁滌槫."
 msgid "Start the Runner!"
 msgstr "Runner 鞁滌瀾!"
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2915,12 +3968,18 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr "鞀れ渼旃� 敫岆灉旃�/韮滉犯"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "韮滉犯"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
 
 msgid "Tags"
 msgstr "韮滉犯 "
@@ -2997,6 +4056,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "雽€靸� 敫岆灉旃�"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3012,15 +4074,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "Coding Stage電� 觳� 氩堨Ц 旎る皨鞐愳劀攵€韯� 毹胳 毽€橃姢韸� 靸濎劚旯岇鞚� 鞁滉皠鞚� 氤挫棳欷嶋媹雼�. 觳� 氩堨Ц 毹胳 毽€橃姢韸胳潉 靸濎劚頃橂┐ 雿办澊韯瓣皜 鞛愲彊鞙茧 鞐赴鞐� 於旉皜霅╇媹雼�."
 
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "頃措嫻 雼硠鞐愳劀 靾橃 霅� 雿办澊韯瓣皜 鞚措菠韸� 氇潓鞐� 於旉皜霅橃棃鞀惦媹雼�."
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "韽伂 甏€瓿勱皜 鞝滉卑霅橃棃鞀惦媹雼�."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "鞚挫妶 雼硠鞐愲姅 鞚挫妶毳� 鞛戩劚頃橃棳 毵堨澕鞀ろ啢鞙茧 歆€鞝曧晿電� 雿� 瓯鸽Μ電� 鞁滉皠 霕愲姅 鞚挫妶 氤措摐鞚� 氇╇鞐� 鞚挫妶毳� 於旉皜頃橂姅 鞁滉皠鞚� 響滌嫓霅╇媹雼�. 鞚� 雼硠鞚� 雿办澊韯半ゼ 氤搓赴 鞙勴暣靹滊姅 鞚挫妶毳� 毹检爛 鞛戩劚頃挫暭 頃╇媹雼�."
 
@@ -3033,12 +4104,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "頂勲雿曥厴 雼硠鞐愳劀電� 氍胳牅毳� 毵岆摛瓿� 旖旊摐毳� 頂勲雿曥厴 頇橁步鞙茧 氚绊彫頃橂姅 雿� 瓯鸽Μ電� 齑� 鞁滉皠鞚� 氤挫棳欷嶋媹雼�. 靸濎偘欤缄赴鞐� 雽€頃� 鞕勳爠頃� 鞎勳澊霐旍柎毳� 鞏混潃 頉勳棎電� 雿办澊韯瓣皜 鞛愲彊鞙茧 於旉皜霅╇媹雼�."
 
@@ -3051,9 +4128,18 @@ msgstr "鞚� 頂勲鞝濏姼電� 鞚胳鞐嗢澊 鞎§劯鞀� 頃� 靾� 鞛堨姷雼堧嫟."
 msgid "The repository for this project does not exist."
 msgstr "鞚� 頂勲鞝濏姼鞚� 鞝€鞛レ唽臧€ 臁挫灛頃橃 鞎婌姷雼堧嫟."
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "Review 雼硠鞐愳劀電� 毹胳 毽€橃姢韸鸽ゼ 鞛戩劚頃� 頉� 毹胳頃橁赴旯岇鞚� 鞁滉皠鞚� 氤挫棳欷嶋媹雼�. 雿办澊韯半姅 觳� 氩堨Ц 毹胳 毽€橃姢韸胳潉 毹胳 頃� 頉勳棎 鞛愲彊鞙茧 於旉皜霅╇媹雼�."
 
+msgid "The roadmap shows the progress of your epics along a timeline"
+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 "Staging 雼硠鞐愳劀電� MR 毹胳瓿� 頂勲雿曥厴 頇橁步鞐� 旖旊摐 氚绊彫 靷澊鞚� 鞁滉皠鞚� 氤挫棳欷嶋媹雼�. 雿办澊韯半ゼ Production 頇橁步鞐� 觳橃潓 氚绊彫頃橂┐ 雿办澊韯瓣皜 鞛愲彊鞙茧 於旉皜霅╇媹雼�."
 
@@ -3084,6 +4170,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr "git storage鞐� 鞝戧芳頃橂姅雿� 氍胳牅臧€ 氚滌儩頄堨姷雼堧嫟. "
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3147,12 +4236,18 @@ msgstr "歃�, 牍� 鞝€鞛レ唽毳� 毵岆摛瓯半倶 旮办〈 鞝€鞛レ唽毳� 臧€鞝胳槵 霑岅箤
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3165,6 +4260,12 @@ msgstr "鞚挫妶臧€ 甑槃霅橁赴 鞝勳潣 鞁滉皠"
 msgid "Time between merge request creation and merge/close"
 msgstr "毹胳 毽€橃姢韸� 靸濎劚瓿� 毹胳 / 雼赴 靷澊鞚� 鞁滉皠"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3314,9 +4415,45 @@ msgstr[0] "攵�"
 msgid "Time|s"
 msgstr "齑�"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3332,19 +4469,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "鞁滉皠 頃╆硠:"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "氇摖 旎る皨 / 毹胳鞚� 齑� 韰岇姢韸� 鞁滉皠"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3356,22 +4490,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3407,6 +4535,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "鞐呺霌滍晿霠る┐ 韥措Ν頃橃嫮鞁滌槫."
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3416,21 +4550,51 @@ msgstr "靹れ爼 欷戩棎 雼れ潓 霌彪 韱犿伆 鞚挫毄 : "
 msgid "Use your global notification setting"
 msgstr "鞝勳泊 鞎岆 靹れ爼 靷毄"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "鞐措Π 毹胳 毽€橃姢韸鸽炒旮�"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "雮措秬"
 
@@ -3455,12 +4619,21 @@ msgstr "鞚� 雼硠毳� 氤挫棳欤缄赴鞐� 於╇秳頃� 雿办澊韯瓣皜 鞐嗢姷雼堧嫟."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3575,22 +4748,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "鞎§劯鞀� 鞖旍箔 觳犿殞"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "%{group_name} 攴鸽9鞚� 鞝滉卑頃橂牑瓿犿暕雼堧嫟. \\\"鞝曤搿淺\\" 頇曥嫟頃╇媹旯�?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "%{project_name_with_namespace} 頂勲鞝濏姼毳� 靷牅頃橂牑瓿犿暕雼堧嫟. \"靷牅霅� 頂勲鞝濏姼毳� 氤奠洂 頃� 靾� 鞐嗢姷雼堧嫟! \\\"鞝曤搿淺\\" 頇曥嫟頃╇媹旯�?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "韽伂 甏€瓿勲ゼ 靻岇姢 頂勲鞝濏姼 %{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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3608,12 +4793,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "頂勲鞝濏姼 靾瀽 頃滊弰鞐� 霃勲嫭頄堨姷雼堧嫟."
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "頂勲鞝濏姼鞐� 氤勴憸毳� 響滌嫓頃橂牑氅� 搿滉犯鞚� 頃挫暭 頃╇媹雼�."
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "甓岉暅鞚� 頃勳殧頃╇媹雼�."
 
@@ -3644,9 +4841,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3659,6 +4877,13 @@ msgstr "攴€頃橃潣 鞚措"
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3668,13 +4893,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3686,7 +4929,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3701,38 +4944,121 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "鞚�"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3757,15 +5083,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3799,6 +5137,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3847,12 +5188,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3881,6 +5228,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3890,6 +5240,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3899,3 +5252,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index 451be6434dbfedbe277b7ab89d279d79418a0475..1b3811198a85afb783b7d090da4af937944f27d2 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 03:59-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:39-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Dutch\n"
 "Language: nl_NL\n"
@@ -29,6 +29,11 @@ msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -44,11 +49,19 @@ msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s andere commit is weggelaten om prestatieproblemen te voorkomen."
 msgstr[1] "%s andere commits zijn weggelaten om prestatieproblemen te voorkomen."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -57,6 +70,12 @@ msgid_plural "%{count} participants"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -66,6 +85,9 @@ msgstr ""
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr ""
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] ""
@@ -94,15 +116,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr ""
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "Over auto deploy"
 
 msgid "Abuse Reports"
 msgstr "Misbruik rapporten"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "Toegangstokens"
 
@@ -112,6 +149,9 @@ msgstr ""
 msgid "Account"
 msgstr "Account"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "Actief"
 
@@ -130,9 +170,15 @@ msgstr ""
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "Licentie toevoegen"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "Nieuwe map toevoegen"
 
@@ -151,12 +197,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -169,9 +248,33 @@ msgstr "Alles"
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -181,6 +284,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -190,12 +299,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -208,12 +341,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr "Uiterlijk"
 
@@ -232,21 +374,24 @@ msgstr ""
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr ""
 
-msgid "Are you sure you want to discard your changes?"
-msgstr ""
-
 msgid "Are you sure you want to reset registration token?"
 msgstr ""
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr ""
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr ""
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -259,6 +404,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -280,6 +434,12 @@ msgstr "Auteur"
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -304,7 +464,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -316,6 +482,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -370,11 +545,8 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -399,6 +571,15 @@ msgstr "BranchSwitcherTitle|Ga naar branch"
 msgid "Branches"
 msgstr "Branches"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr "Branches|Kan geen HEAD-commit vinden voor deze branch"
 
@@ -444,12 +625,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -495,30 +703,45 @@ msgstr "Door bestanden bladeren"
 msgid "Browse files"
 msgstr "Door bestanden bladeren"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "door"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr "Annuleren"
 
-msgid "Cancel edit"
-msgstr "Bewerken annuleren"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr ""
 
@@ -573,6 +796,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -669,9 +898,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -723,6 +964,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -768,12 +1012,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -792,6 +1045,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -822,10 +1078,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -888,6 +1144,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -915,6 +1174,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -960,6 +1222,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr "Opmerkingen"
 
@@ -968,6 +1236,11 @@ msgid_plural "Commits"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "Commit Message"
 msgstr ""
 
@@ -980,6 +1253,9 @@ msgstr ""
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "Commit"
 
@@ -1025,6 +1301,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1040,9 +1322,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1088,6 +1406,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr ""
 
@@ -1121,6 +1445,9 @@ msgstr ""
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr ""
 
@@ -1133,13 +1460,22 @@ msgstr ""
 msgid "Create New Directory"
 msgstr ""
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr ""
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "Maak map aan"
 
-msgid "Create empty bare repository"
+msgid "Create empty repository"
 msgstr ""
 
 msgid "Create epic"
@@ -1148,12 +1484,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr ""
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1169,6 +1511,9 @@ msgstr ""
 msgid "Create new..."
 msgstr ""
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr ""
 
@@ -1178,6 +1523,12 @@ msgstr ""
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr ""
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1196,6 +1547,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr ""
 
@@ -1232,6 +1586,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr ""
 
@@ -1264,7 +1621,7 @@ msgstr ""
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
+msgid "Discard draft"
 msgstr ""
 
 msgid "Discover GitLab Geo."
@@ -1276,9 +1633,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr ""
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr ""
 
@@ -1306,9 +1669,15 @@ msgstr ""
 msgid "DownloadSource|Download"
 msgstr ""
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr ""
 
@@ -1318,12 +1687,54 @@ msgstr ""
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1378,9 +1789,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1447,12 +1870,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr ""
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr ""
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1468,6 +1924,12 @@ msgstr ""
 msgid "Files"
 msgstr ""
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr ""
 
@@ -1477,12 +1939,21 @@ msgstr ""
 msgid "Find file"
 msgstr ""
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr ""
 
 msgid "FirstPushedBy|pushed by"
 msgstr ""
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] ""
@@ -1494,15 +1965,24 @@ msgstr ""
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr ""
 
 msgid "From merge request merge until deploy to production"
 msgstr ""
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1512,12 +1992,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1563,21 +2049,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1590,9 +2103,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1623,6 +2148,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1632,12 +2160,30 @@ msgstr ""
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr ""
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr ""
 
@@ -1650,6 +2196,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1686,9 +2250,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1719,6 +2280,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr ""
 
@@ -1737,6 +2301,15 @@ msgstr ""
 msgid "HealthCheck|Unhealthy"
 msgstr ""
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1748,9 +2321,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr ""
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr ""
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1760,6 +2363,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr ""
 
@@ -1771,6 +2377,9 @@ msgstr[1] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1810,6 +2419,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1822,6 +2434,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1840,6 +2455,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1849,12 +2467,30 @@ msgstr ""
 msgid "LFSStatus|Enabled"
 msgstr ""
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] ""
@@ -1887,6 +2523,12 @@ msgstr ""
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr ""
 
@@ -1905,6 +2547,12 @@ msgstr ""
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1914,7 +2562,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1923,15 +2571,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1953,7 +2616,7 @@ msgstr ""
 msgid "Members"
 msgstr ""
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1974,6 +2637,81 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1989,12 +2727,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr ""
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr ""
 
@@ -2066,6 +2825,9 @@ msgstr ""
 msgid "New tag"
 msgstr ""
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2084,13 +2846,13 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
-msgid "No repository"
+msgid "No labels created yet."
 msgstr ""
 
-msgid "No schedules"
+msgid "No repository"
 msgstr ""
 
-msgid "No time spent"
+msgid "No schedules"
 msgstr ""
 
 msgid "None"
@@ -2102,12 +2864,33 @@ msgstr ""
 msgid "Not available"
 msgstr ""
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr ""
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr ""
 
@@ -2192,6 +2975,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr ""
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2210,12 +2999,21 @@ msgstr ""
 msgid "Options"
 msgstr ""
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr ""
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2228,9 +3026,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr ""
 
@@ -2312,9 +3122,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr ""
 
@@ -2327,6 +3182,9 @@ msgstr ""
 msgid "Pipeline|with stages"
 msgstr ""
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2336,6 +3194,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2348,6 +3212,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr ""
 
@@ -2387,6 +3254,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2411,9 +3281,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr ""
 
@@ -2510,37 +3377,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2558,6 +3476,12 @@ msgstr ""
 msgid "Push events"
 msgstr ""
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2570,6 +3494,9 @@ msgstr ""
 msgid "Readme"
 msgstr ""
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr ""
 
@@ -2603,6 +3530,9 @@ msgstr ""
 msgid "Related Merged Requests"
 msgstr ""
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr ""
 
@@ -2618,9 +3548,24 @@ msgstr ""
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr ""
 
@@ -2633,6 +3578,12 @@ msgstr ""
 msgid "Reset runners registration token"
 msgstr ""
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2644,6 +3595,36 @@ msgstr ""
 msgid "Revert this merge request"
 msgstr ""
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2659,6 +3640,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr ""
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2668,6 +3652,9 @@ msgstr ""
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr ""
 
@@ -2689,12 +3676,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr ""
 
 msgid "Select a timezone"
 msgstr ""
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2707,19 +3700,40 @@ msgstr ""
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
-msgid "September"
+msgid "September"
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
 msgstr ""
 
-msgid "Server version"
+msgid "Set max session time for web terminal."
 msgstr ""
 
-msgid "Service Templates"
+msgid "Set notification email for abuse reports."
 msgstr ""
 
-msgid "Set a password on your account to pull or push via %{protocol}."
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
 msgstr ""
 
 msgid "Set up CI/CD"
@@ -2728,7 +3742,7 @@ msgstr ""
 msgid "Set up Koding"
 msgstr ""
 
-msgid "Set up auto deploy"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
 msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
@@ -2737,6 +3751,12 @@ msgstr ""
 msgid "Settings"
 msgstr ""
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2746,6 +3766,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2769,6 +3792,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2778,13 +3813,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2898,6 +3933,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr ""
 
@@ -2907,12 +3945,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr ""
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2922,6 +3969,15 @@ msgstr ""
 msgid "Start the Runner!"
 msgstr ""
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2934,11 +3990,17 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr ""
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -3017,6 +4079,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr ""
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3032,15 +4097,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr ""
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 ""
 
@@ -3053,12 +4127,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 ""
 
@@ -3071,9 +4151,18 @@ msgstr ""
 msgid "The repository for this project does not exist."
 msgstr ""
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 ""
 
@@ -3104,6 +4193,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr ""
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3167,12 +4259,18 @@ msgstr ""
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3185,6 +4283,12 @@ msgstr ""
 msgid "Time between merge request creation and merge/close"
 msgstr ""
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3336,9 +4440,45 @@ msgstr[1] ""
 msgid "Time|s"
 msgstr "s"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3354,10 +4494,10 @@ msgstr ""
 msgid "Total Time"
 msgstr ""
 
-msgid "Total issue time spent"
+msgid "Total test time for all commits/merges"
 msgstr ""
 
-msgid "Total test time for all commits/merges"
+msgid "Total: %{total}"
 msgstr ""
 
 msgid "Track activity with Contribution Analytics."
@@ -3366,9 +4506,6 @@ msgstr ""
 msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
-msgid "Total: %{total}"
-msgstr ""
-
 msgid "Track time with quick actions"
 msgstr ""
 
@@ -3378,22 +4515,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3429,6 +4560,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr ""
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3438,21 +4575,51 @@ msgstr ""
 msgid "Use your global notification setting"
 msgstr ""
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr ""
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr ""
 
@@ -3477,12 +4644,21 @@ msgstr ""
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3597,22 +4773,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr ""
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr ""
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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?"
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
 msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3630,12 +4818,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr ""
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr ""
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr ""
 
@@ -3666,9 +4866,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3681,6 +4902,14 @@ msgstr ""
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3690,13 +4919,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3708,7 +4955,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3723,25 +4970,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3749,14 +5011,84 @@ msgid_plural "days"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3781,15 +5113,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3823,6 +5167,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3871,12 +5218,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3906,6 +5259,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3915,6 +5271,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3924,3 +5283,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index 9c5455eac676c623a8bd421944b8c568802a7702..4ae57235f90cfc93d3176c35df2a24f93e236478 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -2,15 +2,15 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:01-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:36-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Polish\n"
 "Language: pl_PL\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Plural-Forms: nplurals=4; plural=((n == 1) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || n%10 == 1 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 12 && n%100 <= 14)) ? 2 : 3));\n"
 "X-Generator: crowdin.com\n"
 "X-Crowdin-Project: gitlab-ee\n"
 "X-Crowdin-Language: pl\n"
@@ -24,36 +24,59 @@ msgid_plural "%d commits"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "%d commit behind"
 msgid_plural "%d commits behind"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "%d layer"
 msgid_plural "%d layers"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "%d merge request"
 msgid_plural "%d merge requests"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
 
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
@@ -63,6 +86,13 @@ msgid_plural "%{count} participants"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
 
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
@@ -73,11 +103,15 @@ msgstr ""
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr ""
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "%{text} is available"
 msgstr ""
@@ -96,6 +130,7 @@ msgid_plural "%d pipelines"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "1st contribution!"
 msgstr ""
@@ -103,15 +138,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr ""
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr ""
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -121,6 +171,9 @@ msgstr ""
 msgid "Account"
 msgstr ""
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr ""
 
@@ -139,9 +192,15 @@ msgstr ""
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr ""
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr ""
 
@@ -160,12 +219,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -178,9 +270,33 @@ msgstr ""
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -190,6 +306,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -199,12 +321,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -217,12 +363,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr ""
 
@@ -241,21 +396,24 @@ msgstr ""
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr ""
 
-msgid "Are you sure you want to discard your changes?"
-msgstr ""
-
 msgid "Are you sure you want to reset registration token?"
 msgstr ""
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr ""
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr ""
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -268,6 +426,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -289,6 +456,12 @@ msgstr ""
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -313,7 +486,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -325,6 +504,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -379,14 +567,12 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 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 ""
@@ -409,6 +595,15 @@ msgstr ""
 msgid "Branches"
 msgstr ""
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -454,12 +649,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -505,30 +727,45 @@ msgstr ""
 msgid "Browse files"
 msgstr ""
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr ""
 
 msgid "CI / CD"
 msgstr ""
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr ""
 
-msgid "Cancel edit"
+msgid "Cannot be merged automatically"
 msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr ""
 
@@ -583,6 +820,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -679,9 +922,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -733,6 +988,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -778,12 +1036,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -802,6 +1069,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -832,10 +1102,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -898,6 +1168,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -925,6 +1198,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -970,6 +1246,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr ""
 
@@ -978,6 +1260,14 @@ msgid_plural "Commits"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
+
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "Commit Message"
 msgstr ""
@@ -991,6 +1281,9 @@ msgstr ""
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr ""
 
@@ -1036,6 +1329,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1051,9 +1350,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1099,6 +1434,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr ""
 
@@ -1132,6 +1473,9 @@ msgstr ""
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr ""
 
@@ -1144,13 +1488,22 @@ msgstr ""
 msgid "Create New Directory"
 msgstr ""
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr ""
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr ""
 
-msgid "Create empty bare repository"
+msgid "Create empty repository"
 msgstr ""
 
 msgid "Create epic"
@@ -1159,12 +1512,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr ""
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1180,6 +1539,9 @@ msgstr ""
 msgid "Create new..."
 msgstr ""
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr ""
 
@@ -1189,6 +1551,12 @@ msgstr ""
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr ""
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1207,6 +1575,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr ""
 
@@ -1243,6 +1614,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr ""
 
@@ -1254,6 +1628,7 @@ msgid_plural "Deploys"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Deploy Keys"
 msgstr ""
@@ -1276,7 +1651,7 @@ msgstr ""
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
+msgid "Discard draft"
 msgstr ""
 
 msgid "Discover GitLab Geo."
@@ -1288,9 +1663,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr ""
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr ""
 
@@ -1318,9 +1699,15 @@ msgstr ""
 msgid "DownloadSource|Download"
 msgstr ""
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr ""
 
@@ -1330,12 +1717,54 @@ msgstr ""
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1390,9 +1819,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1459,12 +1900,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr ""
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr ""
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1480,6 +1954,12 @@ msgstr ""
 msgid "Files"
 msgstr ""
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr ""
 
@@ -1489,17 +1969,27 @@ msgstr ""
 msgid "Find file"
 msgstr ""
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr ""
 
 msgid "FirstPushedBy|pushed by"
 msgstr ""
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "ForkedFromProjectPath|Forked from"
 msgstr ""
@@ -1507,15 +1997,24 @@ msgstr ""
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr ""
 
 msgid "From merge request merge until deploy to production"
 msgstr ""
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1525,12 +2024,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1576,21 +2081,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1603,9 +2135,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1636,6 +2180,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1645,12 +2192,30 @@ msgstr ""
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr ""
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr ""
 
@@ -1663,6 +2228,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1699,9 +2282,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1732,6 +2312,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr ""
 
@@ -1750,11 +2333,21 @@ msgstr ""
 msgid "HealthCheck|Unhealthy"
 msgstr ""
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "History"
 msgstr ""
@@ -1762,9 +2355,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr ""
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr ""
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1774,6 +2397,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr ""
 
@@ -1782,10 +2408,14 @@ msgid_plural "Instances"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1825,6 +2455,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1837,6 +2470,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1855,6 +2491,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1864,17 +2503,36 @@ msgstr ""
 msgid "LFSStatus|Enabled"
 msgstr ""
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Last Pipeline"
 msgstr ""
@@ -1903,6 +2561,12 @@ msgstr ""
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr ""
 
@@ -1921,6 +2585,12 @@ msgstr ""
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1930,7 +2600,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1939,15 +2609,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1969,7 +2654,7 @@ msgstr ""
 msgid "Members"
 msgstr ""
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1990,6 +2675,81 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -2005,12 +2765,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr ""
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr ""
 
@@ -2031,6 +2812,7 @@ msgid_plural "New Issues"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "New Kubernetes Cluster"
 msgstr ""
@@ -2083,6 +2865,9 @@ msgstr ""
 msgid "New tag"
 msgstr ""
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2101,13 +2886,13 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
-msgid "No repository"
+msgid "No labels created yet."
 msgstr ""
 
-msgid "No schedules"
+msgid "No repository"
 msgstr ""
 
-msgid "No time spent"
+msgid "No schedules"
 msgstr ""
 
 msgid "None"
@@ -2119,12 +2904,33 @@ msgstr ""
 msgid "Not available"
 msgstr ""
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr ""
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr ""
 
@@ -2209,6 +3015,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr ""
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2227,12 +3039,21 @@ msgstr ""
 msgid "Options"
 msgstr ""
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr ""
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2245,9 +3066,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr ""
 
@@ -2329,9 +3162,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr ""
 
@@ -2344,6 +3222,9 @@ msgstr ""
 msgid "Pipeline|with stages"
 msgstr ""
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2353,6 +3234,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2365,6 +3252,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr ""
 
@@ -2404,6 +3294,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2428,9 +3321,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr ""
 
@@ -2527,37 +3417,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2575,6 +3516,12 @@ msgstr ""
 msgid "Push events"
 msgstr ""
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2587,6 +3534,9 @@ msgstr ""
 msgid "Readme"
 msgstr ""
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr ""
 
@@ -2620,6 +3570,9 @@ msgstr ""
 msgid "Related Merged Requests"
 msgstr ""
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr ""
 
@@ -2635,9 +3588,24 @@ msgstr ""
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr ""
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr ""
 
@@ -2650,11 +3618,18 @@ msgstr ""
 msgid "Reset runners registration token"
 msgstr ""
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Revert this commit"
 msgstr ""
@@ -2662,6 +3637,36 @@ msgstr ""
 msgid "Revert this merge request"
 msgstr ""
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2677,6 +3682,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr ""
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2686,6 +3694,9 @@ msgstr ""
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr ""
 
@@ -2707,12 +3718,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr ""
 
 msgid "Select a timezone"
 msgstr ""
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2725,6 +3742,9 @@ msgstr ""
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2737,7 +3757,25 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
-msgid "Set a password on your account to pull or push via %{protocol}."
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
 msgstr ""
 
 msgid "Set up CI/CD"
@@ -2746,7 +3784,7 @@ msgstr ""
 msgid "Set up Koding"
 msgstr ""
 
-msgid "Set up auto deploy"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
 msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
@@ -2755,6 +3793,12 @@ msgstr ""
 msgid "Settings"
 msgstr ""
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2764,6 +3808,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2775,6 +3822,7 @@ msgid_plural "Showing %d events"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Sidebar|Change weight"
 msgstr ""
@@ -2788,6 +3836,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2797,13 +3857,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2917,6 +3977,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr ""
 
@@ -2926,12 +3989,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr ""
 
 msgid "StarProject|Star"
 msgstr ""
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2941,6 +4013,15 @@ msgstr ""
 msgid "Start the Runner!"
 msgstr ""
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2953,14 +4034,21 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr ""
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Tags"
 msgstr ""
@@ -3037,6 +4125,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr ""
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr ""
 
@@ -3052,15 +4143,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr ""
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 ""
 
@@ -3073,12 +4173,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 ""
 
@@ -3091,9 +4197,18 @@ msgstr ""
 msgid "The repository for this project does not exist."
 msgstr ""
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 ""
 
@@ -3124,6 +4239,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr ""
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3187,12 +4305,18 @@ msgstr ""
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3205,6 +4329,12 @@ msgstr ""
 msgid "Time between merge request creation and merge/close"
 msgstr ""
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3348,19 +4478,57 @@ msgid_plural "Time|hrs"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Time|min"
 msgid_plural "Time|mins"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Time|s"
 msgstr ""
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3376,10 +4544,10 @@ msgstr ""
 msgid "Total Time"
 msgstr ""
 
-msgid "Total issue time spent"
+msgid "Total test time for all commits/merges"
 msgstr ""
 
-msgid "Total test time for all commits/merges"
+msgid "Total: %{total}"
 msgstr ""
 
 msgid "Track activity with Contribution Analytics."
@@ -3388,9 +4556,6 @@ msgstr ""
 msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
-msgid "Total: %{total}"
-msgstr ""
-
 msgid "Track time with quick actions"
 msgstr ""
 
@@ -3400,22 +4565,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3451,6 +4610,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr ""
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3460,21 +4625,51 @@ msgstr ""
 msgid "Use your global notification setting"
 msgstr ""
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr ""
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr ""
 
@@ -3499,12 +4694,21 @@ msgstr ""
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3619,22 +4823,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr ""
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr ""
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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?"
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
 msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3652,12 +4868,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr ""
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr ""
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr ""
 
@@ -3688,9 +4916,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3703,6 +4952,16 @@ msgstr ""
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3712,13 +4971,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3730,7 +5007,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3745,25 +5022,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3771,15 +5063,91 @@ msgid_plural "days"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
 
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
 
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
@@ -3805,15 +5173,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3847,6 +5227,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3895,12 +5278,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3924,6 +5313,7 @@ msgid_plural "parents"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "password"
 msgstr ""
@@ -3931,6 +5321,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3940,6 +5333,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3949,3 +5345,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index 5aef8f452343bb05c40700569a51d6d9352f8c79..96a59d6d0d3d2da39ab30a7d99c62fe9c9b236c3 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:01-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:36-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Portuguese, Brazilian\n"
 "Language: pt_BR\n"
@@ -17,7 +17,7 @@ msgstr ""
 "X-Crowdin-File: /master/locale/gitlab.pot\n"
 
 msgid " and"
-msgstr ""
+msgstr " e"
 
 msgid "%d commit"
 msgid_plural "%d commits"
@@ -26,13 +26,18 @@ msgstr[1] "%d commits"
 
 msgid "%d commit behind"
 msgid_plural "%d commits behind"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d commit atr谩s"
+msgstr[1] "%d commits atr谩s"
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] "%d exporter"
+msgstr[1] "%d exporters"
 
 msgid "%d issue"
 msgid_plural "%d issues"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d issue"
+msgstr[1] "%d issues"
 
 msgid "%d layer"
 msgid_plural "%d layers"
@@ -41,22 +46,36 @@ msgstr[1] "%d camadas"
 
 msgid "%d merge request"
 msgid_plural "%d merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d merge request"
+msgstr[1] "%d merge requests"
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] "%d m茅trica"
+msgstr[1] "%d m茅tricas"
 
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "%s commit adicional foi omitido para prevenir problemas de performance."
 msgstr[1] "%s commits adicionais foram omitidos para prevenir problemas de performance."
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr "%{actionText} & %{openOrClose} %{noteable}"
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
-msgstr ""
+msgstr "%{commit_author_link} fez commit 脿 %{commit_timeago}"
 
 msgid "%{count} participant"
 msgid_plural "%{count} participants"
 msgstr[0] "%{count} participante"
 msgstr[1] "%{count} participantes"
 
+msgid "%{loadingIcon} Started"
+msgstr "%{loadingIcon} Iniciado"
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr "%{lock_path} est谩 bloqueado pelo usu谩rio do GitLab %{lock_user_id}"
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr "%{number_commits_behind} commits atr谩s de %{default_branch}, %{number_commits_ahead} commits 脿 frente"
 
@@ -66,6 +85,9 @@ msgstr "%{number_of_failures} de %{maximum_failures} falhas. O GitLab permitir谩
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} de %{maximum_failures} falhas. O GitLab n茫o tentar谩 mais automaticamente. Redefina as informa莽玫es de storage quando o problema for resolvido."
 
+msgid "%{openOrClose} %{noteable}"
+msgstr "%{openOrClose} %{noteable}"
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}: falha na tentativa de acesso ao storage no host:"
@@ -94,15 +116,30 @@ msgstr "1陋 contribui莽茫o!"
 msgid "2FA enabled"
 msgstr "Autentica莽茫o de 2 passos ativada"
 
+msgid "<strong>Removes</strong> source branch"
+msgstr "<strong>Remover</strong> branch de origem"
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "Uma cole莽茫o de gr谩ficos sobre Integra莽茫o Cont铆nua"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr "Um novo \"branch\" ser谩 criado no seu \"fork\" e um novo merge request ser谩 iniciado."
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr "Um projeto 茅 onde voc锚 armazena seus arquivos (reposit贸rio), planeja seu trabalho (issues), e publica sua documenta莽茫o (wiki), %{among_other_things_link}."
+
+msgid "A user with write access to the source branch selected this option"
+msgstr "Um usu谩rio com permiss茫o de escrita no branch de origem selecionou esta op莽茫o"
+
 msgid "About auto deploy"
 msgstr "Sobre o deploy autom谩tico"
 
 msgid "Abuse Reports"
 msgstr "Relat贸rios de abuso"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "Tokens de acesso"
 
@@ -112,6 +149,9 @@ msgstr "Os acessos 脿 storages com defeito foram temporariamente desabilitados p
 msgid "Account"
 msgstr "Conta"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "Ativo"
 
@@ -130,35 +170,74 @@ msgstr "Adicionar Guia de contribui莽茫o"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr "Adicione o Webhooks de Grupos e GitLab Enterprise Edition."
 
+msgid "Add Kubernetes cluster"
+msgstr "Adicionar cluster Kubernetes"
+
 msgid "Add License"
 msgstr "Adicionar Licen莽a"
 
+msgid "Add Readme"
+msgstr "Adicionar leia-me"
+
 msgid "Add new directory"
 msgstr "Adicionar novo diret贸rio"
 
 msgid "Add todo"
-msgstr ""
+msgstr "Adicionar tarefa"
 
 msgid "AdminArea|Stop all jobs"
-msgstr ""
+msgstr "Parar todos os processos"
 
 msgid "AdminArea|Stop all jobs?"
-msgstr ""
+msgstr "Parar todos os processos?"
 
 msgid "AdminArea|Stop jobs"
-msgstr ""
+msgstr "Parar processos"
 
 msgid "AdminArea|Stopping jobs failed"
-msgstr ""
+msgstr "Erro ao parar processos"
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
-msgstr ""
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr "Voc锚 parar谩 todos os processos. Os processos em execu莽茫o ser茫o abruptamente interrompidos."
 
 msgid "AdminHealthPageLink|health page"
 msgstr "p谩gina de sa煤de"
 
+msgid "AdminProjects|Delete"
+msgstr "Excluir"
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr "Excluir o projeto %{projectName}?"
+
+msgid "AdminProjects|Delete project"
+msgstr "Excluir projeto"
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr "Especifique um dom铆nio a ser usado por padr茫o para os est谩gios de Auto Review Application e Auto Deploy de cada projeto."
+
+msgid "AdminUsers|Block user"
+msgstr "Bloquear usu谩rio"
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr "Excluir o usu谩rio %{username} e suas contribui莽玫es?"
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr "Excluir o usu谩rio %{username}?"
+
+msgid "AdminUsers|Delete user"
+msgstr "Apagar usu谩rio"
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr "Excluir o usu谩rio e suas contribui莽玫es"
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr "Para confirmar, digite %{projectName}"
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr "Para confirmar, digite %{username}"
+
 msgid "Advanced"
-msgstr ""
+msgstr "Avan莽ado"
 
 msgid "Advanced settings"
 msgstr "Configura莽玫es avan莽adas"
@@ -167,53 +246,116 @@ msgid "All"
 msgstr "Todos"
 
 msgid "All changes are committed"
+msgstr "Houve commit com todas as mudan莽as"
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr "Todas as funcionalidades est茫o habilitadas para projetos em branco, a partir de templates ou ao importar, mas voc锚 pode desativ谩-los posteriormente nas configura莽玫es do projeto."
+
+msgid "Allow edits from maintainers."
+msgstr "Permitir as edi莽玫es dos mantenedores."
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
 msgstr ""
 
 msgid "Allows you to add and manage Kubernetes clusters."
+msgstr "Permite adicionar e gerenciar clusters do Kubernetes."
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
 msgstr ""
 
-msgid "An error occurred previewing the blob"
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
 msgstr ""
 
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr "Alternativamente, voc锚 pode usar um %{personal_access_token_link}. Quando voc锚 cria seu Token de Acesso Pessoal, voc锚 precisar谩 selecionar o escopo do <code>reposit贸rio</code>, para que possamos exibir uma lista de seus reposit贸rios p煤blicos e privados que est茫o dispon铆veis para se conectar."
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr "Alternativamente, voc锚 pode usar um %{personal_access_token_link}. Quando voc锚 cria seu Token de Acesso Pessoal, voc锚 precisar谩 selecionar o escopo do <code>reposit贸rio</code>, para que possamos exibir uma lista de seus reposit贸rios p煤blicos e privados que est茫o dispon铆veis para se importar."
+
+msgid "An error occurred previewing the blob"
+msgstr "Erro ao pr茅-visualizar o blob"
+
 msgid "An error occurred when toggling the notification subscription"
 msgstr "Erro ao modificar notifica莽茫o de assinatura"
 
 msgid "An error occurred when updating the issue weight"
 msgstr "Um erro aconteceu ao atualizar o peso da issue"
 
+msgid "An error occurred while adding approver"
+msgstr "Erro ao adicionar o aprovador"
+
+msgid "An error occurred while detecting host keys"
+msgstr "Erro ao detectar a chave do host"
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
-msgstr ""
+msgstr "Erro ao remover alerta. Atualize a p谩gina e tente novamente."
 
 msgid "An error occurred while fetching markdown preview"
-msgstr ""
+msgstr "Erro ao gerar pr茅-visualiza莽茫o do markdown"
 
 msgid "An error occurred while fetching sidebar data"
 msgstr "Erro ao recuperar informa莽玫es da barra lateral"
 
+msgid "An error occurred while fetching the pipeline."
+msgstr "Erro ao recuperar informa莽玫es da pipeline."
+
 msgid "An error occurred while getting projects"
-msgstr ""
+msgstr "Erro ao recuperar projetos"
+
+msgid "An error occurred while importing project"
+msgstr "Erro ao importar o projeto"
+
+msgid "An error occurred while initializing path locks"
+msgstr "Erro ao iniciar o bloqueio do Path"
+
+msgid "An error occurred while loading commits"
+msgstr "Erro ao carregar os Commits"
+
+msgid "An error occurred while loading diff"
+msgstr "Erro ao carregar o Diff"
 
 msgid "An error occurred while loading filenames"
-msgstr ""
+msgstr "Erro ao carregar nomes de arquivos"
+
+msgid "An error occurred while loading the file"
+msgstr "Erro ao carregar o arquivo"
+
+msgid "An error occurred while making the request."
+msgstr "Erro ao fazer a requisi莽茫o."
+
+msgid "An error occurred while removing approver"
+msgstr "Erro ao remover o aprovador"
 
 msgid "An error occurred while rendering KaTeX"
-msgstr ""
+msgstr "Erro ao renderizar o KaTeX"
 
 msgid "An error occurred while rendering preview broadcast message"
-msgstr ""
+msgstr "Erro ao renderizar pr茅-visualiza莽茫o da mensagem de transmiss茫o"
 
 msgid "An error occurred while retrieving calendar activity"
-msgstr ""
+msgstr "Erro ao recuperar calend谩rio de atividades"
 
 msgid "An error occurred while retrieving diff"
-msgstr ""
+msgstr "Erro ao recuperar o diff"
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr "Um erro ocorreu ao sobrescrever o status LDAP."
+
+msgid "An error occurred while saving assignees"
+msgstr "Erro ao salvar assignees"
 
 msgid "An error occurred while validating username"
-msgstr ""
+msgstr "Erro ao validar o nome de usu谩rio"
 
 msgid "An error occurred. Please try again."
 msgstr "Ocorreu um erro. Tente novamente."
 
+msgid "Any Label"
+msgstr "Qualquer Label"
+
 msgid "Appearance"
 msgstr "Apar锚ncia"
 
@@ -232,36 +374,48 @@ msgstr "Projeto arquivado! O reposit贸rio 茅 somente leitura"
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "Tem certeza que deseja excluir este agendamento de pipeline?"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "Voc锚 tem certeza que deseja descartar suas altera莽玫es?"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "Voc锚 tem certeza que quer recriar o token de registro?"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "Voc锚 tem certeza que quer reiniciar o token de status de sa煤de?"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr "Tem certeza que deseja desbloquear %{path_lock_path}?"
+
 msgid "Are you sure?"
 msgstr "Voc锚 tem certeza?"
 
 msgid "Artifacts"
 msgstr "Artefatos"
 
-msgid "Assign custom color like #FF0000"
+msgid "Assertion consumer service URL"
 msgstr ""
 
+msgid "Assign custom color like #FF0000"
+msgstr "Coloque uma cor personalizada, como #FF0000"
+
 msgid "Assign labels"
-msgstr ""
+msgstr "Atribuir labels"
 
 msgid "Assign milestone"
-msgstr ""
+msgstr "Atribuir milestone"
 
 msgid "Assign to"
+msgstr "Atribuir 脿"
+
+msgid "Assigned Issues"
 msgstr ""
 
-msgid "Assignee"
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
 msgstr ""
 
+msgid "Assignee"
+msgstr "Respons谩vel"
+
 msgid "Attach a file by drag &amp; drop or %{upload_link}"
 msgstr "Para anexar arquivo, arraste e solte ou %{upload_link}"
 
@@ -278,13 +432,19 @@ msgid "Author"
 msgstr "Autor"
 
 msgid "Authors: %{authors}"
+msgstr "Autores: %{authors}"
+
+msgid "Auto DevOps enabled"
+msgstr "Auto DevOps ativo"
+
+msgid "Auto DevOps, runners and job artifacts"
 msgstr ""
 
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "Apps de revis茫o e deploy autom谩ticos precisam de um %{kubernetes} para funcionar corretamente."
 
 msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "Apps de revis茫o e deploy autom谩ticos precisam de um nome de dom铆nio e um %{kubernetes} para funcionar corretamente."
 
 msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
 msgstr "Apps de revis茫o autom谩tica e Auto Deploy precisam de um nome de dom铆nio para que funcione corretamente."
@@ -304,18 +464,33 @@ msgstr "Ele gerar谩 a build, testar谩 e far谩 deploy de sua aplica莽茫o automati
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr "Saiba mais em %{link_to_documentation}"
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
-msgstr "Voc锚 pode ativar %{link_to_settings} para esse projeto."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr "Voc锚 pode automaticamente construir e testar sua aplica莽茫o, se voc锚 %{link_to_auto_devops_settings} para este projeto. Voc锚 pode tamb茅m fazer o deploy automaticamente, se voc锚 %{link_to_add_kubernetes_cluster}."
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr "adicionar um cluster Kubernetes"
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr "ativar auto DevOps (Beta)"
 
 msgid "Available"
 msgstr "Dispon铆vel"
 
 msgid "Avatar will be removed. Are you sure?"
-msgstr ""
+msgstr "Foto de perfil ser谩 removida. Tem certeza?"
 
 msgid "Average per day: %{average}"
+msgstr "M茅dia di谩ria: %{average}"
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
 msgstr ""
 
+msgid "Begin with the selected commit"
+msgstr "Comece com o commit selecionado"
+
 msgid "Billing"
 msgstr "Cobran莽a"
 
@@ -370,13 +545,10 @@ msgstr "pago %{price_per_year} anualmente"
 msgid "BillingPlans|per user"
 msgstr "por usu谩rio"
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "Branch"
-msgstr[1] "Branches"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+msgstr[0] "Branch (%{branch_count})"
+msgstr[1] "Branches (%{branch_count})"
 
 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 "O branch <strong>%{branch_name}</strong> foi criado. Para configurar o deploy autom谩tico, selecione um modelo de Yaml do GitLab CI e commit suas mudan莽as. %{link_to_autodeploy_doc}"
@@ -399,6 +571,15 @@ msgstr "Mudar de branch"
 msgid "Branches"
 msgstr "Branches"
 
+msgid "Branches|Active"
+msgstr "Ativos"
+
+msgid "Branches|Active branches"
+msgstr "Branches ativos"
+
+msgid "Branches|All"
+msgstr "Todos"
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr "N茫o foi poss铆vel encontrar o commit HEAD para essa branch"
 
@@ -444,12 +625,39 @@ msgstr "Uma vez que voc锚 confirmar e pressionar %{delete_protected_branch}, n茫
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr "Somente algu茅m master ou dono do projeto poder谩 apagar branches protegidas"
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
-msgstr "Ramos protegidos podem ser gerenciados em %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr "Vis茫o Geral"
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr "Branches protegidas podem ser gerenciadas em %{project_settings_link}."
+
+msgid "Branches|Show active branches"
+msgstr "Mostrar branches ativas"
+
+msgid "Branches|Show all branches"
+msgstr "Mostrar todas as branches"
+
+msgid "Branches|Show more active branches"
+msgstr "Mostrar as branches mais ativas"
+
+msgid "Branches|Show more stale branches"
+msgstr "Mostrar os branches obsoletos"
+
+msgid "Branches|Show overview of the branches"
+msgstr "Mostrar vis茫o geral dos branches"
+
+msgid "Branches|Show stale branches"
+msgstr "Mostrar branches obsoletos"
 
 msgid "Branches|Sort by"
 msgstr "Ordernar por"
 
+msgid "Branches|Stale"
+msgstr "Obsoleto"
+
+msgid "Branches|Stale branches"
+msgstr "Branches obsoletos"
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr "O branch n茫o pode ser atualizado automaticamente porque diverge do seu upstream."
 
@@ -495,14 +703,23 @@ msgstr "Acessar arquivos"
 msgid "Browse files"
 msgstr "Navegar pelos arquivos"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "por"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr "CI/CD"
+
 msgid "CI/CD configuration"
-msgstr ""
+msgstr "Configura莽茫o de CI/CD"
+
+msgid "CI/CD for external repo"
+msgstr "CI/CD para um reposit贸rio externo"
 
 msgid "CICD|Jobs"
 msgstr "Jobs"
@@ -510,15 +727,21 @@ msgstr "Jobs"
 msgid "Cancel"
 msgstr "Cancelar"
 
-msgid "Cancel edit"
-msgstr "Cancelar edi莽茫o"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
+msgstr "N茫o se pode modificar um cluster Kubernetes gerenciado"
+
+msgid "Certificate fingerprint"
 msgstr ""
 
 msgid "Change Weight"
 msgstr "Alterar peso"
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "Pick para um branch"
 
@@ -532,13 +755,13 @@ msgid "ChangeTypeAction|Revert"
 msgstr "Reverter"
 
 msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
-msgstr ""
+msgstr "Isso criar谩 um novo commit para reverter as mudan莽as existentes."
 
 msgid "Changelog"
 msgstr "Registro de mudan莽as"
 
 msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
-msgstr ""
+msgstr "Mudan莽as ser茫o mostradas se revis茫o de <b>origem</b> tiver sofrido merge na revis茫o <b>alvo</b>."
 
 msgid "Charts"
 msgstr "Gr谩ficos"
@@ -547,7 +770,7 @@ msgid "Chat"
 msgstr "Bate-papo"
 
 msgid "Check interval"
-msgstr ""
+msgstr "Intervalo de verifica莽茫o"
 
 msgid "Checking %{text} availability鈥�"
 msgstr "Verificando disponibilidade de %{text}鈥�"
@@ -562,19 +785,25 @@ msgid "Cherry-pick this merge request"
 msgstr "Cherry-pick esse merge request"
 
 msgid "Choose File ..."
-msgstr ""
+msgstr "Escolha o arquivo ..."
 
 msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
-msgstr ""
+msgstr "Escolha a branch/tag (ex: %{master}) ou n煤mero do commit (ex: %{sha}) para ver o que mudou ou para criar um merge request."
 
 msgid "Choose file..."
-msgstr ""
+msgstr "Escolha o arquivo..."
 
 msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "Escolha quais grupos voc锚 deseja sincronizar nesse n贸 secund谩rio."
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr "Escolha quais reposit贸rios voc锚 deseja se conectar e executar CI/CD pipeline."
+
+msgid "Choose which repositories you want to import."
+msgstr "Escolha quais reposit贸rios voc锚 deseja importar."
 
 msgid "Choose which shards you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "Escolha quais shards voc锚 deseja que sincronizem com esse n贸 secund谩rio."
 
 msgid "CiStatusLabel|canceled"
 msgstr "cancelado"
@@ -631,45 +860,57 @@ msgid "CiStatus|running"
 msgstr "executando"
 
 msgid "CiVariables|Input variable key"
-msgstr ""
+msgstr "Digite o nome da vari谩vel"
 
 msgid "CiVariables|Input variable value"
-msgstr ""
+msgstr "Digite o valor da vari谩vel"
 
 msgid "CiVariables|Remove variable row"
-msgstr ""
+msgstr "Remover a vari谩vel"
 
 msgid "CiVariable|* (All environments)"
-msgstr ""
+msgstr "* (Todos os ambientes)"
 
 msgid "CiVariable|All environments"
-msgstr ""
+msgstr "Todos os ambientes"
 
 msgid "CiVariable|Create wildcard"
-msgstr ""
+msgstr "Criar um curinga"
 
 msgid "CiVariable|Error occured while saving variables"
-msgstr ""
+msgstr "Erro ao salvar vari谩veis"
 
 msgid "CiVariable|New environment"
-msgstr ""
+msgstr "Novo ambiente"
 
 msgid "CiVariable|Protected"
-msgstr ""
+msgstr "Protegido"
 
 msgid "CiVariable|Search environments"
-msgstr ""
+msgstr "Procurar ambientes"
 
 msgid "CiVariable|Toggle protected"
-msgstr ""
+msgstr "Alternar prote莽茫o"
 
 msgid "CiVariable|Validation failed"
-msgstr ""
+msgstr "Falha na valida莽茫o"
 
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr "interruptor da api"
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr "Clique no bot茫o abaixo para iniciar o processo de instala莽茫o navegando para a p谩gina do Kubernetes"
+
 msgid "Click to expand text"
+msgstr "Cliquei pra expandir o texto"
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
 msgstr ""
 
 msgid "Clone repository"
@@ -679,28 +920,28 @@ msgid "Close"
 msgstr "Fechar"
 
 msgid "Closed"
-msgstr ""
+msgstr "Fechado"
 
 msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
-msgstr ""
+msgstr "%{appList} foi instalado com sucesso no seu cluster Kubernetes"
 
 msgid "ClusterIntegration|API URL"
 msgstr "API URL"
 
 msgid "ClusterIntegration|Add Kubernetes cluster"
-msgstr ""
+msgstr "Adicionar cluster Kubernetes"
 
 msgid "ClusterIntegration|Add an existing Kubernetes cluster"
-msgstr ""
+msgstr "Adicionar um cluster Kubernetes existente"
 
 msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
-msgstr ""
+msgstr "Op莽玫es avan莽adas na integra莽茫o deste cluster Kubernetes"
 
 msgid "ClusterIntegration|Applications"
 msgstr "Aplica莽玫es"
 
 msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "Tem certeza de que deseja remover a integra莽茫o deste cluster do Kubernetes? Isso n茫o excluir谩 o seu cluster atual do Kubernetes."
 
 msgid "ClusterIntegration|CA Certificate"
 msgstr "Certificado CA"
@@ -709,10 +950,10 @@ msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
 msgstr "Pacote de autoridade certificadora (Formato PEM)"
 
 msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
-msgstr ""
+msgstr "Escolha como configurar a integra莽茫o do cluster Kubernetes"
 
 msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
-msgstr ""
+msgstr "Escolha qual dos ambientes do seu projeto usar谩 este cluster Kubernetes."
 
 msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
 msgstr ""
@@ -723,6 +964,9 @@ msgstr "Copiar URL da API"
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr "Copiar certificado CA"
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -768,12 +1012,21 @@ msgstr "Projeto do Google Kubernetes Engine"
 msgid "ClusterIntegration|Helm Tiller"
 msgstr "Helm Tiller"
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr "Ingressar"
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr "Instalar"
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr "Instalado"
 
@@ -792,6 +1045,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -822,10 +1078,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr "Leia mais sobre %{link_to_documentation}"
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -841,10 +1097,10 @@ msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}
 msgstr ""
 
 msgid "ClusterIntegration|More information"
-msgstr ""
+msgstr "Mais informa莽玫es"
 
 msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
-msgstr ""
+msgstr "Multiplos clusters Kubernetes est茫o dispon铆veis no GitLab Enterprise Edition Premium e Ultimate"
 
 msgid "ClusterIntegration|Note:"
 msgstr "Nota:"
@@ -868,7 +1124,7 @@ msgid "ClusterIntegration|Project namespace (optional, unique)"
 msgstr "Namespace do projeto (opcional, 煤nico)"
 
 msgid "ClusterIntegration|Prometheus"
-msgstr ""
+msgstr "Prometheus"
 
 msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
 msgstr ""
@@ -888,9 +1144,12 @@ msgstr "Solicita莽茫o para in铆cio de instala莽茫o falhou"
 msgid "ClusterIntegration|Save changes"
 msgstr "Salvar altera莽玫es"
 
-msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgid "ClusterIntegration|Security"
 msgstr ""
 
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr "Veja e edite os detalhes de seus cluster Kubernates"
+
 msgid "ClusterIntegration|See machine types"
 msgstr "Ver tipos de m谩quina"
 
@@ -910,11 +1169,14 @@ msgid "ClusterIntegration|Something went wrong on our end."
 msgstr "Alguma coisa deu errado do nosso lado."
 
 msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "Erro ao criar cluster Kubernetes no Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr "Algo deu errado ao instalar %{title}"
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -958,8 +1220,14 @@ msgid "ClusterIntegration|properly configured"
 msgstr "configurado corretamente"
 
 msgid "Collapse"
+msgstr "Recolher"
+
+msgid "Comment and resolve discussion"
 msgstr ""
 
+msgid "Comment and unresolve discussion"
+msgstr "Comente e marque discuss茫o como n茫o resolvida"
+
 msgid "Comments"
 msgstr "Coment谩rios"
 
@@ -968,6 +1236,11 @@ msgid_plural "Commits"
 msgstr[0] "Commit"
 msgstr[1] "Commits"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] "Commit (%{commit_count})"
+msgstr[1] "Commits (%{commit_count})"
+
 msgid "Commit Message"
 msgstr "Mensagem de Commit"
 
@@ -978,7 +1251,10 @@ msgid "Commit message"
 msgstr "Mensagem de commit"
 
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
-msgstr ""
+msgstr "Estat铆sticas de commits para %{ref} %{start_time} - %{end_time}"
+
+msgid "Commit to %{branchName} branch"
+msgstr "Commit para a branch %{branchName}"
 
 msgid "CommitBoxTitle|Commit"
 msgstr "Commit"
@@ -993,25 +1269,25 @@ msgid "Commits feed"
 msgstr "Feed de commits"
 
 msgid "Commits per day hour (UTC)"
-msgstr ""
+msgstr "Commits por hora (UTC)"
 
 msgid "Commits per day of month"
-msgstr ""
+msgstr "Commits por dia do m锚s"
 
 msgid "Commits per weekday"
-msgstr ""
+msgstr "Commits por dia da semana"
 
 msgid "Commits|An error occurred while fetching merge requests data."
-msgstr ""
+msgstr "Erro ao recuperar dados do merge request."
 
 msgid "Commits|Commit: %{commitText}"
-msgstr ""
+msgstr "Commit: %{commitText}"
 
 msgid "Commits|History"
 msgstr "Hist贸rico"
 
 msgid "Commits|No related merge requests found"
-msgstr ""
+msgstr "Nenhum merge request relacionado foi encontrado"
 
 msgid "Committed by"
 msgstr "Commit feito por"
@@ -1020,29 +1296,71 @@ msgid "Compare"
 msgstr "Comparar"
 
 msgid "Compare Git revisions"
-msgstr ""
+msgstr "Parar vers玫es do Git"
 
 msgid "Compare Revisions"
+msgstr "Comparar revis玫es"
+
+msgid "Compare changes with the last commit"
 msgstr ""
 
-msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgid "Compare changes with the merge request target branch"
 msgstr ""
 
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr "%{source_branch} e %{target_branch} s茫o o mesmo."
+
 msgid "CompareBranches|Compare"
-msgstr ""
+msgstr "Comparar"
 
 msgid "CompareBranches|Source"
-msgstr ""
+msgstr "Origem"
 
 msgid "CompareBranches|Target"
-msgstr ""
+msgstr "Alvo"
 
 msgid "CompareBranches|There isn't anything to compare."
+msgstr "N茫o h谩 nada para comparar."
+
+msgid "Confidential"
 msgstr ""
 
 msgid "Confidentiality"
+msgstr "Confidencialidade"
+
+msgid "Configure Gitaly timeouts."
 msgstr ""
 
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr "Conectar"
+
+msgid "Connect all repositories"
+msgstr "Conectar todos reposit贸rios"
+
+msgid "Connect repositories from GitHub"
+msgstr "Conectar reposit贸rios do GitHub"
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr "Conectando..."
+
 msgid "Container Registry"
 msgstr "Container Registry"
 
@@ -1088,6 +1406,12 @@ msgstr "Use nomes de imagem diferentes"
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr "Com o Container Registry do Docker integrado ao Gitlab, todo projeto pode ter seu pr贸prio espa莽o para guardar suas imagens."
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr "Contribui莽玫es"
+
 msgid "Contribution guide"
 msgstr "Guia de contribui莽茫o"
 
@@ -1095,7 +1419,7 @@ msgid "Contributors"
 msgstr "Contribuidores"
 
 msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
-msgstr ""
+msgstr "%{startDate} - %{endDate}"
 
 msgid "ContributorsPage|Building repository graph."
 msgstr "Gerando gr谩fico do reposit贸rio."
@@ -1119,28 +1443,40 @@ msgid "Copy URL to clipboard"
 msgstr "Copiar URL para 谩rea de transfer锚ncia"
 
 msgid "Copy branch name to clipboard"
-msgstr ""
+msgstr "Copiar nome do branch para 谩rea de transfer锚ncia"
+
+msgid "Copy command to clipboard"
+msgstr "Copiar o comando para 谩rea de transfer锚ncia"
 
 msgid "Copy commit SHA to clipboard"
 msgstr "Copiar SHA do commit para a 谩rea de transfer锚ncia"
 
 msgid "Copy reference to clipboard"
-msgstr ""
+msgstr "Copiar refer锚ncia para 谩rea de transfer锚ncia"
 
 msgid "Create"
-msgstr ""
+msgstr "Criar"
 
 msgid "Create New Directory"
 msgstr "Criar Novo Diret贸rio"
 
+msgid "Create a new branch"
+msgstr "Criar uma nova branch"
+
+msgid "Create a new branch and merge request"
+msgstr "Criar um novo branch e abrir merge request"
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "Crie um token de acesso pessoal na sua conta para dar pull ou push via %{protocol}."
 
+msgid "Create branch"
+msgstr "Criar a branch"
+
 msgid "Create directory"
 msgstr "Criar diret贸rio"
 
-msgid "Create empty bare repository"
-msgstr "Criar reposit贸rio bruto vazio"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr "Criar 茅pico"
@@ -1148,12 +1484,18 @@ msgstr "Criar 茅pico"
 msgid "Create file"
 msgstr "Criar arquivo"
 
-msgid "Create lists from labels. Issues with that label appear in that list."
+msgid "Create group label"
 msgstr ""
 
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr "Criar lista a partir de labels. Issues com labels aparecem nestas listas."
+
 msgid "Create merge request"
 msgstr "Criar merge request"
 
+msgid "Create merge request and branch"
+msgstr "Abrir merge request e criar branch"
+
 msgid "Create new branch"
 msgstr "Criar novo branch"
 
@@ -1164,11 +1506,14 @@ msgid "Create new file"
 msgstr "Criar novo arquivo"
 
 msgid "Create new label"
-msgstr ""
+msgstr "Criar nova label"
 
 msgid "Create new..."
 msgstr "Criar novo..."
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "Fork"
 
@@ -1178,6 +1523,12 @@ msgstr "Tag"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "criar um token de acesso pessoal"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr "Cria um novo branch de %{branchName}"
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr "Criando 茅pico"
 
@@ -1188,7 +1539,7 @@ msgid "Cron syntax"
 msgstr "Sintaxe do cron"
 
 msgid "Current node"
-msgstr ""
+msgstr "N贸 atual"
 
 msgid "Custom notification events"
 msgstr "Eventos de notifica莽茫o personalizados"
@@ -1196,6 +1547,9 @@ msgstr "Eventos de notifica莽茫o personalizados"
 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 "N铆veis de notifica莽茫o personalizados s茫o equivalentes a n铆veis de participa莽茫o. Com n铆veis de notifica莽茫o personalizados voc锚 tamb茅m ser谩 notificado sobre eventos selecionados. Para mais informa莽玫es, visite %{notification_link}."
 
+msgid "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "An谩lise de Ciclo"
 
@@ -1232,6 +1586,9 @@ msgstr "Dez"
 msgid "December"
 msgstr "Dezembro"
 
+msgid "Default classification label"
+msgstr "Label de classifica莽茫o padr茫o"
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "Defina um padr茫o personalizado utilizando a sintaxe do cron"
 
@@ -1256,19 +1613,19 @@ msgid "Details"
 msgstr "Detalhes"
 
 msgid "Diffs|No file name available"
-msgstr ""
+msgstr "Nenhum nome de arquivo dispon铆vel"
 
 msgid "Directory name"
 msgstr "Nome do diret贸rio"
 
 msgid "Disable"
-msgstr ""
+msgstr "Desabilitar"
 
-msgid "Discard changes"
-msgstr "Descartar altera莽玫es"
+msgid "Discard draft"
+msgstr "Descartar rascunho"
 
 msgid "Discover GitLab Geo."
-msgstr ""
+msgstr "Descubra Gitlab Geo."
 
 msgid "Dismiss Cycle Analytics introduction box"
 msgstr "Ignorar introdu莽茫o do Cycle Analytics"
@@ -1276,9 +1633,15 @@ msgstr "Ignorar introdu莽茫o do Cycle Analytics"
 msgid "Dismiss Merge Request promotion"
 msgstr "Ignorar an煤ncio do merge request"
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "N茫o exibir novamente"
 
+msgid "Done"
+msgstr "Pronto"
+
 msgid "Download"
 msgstr "Baixar"
 
@@ -1306,7 +1669,13 @@ msgstr "Arquivo de texto com as mudan莽as"
 msgid "DownloadSource|Download"
 msgstr "Baixar"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
+msgstr "Validade"
+
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
 msgstr ""
 
 msgid "Edit"
@@ -1316,12 +1685,54 @@ msgid "Edit Pipeline Schedule %{id}"
 msgstr "Alterar Agendamento do Pipeline %{id}"
 
 msgid "Edit files in the editor and commit changes here"
+msgstr "Alterar arquivos no editor e fazer commit das altera莽玫es aqui"
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
 msgstr ""
 
 msgid "Emails"
 msgstr "Emails"
 
 msgid "Enable"
+msgstr "Ativar"
+
+msgid "Enable Auto DevOps"
+msgstr "Ativar Auto DevOps"
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
 msgstr ""
 
 msgid "Environments|An error occurred while fetching the environments."
@@ -1378,38 +1789,50 @@ msgstr "脡pico ser谩 removido! Tem certeza?"
 msgid "Epics"
 msgstr "脡picos"
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr "Epics permite que voc锚 gerencie seu portf贸lio de projetos de forma mais eficiente e com menos esfor莽o"
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr "Erro ao verificar dados do branch. Favor tentar novamente."
+
+msgid "Error committing changes. Please try again."
+msgstr "Erro ao realizar o commit das altera莽玫es. Favor tentar novamente."
+
 msgid "Error creating epic"
 msgstr "Erro ao criar 茅pico"
 
 msgid "Error fetching contributors data."
-msgstr ""
+msgstr "Erro ao recuperar informa莽玫es de contribuintes."
 
 msgid "Error fetching labels."
-msgstr ""
+msgstr "Erro ao carregar labels."
 
 msgid "Error fetching network graph."
-msgstr ""
+msgstr "Erro ao recuperar gr谩fico de rede."
 
 msgid "Error fetching refs"
-msgstr ""
+msgstr "Erro ao recuperar refs"
 
 msgid "Error fetching usage ping data."
-msgstr ""
+msgstr "Erro ao recupera dados de ping."
 
 msgid "Error occurred when toggling the notification subscription"
 msgstr "Erro ao alterar configura莽茫o de notifica莽茫o de assinatura"
 
 msgid "Error saving label update."
-msgstr ""
+msgstr "Erro ao salvar altera莽茫o de label."
 
 msgid "Error updating status for all todos."
-msgstr ""
+msgstr "Erro ao atualizar status para todas as tarefas."
 
 msgid "Error updating todo status."
-msgstr ""
+msgstr "Erro ao atualizar status das tarefas."
 
 msgid "EventFilterBy|Filter by all"
 msgstr "EventFilterBy|Filtrar por tudo"
@@ -1439,7 +1862,7 @@ msgid "Every week (Sundays at 4:00am)"
 msgstr "Toda semana (domingos 脿s 4:00)"
 
 msgid "Expand"
-msgstr ""
+msgstr "Expandir"
 
 msgid "Explore projects"
 msgstr "Explorar projetos"
@@ -1447,12 +1870,45 @@ msgstr "Explorar projetos"
 msgid "Explore public groups"
 msgstr "Explorar grupos p煤blicos"
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr "O acesso a este projeto foi negado para autoriza莽茫o externa"
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr "Falha"
+
+msgid "Failed Jobs"
+msgstr "Jobs falharam"
+
 msgid "Failed to change the owner"
 msgstr "Erro ao alterar o propriet谩rio"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "Erro ao excluir o agendamento do pipeline"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr "Fev"
 
@@ -1460,7 +1916,7 @@ msgid "February"
 msgstr "Fevereiro"
 
 msgid "Fields on this page are now uneditable, you can configure"
-msgstr ""
+msgstr "Campos nessa p谩gina n茫o s茫o mais edit谩veis, voc锚 pode configurar"
 
 msgid "File name"
 msgstr "Nome do arquivo"
@@ -1468,6 +1924,12 @@ msgstr "Nome do arquivo"
 msgid "Files"
 msgstr "Arquivos"
 
+msgid "Files (%{human_size})"
+msgstr "Arquivos (%{human_size})"
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "Filtrar por mensagem de commit"
 
@@ -1477,12 +1939,21 @@ msgstr "Localizar por caminho"
 msgid "Find file"
 msgstr "Localizar arquivo"
 
+msgid "Finished"
+msgstr "Finalizado"
+
 msgid "FirstPushedBy|First"
 msgstr "Primeiro"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "publicado por"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "Fork"
@@ -1494,76 +1965,103 @@ msgstr "Fork criado a partir de"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr "Fork a partir de %{project_name} (apagado)"
 
+msgid "Forking in progress"
+msgstr "Fork em andamento"
+
 msgid "Format"
 msgstr "Formato"
 
+msgid "From %{provider_title}"
+msgstr "De %{provider_title}"
+
 msgid "From issue creation until deploy to production"
 msgstr "Da abertura de tarefas at茅 a implanta莽茫o para a produ莽茫o"
 
 msgid "From merge request merge until deploy to production"
 msgstr "Do merge request at茅 a implanta莽茫o em produ莽茫o"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr "Chaves GPG"
 
 msgid "Generate a default set of labels"
-msgstr ""
+msgstr "Gerar labels padr茫o"
 
 msgid "Geo Nodes"
 msgstr "N贸s de geo"
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr "N贸 est谩 falhando ou quebrado."
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr "N贸 est谩 lento, sobrecarregado, ou acabou de recuperar ap贸s uma interrup莽茫o."
 
-msgid "GeoNodes|Database replication lag:"
+msgid "GeoNodes|Checksummed"
 msgstr ""
 
+msgid "GeoNodes|Database replication lag:"
+msgstr "Atraso na replica莽茫o do banco de dados:"
+
 msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
+msgstr "Desabilitar um n贸 para o processo de sincroniza莽茫o. Voc锚 tem certeza?"
 
 msgid "GeoNodes|Does not match the primary storage configuration"
-msgstr ""
+msgstr "N茫o corresponde 谩 configura莽茫o de armazenamento prim谩rio"
 
 msgid "GeoNodes|Failed"
-msgstr ""
+msgstr "Falha"
 
 msgid "GeoNodes|Full"
-msgstr ""
+msgstr "Completo"
 
 msgid "GeoNodes|GitLab version does not match the primary node version"
-msgstr ""
+msgstr "Vers茫o do GitLab n茫o corresponde a vers茫o do n贸 prim谩rio"
 
 msgid "GeoNodes|GitLab version:"
-msgstr ""
+msgstr "Vers茫o do GitLab:"
 
 msgid "GeoNodes|Health status:"
-msgstr ""
+msgstr "Sa煤de dos servi莽os:"
 
 msgid "GeoNodes|Last event ID processed by cursor:"
-msgstr ""
+msgstr "脷ltimo ID de evento processado pelo cursor:"
 
 msgid "GeoNodes|Last event ID seen from primary:"
-msgstr ""
+msgstr "脷ltimo ID de evento visto pelo prim谩rio:"
 
 msgid "GeoNodes|Loading nodes"
-msgstr ""
+msgstr "Carregando n贸s"
 
 msgid "GeoNodes|Local Attachments:"
-msgstr ""
+msgstr "Anexos locais:"
 
 msgid "GeoNodes|Local LFS objects:"
-msgstr ""
+msgstr "Objetos LFS locais:"
 
 msgid "GeoNodes|Local job artifacts:"
-msgstr ""
+msgstr "Artefatos de processos locais:"
 
 msgid "GeoNodes|New node"
+msgstr "Novo n贸"
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
 msgstr ""
 
 msgid "GeoNodes|Out of sync"
+msgstr "Fora de sincronia"
+
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
 msgstr ""
 
 msgid "GeoNodes|Replication slot WAL:"
@@ -1572,12 +2070,27 @@ msgstr ""
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1590,26 +2103,38 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
-msgid "GeoNodes|Wikis:"
+msgid "GeoNodes|Verified"
 msgstr ""
 
-msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgid "GeoNodes|Wiki checksums verified:"
 msgstr ""
 
-msgid "Geo|All projects"
+msgid "GeoNodes|Wikis checksummed:"
 msgstr ""
 
+msgid "GeoNodes|Wikis:"
+msgstr "Wikis:"
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr "Voc锚 configurou Geo nodes usando uma conex茫o HTTP insegura. Recomendamos o uso de HTTPS."
+
+msgid "Geo|All projects"
+msgstr "Todos os projetos"
+
 msgid "Geo|File sync capacity"
 msgstr "Capacidade de sincroniza莽茫o de arquivos"
 
 msgid "Geo|Groups to synchronize"
-msgstr ""
+msgstr "Grupos para sincronizar"
 
 msgid "Geo|Projects in certain groups"
-msgstr ""
+msgstr "Projetos em certos grupos"
 
 msgid "Geo|Projects in certain storage shards"
 msgstr ""
@@ -1623,21 +2148,42 @@ msgstr "Selecione grupos para replicar."
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
-msgid "Git revision"
+msgid "Git repository URL"
 msgstr ""
 
+msgid "Git revision"
+msgstr "Revis茫o do Git"
+
 msgid "Git storage health information has been reset"
 msgstr "Informa莽玫es sobre o status de sa煤de do storage Git foram reiniciadas"
 
 msgid "Git version"
+msgstr "Vers茫o do Git"
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
 msgstr ""
 
 msgid "GitLab Runner section"
 msgstr "Se莽茫o GitLab Runner"
 
-msgid "Gitaly Servers"
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
 msgstr ""
 
+msgid "Gitaly Servers"
+msgstr "Servidores Gitaly"
+
+msgid "Go back"
+msgstr "Voltar"
+
 msgid "Go to your fork"
 msgstr "Ir para seu fork"
 
@@ -1648,6 +2194,24 @@ msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab ad
 msgstr "Autentica莽茫o do Google n茫o est谩 %{link_to_documentation}. Pe莽a ao administrador do Gitlab se voc锚 deseja usar esse servi莽o."
 
 msgid "Got it!"
+msgstr "Entendi!"
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
 msgstr ""
 
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
@@ -1686,9 +2250,6 @@ msgstr "Nenhum grupo encontrado"
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr "Voc锚 pode gerenciar permiss玫es de membros e acesso do seu grupo para cada projeto no grupo."
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr "Criar um projeto nesse grupo."
 
@@ -1719,6 +2280,9 @@ msgstr "Desculpe, nenhum grupo ou projeto correspondem 脿 sua pesquisa"
 msgid "Have your users email"
 msgstr "E-mail para abertura de issues"
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "Status de Sa煤de"
 
@@ -1737,10 +2301,19 @@ msgstr "Nenhum problema de sa煤de detectado"
 msgid "HealthCheck|Unhealthy"
 msgstr "N茫o saud谩vel"
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Ocultar valor"
+msgstr[1] "Ocultar valores"
 
 msgid "History"
 msgstr "Hist贸rico"
@@ -1748,9 +2321,39 @@ msgstr "Hist贸rico"
 msgid "Housekeeping successfully started"
 msgstr "Manuten莽茫o iniciada com sucesso"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr "Importar"
+
+msgid "Import all repositories"
+msgstr "Importar todos reposit贸rios"
+
+msgid "Import in progress"
+msgstr "Importa莽茫o em andamento"
+
+msgid "Import repositories from GitHub"
+msgstr "Importar reposit贸rios do GitHub"
+
 msgid "Import repository"
 msgstr "Importar reposit贸rio"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr "Melhorar issue boards com o GitLab Enterprise Edition."
 
@@ -1760,6 +2363,9 @@ msgstr "Melhore a ger锚ncia de issues com pesos no GitLab Enterprise Edition."
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr "Encontre o que precisa mais facilmente com a pesquisa global avan莽ada com GitLab Enterprise Edition."
 
+msgid "Install Runner on Kubernetes"
+msgstr "Instalar Runner no Kubernates"
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "Instalar um Runner compat铆vel com o GitLab CI"
 
@@ -1769,6 +2375,9 @@ msgstr[0] "Inst芒ncia"
 msgstr[1] "Inst芒ncias"
 
 msgid "Instance does not support multiple Kubernetes clusters"
+msgstr "A inst芒ncia n茫o suporta m煤ltiplos clusters Kubernetes"
+
+msgid "Integrations"
 msgstr ""
 
 msgid "Interested parties can even contribute by pushing commits if they want to."
@@ -1810,6 +2419,9 @@ msgstr "Jan"
 msgid "January"
 msgstr "Janeiro"
 
+msgid "Jobs"
+msgstr "Jobs"
+
 msgid "Jul"
 msgstr "Jul"
 
@@ -1822,11 +2434,14 @@ msgstr "Jun"
 msgid "June"
 msgstr "Junho"
 
-msgid "Kubernetes"
+msgid "Koding"
 msgstr ""
 
+msgid "Kubernetes"
+msgstr "Kubernetes"
+
 msgid "Kubernetes Cluster"
-msgstr ""
+msgstr "Cluster Kubernetes"
 
 msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
 msgstr ""
@@ -1840,6 +2455,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1849,12 +2467,30 @@ msgstr "Desabilitado"
 msgid "LFSStatus|Enabled"
 msgstr "Habilitado"
 
+msgid "Label"
+msgstr "Label"
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr "Etiquetas"
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "脷ltimo %d dia"
@@ -1885,6 +2521,12 @@ msgid "LastPushEvent|at"
 msgstr "em"
 
 msgid "Learn more"
+msgstr "Saiba mais"
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
 msgstr ""
 
 msgid "Learn more in the"
@@ -1905,17 +2547,23 @@ msgstr "Sair do projeto"
 msgid "License"
 msgstr "Licen莽a"
 
-msgid "Loading the GitLab IDE..."
+msgid "List"
+msgstr "Lista"
+
+msgid "List your GitHub repositories"
 msgstr ""
 
+msgid "Loading the GitLab IDE..."
+msgstr "Carregando IDE do GitLab..."
+
 msgid "Lock"
 msgstr "Bloquear"
 
 msgid "Lock %{issuableDisplayName}"
-msgstr ""
+msgstr "Bloquear %{issuableDisplayName}"
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
-msgstr ""
+msgid "Lock not found"
+msgstr "Bloqueio n茫o encontrado"
 
 msgid "Locked"
 msgstr "Bloqueado"
@@ -1923,13 +2571,28 @@ msgstr "Bloqueado"
 msgid "Locked Files"
 msgstr "Arquivos bloqueados"
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr "Entrar"
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
+msgstr "Gerenciar etiquetas"
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
 msgstr ""
 
 msgid "Mar"
@@ -1939,7 +2602,7 @@ msgid "March"
 msgstr "Mar莽o"
 
 msgid "Mark done"
-msgstr ""
+msgstr "Marcar como feito"
 
 msgid "Maximum git storage failures"
 msgstr "M谩ximo de falhas do git storage"
@@ -1953,7 +2616,7 @@ msgstr "Mediana"
 msgid "Members"
 msgstr "Membros"
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1966,49 +2629,145 @@ msgid "Merge request"
 msgstr "Merge requests"
 
 msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
-msgstr ""
+msgstr "A tela de Merge request 茅 um lugar para propor mudan莽as em um projeto e discutir essas mudan莽as com outros"
 
 msgid "Merged"
-msgstr ""
+msgstr "Merge realizado"
 
 msgid "Messages"
 msgstr "Mensagens"
 
-msgid "Milestone"
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
 msgstr ""
 
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Milestone"
+msgstr "Milestone"
+
 msgid "Milestones|Delete milestone"
 msgstr ""
 
 msgid "Milestones|Delete milestone %{milestoneTitle}?"
 msgstr ""
 
-msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
 msgstr ""
 
-msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgid "Milestones|This action cannot be reversed."
 msgstr ""
 
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "adicione uma chave SSH"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr "Monitoramento"
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr "Mais informa莽玫es est茫o dispon铆veis|aqui"
 
 msgid "Move"
-msgstr ""
+msgstr "Mover"
 
 msgid "Move issue"
-msgstr ""
+msgstr "Mover issue"
 
 msgid "Multiple issue boards"
 msgstr "M煤ltiplos issue boards"
 
 msgid "Name new label"
-msgstr ""
+msgstr "Nome da nova label"
 
 msgid "New Issue"
 msgid_plural "New Issues"
@@ -2016,10 +2775,10 @@ msgstr[0] "Nova Issue"
 msgstr[1] "Novas Issues"
 
 msgid "New Kubernetes Cluster"
-msgstr ""
+msgstr "Novo cluster Kubernetes"
 
 msgid "New Kubernetes cluster"
-msgstr ""
+msgstr "Novo cluster Kubernetes"
 
 msgid "New Pipeline Schedule"
 msgstr "Novo Agendamento de Pipeline"
@@ -2046,7 +2805,7 @@ msgid "New issue"
 msgstr "Nova issue"
 
 msgid "New label"
-msgstr ""
+msgstr "Nova label"
 
 msgid "New merge request"
 msgstr "Novo merge request"
@@ -2066,22 +2825,28 @@ msgstr "Novo subgrupo"
 msgid "New tag"
 msgstr "Nova tag"
 
-msgid "No assignee"
+msgid "No Label"
 msgstr ""
 
+msgid "No assignee"
+msgstr "Sem respons谩vel"
+
 msgid "No changes"
-msgstr ""
+msgstr "Sem alterar莽玫es"
 
 msgid "No connection could be made to a Gitaly Server, please check your logs!"
-msgstr ""
+msgstr "Nenhuma conex茫o pode ser feita para um servidor Gitaly, por favor check os logs!"
 
 msgid "No due date"
-msgstr ""
+msgstr "Sem validade"
 
 msgid "No estimate or time spent"
-msgstr ""
+msgstr "Sem estimativa de tempo gasto"
 
 msgid "No file chosen"
+msgstr "Nenhum arquivo escolhido"
+
+msgid "No labels created yet."
 msgstr ""
 
 msgid "No repository"
@@ -2090,24 +2855,42 @@ msgstr "Nenhum reposit贸rio"
 msgid "No schedules"
 msgstr "Nenhum agendamento"
 
-msgid "No time spent"
-msgstr "Nenhum tempo gasto"
-
 msgid "None"
 msgstr "Nenhum"
 
 msgid "Not allowed to merge"
-msgstr ""
+msgstr "Merge n茫o permitido"
 
 msgid "Not available"
 msgstr "N茫o dispon铆vel"
 
-msgid "Not confidential"
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
 msgstr ""
 
+msgid "Not confidential"
+msgstr "N茫o confidencial"
+
 msgid "Not enough data"
 msgstr "Dados insuficientes"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "Eventos de notifica莽茫o"
 
@@ -2166,10 +2949,10 @@ msgid "Notifications"
 msgstr "Notifica莽玫es"
 
 msgid "Notifications off"
-msgstr ""
+msgstr "Notifica莽玫es deligadas"
 
 msgid "Notifications on"
-msgstr ""
+msgstr "Notifica莽玫es ligadas"
 
 msgid "Nov"
 msgstr "Nov"
@@ -2181,7 +2964,7 @@ msgid "Number of access attempts"
 msgstr "N煤mero de tentativas de acesso"
 
 msgid "OK"
-msgstr ""
+msgstr "OK"
 
 msgid "Oct"
 msgstr "Out"
@@ -2192,11 +2975,17 @@ msgstr "Outubro"
 msgid "OfSearchInADropdown|Filter"
 msgstr "Filtrar"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr "Somente membros do projeto podem comentar."
 
 msgid "Open"
-msgstr ""
+msgstr "Abrir"
 
 msgid "Opened"
 msgstr "Aberto"
@@ -2210,12 +2999,21 @@ msgstr "Abrir em nova janela"
 msgid "Options"
 msgstr "Op莽玫es"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "Vis茫o geral"
 
 msgid "Owner"
 msgstr "Propriet谩rio"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr "脷ltimo >>"
 
@@ -2228,9 +3026,21 @@ msgstr "Anterior"
 msgid "Pagination|芦 First"
 msgstr "<< Primeiro"
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "Senha"
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "Pipeline"
 
@@ -2310,9 +3120,54 @@ msgid "Pipelines for last year"
 msgstr "Pipelines para o 煤ltimo ano"
 
 msgid "Pipelines|Build with confidence"
+msgstr "Construa com confian莽a"
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
 msgstr ""
 
 msgid "Pipelines|Get started with Pipelines"
+msgstr "Saiba como funcionam as pipelines"
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
 msgstr ""
 
 msgid "Pipeline|all"
@@ -2327,20 +3182,29 @@ msgstr "com etapa"
 msgid "Pipeline|with stages"
 msgstr "com etapas"
 
-msgid "Play"
+msgid "PlantUML"
 msgstr ""
 
+msgid "Play"
+msgstr "Iniciar"
+
 msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
-msgstr ""
+msgstr "Por favor, <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener no referrer\">ative a cobran莽a para um de seus projetos para ser poss铆vel criar um cluster Kubernetes</a>, depois tente novamente."
 
 msgid "Please solve the reCAPTCHA"
 msgstr "Por favor, resolva o reCAPTCHA"
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr "Prefer锚ncias"
 
 msgid "Primary"
-msgstr ""
+msgstr "Prim谩rio"
 
 msgid "Private - Project access must be granted explicitly to each user."
 msgstr "Privado - O acesso ao projeto deve ser concedido explicitamente para cada usu谩rio."
@@ -2348,6 +3212,9 @@ msgstr "Privado - O acesso ao projeto deve ser concedido explicitamente para cad
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr "Privado - O grupo e seus projetos s贸 podem ser vistos por seus membros."
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr "Perfil"
 
@@ -2387,9 +3254,12 @@ msgstr "Sua conta 茅 atualmente propriet谩ria dos seguintes grupos:"
 msgid "Profiles|your account"
 msgstr "sua conta"
 
-msgid "Programming languages used in this repository"
+msgid "Profiling - Performance bar"
 msgstr ""
 
+msgid "Programming languages used in this repository"
+msgstr "Linguagens de programa莽茫o usadas nesse reposit贸rio"
+
 msgid "Project '%{project_name}' is in the process of being deleted."
 msgstr "O projeto '%{project_name}' est谩 sendo exclu铆do."
 
@@ -2406,13 +3276,10 @@ msgid "Project access must be granted explicitly to each user."
 msgstr "Acesso ao projeto deve ser concedido explicitamente para cada usu谩rio."
 
 msgid "Project avatar"
-msgstr ""
+msgstr "Imagem do projeto"
 
 msgid "Project avatar in repository: %{link}"
-msgstr ""
-
-msgid "Project cache successfully reset."
-msgstr ""
+msgstr "Imagem do projeto no reposit贸rio: %{link}"
 
 msgid "Project details"
 msgstr "Detalhes do projeto"
@@ -2433,19 +3300,19 @@ msgid "ProjectActivityRSS|Subscribe"
 msgstr "Inscreva-se"
 
 msgid "ProjectCreationLevel|Allowed to create projects"
-msgstr ""
+msgstr "Permitido a cria莽茫o de projetos"
 
 msgid "ProjectCreationLevel|Default project creation protection"
-msgstr ""
+msgstr "Prote莽茫o de cria莽茫o de projeto padr茫o"
 
 msgid "ProjectCreationLevel|Developers + Masters"
-msgstr ""
+msgstr "Desenvolvedores + Masters"
 
 msgid "ProjectCreationLevel|Masters"
-msgstr ""
+msgstr "Masters"
 
 msgid "ProjectCreationLevel|No one"
-msgstr ""
+msgstr "Ningu茅m"
 
 msgid "ProjectFeature|Disabled"
 msgstr "Desabilitado"
@@ -2510,42 +3377,93 @@ msgstr "Desculpe, nenhum projeto corresponde a sua pesquisa"
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr "Esta funcionalidade necessita de suporte 脿 localStorage do navegador"
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr "Por padr茫o, Prometheus escuta em 'http://localhost:9090'. N茫o 茅 recomendado mudar o endere莽o padr茫o e sua porta, porque pode conflitar com outros servi莽os que est茫o executando no sevidor do Gitlab."
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr "Encontrando e configurando m茅tricas..."
 
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
 msgid "PrometheusService|Metrics"
 msgstr "M茅tricas"
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
-msgstr "M茅tricas s茫o automaticamente configuradas e monitoradas baseadas na biblioteca de m茅tricas de exportadores populares."
-
 msgid "PrometheusService|Missing environment variable"
 msgstr "Vari谩vel de ambiente ausente"
 
-msgid "PrometheusService|Monitored"
-msgstr "Monitorado"
-
 msgid "PrometheusService|More information"
 msgstr "Mais informa莽玫es"
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr "Nenhuma m茅trica est谩 sendo monitorada. Para inicar o monitoramento, fa莽a deploy em um ambiente."
+msgid "PrometheusService|New metric"
+msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr "URL da API base do Prometheus. como http://prometheus.example.com/"
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
+msgstr "Servi莽o de monitoramento de tempo-de-s茅rie"
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
 msgstr ""
 
-msgid "PrometheusService|View environments"
-msgstr "Ver ambientes"
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
 
-msgid "Protip:"
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
+msgid "Protip:"
+msgstr "Dicas:"
+
 msgid "Public - The group and any public projects can be viewed without any authentication."
 msgstr "P煤blico - O grupo e seus projetos podem ser visualizados por todos sem autentica莽茫o."
 
@@ -2558,11 +3476,17 @@ msgstr "Regras de push"
 msgid "Push events"
 msgstr "Eventos de push"
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr "Restri莽茫o de commit"
 
 msgid "Quick actions can be used in the issues description and comment boxes."
-msgstr ""
+msgstr "A莽玫es r谩pidas podem ser usadas nas descri莽玫es das issues e nas caixas de coment谩rio."
 
 msgid "Read more"
 msgstr "Leia mais"
@@ -2570,6 +3494,9 @@ msgstr "Leia mais"
 msgid "Readme"
 msgstr "Leia-me"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "Branches"
 
@@ -2577,10 +3504,10 @@ msgid "RefSwitcher|Tags"
 msgstr "Tags"
 
 msgid "Reference:"
-msgstr ""
+msgstr "Refer锚ncia:"
 
 msgid "Register / Sign In"
-msgstr ""
+msgstr "Registrar/Login"
 
 msgid "Registry"
 msgstr "Registro"
@@ -2603,24 +3530,42 @@ msgstr "Merge Requests Relacionados"
 msgid "Related Merged Requests"
 msgstr "Merge Requests Relacionados"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "Lembrar mais tarde"
 
 msgid "Remove"
-msgstr ""
+msgstr "Remover"
 
 msgid "Remove avatar"
-msgstr ""
+msgstr "Remover imagem"
 
 msgid "Remove project"
 msgstr "Remover projeto"
 
 msgid "Repair authentication"
+msgstr "Reparar autentica莽茫o"
+
+msgid "Repo by URL"
 msgstr ""
 
 msgid "Repository"
 msgstr "Reposit贸rio"
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "Solicitar acesso"
 
@@ -2633,10 +3578,16 @@ msgstr "Recriar o token de status de sa煤de"
 msgid "Reset runners registration token"
 msgstr "Recriar o token de registro de runners"
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Mostrar valor"
+msgstr[1] "Mostrar valores"
 
 msgid "Revert this commit"
 msgstr "Reverter este commit"
@@ -2644,6 +3595,36 @@ msgstr "Reverter este commit"
 msgid "Revert this merge request"
 msgstr "Reverter esse merge request"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr "Executando"
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "Chaves SSH"
 
@@ -2654,11 +3635,14 @@ msgid "Save pipeline schedule"
 msgstr "Salvar agendamento da pipeline"
 
 msgid "Save variables"
-msgstr ""
+msgstr "Salvar vari谩veis"
 
 msgid "Schedule a new pipeline"
 msgstr "Agendar nova pipeline"
 
+msgid "Scheduled"
+msgstr "Agendado"
+
 msgid "Schedules"
 msgstr "Agendamentos"
 
@@ -2668,17 +3652,20 @@ msgstr "Agendando pipelines"
 msgid "Scoped issue boards"
 msgstr "Issue board de escopo"
 
+msgid "Search"
+msgstr "Pesquisar"
+
 msgid "Search branches and tags"
 msgstr "Procurar branch e tags"
 
 msgid "Search milestones"
-msgstr ""
+msgstr "Pesquisar milestones"
 
 msgid "Search project"
-msgstr ""
+msgstr "Procurar projeto"
 
 msgid "Search users"
-msgstr ""
+msgstr "Procurar usu谩rios"
 
 msgid "Seconds before reseting failure information"
 msgstr "Segundos antes de redefinir as informa莽玫es de falha"
@@ -2687,7 +3674,10 @@ msgid "Seconds to wait for a storage access attempt"
 msgstr "Segundo de espera para tentativa de acesso ao storage"
 
 msgid "Secret variables"
-msgstr ""
+msgstr "Vari谩veis secretas"
+
+msgid "Security report"
+msgstr "Relat贸rio de seguran莽a"
 
 msgid "Select Archive Format"
 msgstr "Selecionar Formato do Arquivo"
@@ -2695,17 +3685,23 @@ msgstr "Selecionar Formato do Arquivo"
 msgid "Select a timezone"
 msgstr "Selecionar fuso hor谩rio"
 
-msgid "Select assignee"
+msgid "Select an existing Kubernetes cluster or create a new one"
 msgstr ""
 
+msgid "Select assignee"
+msgstr "Selecione o respons谩vel"
+
 msgid "Select branch/tag"
-msgstr ""
+msgstr "Selecionar o branch/tag"
 
 msgid "Select target branch"
 msgstr "Selecionar branch de destino"
 
 msgid "Selective synchronization"
-msgstr ""
+msgstr "Sincroniza莽茫o seletiva"
+
+msgid "Send email"
+msgstr "Enviar e-mail"
 
 msgid "Sep"
 msgstr "Set"
@@ -2714,22 +3710,40 @@ msgid "September"
 msgstr "Setembro"
 
 msgid "Server version"
-msgstr ""
+msgstr "Vers茫o do servidor"
 
 msgid "Service Templates"
 msgstr "Modelos de servi莽o"
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "Defina uma senha para sua conta para aceitar ou entregar c贸digo via %{protocol}."
 
-msgid "Set up CI/CD"
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
 msgstr ""
 
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr "Configurar CI/CD"
+
 msgid "Set up Koding"
 msgstr "Configurar Koding"
 
-msgid "Set up auto deploy"
-msgstr "Configurar implanta莽茫o autom谩tica"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "defina uma senha"
@@ -2737,6 +3751,12 @@ msgstr "defina uma senha"
 msgid "Settings"
 msgstr "Configura莽玫es"
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2746,6 +3766,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr "Mostrar p谩ginas acima"
 
@@ -2769,6 +3792,18 @@ msgstr "Nenhum"
 msgid "Sidebar|Weight"
 msgstr "Peso"
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr "Snippets"
 
@@ -2776,15 +3811,15 @@ msgid "Something went wrong on our end"
 msgstr ""
 
 msgid "Something went wrong on our end."
-msgstr "Algo deu errado do nosso lado."
+msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
-msgstr "Algo deu errado ao tentar mudar o estado de ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
+msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2794,7 +3829,7 @@ msgid "Something went wrong while fetching the registry list."
 msgstr "Algo deu errado ao recuperar a lista de registro."
 
 msgid "Something went wrong. Please try again."
-msgstr ""
+msgstr "Algo deu errado. Por favor, tente novamente."
 
 msgid "Sort by"
 msgstr "Ordenar por"
@@ -2898,6 +3933,9 @@ msgstr "Peso"
 msgid "Source"
 msgstr "Origem"
 
+msgid "Source (branch or tag)"
+msgstr "Fonte (branch or tag)"
+
 msgid "Source code"
 msgstr "C贸digo-fonte"
 
@@ -2907,12 +3945,21 @@ msgstr "Origem n茫o est谩 dispon铆vel"
 msgid "Spam Logs"
 msgstr "Logs de spam"
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "Especifique a seguinte URL durante a configura莽茫o do Runner:"
 
 msgid "StarProject|Star"
 msgstr "Marcar"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr "Projetos favoritos"
 
@@ -2922,11 +3969,20 @@ msgstr "Iniciar um %{new_merge_request} a partir dessas altera莽玫es"
 msgid "Start the Runner!"
 msgstr "Inicie o Runner!"
 
+msgid "Started"
+msgstr "Iniciado"
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr "Status"
+
 msgid "Stopped"
 msgstr "Parado"
 
 msgid "Storage"
-msgstr ""
+msgstr "Armazenamento"
 
 msgid "Subgroups"
 msgstr "Subgrupos"
@@ -2934,13 +3990,19 @@ msgstr "Subgrupos"
 msgid "Switch branch/tag"
 msgstr "Trocar branch/tag"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr "Hooks do sistema"
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "Tag"
-msgstr[1] "Tags"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] "Tag (%{tag_count})"
+msgstr[1] "Tags (%{tag_count})"
 
 msgid "Tags"
 msgstr "Tags"
@@ -3017,6 +4079,9 @@ msgstr "protegido"
 msgid "Target Branch"
 msgstr "Branch de destino"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr "Equipe"
 
@@ -3027,9 +4092,12 @@ msgid "The Advanced Global Search in GitLab is a powerful search service that sa
 msgstr "A pesquisa global avan莽ada no GitLab 茅 um servi莽o de pesquisa poderoso que economiza seu tempo. Ao inv茅s de criar c贸digos duplicados e perder seu tempo, voc锚 pode agora pesquisar c贸digos de outros times que podem ajudar em seu projeto."
 
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
-msgstr ""
+msgstr "Issue Tracker 茅 o lugar para adicionar coisas que precisam ser melhoradas ou resolvidas em um projeto"
 
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr "Issue Tracker 茅 o lugar para adicionar coisas que precisam ser melhoradas ou resolvidas em um projeto. Voc锚 precisa se registrar ou fazer login para criar alguma Issue para este projeto."
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
 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."
@@ -3038,9 +4106,15 @@ msgstr "A etapa de codifica莽茫o mostra o tempo desde a entrega do primeiro comm
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "A cole莽茫o de eventos adicionados aos dados coletados para essa etapa."
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "O relacionamento como fork foi removido."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "A etapa de planejamento mostra o tempo que se leva desde a cria莽茫o de uma issue at茅 sua atribui莽茫o 脿 um milestone, ou sua adi莽茫o a uma lista no seu Issue Board. Comece a criar issues para ver dados para esta etapa."
 
@@ -3053,12 +4127,18 @@ msgstr "O n煤mero de tentativas que gitlab far谩 para acessar um storage."
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr "O n煤mero de falhas para que o GitLab desabilite o acesso ao storage. O n煤mero de falhas pode ser redefinido na interface do administrador: %{link_to_health_page} ou %{api_documentation_link}."
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
 msgid "The phase of the development lifecycle."
 msgstr "A fase do ciclo de vida do desenvolvimento."
 
 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 "A etapa de planejamento mostra o tempo do passo anterior at茅 a publica莽茫o de seu primeiro conjunto de mudan莽as. Este tempo ser谩 adicionado automaticamente assim que voc锚 enviar seu primeiro conjunto de mudan莽as."
 
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "A etapa de produ莽茫o mostra o tempo total que leva entre criar uma issue e implantar o c贸digo em produ莽茫o. Os dados ser茫o adicionados automaticamente assim que voc锚 completar todo o ciclo de produ莽茫o."
 
@@ -3071,9 +4151,18 @@ msgstr "O projeto pode ser acessado sem a necessidade de autentica莽茫o."
 msgid "The repository for this project does not exist."
 msgstr "N茫o existe reposit贸rio para este projeto."
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "A etapa de revis茫o mostra o tempo de cria莽茫o de uma solicita莽茫o de incorpora莽茫o at茅 sua aceita莽茫o. Os dados ser茫o automaticamente adicionados depois que sua primeira solicita莽茫o de incorpora莽茫o for aceita."
 
+msgid "The roadmap shows the progress of your epics along a timeline"
+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 "A etapa de homologa莽茫o mostra o tempo entre o aceite da solicita莽茫o de incorpora莽茫o e a implanta莽茫o do c贸digo no ambiente de produ莽茫o. Os dados ser茫o automaticamente adicionados depois que voc锚 implantar em produ莽茫o pela primeira vez."
 
@@ -3096,37 +4185,40 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
 msgstr "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."
 
 msgid "There are no issues to show"
-msgstr ""
+msgstr "N茫o h谩 issues para mostrar"
 
 msgid "There are no merge requests to show"
-msgstr ""
+msgstr "N茫o h谩 merge requests pra mostrar"
 
 msgid "There are problems accessing Git storage: "
 msgstr "H谩 problemas para acessar o storage Git: "
 
-msgid "There was an error loading users activity calendar."
+msgid "There was an error loading results"
 msgstr ""
 
+msgid "There was an error loading users activity calendar."
+msgstr "Erro ao carregar calend谩rio de atividades."
+
 msgid "There was an error saving your notification settings."
-msgstr ""
+msgstr "Erro ao salvar suas configura莽玫es de notifica莽茫o."
 
 msgid "There was an error subscribing to this label."
-msgstr ""
+msgstr "Erro ao se inscrever nessa label."
 
 msgid "There was an error when reseting email token."
-msgstr ""
+msgstr "Erro ao redefinir token do email."
 
 msgid "There was an error when subscribing to this label."
-msgstr ""
+msgstr "Erro ao se inscrever nessa label."
 
 msgid "There was an error when unsubscribing from this label."
-msgstr ""
+msgstr "Erro ao se anular a inscri莽茫o dessa label."
 
 msgid "This board\\'s scope is reduced"
 msgstr "O escopo desse board est谩 reduzido"
 
 msgid "This directory"
-msgstr ""
+msgstr "Esse diret贸rio"
 
 msgid "This is a confidential issue."
 msgstr "Essa issue 茅 confidencial."
@@ -3135,7 +4227,7 @@ msgid "This is the author's first Merge Request to this project."
 msgstr "Esse 茅 o autor do primeiro merge request desse projeto."
 
 msgid "This issue is confidential"
-msgstr ""
+msgstr "Essa issue 茅 confidencial"
 
 msgid "This issue is confidential and locked."
 msgstr "Essa issue 茅 confidencial e est谩 bloqueada."
@@ -3153,13 +4245,13 @@ msgid "This job has not been triggered yet"
 msgstr ""
 
 msgid "This job has not started yet"
-msgstr ""
+msgstr "Esse processo ainda n茫o come莽ou"
 
 msgid "This job is in pending state and is waiting to be picked by a runner"
 msgstr ""
 
 msgid "This job requires a manual action"
-msgstr ""
+msgstr "Este Job exige uma a莽茫o manual"
 
 msgid "This means you can not push code until you create an empty repository or import existing one."
 msgstr "Isto significa que voc锚 n茫o pode entregar c贸digo at茅 que crie um reposit贸rio vazio ou importe um existente."
@@ -3167,11 +4259,17 @@ msgstr "Isto significa que voc锚 n茫o pode entregar c贸digo at茅 que crie um rep
 msgid "This merge request is locked."
 msgstr "Esse merge request est谩 bloqueado."
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr "Esta p谩gina n茫o est谩 dispon铆vel porque voc锚 n茫o tem permiss茫o para ler informa莽玫es de v谩rios projetos."
+
 msgid "This project"
-msgstr ""
+msgstr "Esse projeto"
 
 msgid "This repository"
-msgstr ""
+msgstr "Esse reposit贸rio"
+
+msgid "This will delete the custom metric, Are you sure?"
+msgstr "Esta a莽茫o excluir谩 uma m茅trica personalizada, voc锚 tem certeza?"
 
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr "Esses e-mails se tornar茫o issues automaticamente (com os coment谩rios se tornando uma conversa de e-mail) listadas aqui."
@@ -3185,6 +4283,12 @@ msgstr "Tempo at茅 que uma issue comece a ser implementado"
 msgid "Time between merge request creation and merge/close"
 msgstr "Tempo entre a cria莽茫o da solicita莽茫o de incorpora莽茫o e a aceita莽茫o/fechamento"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3336,39 +4440,72 @@ msgstr[1] "mins"
 msgid "Time|s"
 msgstr "s"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr "T铆tulo"
 
-msgid "Todo"
+msgid "To GitLab"
 msgstr ""
 
-msgid "Toggle sidebar"
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
 msgstr ""
 
-msgid "ToggleButton|Toggle Status: OFF"
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
 msgstr ""
 
-msgid "ToggleButton|Toggle Status: ON"
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
 msgstr ""
 
+msgid "Todo"
+msgstr "Pendente"
+
+msgid "Toggle sidebar"
+msgstr "Ativar/Desativar barra lateral"
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr "Mudar Status: Desligado"
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr "Mudar Status: Ligado"
+
 msgid "Total Time"
 msgstr "Tempo Total"
 
-msgid "Total issue time spent"
-msgstr "Tempo total gasto"
-
 msgid "Total test time for all commits/merges"
 msgstr "Tempo de teste total para todos os commits/merges"
 
+msgid "Total: %{total}"
+msgstr "Total: %{total}"
+
 msgid "Track activity with Contribution Analytics."
 msgstr "Acompanhe a atividade com o Contribution Analytics."
 
 msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr "Acompanhe grupos de quest玫es que compartilhem um tema, em projetos e milestones"
 
-msgid "Total: %{total}"
-msgstr ""
-
 msgid "Track time with quick actions"
 msgstr ""
 
@@ -3378,29 +4515,23 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr "Ativar Service Desk"
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
-msgstr ""
+msgstr "Desconhecido"
 
 msgid "Unlock"
 msgstr "Desbloquear"
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
-
 msgid "Unlocked"
 msgstr "Desbloqueado"
 
+msgid "Unresolve discussion"
+msgstr ""
+
 msgid "Unstar"
 msgstr "Desmarcar"
 
 msgid "Up to date"
-msgstr ""
+msgstr "Atualizado"
 
 msgid "Upgrade your plan to activate Advanced Global Search."
 msgstr "Atualize seu plano para ativar a Pesquisa Global Avan莽ada."
@@ -3424,11 +4555,17 @@ msgid "Upload file"
 msgstr "Enviar arquivo"
 
 msgid "Upload new avatar"
-msgstr ""
+msgstr "Fazer upload de nova imagem"
 
 msgid "UploadLink|click to upload"
 msgstr "clique para fazer upload"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr "Use o Service Desk para se conectar com seus usu谩rios (por exemplo, para oferecer suporte ao cliente) por email dentro do GitLab"
 
@@ -3438,21 +4575,51 @@ msgstr "Use o seguinte token de registro durante a configura莽茫o:"
 msgid "Use your global notification setting"
 msgstr "Utilizar configura莽茫o de notifica莽茫o global"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr "Ver arquivo @ "
 
-msgid "View labels"
+msgid "View group labels"
 msgstr ""
 
+msgid "View labels"
+msgstr "Visualizar etiquetas"
+
 msgid "View open merge request"
 msgstr "Ver merge request aberto"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr "Ver arquivo substitu铆do @ "
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "Interno"
 
@@ -3469,7 +4636,7 @@ msgid "Want to see the data? Please ask an administrator for access."
 msgstr "Precisa visualizar os dados? Solicite acesso ao administrador."
 
 msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
-msgstr ""
+msgstr "N茫o foi poss铆vel verificar se um dos seus projetos no GCP possui o faturamento ativado. Por favor, tente novamente."
 
 msgid "We don't have enough data to show this stage."
 msgstr "Esta etapa n茫o possui dados suficientes para exibi莽茫o."
@@ -3477,12 +4644,21 @@ msgstr "Esta etapa n茫o possui dados suficientes para exibi莽茫o."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr "Queremos ter certeza de que 茅 voc锚, confirme que voc锚 n茫o 茅 um rob么."
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr "Webhooks permitem que voc锚 acione uma URL se, por exemplo, quando um novo c贸digo for feito push ou uma nova issue criada. Voc锚 pode configurar os webhooks para escutar eventos espec铆ficos como push, issue ou merge request. Webhooks de grupo aplicar茫o para todos os projetos no grupo, permitindo voc锚 padronizar o funcionamento em todo o grupo."
 
 msgid "Weight"
 msgstr "Peso"
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr "Wiki"
 
@@ -3597,32 +4773,44 @@ msgstr "Com a an谩lise de contribui莽茫o, voc锚 pode ter uma vis茫o geral da ati
 msgid "Withdraw Access Request"
 msgstr "Remover Requisi莽茫o de Acesso"
 
+msgid "Write a commit message..."
+msgstr "Escrever uma mensagem de commit..."
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "Voc锚 vai remover %{group_name}. Grupos removidos N脙O PODEM ser restaurados! Voc锚 est谩 ABSOLUTAMENTE certo?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "Voc锚 ir谩 remover %{project_name_with_namespace}. O projeto removido N脙O PODE ser restaurado! Tem certeza ABSOLUTA?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Voc锚 ir谩 remover %{project_full_name}. O projeto removido N脙O PODE ser restaurado! Tem certeza ABSOLUTA?"
 
 msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
 msgstr "Voc锚 est谩 prestes a remover a rela莽茫o de fork do projeto original %{forked_from_project}. Voc锚 tem CERTEZA disso?"
 
-msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
-msgstr "Voc锚 ir谩 transferir %{project_name_with_namespace} para outro propriet谩rio. Tem certeza ABSOLUTA?"
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr "Voc锚 ir谩 transferir %{project_full_name} para outro propriet谩rio. Tem certeza ABSOLUTA?"
 
-msgid "You can also star a label to make it a priority label."
+msgid "You are on a read-only GitLab instance."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
 msgstr ""
 
+msgid "You can also create a project from the command line."
+msgstr "Voc锚 tamb茅m pode criar um projeto a partir da linha de comando."
+
+msgid "You can also star a label to make it a priority label."
+msgstr "Voc锚 tamb茅m pode marcar uma label para torn谩-la uma label de prioridade."
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr "Voc锚 pode instalar facilmente um Runner em um cluster Kubernetes. %{link_to_help_page}"
+
 msgid "You can move around the graph by using the arrow keys."
-msgstr ""
+msgstr "Voc锚 pode mover o gr谩fico usando as setas do teclado."
 
 msgid "You can only add files when you are on a branch"
 msgstr "Voc锚 somente pode adicionar arquivos quando estiver em um branch"
 
 msgid "You can only edit files when you are on a branch"
-msgstr ""
+msgstr "Voc锚 s贸 pode editar arquivos quando estiver em um branch"
 
 msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
 msgstr "Voc锚 n茫o pode escrever numa inst芒ncia secund谩ria de somente leitura do GitLab Geo. Por favor use %{link_to_primary_node}."
@@ -3630,12 +4818,24 @@ msgstr "Voc锚 n茫o pode escrever numa inst芒ncia secund谩ria de somente leitura
 msgid "You cannot write to this read-only GitLab instance."
 msgstr "Voc锚 n茫o pode escrever nesta inst芒ncia somente-leitura do GitLab."
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr "Voc锚 n茫o tem permiss茫o"
+
 msgid "You have reached your project limit"
 msgstr "Voc锚 atingiu o limite de seu projeto"
 
+msgid "You must have master access to force delete a lock"
+msgstr "Voc锚 deve ter o acesso master para apagar um bloqueio"
+
 msgid "You must sign in to star a project"
 msgstr "Voc锚 deve estar autenticado para marcar um projeto"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "Voc锚 precisa de permiss茫o."
 
@@ -3664,11 +4864,32 @@ msgid "You won't be able to pull or push project code via SSH until you add an S
 msgstr "Voc锚 n茫o poder谩 fazer push ou pull do c贸digo via SSH enquanto n茫o adicionar sua chave SSH no seu perfil"
 
 msgid "You'll need to use different branch names to get a valid comparison."
+msgstr "Voc锚 precisar谩 usar nomes de branch diferentes para obter uma compara莽茫o v谩lida."
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
 msgstr ""
 
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr "Commit com suas altera莽玫es realizado. Commit %{commitId} %{commitStats}"
+
 msgid "Your comment will not be visible to the public."
 msgstr "Seu coment谩rio n茫o estar谩 vis铆vel ao p煤blico."
 
@@ -3681,22 +4902,48 @@ msgstr "Seu nome"
 msgid "Your projects"
 msgstr "Seus projetos"
 
-msgid "assign yourself"
+msgid "among other things"
 msgstr ""
 
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "assign yourself"
+msgstr "atribuir a si mesmo"
+
 msgid "branch name"
 msgstr "nome da branch"
 
 msgid "by"
 msgstr "por"
 
-msgid "ciReport|Code quality"
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
 msgstr ""
 
+msgid "ciReport|Code quality"
+msgstr "Qualidade de c贸digo"
+
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3708,7 +4955,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3723,25 +4970,40 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
-msgstr "commit"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
+msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3749,14 +5011,84 @@ msgid_plural "days"
 msgstr[0] "dia"
 msgstr[1] "dias"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3781,15 +5113,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3823,6 +5167,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3871,12 +5218,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3893,7 +5246,7 @@ msgid "notification emails"
 msgstr "emails de notifica莽茫o"
 
 msgid "or"
-msgstr ""
+msgstr "ou"
 
 msgid "parent"
 msgid_plural "parents"
@@ -3906,13 +5259,19 @@ msgstr "senha"
 msgid "personal access token"
 msgstr "token de acesso pessoal"
 
-msgid "remove due date"
+msgid "private key does not match certificate."
 msgstr ""
 
+msgid "remove due date"
+msgstr "remover a data de vencimento"
+
 msgid "source"
 msgstr "origem"
 
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr "%{slash_command} ir谩 atualizar a soma do tempo gasto."
+
+msgid "this document"
 msgstr ""
 
 msgid "to help your contributors communicate effectively!"
@@ -3924,3 +5283,6 @@ msgstr "nome do usu谩rio"
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index 0adb8e5b716f1002959ca77c34f5b2c9c07ce43c..a5e145a06edfb30b9aba3ed5a37bcf5c95eb7da1 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -2,58 +2,81 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 04:01-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:35-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Russian\n"
 "Language: ru_RU\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
 "X-Generator: crowdin.com\n"
 "X-Crowdin-Project: gitlab-ee\n"
 "X-Crowdin-Language: ru\n"
 "X-Crowdin-File: /master/locale/gitlab.pot\n"
 
 msgid " and"
-msgstr ""
+msgstr " 懈"
 
 msgid "%d commit"
 msgid_plural "%d commits"
 msgstr[0] "%d 泻芯屑屑懈褌"
 msgstr[1] "%d 泻芯屑屑懈褌邪"
 msgstr[2] "%d 泻芯屑屑懈褌芯胁"
+msgstr[3] "%d 泻芯屑屑懈褌芯胁"
 
 msgid "%d commit behind"
 msgid_plural "%d commits behind"
+msgstr[0] "薪邪 %d 泻芯屑屑懈褌 锌芯蟹邪写懈"
+msgstr[1] "薪邪 %d 泻芯屑屑懈褌邪 锌芯蟹邪写懈"
+msgstr[2] "薪邪 %d 泻芯屑屑懈褌芯胁 锌芯蟹邪写懈"
+msgstr[3] "薪邪 %d 泻芯屑屑懈褌芯胁 锌芯蟹邪写懈"
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "%d issue"
 msgid_plural "%d issues"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "%d 芯斜褋褍卸写械薪懈械"
+msgstr[1] "%d 芯斜褋褍卸写械薪懈泄"
+msgstr[2] "%d 芯斜褋褍卸写械薪懈泄"
+msgstr[3] "%d 芯斜褋褍卸写械薪懈泄"
 
 msgid "%d layer"
 msgid_plural "%d layers"
 msgstr[0] "%d 褋谢芯泄"
 msgstr[1] "%d 褋谢芯褟"
-msgstr[2] "%d 褋谢芯褢胁"
+msgstr[2] "%d 褋谢芯械胁"
+msgstr[3] "%d 褋谢芯褢胁"
 
 msgid "%d merge request"
 msgid_plural "%d merge requests"
+msgstr[0] "%d 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
+msgstr[1] "%d 蟹邪锌褉芯褋邪 薪邪 褋谢懈褟薪懈械"
+msgstr[2] "%d 蟹邪锌褉芯褋芯胁 薪邪 褋谢懈褟薪懈械"
+msgstr[3] "%d 蟹邪锌褉芯褋芯胁 薪邪 褋谢懈褟薪懈械"
+
+msgid "%d metric"
+msgid_plural "%d metrics"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
-msgstr[0] "%s 写芯斜邪胁谢械薪薪褘泄 泻芯屑屑懈褌 斜褘谢 懈褋泻谢褞褔械薪 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 锌褉芯斜谢械屑 褋 锌褉芯懈蟹胁芯写懈褌械谢褜薪芯褋褌褜褞."
-msgstr[1] "%s 写芯斜邪胁谢械薪薪褘褏 泻芯屑屑懈褌邪 斜褘谢懈 懈褋泻谢褞褔械薪褘 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 锌褉芯斜谢械屑 褋 锌褉芯懈蟹胁芯写懈褌械谢褜薪芯褋褌褜褞."
-msgstr[2] "%s 写芯斜邪胁谢械薪薪褘褏 泻芯屑屑懈褌芯胁 斜褘谢懈 懈褋泻谢褞褔械薪褘 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 锌褉芯斜谢械屑 褋 锌褉芯懈蟹胁芯写懈褌械谢褜薪芯褋褌褜褞."
+msgstr[0] "%s 写芯锌芯谢薪懈褌械谢褜薪褘泄 泻芯屑屑懈褌 斜褘谢 锌褉芯锌褍褖械薪 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 锌褉芯斜谢械屑 褋 锌褉芯懈蟹胁芯写懈褌械谢褜薪芯褋褌褜褞."
+msgstr[1] "%s 写芯锌芯谢薪懈褌械谢褜薪褘褏 泻芯屑屑懈褌邪 斜褘谢芯 锌褉芯锌褍褖械薪芯 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 锌褉芯斜谢械屑 褋 锌褉芯懈蟹胁芯写懈褌械谢褜薪芯褋褌褜褞."
+msgstr[2] "%s 写芯锌芯谢薪懈褌械谢褜薪褘褏 泻芯屑屑懈褌芯胁 斜褘谢芯 锌褉芯锌褍褖械薪芯 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 锌褉芯斜谢械屑 褋 锌褉芯懈蟹胁芯写懈褌械谢褜薪芯褋褌褜褞."
+msgstr[3] "%s 写芯锌芯谢薪懈褌械谢褜薪褘褏 泻芯屑屑懈褌芯胁 斜褘谢芯 锌褉芯锌褍褖械薪芯 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 锌褉芯斜谢械屑 褋 锌褉芯懈蟹胁芯写懈褌械谢褜薪芯褋褌褜褞."
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
 
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
@@ -63,6 +86,13 @@ msgid_plural "%{count} participants"
 msgstr[0] "%{count} 褍褔邪褋褌薪懈泻"
 msgstr[1] "%{count} 褍褔邪褋褌薪懈泻邪"
 msgstr[2] "%{count} 褍褔邪褋褌薪懈泻芯胁"
+msgstr[3] "%{count} 褍褔邪褋褌薪懈泻芯胁"
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr "%{lock_path} 蟹邪斜谢芯泻懈褉芯胁邪薪 锌芯谢褜蟹芯胁邪褌械谢械屑 GitLab %{lock_user_id}"
 
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr "薪邪 %{number_commits_behind} 泻芯屑屑懈褌芯胁 锌芯蟹邪写懈 %{default_branch}, 薪邪 %{number_commits_ahead} 泻芯屑屑懈褌芯胁 胁锌械褉械写懈"
@@ -73,11 +103,15 @@ msgstr "%{number_of_failures} 懈蟹 %{maximum_failures} 胁芯蟹屑芯卸薪褘褏 薪械褍
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} 懈蟹 %{maximum_failures} 胁芯蟹屑芯卸薪褘褏 薪械褍写邪褔薪褘褏 锌芯锌褘褌芯泻. GitLab 薪械 斜褍写械褌 邪胁褌芯屑邪褌懈褔械褋泻懈 锌芯胁褌芯褉褟褌褜 锌芯锌褘褌泻褍. 小斜褉芯褋褜褌械 懈薪褎芯褉屑邪褑懈褞 褏褉邪薪懈谢懈褖邪 锌芯褋谢械 褍褋褌褉邪薪械薪懈褟 锌褉芯斜谢械屑褘."
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}: 薪械褍写邪褔薪邪褟 锌芯锌褘褌泻邪 写芯褋褌褍锌邪 泻 褏褉邪薪懈谢懈褖褍 薪邪 褏芯褋褌械:"
-msgstr[1] "%{storage_name}: %{failed_attempts} - 薪械褍写邪褔薪褘械 锌芯锌褘褌泻懈 写芯褋褌褍锌邪 泻 褏褉邪薪懈谢懈褖褍:"
-msgstr[2] "%{storage_name}: %{failed_attempts} - 薪械褍写邪褔薪褘械 锌芯锌褘褌泻懈 写芯褋褌褍锌邪 泻 褏褉邪薪懈谢懈褖褍:"
+msgstr[1] "%{storage_name}: %{failed_attempts} 薪械褍写邪褔薪褘械 锌芯锌褘褌泻懈 写芯褋褌褍锌邪 泻 褏褉邪薪懈谢懈褖褍:"
+msgstr[2] "%{storage_name}: %{failed_attempts} 薪械褍写邪褔薪褘褏 锌芯锌褘褌芯泻 写芯褋褌褍锌邪 泻 褏褉邪薪懈谢懈褖褍:"
+msgstr[3] "%{storage_name}: %{failed_attempts} 薪械褍写邪褔薪褘褏 锌芯锌褘褌芯泻 写芯褋褌褍锌邪 泻 褏褉邪薪懈谢懈褖褍:"
 
 msgid "%{text} is available"
 msgstr "%{text} 写芯褋褌褍锌械薪"
@@ -96,6 +130,7 @@ msgid_plural "%d pipelines"
 msgstr[0] "1 褋斜芯褉芯褔薪邪褟 谢懈薪懈褟"
 msgstr[1] "%d 褋斜芯褉芯褔薪褘褏 谢懈薪懈懈"
 msgstr[2] "%d 褋斜芯褉芯褔薪褘褏 谢懈薪懈泄"
+msgstr[3] "%d 褋斜芯褉芯褔薪褘褏 谢懈薪懈泄"
 
 msgid "1st contribution!"
 msgstr "袩械褉胁褘泄 胁泻谢邪写!"
@@ -103,15 +138,30 @@ msgstr "袩械褉胁褘泄 胁泻谢邪写!"
 msgid "2FA enabled"
 msgstr "袛胁褍褏褎邪泻褌芯褉薪邪褟 邪胁褌芯褉懈蟹邪褑懈褟 胁泻谢褞褔械薪邪"
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "袚褉邪褎懈泻懈 薪械锌褉械褉褘胁薪芯泄 懈薪褌械谐褉邪褑懈懈 (CI)"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "袨斜 邪胁褌芯屑邪褌懈褔械褋泻芯屑 褉邪蟹胁褢褉褌褘胁邪薪懈懈"
 
 msgid "Abuse Reports"
 msgstr "袨褌褔褢褌褘 芯 袞邪谢芯斜邪褏"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "孝芯泻械薪褘 袛芯褋褌褍锌邪"
 
@@ -121,6 +171,9 @@ msgstr "袛芯褋褌褍锌 泻 胁褘褕械写褕懈屑 懈蟹 褋褌褉芯褟 褏褉邪薪懈谢懈褖邪屑 胁
 msgid "Account"
 msgstr "校褔械褌薪邪褟 蟹邪锌懈褋褜"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "袗泻褌懈胁薪褘泄"
 
@@ -139,35 +192,74 @@ msgstr "袛芯斜邪胁懈褌褜 袪褍泻芯胁芯写褋褌胁芯 褍褔邪褋褌薪懈泻邪"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr "袛芯斜邪胁懈褌褜 谐褉褍锌锌芯胁褘械 胁械斜-芯斜褉邪斜芯褌褔懈泻懈 懈 GitLab Enterprise Edition."
 
+msgid "Add Kubernetes cluster"
+msgstr "袛芯斜邪胁懈褌褜 Kubernetes 泻谢邪褋褌械褉"
+
 msgid "Add License"
 msgstr "袛芯斜邪胁懈褌褜 袥懈褑械薪蟹懈褞"
 
+msgid "Add Readme"
+msgstr "袛芯斜邪胁懈褌褜 袠薪褎芯褉屑邪褑懈褞"
+
 msgid "Add new directory"
 msgstr "袛芯斜邪胁懈褌褜 薪芯胁褘泄 泻邪褌邪谢芯谐"
 
 msgid "Add todo"
-msgstr ""
+msgstr "袛芯斜邪胁懈褌褜 todo"
 
 msgid "AdminArea|Stop all jobs"
-msgstr ""
+msgstr "袨褋褌邪薪芯胁懈褌褜 胁褋械 蟹邪写邪薪懈褟"
 
 msgid "AdminArea|Stop all jobs?"
-msgstr ""
+msgstr "袨褋褌邪薪芯胁懈褌褜 胁褋械 蟹邪写邪薪懈褟?"
 
 msgid "AdminArea|Stop jobs"
-msgstr ""
+msgstr "袨褋褌邪薪芯胁懈褌褜 蟹邪写邪薪懈褟"
 
 msgid "AdminArea|Stopping jobs failed"
-msgstr ""
+msgstr "袨褋褌邪薪芯胁泻邪 蟹邪写邪薪懈泄 薪械 褍写邪谢邪褋褜"
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
-msgstr ""
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr "袙褘 褋芯斜懈褉邪械褌械褋褜 芯褋褌邪薪芯胁懈褌褜 胁褋械 蟹邪写邪薪懈褟. 协褌芯 写械泄褋褌胁懈械 芯褋褌邪薪芯胁懈褌 胁褋械 褌械泻褍褖懈械 蟹邪写邪薪懈褟, 薪邪褏芯写褟褖懈械褋褟 胁 锌褉芯褑械褋褋械 胁褘锌芯谢薪械薪懈褟."
 
 msgid "AdminHealthPageLink|health page"
 msgstr "褋褌褉邪薪懈褑邪 褉邪斜芯褌芯褋锌芯褋芯斜薪芯褋褌懈"
 
+msgid "AdminProjects|Delete"
+msgstr "校写邪谢懈褌褜"
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr "校写邪谢懈褌褜 袩褉芯械泻褌 %{projectName}?"
+
+msgid "AdminProjects|Delete project"
+msgstr "校写邪谢懈褌褜 锌褉芯械泻褌"
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr "校泻邪卸懈褌械 写芯屑械薪, 泻芯褌芯褉褘泄 斜褍写械褌 懈褋锌芯谢褜蟹芯胁邪褌褜褋褟 锌芯 褍屑芯谢褔邪薪懈褞 写谢褟 胁褋械褏 锌褉芯械泻褌芯胁 胁 Auto Review 锌褉懈谢芯卸械薪懈褟褏 懈 褋褌邪写懈褟褏 Auto Deploy."
+
+msgid "AdminUsers|Block user"
+msgstr "袟邪斜谢芯泻懈褉芯胁邪褌褜 锌芯谢褜蟹芯胁邪褌械谢褟"
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr "校写邪谢懈褌褜 袩芯谢褜蟹芯胁邪褌械谢褟 %{username} 懈 胁薪械褋褢薪薪褘械 懈屑 懈蟹屑械薪械薪懈褟 ?"
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr "校写邪谢懈褌褜 锌芯谢褜蟹芯胁邪褌械谢褟 %{username} ?"
+
+msgid "AdminUsers|Delete user"
+msgstr "校写邪谢懈褌褜 锌芯谢褜蟹芯胁邪褌械谢褟"
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr "校写邪谢懈褌褜 锌芯谢褜蟹芯胁邪褌械谢褟 懈 胁薪械褋褢薪薪褘械 懈屑 懈蟹屑械薪械薪懈褟"
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr "袛谢褟 锌芯写褌胁械褉卸写械薪懈褟, 胁胁械写懈褌械 %{projectName}"
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr "袛谢褟 锌芯写褌胁械褉卸写械薪懈褟, 胁胁械写懈褌械 %{username}"
+
 msgid "Advanced"
-msgstr ""
+msgstr "袛芯锌芯谢薪懈褌械谢褜薪芯"
 
 msgid "Advanced settings"
 msgstr "袪邪褋褕懈褉械薪薪褘械 薪邪褋褌褉芯泄泻懈"
@@ -176,53 +268,116 @@ msgid "All"
 msgstr "袙褋械"
 
 msgid "All changes are committed"
+msgstr "袙褋械 懈蟹屑械薪械薪懈褟 蟹邪褎懈泻褋懈褉芯胁邪薪褘"
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
 msgstr ""
 
 msgid "Allows you to add and manage Kubernetes clusters."
+msgstr "袩芯蟹胁芯谢褟械褌 写芯斜邪胁谢褟褌褜 懈 褍锌褉邪胁谢褟褌褜 泻谢邪褋褌械褉邪屑懈 Kubernetes."
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
 msgstr ""
 
-msgid "An error occurred previewing the blob"
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
 msgstr ""
 
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌褉械写胁邪褉懈褌械谢褜薪芯屑 锌褉芯褋屑芯褌褉械 芯斜褗械泻褌邪"
+
 msgid "An error occurred when toggling the notification subscription"
 msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌械褉械泻谢褞褔械薪懈懈 锌芯写锌懈褋泻懈 薪邪 芯锌芯胁械褖械薪懈褟"
 
 msgid "An error occurred when updating the issue weight"
 msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 芯斜薪芯胁谢械薪懈懈 胁械褋邪 芯斜褋褍卸写械薪懈褟"
 
+msgid "An error occurred while adding approver"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 写芯斜邪胁谢械薪懈懈 褍褌胁械褉卸写邪褞褖械谐芯"
+
+msgid "An error occurred while detecting host keys"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 芯斜薪邪褉褍卸械薪懈懈 泻谢褞褔械泄 褏芯褋褌邪"
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 芯褌泻谢褞褔械薪懈懈 褍胁械写芯屑谢械薪懈褟 芯 褎褍薪泻褑懈懈. 袨斜薪芯胁懈褌械 褋褌褉邪薪懈褑褍 懈 锌芯胁褌芯褉懈褌械 锌芯锌褘褌泻褍."
 
 msgid "An error occurred while fetching markdown preview"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌褉械写胁邪褉懈褌械谢褜薪芯屑 锌褉芯褋屑芯褌褉械 markdown"
 
 msgid "An error occurred while fetching sidebar data"
 msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 写械薪械谐 写邪薪薪褘褏 写谢褟 斜芯泻芯胁芯泄 锌邪薪械谢懈"
 
+msgid "An error occurred while fetching the pipeline."
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 褋斜芯褉芯褔薪芯泄 谢懈薪懈懈."
+
 msgid "An error occurred while getting projects"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 褋锌懈褋泻邪 锌褉芯械泻褌芯胁"
+
+msgid "An error occurred while importing project"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 懈屑锌芯褉褌械 锌褉芯械泻褌邪"
+
+msgid "An error occurred while initializing path locks"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 懈薪懈褑懈邪谢懈蟹邪褑懈懈 斜谢芯泻懈褉芯胁芯泻 锌褍褌懈"
+
+msgid "An error occurred while loading commits"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 蟹邪谐褉褍蟹泻械 泻芯屑屑懈褌芯胁"
+
+msgid "An error occurred while loading diff"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 蟹邪谐褉褍蟹泻械 褉邪蟹谢懈褔懈泄"
 
 msgid "An error occurred while loading filenames"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 蟹邪谐褉褍蟹泻械 褋锌懈褋泻邪 褎邪泄谢芯胁"
+
+msgid "An error occurred while loading the file"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 蟹邪谐褉褍蟹泻械 褎邪泄谢邪"
+
+msgid "An error occurred while making the request."
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 胁褘锌芯谢薪械薪懈懈 蟹邪锌褉芯褋邪."
+
+msgid "An error occurred while removing approver"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 褍写邪谢械薪懈懈 褍褌胁械褉卸写邪褞褖械谐芯"
 
 msgid "An error occurred while rendering KaTeX"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 褉械薪写械褉懈薪谐械 KaTeX"
 
 msgid "An error occurred while rendering preview broadcast message"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 胁懈蟹褍邪谢懈蟹邪褑懈懈 锌褉芯褋屑芯褌褉邪 褕懈褉芯泻芯胁械褖邪褌械谢褜薪芯谐芯 褋芯芯斜褖械薪懈褟"
 
 msgid "An error occurred while retrieving calendar activity"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 泻邪谢械薪写邪褉褟 邪泻褌懈胁薪芯褋褌懈"
 
 msgid "An error occurred while retrieving diff"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 褉邪蟹谢懈褔懈泄"
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 褋芯褏褉邪薪械薪懈懈 褋褌邪褌褍褋邪 薪邪褋褌褉芯械薪薪芯谐芯 LDAP. 袩芯卸邪谢褍泄褋褌邪, 锌芯锌褉芯斜褍泄褌械 械褖械 褉邪蟹."
+
+msgid "An error occurred while saving assignees"
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 褋芯褏褉邪薪械薪懈懈 懈褋锌芯谢薪懈褌械谢褟"
 
 msgid "An error occurred while validating username"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌褉芯胁械褉泻械 懈屑械薪懈 锌芯谢褜蟹芯胁邪褌械谢褟"
 
 msgid "An error occurred. Please try again."
 msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪. 袩芯卸邪谢褍泄褋褌邪, 锌芯锌褉芯斜褍泄褌械 褋薪芯胁邪."
 
+msgid "Any Label"
+msgstr "袥褞斜邪褟 袦械褌泻邪"
+
 msgid "Appearance"
 msgstr "袨褎芯褉屑谢械薪懈械"
 
@@ -241,31 +396,43 @@ msgstr "袗褉褏懈胁薪褘泄 锌褉芯械泻褌! 袪械锌芯蟹懈褌芯褉懈泄 写芯褋褌褍锌械薪 
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "袙褘 写械泄褋褌胁懈褌械谢褜薪芯 褏芯褌懈褌械 褍写邪谢懈褌褜 褝褌芯 褉邪褋锌懈褋邪薪懈械 褋斜芯褉芯褔薪芯泄 谢懈薪懈懈?"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 芯褌屑械薪懈褌褜 胁邪褕懈 懈蟹屑械薪械薪懈褟?"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 褋斜褉芯褋懈褌褜 褝褌芯褌 褉械谐懈褋褌褉邪褑懈芯薪薪褘泄 褌芯泻械薪?"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 褋斜褉芯褋懈褌褜 褝褌芯褌 褌芯泻械薪 锌褉芯胁械褉泻懈 褉邪斜芯褌芯褋锌芯褋芯斜薪芯褋褌懈?"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr "袙褘 写械泄褋褌胁懈褌械谢褜薪芯 褏芯褌懈褌械 褉邪蟹斜谢芯泻懈褉芯胁邪褌褜 %{path_lock_path}?"
+
 msgid "Are you sure?"
 msgstr "袙褘 褍胁械褉械薪褘?"
 
 msgid "Artifacts"
 msgstr "袗褉褌械褎邪泻褌褘"
 
-msgid "Assign custom color like #FF0000"
+msgid "Assertion consumer service URL"
 msgstr ""
 
+msgid "Assign custom color like #FF0000"
+msgstr "袧邪蟹薪邪褔褜褌械 锌芯谢褜蟹芯胁邪褌械谢褜褋泻懈泄 褑胁械褌, 薪邪锌褉懈屑械褉 #FF0000"
+
 msgid "Assign labels"
-msgstr ""
+msgstr "袧邪蟹薪邪褔懈褌褜 屑械褌泻懈"
 
 msgid "Assign milestone"
-msgstr ""
+msgstr "袧邪蟹薪邪褔懈褌褜 褝褌邪锌"
 
 msgid "Assign to"
+msgstr "袧邪蟹薪邪褔懈褌褜"
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
 msgstr ""
 
 msgid "Assignee"
@@ -287,13 +454,19 @@ msgid "Author"
 msgstr "袗胁褌芯褉"
 
 msgid "Authors: %{authors}"
+msgstr "袗胁褌芯褉褘: %{authors}"
+
+msgid "Auto DevOps enabled"
+msgstr "Auto DevOps 胁泻谢褞褔械薪"
+
+msgid "Auto DevOps, runners and job artifacts"
 msgstr ""
 
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "袩褉懈谢芯卸械薪懈褟 写谢褟 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 褉械胁褜褞 懈 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 褉邪蟹胁褢褉褌褘胁邪薪懈褟 褌褉械斜褍褞褌 褍泻邪蟹邪薪懈褟 %{kubernetes} 写谢褟 泻芯褉褉械泻褌薪芯泄 褉邪斜芯褌褘."
 
 msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "袩褉懈谢芯卸械薪懈褟 写谢褟 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 褉械胁褜褞 懈 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 褉邪蟹胁褢褉褌褘胁邪薪懈褟 褌褉械斜褍褞褌 褍泻邪蟹邪薪懈褟 懈屑械薪懈 写芯屑械薪邪 懈 %{kubernetes} 写谢褟 泻芯褉褉械泻褌薪芯泄 褉邪斜芯褌褘."
 
 msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
 msgstr "袩褉懈谢芯卸械薪懈褟 写谢褟 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 褉械胁褜褞 懈 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 褉邪蟹胁褢褉褌褘胁邪薪懈褟 褌褉械斜褍褞褌 褍泻邪蟹邪薪懈褟 懈屑械薪懈 写芯屑械薪邪 写谢褟 泻芯褉褉械泻褌薪芯泄 褉邪斜芯褌褘."
@@ -313,18 +486,33 @@ msgstr "袛谢褟 褝褌芯谐芯 锌褉芯械泻褌邪 屑芯卸械褌 斜褘褌褜 邪泻褌懈胁懈褉芯胁
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr "袩芯写褉芯斜薪械械 锌芯 褋褋褘谢泻械 %{link_to_documentation}"
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
-msgstr "袙褘 屑芯卸械褌械 邪泻褌懈胁懈褉芯胁邪褌褜 %{link_to_settings} 写谢褟 褝褌芯谐芯 锌褉芯械泻褌邪."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr "袙褘 屑芯卸械褌械 邪胁褌芯屑邪褌懈褔械褋泻懈 褋芯斜懈褉邪褌褜 懈 褌械褋褌懈褉芯胁邪褌褜 褋胁芯械 锌褉懈谢芯卸械薪懈械, 械褋谢懈 写谢褟 褝褌芯谐芯 锌褉芯械泻褌邪 %{link_to_auto_devops_settings}. 袙褘 褌邪泻卸械 屑芯卸械褌械 邪胁褌芯屑邪褌懈褔械褋泻懈 褉邪蟹胁械褉薪褍褌褜 褋胁芯械 锌褉懈谢芯卸械薪懈械, 械褋谢懈 胁褘 %{link_to_add_kubernetes_cluster}."
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr "写芯斜邪胁懈褌械 泻谢邪褋褌械褉 Kubernetes"
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr "胁泻谢褞褔械薪 Auto DevOps (Beta)"
 
 msgid "Available"
 msgstr "袛芯褋褌褍锌械薪"
 
 msgid "Avatar will be removed. Are you sure?"
-msgstr ""
+msgstr "袗胁邪褌邪褉 斜褍写械褌 褍写邪谢械薪. 袙褘 褍胁械褉械薪褘?"
 
 msgid "Average per day: %{average}"
+msgstr "袙 褋褉械写薪械屑 蟹邪 写械薪褜: %{average}"
+
+msgid "Background Color"
 msgstr ""
 
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr "袧邪褔邪褌褜 褋 胁褘斜褉邪薪薪芯谐芯 泻芯屑屑懈褌邪"
+
 msgid "Billing"
 msgstr "孝邪褉懈褎"
 
@@ -379,14 +567,12 @@ msgstr "芯锌谢邪褔懈胁邪械褌褋褟 械卸械谐芯写薪芯 胁 褉邪蟹屑械褉械 %{price_per_
 msgid "BillingPlans|per user"
 msgstr "蟹邪 锌芯谢褜蟹芯胁邪褌械谢褟"
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "袙械褌泻邪"
-msgstr[1] "袙械褌泻懈"
-msgstr[2] "袙械褌泻懈"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+msgstr[0] "袙械褌泻邪 (%{branch_count})"
+msgstr[1] "袙械褌泻懈 (%{branch_count})"
+msgstr[2] "袙械褌泻懈 (%{branch_count})"
+msgstr[3] "袙械褌泻懈 (%{branch_count})"
 
 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}"
@@ -409,6 +595,15 @@ msgstr "袩械褉械泻谢褞褔懈褌褜 胁械褌泻褍"
 msgid "Branches"
 msgstr "袙械褌泻懈"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr "袧械胁芯蟹屑芯卸薪芯 薪邪泄褌懈 HEAD-泻芯屑屑懈褌 褝褌芯泄 胁械褌泻懈"
 
@@ -431,7 +626,7 @@ msgid "Branches|Delete protected branch '%{branch_name}'?"
 msgstr "校写邪谢懈褌褜 蟹邪褖懈褖褢薪薪褍褞 胁械褌泻褍 '%{branch_name}'?"
 
 msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
-msgstr "校写械谢械薪懈械 胁械褌泻懈 '%{branch_name}' 薪械胁芯蟹屑芯卸薪芯 芯褌屑械薪懈褌褜. 袙褘 褍胁械褉械薪褘?"
+msgstr "校写邪谢械薪懈械 胁械褌泻懈 '%{branch_name}' 薪械胁芯蟹屑芯卸薪芯 芯褌屑械薪懈褌褜. 袙褘 褍胁械褉械薪褘?"
 
 msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
 msgstr "校写邪谢械薪懈械 胁谢懈褌褘褏 胁械褌芯泻 薪械胁芯蟹屑芯卸薪芯 芯褌屑械薪懈褌褜. 袙褘 褍胁械褉械薪褘?"
@@ -454,12 +649,39 @@ msgstr "袣邪泻 褌芯谢褜泻芯 胁褘 锌芯写褌胁械褉写懈褌械 懈 薪邪卸屑褢褌械 %{dele
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr "孝芯谢褜泻芯 屑邪褋褌械褉 懈谢懈 胁谢邪写械谢械褑 锌褉芯械泻褌邪 屑芯卸械褌 褍写邪谢懈褌褜 蟹邪褖懈褖褢薪薪褍褞 胁械褌泻褍"
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
-msgstr "校锌褉邪胁谢械薪懈械 蟹邪褖懈褖褢薪薪褘屑懈 胁械褌泻邪屑懈 胁芯蟹屑芯卸薪芯 胁 %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
 
 msgid "Branches|Sort by"
 msgstr "小芯褉褌懈褉芯胁邪褌褜 锌芯"
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr "袙械褌胁褜 薪械 屑芯卸械褌 斜褘褌褜 芯斜薪芯胁谢械薪邪 邪胁褌芯屑邪褌懈褔械褋泻懈, 锌芯褌芯屑褍 褔褌芯 芯薪邪 懈屑械械褌 褉邪褋褏芯卸写械薪懈褟 褋 褉芯写懈褌械谢褜褋泻懈屑 褉械锌芯蟹懈褌芯褉懈械屑."
 
@@ -505,14 +727,23 @@ msgstr "袩褉芯褋屑芯褌褉 褎邪泄谢芯胁"
 msgid "Browse files"
 msgstr "袩褉芯褋屑芯褌褉 褎邪泄谢芯胁"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "锌芯 邪胁褌芯褉褍"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr "CI/CD"
+
 msgid "CI/CD configuration"
-msgstr ""
+msgstr "袣芯薪褎懈谐褍褉邪褑懈褟 CI/CD"
+
+msgid "CI/CD for external repo"
+msgstr "CI/CD 写谢褟 胁薪械褕薪械谐芯 褉械锌芯蟹懈褌芯褉懈褟"
 
 msgid "CICD|Jobs"
 msgstr "袟邪写邪薪懈褟"
@@ -520,15 +751,21 @@ msgstr "袟邪写邪薪懈褟"
 msgid "Cancel"
 msgstr "袨褌屑械薪邪"
 
-msgid "Cancel edit"
-msgstr "袨褌屑械薪懈褌褜 褉械写邪泻褌懈褉芯胁邪薪懈械"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
+msgstr "袧械胁芯蟹屑芯卸薪芯 懈蟹屑械薪懈褌褜 褍锌褉邪胁谢褟械屑褘泄 泻谢邪褋褌械褉 Kubernetes"
+
+msgid "Certificate fingerprint"
 msgstr ""
 
 msgid "Change Weight"
 msgstr "袠蟹屑械薪懈褌褜 袙械褋"
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "袙褘斜褉邪褌褜 胁 胁械褌泻械"
 
@@ -542,7 +779,7 @@ msgid "ChangeTypeAction|Revert"
 msgstr "袨褌屑械薪懈褌褜"
 
 msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
-msgstr ""
+msgstr "协褌芯 褋芯蟹写邪褋褌 薪芯胁褘泄 泻芯屑屑懈褌 写谢褟 褌芯谐芯, 褔褌芯斜褘 芯褌泻邪褌懈褌褜 褋褍褖械褋褌胁褍褞褖懈械 懈蟹屑械薪械薪懈褟."
 
 msgid "Changelog"
 msgstr "袞褍褉薪邪谢 懈蟹屑械薪械薪懈泄"
@@ -557,7 +794,7 @@ msgid "Chat"
 msgstr "效邪褌"
 
 msgid "Check interval"
-msgstr ""
+msgstr "袠薪褌械褉胁邪谢 锌褉芯胁械褉泻懈"
 
 msgid "Checking %{text} availability鈥�"
 msgstr "袩褉芯胁械褉泻邪 写芯褋褌褍锌薪芯褋褌懈 %{text} ..."
@@ -572,15 +809,21 @@ msgid "Cherry-pick this merge request"
 msgstr "袩芯写芯斜褉邪褌褜 褝褌芯褌 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
 
 msgid "Choose File ..."
-msgstr ""
+msgstr "袙褘斜械褉懈褌械 肖邪泄谢..."
 
 msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
-msgstr ""
+msgstr "袙褘斜械褉懈褌械 胁械褌泻褍/褌械谐 (薪邪锌褉懈屑械褉, %{master}) 懈谢懈 胁胁械写懈褌械 泻芯屑屑懈褌(薪邪锌褉懈屑械褉, %{sha}), 褔褌芯斜褘 褍胁懈写械褌褜, 褔褌芯 懈蟹屑械薪懈谢芯褋褜 懈谢懈 褋芯蟹写邪褌褜 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械."
 
 msgid "Choose file..."
-msgstr ""
+msgstr "袙褘斜械褉懈褌械 褎邪泄谢..."
 
 msgid "Choose which groups you wish to synchronize to this secondary node."
+msgstr "袙褘斜械褉懈褌械 谐褉褍锌锌褘, 泻芯褌芯褉褘械 胁褘 褏芯褌懈褌械 褋懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 褋 褝褌懈屑 胁褌芯褉懈褔薪褘屑 褍蟹谢芯屑."
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
 msgstr ""
 
 msgid "Choose which shards you wish to synchronize to this secondary node."
@@ -641,45 +884,57 @@ msgid "CiStatus|running"
 msgstr "胁褘锌芯谢薪褟械褌褋褟"
 
 msgid "CiVariables|Input variable key"
-msgstr ""
+msgstr "袣谢褞褔 胁褏芯写薪芯泄 锌械褉械屑械薪薪芯泄"
 
 msgid "CiVariables|Input variable value"
-msgstr ""
+msgstr "袟薪邪褔械薪懈械 胁褏芯写薪芯泄 锌械褉械屑械薪薪芯泄"
 
 msgid "CiVariables|Remove variable row"
-msgstr ""
+msgstr "校写邪谢懈褌褜 褋褌褉芯泻褍 锌械褉械屑械薪薪褘褏"
 
 msgid "CiVariable|* (All environments)"
-msgstr ""
+msgstr " * (袙褋械 褋褉械写褘)"
 
 msgid "CiVariable|All environments"
-msgstr ""
+msgstr "袙褋械 褋褉械写褘"
 
 msgid "CiVariable|Create wildcard"
-msgstr ""
+msgstr "小芯蟹写邪褌褜 褕邪斜谢芯薪"
 
 msgid "CiVariable|Error occured while saving variables"
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 褋芯褏褉邪薪械薪懈懈 锌械褉械屑械薪薪褘褏"
 
 msgid "CiVariable|New environment"
-msgstr ""
+msgstr "袧芯胁邪褟 褋褉械写邪"
 
 msgid "CiVariable|Protected"
-msgstr ""
+msgstr "袟邪褖懈褖械薪芯"
 
 msgid "CiVariable|Search environments"
-msgstr ""
+msgstr "袩芯懈褋泻 褋褉械写"
 
 msgid "CiVariable|Toggle protected"
-msgstr ""
+msgstr "袙泻谢褞褔懈褌褜 蟹邪褖懈褌褍"
 
 msgid "CiVariable|Validation failed"
-msgstr ""
+msgstr "袩褉芯胁械褉泻邪 薪械 褍写邪谢邪褋褜"
 
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr "CircuitBreaker API"
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr "袧邪卸屑懈褌械 泻薪芯锌泻褍 薪懈卸械, 褔褌芯斜褘 薪邪褔邪褌褜 锌褉芯褑械褋褋 褍褋褌邪薪芯胁泻懈, 锌械褉械泄写褟 薪邪 褋褌褉邪薪懈褑褍 Kubernetes"
+
 msgid "Click to expand text"
+msgstr "袧邪卸屑懈褌械, 褔褌芯斜褘 褉邪褋泻褉褘褌褜 褌械泻褋褌"
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
 msgstr ""
 
 msgid "Clone repository"
@@ -689,28 +944,28 @@ msgid "Close"
 msgstr "袟邪泻褉褘褌褜"
 
 msgid "Closed"
-msgstr ""
+msgstr "袟邪泻褉褘褌芯"
 
 msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
-msgstr ""
+msgstr "%{appList} 褍褋锌械褕薪芯 褍褋褌邪薪芯胁谢械薪褘 薪邪 胁邪褕械屑 泻谢邪褋褌械褉械 Kubernetes"
 
 msgid "ClusterIntegration|API URL"
 msgstr "袗写褉械褋 API"
 
 msgid "ClusterIntegration|Add Kubernetes cluster"
-msgstr ""
+msgstr "袛芯斜邪胁懈褌褜 泻谢邪褋褌械褉 Kubernetes"
 
 msgid "ClusterIntegration|Add an existing Kubernetes cluster"
-msgstr ""
+msgstr "袛芯斜邪胁懈褌褜 褋褍褖械褋褌胁褍褞褖懈泄 泻谢邪褋褌械褉 Kubernetes"
 
 msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
-msgstr ""
+msgstr "袛芯锌芯谢薪懈褌械谢褜薪褘械 芯锌褑懈懈 写谢褟 褝褌芯泄 懈薪褌械谐褉邪褑懈懈 褋 泻谢邪褋褌械褉芯屑 Kubernetes"
 
 msgid "ClusterIntegration|Applications"
 msgstr "袩褉懈谢芯卸械薪懈褟"
 
 msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 褍写邪谢懈褌褜 懈薪褌械谐褉邪褑懈褞 褋 褝褌懈屑 泻谢邪褋褌械褉芯屑 Kubernetes? 协褌芯 薪械 锌褉懈胁械写械褌 泻 褍写邪谢械薪懈褞 胁邪褕械谐芯 泻谢邪褋褌械褉邪 Kubernetes."
 
 msgid "ClusterIntegration|CA Certificate"
 msgstr "小械褉褌懈褎懈泻邪褌 褍写芯褋褌芯胁械褉褟褞褖械谐芯 褑械薪褌褉邪"
@@ -719,13 +974,13 @@ msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
 msgstr "袣芯屑锌谢械泻褌 褋械褉褌懈褎懈泻邪褌芯胁 褍写芯褋褌芯胁械褉褟褞褖械谐芯 褑械薪褌褉邪 (褎芯褉屑邪褌 PEM)"
 
 msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
-msgstr ""
+msgstr "袙褘斜械褉懈褌械 褋锌芯褋芯斜 薪邪褋褌褉芯泄泻懈 懈薪褌械谐褉邪褑懈懈 褋 泻谢邪褋褌械褉芯屑 Kubernetes"
 
 msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
-msgstr ""
+msgstr "袙褘斜械褉懈褌械, 泻邪泻懈械 褋褉械写褘 胁邪褕械谐芯 锌褉芯械泻褌邪 斜褍写褍褌 懈褋锌芯谢褜蟹芯胁邪褌褜 褝褌芯褌 泻谢邪褋褌械褉 Kubernetes."
 
 msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
-msgstr ""
+msgstr "校锌褉邪胁谢褟泄褌械 褌械屑, 泻邪泻 胁邪褕 泻谢邪褋褌械褉 Kubernetes 懈薪褌械谐褉懈褉褍械褌褋褟 褋 GitLab"
 
 msgid "ClusterIntegration|Copy API URL"
 msgstr "小泻芯锌懈褉芯胁邪褌褜 邪写褉械褋 API"
@@ -733,20 +988,23 @@ msgstr "小泻芯锌懈褉芯胁邪褌褜 邪写褉械褋 API"
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr "袣芯锌懈褉芯胁邪褌褜 小械褉褌懈褎懈泻邪褌 校写芯褋褌芯胁械褉褟褞褖械谐芯 笑械薪褌褉邪"
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr "小泻芯锌懈褉芯胁邪褌褜 IP-邪写褉械褋 Ingress 胁 斜褍褎械褉 芯斜屑械薪邪"
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
-msgstr ""
+msgstr "小泻芯锌懈褉芯胁邪褌褜 懈屑褟 泻谢邪褋褌械褉邪 Kubernetes"
 
 msgid "ClusterIntegration|Copy Token"
 msgstr "小泻芯锌懈褉芯胁邪褌褜 孝芯泻械薪"
 
 msgid "ClusterIntegration|Create Kubernetes cluster"
-msgstr ""
+msgstr "小芯蟹写邪褌褜 泻谢邪褋褌械褉 Kubernetes"
 
 msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "小芯蟹写邪褌褜 泻谢邪褋褌械褉 锌褉懈 锌芯屑芯褖懈 Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
-msgstr ""
+msgstr "小芯蟹写邪泄褌械 薪芯胁褘泄 泻谢邪褋褌械褉 Kubernetes 胁 Google Kubernetes Engine 锌褉褟屑芯 懈蟹 GitLab"
 
 msgid "ClusterIntegration|Create on GKE"
 msgstr "小芯蟹写邪褌褜 胁 GKE"
@@ -755,13 +1013,13 @@ msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
 msgstr "校泻邪卸懈褌械 锌邪褉邪屑械褌褉褘 褋褍褖械褋褌胁褍褞褖械谐芯 泻谢邪褋褌械褉邪 Kubernetes"
 
 msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
-msgstr ""
+msgstr "袙胁械写懈褌械 褋胁械写械薪懈褟 芯 胁邪褕械屑 泻谢邪褋褌械褉械 Kubernetes"
 
 msgid "ClusterIntegration|Environment scope"
 msgstr ""
 
 msgid "ClusterIntegration|GitLab Integration"
-msgstr ""
+msgstr "袠薪褌械谐褉邪褑懈褟 褋 GitLab"
 
 msgid "ClusterIntegration|GitLab Runner"
 msgstr "GitLab Runner"
@@ -778,12 +1036,21 @@ msgstr "袩褉芯械泻褌 Google Kubernetes Engine"
 msgid "ClusterIntegration|Helm Tiller"
 msgstr "Helm Tiller"
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr "Ingress"
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr "IP-邪写褉械褋 Ingress"
+
 msgid "ClusterIntegration|Install"
 msgstr "校褋褌邪薪芯胁懈褌褜"
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr "校褋褌邪薪芯胁谢械薪"
 
@@ -791,51 +1058,54 @@ msgid "ClusterIntegration|Installing"
 msgstr "校褋褌邪薪芯胁泻邪"
 
 msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
-msgstr ""
+msgstr "袠薪褌械谐褉邪褑懈褟 邪胁褌芯屑邪褌懈蟹邪褑懈懈 泻谢邪褋褌械褉芯胁 Kubernetes"
 
 msgid "ClusterIntegration|Integration status"
-msgstr ""
+msgstr " 小褌邪褌褍褋 懈薪褌械谐褉邪褑懈懈"
 
 msgid "ClusterIntegration|Kubernetes cluster"
-msgstr ""
+msgstr "袣谢邪褋褌械褉 Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr "小胁械写械薪懈褟 芯 泻谢邪褋褌械褉械 Kubernetes"
+
+msgid "ClusterIntegration|Kubernetes cluster health"
 msgstr ""
 
 msgid "ClusterIntegration|Kubernetes cluster integration"
-msgstr ""
+msgstr "袠薪褌械谐褉邪褑懈褟 泻谢邪褋褌械褉邪 Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
-msgstr ""
+msgstr "袠薪褌械谐褉邪褑懈褟 泻谢邪褋褌械褉邪 Kubernetes 写谢褟 褝褌芯谐芯 锌褉芯械泻褌邪 芯褌泻谢褞褔械薪邪."
 
 msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
-msgstr ""
+msgstr "袠薪褌械谐褉邪褑懈褟 泻谢邪褋褌械褉邪 Kubernetes 写谢褟 褝褌芯谐芯 锌褉芯械泻褌邪 胁泻谢褞褔械薪邪."
 
 msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
-msgstr ""
+msgstr "袛谢褟 褝褌芯谐芯 锌褉芯械泻褌邪 胁泻谢褞褔械薪邪 懈薪褌械谐褉邪褑懈褟 泻谢邪褋褌械褉邪 Kubernetes. 袨褌泻谢褞褔械薪懈械 懈薪褌械谐褉邪褑懈懈 薪械 锌芯胁谢懈褟械褌 薪邪 泻谢邪褋褌械褉 Kubernetes, 薪芯 褋芯械写懈薪械薪懈械 褋 GitLab 斜褍写械褌 胁褉械屑械薪薪芯 芯褌泻谢褞褔械薪芯."
 
 msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
-msgstr ""
+msgstr "袣谢邪褋褌械褉 Kubernetes 褋芯蟹写邪褢褌褋褟 胁 Google Kubernetes Engine..."
 
 msgid "ClusterIntegration|Kubernetes cluster name"
-msgstr ""
+msgstr "袠屑褟 泻谢邪褋褌械褉邪 Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
-msgstr ""
+msgstr "袣谢邪褋褌械褉 Kubernetes 斜褘谢 褍褋锌械褕薪芯 褋芯蟹写邪薪 胁 Google Kubernetes Engine. 袨斜薪芯胁懈褌械 褋褌褉邪薪懈褑褍, 褔褌芯斜褘 褍胁懈写械褌褜 褋胁械写械薪懈褟 芯 泻谢邪褋褌械褉械 Kubernetes"
 
 msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
-msgstr ""
+msgstr "袣谢邪褋褌械褉 Kubernetes 锌芯蟹胁芯谢褟械褌 胁邪屑 懈褋锌芯谢褜蟹芯胁邪褌褜 锌褉懈谢芯卸械薪懈褟 写谢褟 褉械胁褜褞, 褉邪蟹胁褢褉褌褘胁邪褌褜 胁邪褕懈 锌褉懈谢芯卸械薪懈褟, 蟹邪锌褍褋泻邪褌褜 胁邪褕懈 褋斜芯褉芯褔薪褘械 谢懈薪懈懈 懈 屑薪芯谐芯械 写褉褍谐芯械 斜芯谢械械 锌褉芯褋褌褘屑 褋锌芯褋芯斜芯屑. %{link_to_help_page}"
 
 msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
-msgstr ""
+msgstr "袣谢邪褋褌械褉褘 Kubernetes 屑芯谐褍褌 懈褋锌芯谢褜蟹芯胁邪褌褜褋褟 写谢褟 褉邪蟹胁械褉褌褘胁邪薪懈褟 锌褉懈谢芯卸械薪懈泄 懈 锌褉械写芯褋褌邪胁谢械薪懈褟 锌褉懈谢芯卸械薪懈泄 Review 写谢褟 褝褌芯谐芯 锌褉芯械泻褌邪"
 
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr "校蟹薪邪泄褌械 斜芯谢褜褕械 薪邪 %{link_to_documentation}"
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
-msgstr ""
-
 msgid "ClusterIntegration|Learn more about environments"
+msgstr "校蟹薪邪泄褌械 斜芯谢褜褕械 芯 褋褉械写邪褏"
+
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -845,16 +1115,16 @@ msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to crea
 msgstr ""
 
 msgid "ClusterIntegration|Manage"
-msgstr ""
+msgstr "校锌褉邪胁谢械薪懈械"
 
 msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
-msgstr ""
+msgstr "校锌褉邪胁谢褟泄褌械 胁邪褕械屑褍 泻谢邪褋褌械褉芯屑 Kubernetes, 锌械褉械泄写褟 锌芯 褋褋褘谢泻械 %{link_gke}"
 
 msgid "ClusterIntegration|More information"
-msgstr ""
+msgstr "袛芯锌芯谢薪懈褌械谢褜薪邪褟 懈薪褎芯褉屑邪褑懈褟"
 
 msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
-msgstr ""
+msgstr "袧械褋泻芯谢褜泻芯 泻谢邪褋褌械褉芯胁 Kubernetes 写芯褋褌褍锌薪芯 胁 GitLab Entreprise Edition Premium 懈 Ultimate"
 
 msgid "ClusterIntegration|Note:"
 msgstr "袩褉懈屑械褔邪薪懈械:"
@@ -863,7 +1133,7 @@ msgid "ClusterIntegration|Number of nodes"
 msgstr "袣芯谢懈褔械褋褌胁芯 褍蟹谢芯胁"
 
 msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
-msgstr ""
+msgstr "袩芯卸邪谢褍泄褋褌邪, 褍泻邪卸懈褌械 锌邪褉邪屑械褌褉褘 写芯褋褌褍锌邪 泻 胁邪褕械屑褍 泻谢邪褋褌械褉褍 Kubernetes. 袝褋谢懈 胁邪屑 薪械芯斜褏芯写懈屑邪 锌芯屑芯褖褜, 胁褘 屑芯卸械褌械 芯蟹薪邪泻芯屑懈褌褜褋褟 褋 薪邪褕械泄 褋褌褉邪薪懈褑械泄 %{link_to_help_page} 锌芯 Kubernetes"
 
 msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
 msgstr "袩芯卸邪谢褍泄褋褌邪, 褍斜械写懈褌械褋褜, 褔褌芯 胁邪褕 邪泻泻邪褍薪褌 Google 芯褌胁械褔邪械褌 褋谢械写褍褞褖懈屑 褌褉械斜芯胁邪薪懈褟屑:"
@@ -884,13 +1154,13 @@ msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster in
 msgstr ""
 
 msgid "ClusterIntegration|Remove Kubernetes cluster integration"
-msgstr ""
+msgstr "校写邪谢懈褌褜 懈薪褌械谐褉邪褑懈褞 泻谢邪褋褌械褉邪 Kubernetes"
 
 msgid "ClusterIntegration|Remove integration"
 msgstr "校写邪谢懈褌褜 懈薪褌械谐褉邪褑懈褞"
 
 msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "校写邪谢懈褌褜 褝褌褍 泻芯薪褎懈谐褍褉邪褑懈褞 泻谢邪褋褌械褉邪 Kubernetes 懈蟹 褝褌芯谐芯 锌褉芯械泻褌邪. 协褌芯 薪械 锌褉懈胁械写械褌 泻 褍写邪谢械薪懈褞 胁邪褕械谐芯 褎邪泻褌懈褔械褋泻芯谐芯 泻谢邪褋褌械褉邪 Kubernetes."
 
 msgid "ClusterIntegration|Request to begin installing failed"
 msgstr "袧械 褍写邪谢芯褋褜 胁褘锌芯谢薪懈褌褜 蟹邪锌褉芯褋 薪邪 蟹邪锌褍褋泻 锌褉芯褑械褋褋邪 褍褋褌邪薪芯胁泻懈"
@@ -898,9 +1168,12 @@ msgstr "袧械 褍写邪谢芯褋褜 胁褘锌芯谢薪懈褌褜 蟹邪锌褉芯褋 薪邪 蟹邪锌褍褋泻 锌
 msgid "ClusterIntegration|Save changes"
 msgstr "小芯褏褉邪薪懈褌褜 懈蟹屑械薪械薪懈褟"
 
-msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgid "ClusterIntegration|Security"
 msgstr ""
 
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr "袩褉芯褋屑芯褌褉 懈 褉械写邪泻褌懈褉芯胁邪薪懈械 懈薪褎芯褉屑邪褑懈懈 芯 胁邪褕械屑 泻谢邪褋褌械褉械 Kubernetes"
+
 msgid "ClusterIntegration|See machine types"
 msgstr "小屑. 褌懈锌褘 屑邪褕懈薪"
 
@@ -920,25 +1193,28 @@ msgid "ClusterIntegration|Something went wrong on our end."
 msgstr " 校 薪邪褋 褔褌芯-褌芯 锌芯褕谢芯 薪械 褌邪泻."
 
 msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "效褌芯-褌芯 锌芯褕谢芯 薪械 褌邪泻 锌褉懈 褋芯蟹写邪薪懈懈 泻谢邪褋褌械褉邪 Kubernetes 胁 Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr "袩褉芯懈蟹芯褕谢懈 芯褕懈斜泻懈 胁芯 胁褉械屑褟 褍褋褌邪薪芯胁泻懈 %{title}"
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
 msgid "ClusterIntegration|Toggle Kubernetes Cluster"
-msgstr ""
+msgstr "袩械褉械泻谢褞褔懈褌褜 袣谢邪褋褌械褉 Kubernetes"
 
 msgid "ClusterIntegration|Toggle Kubernetes cluster"
-msgstr ""
+msgstr "袩械褉械泻谢褞褔懈褌褜 泻谢邪褋褌械褉 Kubernetes"
 
 msgid "ClusterIntegration|Token"
 msgstr "孝芯泻械薪"
 
 msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
-msgstr ""
+msgstr "袝褋谢懈 锌褉懈胁褟蟹邪褌褜 泻谢邪褋褌械褉 Kubernetes 泻 褝褌芯屑褍 锌褉芯械泻褌褍, 胁褘 褋 谢褢谐泻芯褋褌褜褞 褋屑芯卸械褌械 懈褋锌芯谢褜蟹芯胁邪褌褜 锌褉懈谢芯卸械薪懈褟 写谢褟 褉械胁褜褞, 褉邪蟹胁械褉褌褘胁邪褌褜 胁邪褕懈 锌褉懈谢芯卸械薪懈褟, 蟹邪锌褍褋泻邪褌褜 褋斜芯褉芯褔薪褘械 谢懈薪懈懈 懈 屑薪芯谐芯械 写褉褍谐芯械."
 
 msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
 msgstr "袙邪褕邪 褍褔械褌薪邪褟 蟹邪锌懈褋褜 写芯谢卸薪邪 懈屑械褌褜 %{link_to_kubernetes_engine}"
@@ -968,7 +1244,13 @@ msgid "ClusterIntegration|properly configured"
 msgstr "锌褉邪胁懈谢褜薪芯 薪邪褋褌褉芯械薪"
 
 msgid "Collapse"
-msgstr ""
+msgstr "小胁械褉薪褍褌褜"
+
+msgid "Comment and resolve discussion"
+msgstr "袩褉芯泻芯屑屑械薪褌懈褉芯胁邪褌褜 懈 蟹邪泻褉褘褌褜 写懈褋泻褍褋褋懈褞"
+
+msgid "Comment and unresolve discussion"
+msgstr "袩褉芯泻芯屑屑械薪褌懈褉芯胁邪褌褜 懈 锌械褉械芯褌泻褉褘褌褜 写懈褋泻褍褋褋懈褞"
 
 msgid "Comments"
 msgstr "袣芯屑屑械薪褌邪褉懈懈"
@@ -976,8 +1258,16 @@ msgstr "袣芯屑屑械薪褌邪褉懈懈"
 msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "袣芯屑屑懈褌"
-msgstr[1] "袣芯屑屑懈褌褘"
-msgstr[2] "袣芯屑屑懈褌褘"
+msgstr[1] "袣芯屑屑懈褌邪"
+msgstr[2] "袣芯屑屑懈褌芯胁"
+msgstr[3] "袣芯屑屑懈褌褘"
+
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] "袣芯屑屑懈褌 (%{commit_count})"
+msgstr[1] "袣芯屑屑懈褌邪 (%{commit_count})"
+msgstr[2] "袣芯屑屑懈褌芯胁 (%{commit_count})"
+msgstr[3] "袣芯屑屑懈褌芯胁 (%{commit_count})"
 
 msgid "Commit Message"
 msgstr "袨锌懈褋邪薪懈械 袣芯屑屑懈褌邪"
@@ -991,6 +1281,9 @@ msgstr "袨锌懈褋邪薪懈械 泻芯屑屑懈褌邪"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "袣芯屑屑懈褌"
 
@@ -1013,7 +1306,7 @@ msgid "Commits per weekday"
 msgstr ""
 
 msgid "Commits|An error occurred while fetching merge requests data."
-msgstr ""
+msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 写邪薪薪褘褏 蟹邪锌褉芯褋邪 薪邪 褋谢懈褟薪懈褟."
 
 msgid "Commits|Commit: %{commitText}"
 msgstr ""
@@ -1036,35 +1329,77 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
 msgid "CompareBranches|Compare"
-msgstr ""
+msgstr "小褉邪胁薪懈褌褜"
 
 msgid "CompareBranches|Source"
-msgstr ""
+msgstr "袠褋褌芯褔薪懈泻"
 
 msgid "CompareBranches|Target"
-msgstr ""
+msgstr "笑械谢褜"
 
 msgid "CompareBranches|There isn't anything to compare."
+msgstr "袧械褔械谐芯 褋褉邪胁薪懈胁邪褌褜."
+
+msgid "Confidential"
 msgstr ""
 
 msgid "Confidentiality"
+msgstr "袣芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌褜"
+
+msgid "Configure Gitaly timeouts."
 msgstr ""
 
-msgid "Container Registry"
-msgstr "袪械械褋褌褉 袣芯薪褌械泄薪械褉芯胁"
+msgid "Configure Sidekiq job throttling."
+msgstr ""
 
-msgid "ContainerRegistry|Created"
-msgstr "小芯蟹写邪薪"
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
 
-msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
-msgstr "小薪邪褔邪谢邪 邪胁褌芯褉懈蟹褍泄褌械褋褜 胁 褉械械褋褌褉械 泻芯薪褌械泄薪械褉芯胁 GitLab, 懈褋锌芯谢褜蟹褍褟 褋胁芯懈 懈屑褟 锌芯谢褜蟹芯胁邪褌械谢褟 懈 锌邪褉芯谢褜. 袝褋谢懈 褍 胁邪褋 械褋褌褜 %{link_2fa}, 胁邪屑 薪械芯斜褏芯写懈屑芯 懈褋锌芯谢褜蟹芯胁邪褌褜 %{link_token}:"
+msgid "Configure limits for web and API requests."
+msgstr ""
 
-msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
-msgstr "GitLab 锌芯写写械褉卸懈胁邪械褌 写芯 褌褉械褏 褍褉芯胁薪械泄 懈屑褢薪 芯斜褉邪蟹芯胁. 小谢械写褍褞褖懈械 锌褉懈屑械褉褘 懈屑褢薪 芯斜褉邪蟹芯胁 锌芯写褏芯写褟褌 写谢褟 胁邪褕械谐芯 锌褉芯械泻褌邪:"
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr "袩芯写泻谢褞褔械薪懈械..."
+
+msgid "Container Registry"
+msgstr "袪械械褋褌褉 袣芯薪褌械泄薪械褉芯胁"
+
+msgid "ContainerRegistry|Created"
+msgstr "小芯蟹写邪薪"
+
+msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
+msgstr "小薪邪褔邪谢邪 邪胁褌芯褉懈蟹褍泄褌械褋褜 胁 褉械械褋褌褉械 泻芯薪褌械泄薪械褉芯胁 GitLab, 懈褋锌芯谢褜蟹褍褟 褋胁芯懈 懈屑褟 锌芯谢褜蟹芯胁邪褌械谢褟 懈 锌邪褉芯谢褜. 袝褋谢懈 褍 胁邪褋 械褋褌褜 %{link_2fa}, 胁邪屑 薪械芯斜褏芯写懈屑芯 懈褋锌芯谢褜蟹芯胁邪褌褜 %{link_token}:"
+
+msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
+msgstr "GitLab 锌芯写写械褉卸懈胁邪械褌 写芯 褌褉械褏 褍褉芯胁薪械泄 懈屑褢薪 芯斜褉邪蟹芯胁. 小谢械写褍褞褖懈械 锌褉懈屑械褉褘 懈屑褢薪 芯斜褉邪蟹芯胁 锌芯写褏芯写褟褌 写谢褟 胁邪褕械谐芯 锌褉芯械泻褌邪:"
 
 msgid "ContainerRegistry|How to use the Container Registry"
 msgstr "袣邪泻 懈褋锌芯谢褜蟹芯胁邪褌褜 袪械械褋褌褉 袣芯薪褌械泄薪械褉芯胁"
@@ -1099,6 +1434,12 @@ msgstr "袠褋锌芯谢褜蟹芯胁邪褌褜 褉邪蟹谢懈褔薪褘械 懈屑械薪邪 芯斜褉邪蟹芯胁"
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr "袣芯谐写邪 褉械械褋褌褉 泻芯薪褌械泄薪械褉芯胁 Docker 懈薪褌械谐褉懈褉芯胁邪薪 褋 GitLab, 泻邪卸写褘泄 锌褉芯械泻褌 屑芯卸械褌 懈屑械褌褜 褋胁芯械 褋芯斜褋褌胁械薪薪芯械 锌褉芯褋褌褉邪薪褋褌胁芯 写谢褟 褏褉邪薪械薪懈褟 械谐芯 Docker 芯斜褉邪蟹芯胁."
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "袪褍泻芯胁芯写褋褌胁芯 褍褔邪褋褌薪懈泻邪"
 
@@ -1106,7 +1447,7 @@ msgid "Contributors"
 msgstr "校褔邪褋褌薪懈泻懈"
 
 msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
-msgstr ""
+msgstr "%{startDate} - %{endDate}"
 
 msgid "ContributorsPage|Building repository graph."
 msgstr "袩芯褋褌褉芯械薪懈械 谐褉邪褎邪 褉械锌芯蟹懈褌芯褉懈褟."
@@ -1130,28 +1471,40 @@ msgid "Copy URL to clipboard"
 msgstr "袣芯锌懈褉芯胁邪褌褜 URL 胁 斜褍褎械褉 芯斜屑械薪邪"
 
 msgid "Copy branch name to clipboard"
-msgstr ""
+msgstr "小泻芯锌懈褉芯胁邪褌褜 懈屑褟 胁械褌泻懈 胁 斜褍褎械褉 芯斜屑械薪邪"
+
+msgid "Copy command to clipboard"
+msgstr "袣芯锌懈褉芯胁邪褌褜 泻芯屑邪薪写褍 胁 斜褍褎械褉 芯斜屑械薪邪"
 
 msgid "Copy commit SHA to clipboard"
 msgstr "袣芯锌懈褉芯胁邪褌褜 SHA 泻芯屑屑懈褌邪 胁 斜褍褎械褉 芯斜屑械薪邪"
 
 msgid "Copy reference to clipboard"
-msgstr ""
+msgstr "小泻芯锌懈褉芯胁邪褌褜 褋褋褘谢泻褍 胁 斜褍褎械褉 芯斜屑械薪邪"
 
 msgid "Create"
-msgstr ""
+msgstr "小芯蟹写邪褌褜"
 
 msgid "Create New Directory"
 msgstr "小芯蟹写邪褌褜 袧芯胁褘泄 泻邪褌邪谢芯谐"
 
+msgid "Create a new branch"
+msgstr "小芯蟹写邪褌褜 薪芯胁褍褞 胁械褌泻褍"
+
+msgid "Create a new branch and merge request"
+msgstr "小芯蟹写邪褌褜 薪芯胁褍褞 胁械褌泻褍 懈 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "小芯蟹写邪褌褜 谢懈褔薪褘泄 褌芯泻械薪 薪邪 邪泻泻邪褍薪褌械 写谢褟 锌芯谢褍褔械薪懈褟 懈谢懈 芯褌锌褉邪胁泻懈 褔械褉械蟹 %{protocol}."
 
+msgid "Create branch"
+msgstr "小芯蟹写邪褌褜 胁械褌泻褍"
+
 msgid "Create directory"
 msgstr "小芯蟹写邪褌褜 泻邪褌邪谢芯谐"
 
-msgid "Create empty bare repository"
-msgstr "小芯蟹写邪褌褜 锌褍褋褌芯泄 褉械锌芯蟹懈褌芯褉懈泄"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr "小芯蟹写邪褌褜 褝锌懈泻"
@@ -1159,12 +1512,18 @@ msgstr "小芯蟹写邪褌褜 褝锌懈泻"
 msgid "Create file"
 msgstr "小芯蟹写邪褌褜 褎邪泄谢"
 
-msgid "Create lists from labels. Issues with that label appear in that list."
+msgid "Create group label"
 msgstr ""
 
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr "小芯蟹写邪褌褜 褋锌懈褋芯泻 懈蟹 屑械褌芯泻. 袨斜褋褍卸写械薪懈褟 褋 褝褌芯泄 屑械褌泻芯泄 锌芯褟胁谢褟褞褌褋褟 胁 褝褌芯屑 褋锌懈褋泻械."
+
 msgid "Create merge request"
 msgstr "小芯蟹写邪褌褜 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
 
+msgid "Create merge request and branch"
+msgstr "小芯蟹写邪褌褜 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械 懈 胁械褌泻褍"
+
 msgid "Create new branch"
 msgstr "小芯蟹写邪褌褜 薪芯胁褍褞 胁械褌泻褍"
 
@@ -1175,11 +1534,14 @@ msgid "Create new file"
 msgstr "小芯蟹写邪褌褜 薪芯胁褘泄 褎邪泄谢"
 
 msgid "Create new label"
-msgstr ""
+msgstr "小芯蟹写邪褌褜 薪芯胁褍褞 屑械褌泻褍"
 
 msgid "Create new..."
 msgstr "袧芯胁褘泄"
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "袨褌胁械褌胁懈褌褜"
 
@@ -1189,6 +1551,12 @@ msgstr "孝械谐"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "褋芯蟹写邪褌褜 锌械褉褋芯薪邪谢褜薪褘泄 褌芯泻械薪 写芯褋褌褍锌邪"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr "小芯蟹写邪械褌 薪芯胁褍褞 胁械褌泻褍 懈蟹 %{branchName}"
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr "小芯蟹写邪械褌 薪芯胁褍褞 胁械褌泻褍 懈蟹 %{branchName} 懈 锌械褉械薪邪锌褉邪胁谢褟械褌 薪邪 褋芯蟹写邪薪懈械 薪芯胁芯谐芯 蟹邪锌褉芯褋邪 薪邪 褋谢懈褟薪懈械"
+
 msgid "Creating epic"
 msgstr "小芯蟹写邪薪懈械 褝锌懈泻邪"
 
@@ -1199,7 +1567,7 @@ msgid "Cron syntax"
 msgstr "小懈薪褌邪泻褋懈褋 Cron"
 
 msgid "Current node"
-msgstr ""
+msgstr "孝械泻褍褖懈泄 褍蟹械谢"
 
 msgid "Custom notification events"
 msgstr "小芯斜褘褌懈褟 薪邪褋褌褉邪懈胁邪械屑褘褏 褍胁械写芯屑谢械薪懈泄"
@@ -1207,6 +1575,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "袗薪邪谢懈褌懈泻邪 笑懈泻谢邪"
 
@@ -1243,6 +1614,9 @@ msgstr "袛械泻."
 msgid "December"
 msgstr "袛械泻邪斜褉褜"
 
+msgid "Default classification label"
+msgstr "袦械褌泻邪 泻谢邪褋褋懈褎懈泻邪褑懈懈 锌芯 褍屑芯谢褔邪薪懈褞"
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "袨锌褉械写械谢懈褌褜 薪邪褋褌褉邪懈胁邪械屑褘泄 褕邪斜谢芯薪 褋 褋懈薪褌邪泻褋懈褋芯屑 cron"
 
@@ -1251,9 +1625,10 @@ msgstr "校写邪谢懈褌褜"
 
 msgid "Deploy"
 msgid_plural "Deploys"
-msgstr[0] "袪邪蟹胁械褉薪褍褌褜"
-msgstr[1] "袪邪蟹胁械褉褌褘胁邪薪懈械"
-msgstr[2] "袪邪蟹胁械褉褌褘胁邪薪懈械"
+msgstr[0] "袪邪蟹胁械褉褌褘胁邪薪懈械"
+msgstr[1] "袪邪蟹胁械褉褌褘胁邪薪懈褟"
+msgstr[2] "袪邪蟹胁械褉褌褘胁邪薪懈泄"
+msgstr[3] "袪邪蟹胁械褉褌褘胁邪薪懈褟"
 
 msgid "Deploy Keys"
 msgstr "袣谢褞褔懈 袪邪蟹胁械褉褌褘胁邪薪懈褟"
@@ -1268,19 +1643,19 @@ msgid "Details"
 msgstr "袩芯写褉芯斜薪邪褟 懈薪褎芯褉屑邪褑懈褟"
 
 msgid "Diffs|No file name available"
-msgstr ""
+msgstr "袠屑褟 褎邪泄谢邪 薪械写芯褋褌褍锌薪芯"
 
 msgid "Directory name"
 msgstr "袠屑褟 泻邪褌邪谢芯谐邪"
 
 msgid "Disable"
-msgstr ""
+msgstr "袨褌泻谢褞褔懈褌褜"
 
-msgid "Discard changes"
-msgstr "袨褌屑械薪懈褌褜 懈蟹屑械薪械薪懈褟"
+msgid "Discard draft"
+msgstr "校写邪谢懈褌褜 褔械褉薪芯胁懈泻"
 
 msgid "Discover GitLab Geo."
-msgstr ""
+msgstr "袨褌泻褉芯泄褌械 写谢褟 褋械斜褟 GitLab Geo."
 
 msgid "Dismiss Cycle Analytics introduction box"
 msgstr "袨褌泻谢褞褔懈褌褜 斜谢芯泻 胁胁械写械薪懈褟 胁 袗薪邪谢懈褌懈泻褍 笑懈泻谢邪"
@@ -1288,9 +1663,15 @@ msgstr "袨褌泻谢褞褔懈褌褜 斜谢芯泻 胁胁械写械薪懈褟 胁 袗薪邪谢懈褌懈泻褍 笑懈
 msgid "Dismiss Merge Request promotion"
 msgstr "袨褌泻谢褞褔懈褌褜 邪薪芯薪褋褘 写谢褟 袟邪锌褉芯褋芯胁 薪邪 褋谢懈褟薪懈械"
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "袧械 锌芯泻邪蟹褘胁邪褌褜 褋薪芯胁邪"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "小泻邪褔邪褌褜"
 
@@ -1318,7 +1699,13 @@ msgstr "袩褉芯褋褌芯泄 Diff"
 msgid "DownloadSource|Download"
 msgstr "小泻邪褔邪褌褜"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
+msgstr "小褉芯泻"
+
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
 msgstr ""
 
 msgid "Edit"
@@ -1328,12 +1715,54 @@ msgid "Edit Pipeline Schedule %{id}"
 msgstr "袠蟹屑械薪懈褌褜 褉邪褋锌懈褋邪薪懈械 褋斜芯褉芯褔薪芯泄 谢懈薪懈懈 %{id}"
 
 msgid "Edit files in the editor and commit changes here"
+msgstr "袪械写邪泻褌懈褉褍泄褌械 褎邪泄谢褘 胁 褉械写邪泻褌芯褉械 懈 蟹邪褎懈泻褋懈褉褍泄褌械 懈蟹屑械薪械薪懈褟 蟹写械褋褜"
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
 msgstr ""
 
 msgid "Emails"
 msgstr "Email-邪写褉械褋邪"
 
 msgid "Enable"
+msgstr "袙泻谢褞褔懈褌褜"
+
+msgid "Enable Auto DevOps"
+msgstr "袙泻谢褞褔懈褌褜 Auto DevOps"
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
 msgstr ""
 
 msgid "Environments|An error occurred while fetching the environments."
@@ -1390,38 +1819,50 @@ msgstr "协锌懈泻 斜褍写械褌 褍写邪谢械薪! 袙褘 褍胁械褉械薪褘?"
 msgid "Epics"
 msgstr "协锌懈泻懈"
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr "协锌懈泻懈 锌芯蟹胁芯谢褟褌 胁邪屑 褍锌褉邪胁谢褟褌褜 锌芯褉褌褎械谢械屑 锌褉芯械泻褌芯胁 斜芯谢械械 褝褎褎械泻褌懈胁薪芯 懈 褋 屑械薪褜褕懈屑懈 褍褋懈谢懈褟屑懈"
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr "袨褕懈斜泻邪 锌褉芯胁械褉泻懈 写邪薪薪褘褏 胁械褌泻懈. 袩芯卸邪谢褍泄褋褌邪, 锌芯锌褉芯斜褍泄褌械 械褖械 褉邪蟹."
+
+msgid "Error committing changes. Please try again."
+msgstr "袨褕懈斜泻邪 锌褉懈 褋芯褏褉邪薪械薪懈懈 懈蟹屑械薪械薪懈泄. 袩芯卸邪谢褍泄褋褌邪, 锌芯锌褉芯斜褍泄褌械 械褖械 褉邪蟹."
+
 msgid "Error creating epic"
 msgstr "袨褕懈斜泻邪 褋芯蟹写邪薪懈褟 褝锌懈泻邪"
 
 msgid "Error fetching contributors data."
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌芯谢褍褔械薪懈褟 写邪薪薪褘褏 褍褔邪褋褌薪懈泻芯胁."
 
 msgid "Error fetching labels."
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌芯谢褍褔械薪懈褟 屑械褌芯泻."
 
 msgid "Error fetching network graph."
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌芯谢褍褔械薪懈褟 褋械褌械胁芯谐芯 谐褉邪褎邪."
 
 msgid "Error fetching refs"
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌芯谢褍褔械薪懈褟 褋褋褘谢芯泻"
 
 msgid "Error fetching usage ping data."
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 写邪薪薪褘褏 芯斜 懈褋锌芯谢褜蟹芯胁邪薪懈懈 ping."
 
 msgid "Error occurred when toggling the notification subscription"
 msgstr "袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 锌械褉械泻谢褞褔械薪懈懈 锌芯写锌懈褋泻懈 薪邪 芯锌芯胁械褖械薪懈械"
 
 msgid "Error saving label update."
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌褉懈 芯斜薪芯胁谢械薪懈懈 屑械褌泻懈."
 
 msgid "Error updating status for all todos."
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌褉懈 芯斜薪芯胁谢械薪懈懈 褋褌邪褌褍褋邪 写谢褟 胁褋械褏 todo."
 
 msgid "Error updating todo status."
-msgstr ""
+msgstr "袨褕懈斜泻邪 锌褉懈 芯斜薪芯胁谢械薪懈懈 褋褌邪褌褍褋邪 todo."
 
 msgid "EventFilterBy|Filter by all"
 msgstr "肖懈谢褜褌褉 锌芯 胁褋械屑褍"
@@ -1451,7 +1892,7 @@ msgid "Every week (Sundays at 4:00am)"
 msgstr "袝卸械薪械写械谢褜薪芯 (锌芯 胁芯褋泻褉械褋械薪懈褟屑 胁 4:00)"
 
 msgid "Expand"
-msgstr ""
+msgstr "袪邪蟹胁械褉薪褍褌褜"
 
 msgid "Explore projects"
 msgstr "袨斜蟹芯褉 锌褉芯械泻褌芯胁"
@@ -1459,12 +1900,45 @@ msgstr "袨斜蟹芯褉 锌褉芯械泻褌芯胁"
 msgid "Explore public groups"
 msgstr "袠褋褋谢械写芯胁邪褌褜 锌褍斜谢懈褔薪褘械 谐褉褍锌锌褘"
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr "袧械胁褘锌芯谢薪械薪薪褘械 袟邪写邪薪懈褟"
+
 msgid "Failed to change the owner"
 msgstr "袧械 褍写邪谢芯褋褜 懈蟹屑械薪懈褌褜 胁谢邪写械谢褜褑邪"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr "袨褕懈斜泻邪 锌褉懈 褍写邪谢械薪懈懈 芯斜褋褍卸写械薪懈褟 褋 写芯褋泻懈, 锌芯胁褌芯褉懈褌械 锌芯锌褘褌泻褍."
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "袧械 褍写邪谢芯褋褜 褍写邪谢懈褌褜 褉邪褋锌懈褋邪薪懈械 褋斜芯褉芯褔薪芯泄 谢懈薪懈懈"
 
+msgid "Failed to update issues, please try again."
+msgstr "袨褕懈斜泻邪 芯斜薪芯胁谢械薪懈褟 芯斜褋褍卸写械薪懈泄, 锌芯卸邪谢褍泄褋褌邪, 锌芯锌褉芯斜褍泄褌械 褋薪芯胁邪."
+
 msgid "Feb"
 msgstr "肖械胁."
 
@@ -1472,7 +1946,7 @@ msgid "February"
 msgstr "肖械胁褉邪谢褜"
 
 msgid "Fields on this page are now uneditable, you can configure"
-msgstr ""
+msgstr "袩芯谢褟 薪邪 褝褌芯泄 褋褌褉邪薪懈褑械 褋械泄褔邪褋 薪械写芯褋褌褍锌薪褘 写谢褟 褉械写邪泻褌懈褉芯胁邪薪懈褟, 胁褘 屑芯卸械褌械 薪邪褋褌褉芯懈褌褜"
 
 msgid "File name"
 msgstr "袠屑褟 褎邪泄谢邪"
@@ -1480,6 +1954,12 @@ msgstr "袠屑褟 褎邪泄谢邪"
 msgid "Files"
 msgstr "肖邪泄谢褘"
 
+msgid "Files (%{human_size})"
+msgstr "肖邪泄谢芯胁 (%{human_size})"
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "肖懈谢褜褌褉 锌芯 泻芯屑屑械薪褌邪褉懈褟屑懈 泻 泻芯屑屑懈褌邪屑"
 
@@ -1489,17 +1969,27 @@ msgstr "袩芯懈褋泻 锌芯 锌褍褌懈"
 msgid "Find file"
 msgstr "袧邪泄褌懈 褎邪泄谢"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "袩械褉胁褘泄"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "芯褌锌褉邪胁谢械薪芯 邪胁褌芯褉芯屑"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "袨褌胁械褌胁谢械薪懈械"
 msgstr[1] "袨褌胁械褌胁谢械薪懈褟"
-msgstr[2] "袨褌胁械褌胁谢械薪懈褟"
+msgstr[2] "袨褌胁械褌胁谢械薪懈泄"
+msgstr[3] "袨褌胁械褌胁谢械薪懈褟"
 
 msgid "ForkedFromProjectPath|Forked from"
 msgstr "袨褌胁械褌胁谢械薪芯 芯褌"
@@ -1507,30 +1997,45 @@ msgstr "袨褌胁械褌胁谢械薪芯 芯褌"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr "袨褌胁械褌胁谢械薪懈械 芯褌 %{project_name} (褍写邪谢械薪芯)"
 
+msgid "Forking in progress"
+msgstr "袙褘锌芯谢薪褟械褌褋褟 芯褌胁械褌胁谢械薪懈械"
+
 msgid "Format"
 msgstr "肖芯褉屑邪褌"
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr "袨褌 褋芯蟹写邪薪懈褟 芯斜褋褍卸写械薪懈褟 写芯 褉邪蟹胁械褉褌褘胁邪薪懈褟 褉械邪谢懈蟹邪褑懈懈 胁 褉邪斜芯褔械泄 褋褉械写械"
 
 msgid "From merge request merge until deploy to production"
 msgstr "袨褌 蟹邪锌褉芯褋邪 薪邪 褋谢懈褟薪懈械 写芯 褉邪蟹胁械褉褌褘胁邪薪懈褟 胁 褉邪斜芯褔械泄 褋褉械写械"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr "GPG 袣谢褞褔懈"
 
 msgid "Generate a default set of labels"
-msgstr ""
+msgstr "小芯蟹写邪褌褜 褋褌邪薪写邪褉褌薪褘泄 薪邪斜芯褉 屑械褌芯泻"
 
 msgid "Geo Nodes"
 msgstr "袚械芯谐褉邪褎懈褔械褋泻懈械 校蟹谢褘"
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr "袧邪 褍蟹谢械 褋斜芯泄 懈谢懈 芯薪 薪械 褉邪斜芯褌邪械褌."
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr "校蟹械谢 褎褍薪泻褑懈芯薪懈褉褍械褌 屑械写谢械薪薪芯, 锌械褉械谐褍卸械薪 懈谢懈 褌芯谢褜泻芯 褔褌芯 胁芯褋褋褌邪薪芯胁谢械薪 锌芯褋谢械 褋斜芯褟."
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1576,21 +2081,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1603,9 +2135,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1636,21 +2180,42 @@ msgstr "袙褘斜械褉懈褌械 谐褉褍锌锌褘 写谢褟 褉械锌谢懈泻邪褑懈懈."
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr "URL-邪写褉械褋 褉械锌芯蟹懈褌芯褉懈褟 Git"
+
 msgid "Git revision"
-msgstr ""
+msgstr "袪械胁懈蟹懈褟 git"
 
 msgid "Git storage health information has been reset"
 msgstr "袠薪褎芯褉屑邪褑懈褟 芯 褋褌邪斜懈谢褜薪芯褋褌懈 Git 褏褉邪薪懈谢懈褖邪 斜褘谢邪 褋斜褉芯褕械薪邪"
 
 msgid "Git version"
+msgstr "袙械褉褋懈褟 Git"
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
 msgstr ""
 
 msgid "GitLab Runner section"
 msgstr "小械泻褑懈褟 Gitlab Runner"
 
-msgid "Gitaly Servers"
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
 msgstr ""
 
+msgid "Gitaly Servers"
+msgstr "小械褉胁械褉褘 Gitaly"
+
+msgid "Go back"
+msgstr "袙械褉薪褍褌褜褋褟"
+
 msgid "Go to your fork"
 msgstr "袩械褉械泄褌懈 泻 胁邪褕械屑褍 芯褌胁械褌胁谢械薪懈褞"
 
@@ -1661,6 +2226,24 @@ msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab ad
 msgstr "袗褍褌械薪褌懈褎懈泻邪褑懈褟 Google 薪械 %{link_to_documentation}. 袩芯锌褉芯褋懈褌械 褋胁芯械谐芯 邪写屑懈薪懈褋褌褉邪褌芯褉邪 GitLab, 械褋谢懈 胁褘 褏芯褌懈褌械 胁芯褋锌芯谢褜蟹芯胁邪褌褜褋褟 褝褌懈屑 褋械褉胁懈褋芯屑."
 
 msgid "Got it!"
+msgstr "袩芯薪褟褌薪芯!"
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
 msgstr ""
 
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
@@ -1699,9 +2282,6 @@ msgstr "袚褉褍锌锌褘 薪械 薪邪泄写械薪褘"
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr "袙褘 屑芯卸械褌械 褍锌褉邪胁谢褟褌褜 锌褉邪胁邪屑懈 懈 写芯褋褌褍锌芯屑 褍褔邪褋褌薪懈泻芯胁 胁邪褕械泄 谐褉褍锌锌褘 泻 泻邪卸写芯屑褍 锌褉芯械泻褌褍 胁 谐褉褍锌锌械."
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr "小芯蟹写邪褌褜 锌褉芯械泻褌 胁 褝褌芯泄 谐褉褍锌锌械."
 
@@ -1732,6 +2312,9 @@ msgstr "袣 褋芯卸邪谢械薪懈褞, 锌芯 胁邪褕械屑褍 蟹邪锌褉芯褋褍 谐褉褍锌锌 懈谢
 msgid "Have your users email"
 msgstr "协谢械泻褌褉芯薪薪邪褟 锌芯褔褌邪 写谢褟 芯斜褉邪褖械薪懈泄 锌芯谢褜蟹芯胁邪褌械谢械泄"
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "袩褉芯胁械褉泻邪 褉邪斜芯褌芯褋锌芯褋芯斜薪芯褋褌懈"
 
@@ -1750,11 +2333,21 @@ msgstr "袩褉芯斜谢械屑 褉邪斜芯褌芯褋锌芯褋芯斜薪芯褋褌懈 薪械 芯斜薪邪褉褍卸械
 msgid "HealthCheck|Unhealthy"
 msgstr "袧械褋褌邪斜懈谢褜薪褘泄"
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "History"
 msgstr "袠褋褌芯褉懈褟"
@@ -1762,9 +2355,39 @@ msgstr "袠褋褌芯褉懈褟"
 msgid "Housekeeping successfully started"
 msgstr "袨褔懈褋褌泻邪 褍褋锌械褕薪芯 蟹邪锌褍褖械薪邪"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr "袙褘锌芯谢薪褟械褌褋褟 懈屑锌芯褉褌"
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "袠屑锌芯褉褌 褉械锌芯蟹懈褌芯褉懈褟"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr "校谢褍褔褕懈褌褜 写芯褋泻懈 芯斜褋褍卸写械薪懈泄 褋 锌芯屑芯褖褜褞 GitLab Enterprise Edition."
 
@@ -1774,6 +2397,9 @@ msgstr "校谢褍褔褕懈褌褜 褍锌褉邪胁谢械薪懈械 芯斜褋褍卸写械薪懈褟屑懈 胁芯蟹屑
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr "校谢褍褔褕懈褌褜 锌芯懈褋泻 锌褉懈 锌芯屑芯褖懈 袪邪褋褕懈褉械薪薪芯谐芯 袚谢芯斜邪谢褜薪芯谐芯 袩芯懈褋泻邪 懈 GitLab Enterprise Edition."
 
+msgid "Install Runner on Kubernetes"
+msgstr "校褋褌邪薪芯胁懈褌褜 Runner 薪邪 Kubernetes"
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "校褋褌邪薪芯胁懈褌械 Gitlab Runner 褋芯胁屑械褋褌懈屑褘泄 褋 Gitlab CI"
 
@@ -1782,8 +2408,12 @@ msgid_plural "Instances"
 msgstr[0] "协泻蟹械屑锌谢褟褉"
 msgstr[1] "协泻蟹械屑锌谢褟褉邪"
 msgstr[2] "协泻蟹械屑锌谢褟褉芯胁"
+msgstr[3] "协泻蟹械屑锌谢褟褉褘"
 
 msgid "Instance does not support multiple Kubernetes clusters"
+msgstr "协泻蟹械屑锌谢褟褉 薪械 锌芯写写械褉卸懈胁邪械褌 薪械褋泻芯谢褜泻芯 泻谢邪褋褌械褉芯胁 Kubernetes"
+
+msgid "Integrations"
 msgstr ""
 
 msgid "Interested parties can even contribute by pushing commits if they want to."
@@ -1825,6 +2455,9 @@ msgstr "携薪胁."
 msgid "January"
 msgstr "携薪胁邪褉褜"
 
+msgid "Jobs"
+msgstr "袟邪写邪薪懈褟"
+
 msgid "Jul"
 msgstr "袠褞谢."
 
@@ -1837,23 +2470,29 @@ msgstr "袠褞薪."
 msgid "June"
 msgstr "袠褞薪褜"
 
-msgid "Kubernetes"
+msgid "Koding"
 msgstr ""
 
+msgid "Kubernetes"
+msgstr "Kubernetes"
+
 msgid "Kubernetes Cluster"
-msgstr ""
+msgstr "袣谢邪褋褌械褉 Kubernetes"
 
 msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
-msgstr ""
+msgstr "袙褉械屑褟 褋芯蟹写邪薪懈褟 泻谢邪褋褌械褉邪 Kubernetes 锌褉械胁褘褕邪械褌 胁褉械屑褟 芯卸懈写邪薪懈褟; %{timeout}"
 
 msgid "Kubernetes cluster integration was not removed."
-msgstr ""
+msgstr "袠薪褌械谐褉邪褑懈褟 泻谢邪褋褌械褉邪 袣褍斜械褉薪械褌械 薪械 斜褘谢邪 褍写邪谢械薪邪."
 
 msgid "Kubernetes cluster integration was successfully removed."
-msgstr ""
+msgstr "袠薪褌械谐褉邪褑懈褟 泻谢邪褋褌械褉邪 Kubernetes 斜褘谢邪 褍褋锌械褕薪芯 褍写邪谢械薪邪."
 
 msgid "Kubernetes cluster was successfully updated."
-msgstr ""
+msgstr "袣谢邪褋褌械褉 Kubernetes 斜褘谢 褍褋锌械褕薪芯 芯斜薪芯胁谢褢薪."
+
+msgid "Kubernetes configured"
+msgstr "Kubernetes 薪邪褋褌褉芯械薪"
 
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
@@ -1864,17 +2503,36 @@ msgstr "袨褌泻谢褞褔械薪芯"
 msgid "LFSStatus|Enabled"
 msgstr "袙泻谢褞褔械薪芯"
 
+msgid "Label"
+msgstr "袦械褌泻邪"
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr "袦械褌泻懈"
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "袩芯褋谢械写薪懈泄 %d 写械薪褜"
-msgstr[1] "袩芯褋谢械写薪懈械 %d 写薪懈"
-msgstr[2] "袩芯褋谢械写薪懈械 %d 写薪懈"
+msgstr[1] "袩芯褋谢械写薪懈械 %d 写薪褟"
+msgstr[2] "袩芯褋谢械写薪懈械 %d 写薪械泄"
+msgstr[3] "袩芯褋谢械写薪懈械 %d 写薪懈"
 
 msgid "Last Pipeline"
 msgstr "袩芯褋谢械写薪褟褟 小斜芯褉芯褔薪邪褟 袥懈薪懈褟"
@@ -1903,6 +2561,12 @@ msgstr "胁"
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "校蟹薪邪泄褌械 斜芯谢褜褕械 胁"
 
@@ -1921,16 +2585,22 @@ msgstr "袩芯泻懈薪褍褌褜 锌褉芯械泻褌"
 msgid "License"
 msgstr "袥懈褑械薪蟹懈褟"
 
-msgid "Loading the GitLab IDE..."
+msgid "List"
+msgstr "小锌懈褋芯泻"
+
+msgid "List your GitHub repositories"
 msgstr ""
 
+msgid "Loading the GitLab IDE..."
+msgstr "袟邪谐褉褍蟹泻邪 GitLab IDE..."
+
 msgid "Lock"
 msgstr "袘谢芯泻懈褉芯胁泻邪"
 
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1939,56 +2609,146 @@ msgstr "袟邪斜谢芯泻懈褉芯胁邪薪芯"
 msgid "Locked Files"
 msgstr "袟邪斜谢芯泻懈褉芯胁邪薪薪褘械 肖邪泄谢褘"
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr "袙芯泄褌懈"
 
-msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "Mar"
+msgstr "袦邪褉."
+
+msgid "March"
+msgstr "袦邪褉褌"
+
+msgid "Mark done"
+msgstr ""
+
+msgid "Maximum git storage failures"
+msgstr "袦邪泻褋懈屑邪谢褜薪芯械 泻芯谢懈褔械褋褌胁芯 褋斜芯械胁 褏褉邪薪懈谢懈褖邪 git"
+
+msgid "May"
+msgstr "袦邪泄"
+
+msgid "Median"
+msgstr "小褉械写薪械械"
+
+msgid "Members"
+msgstr "校褔邪褋褌薪懈泻懈"
+
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "Merge Requests"
+msgstr "袟邪锌褉芯褋褘 薪邪 小谢懈褟薪懈械"
+
+msgid "Merge events"
+msgstr "小芯斜褘褌懈褟 褋谢懈褟薪懈泄"
+
+msgid "Merge request"
+msgstr "袟邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Messages"
+msgstr "小芯芯斜褖械薪懈褟"
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
 msgstr ""
 
-msgid "Manage labels"
+msgid "Metrics|Name"
 msgstr ""
 
-msgid "Mar"
-msgstr "袦邪褉."
+msgid "Metrics|New metric"
+msgstr ""
 
-msgid "March"
-msgstr "袦邪褉褌"
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
 
-msgid "Mark done"
+msgid "Metrics|Query"
 msgstr ""
 
-msgid "Maximum git storage failures"
-msgstr "袦邪泻褋懈屑邪谢褜薪芯械 泻芯谢懈褔械褋褌胁芯 褋斜芯械胁 褏褉邪薪懈谢懈褖邪 git"
+msgid "Metrics|Response"
+msgstr ""
 
-msgid "May"
-msgstr "袦邪泄"
+msgid "Metrics|System"
+msgstr ""
 
-msgid "Median"
-msgstr "小褉械写薪械械"
+msgid "Metrics|Type"
+msgstr ""
 
-msgid "Members"
-msgstr "校褔邪褋褌薪懈泻懈"
+msgid "Metrics|Unit label"
+msgstr ""
 
-msgid "Merge Request"
+msgid "Metrics|Used as a title for the chart"
 msgstr ""
 
-msgid "Merge Requests"
-msgstr "袟邪锌褉芯褋褘 薪邪 小谢懈褟薪懈械"
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
 
-msgid "Merge events"
-msgstr "小芯斜褘褌懈褟 褋谢懈褟薪懈泄"
+msgid "Metrics|Y-axis label"
+msgstr ""
 
-msgid "Merge request"
-msgstr "袟邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
 
-msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgid "Metrics|e.g. Requests/second"
 msgstr ""
 
-msgid "Merged"
+msgid "Metrics|e.g. Throughput"
 msgstr ""
 
-msgid "Messages"
-msgstr "小芯芯斜褖械薪懈褟"
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
 
 msgid "Milestone"
 msgstr ""
@@ -2005,12 +2765,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "写芯斜邪胁懈褌褜 泻谢褞褔 SSH"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr "袦芯薪懈褌芯褉懈薪谐"
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr "袘芯谢褜褕械 懈薪褎芯褉屑邪褑懈懈 写芯褋褌褍锌薪芯|褌褍褌"
 
@@ -2031,6 +2812,7 @@ msgid_plural "New Issues"
 msgstr[0] "袧芯胁芯械 袨斜褋褍卸写械薪懈械"
 msgstr[1] "袧芯胁褘褏 袨斜褋褍卸写械薪懈褟"
 msgstr[2] "袧芯胁褘褏 袨斜褋褍卸写械薪懈泄"
+msgstr[3] "袧芯胁褘械 袨斜褋褍卸写械薪懈褟"
 
 msgid "New Kubernetes Cluster"
 msgstr ""
@@ -2083,6 +2865,9 @@ msgstr "袧芯胁邪褟 锌芯写谐褉褍锌锌邪"
 msgid "New tag"
 msgstr "袧芯胁褘泄 褌械谐"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2101,15 +2886,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "袧械褌 褉械锌芯蟹懈褌芯褉懈褟"
 
 msgid "No schedules"
 msgstr "袧械褌 褉邪褋锌懈褋邪薪懈泄"
 
-msgid "No time spent"
-msgstr "袧械褌 蟹邪褌褉邪褔械薪薪芯谐芯 胁褉械屑械薪懈"
-
 msgid "None"
 msgstr "袩褍褋褌芯"
 
@@ -2119,12 +2904,33 @@ msgstr ""
 msgid "Not available"
 msgstr "袧械写芯褋褌褍锌薪芯"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "袧械写芯褋褌邪褌芯褔薪芯 写邪薪薪褘褏"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "校胁械写芯屑谢械薪懈褟 芯 褋芯斜褘褌懈褟褏"
 
@@ -2209,6 +3015,12 @@ msgstr "袨泻褌褟斜褉褜"
 msgid "OfSearchInADropdown|Filter"
 msgstr "肖懈谢褜褌褉"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr "孝芯谢褜泻芯 褍褔邪褋褌薪懈泻懈 锌褉芯械泻褌邪 屑芯谐褍褌 芯褋褌邪胁谢褟褌褜 泻芯屑屑械薪褌邪褉懈懈."
 
@@ -2227,12 +3039,21 @@ msgstr "袨褌泻褉芯械褌褋褟 胁 薪芯胁芯屑 芯泻薪械"
 msgid "Options"
 msgstr "袧邪褋褌褉芯泄泻懈"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "袨斜蟹芯褉"
 
 msgid "Owner"
 msgstr "袙谢邪写械谢械褑"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr "袩芯褋谢械写薪褟褟 禄"
 
@@ -2245,9 +3066,21 @@ msgstr "袩褉械写褘写褍褖邪褟"
 msgid "Pagination|芦 First"
 msgstr "芦 袩械褉胁邪褟"
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "袩邪褉芯谢褜"
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "小斜芯褉芯褔薪邪褟 谢懈薪懈褟"
 
@@ -2329,9 +3162,54 @@ msgstr "小斜芯褉芯褔薪褘械 谢懈薪懈懈 蟹邪 锌芯褋谢械写薪懈泄 谐芯写"
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "胁褋械"
 
@@ -2344,6 +3222,9 @@ msgstr "褋芯 褋褌邪写懈械泄"
 msgid "Pipeline|with stages"
 msgstr "褋芯 褋褌邪写懈褟屑懈"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2353,6 +3234,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr "袩芯卸邪谢褍泄褋褌邪, 褉械褕懈褌械 reCAPTCHA"
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr "袩褉械写锌芯褔褌械薪懈褟"
 
@@ -2365,6 +3252,9 @@ msgstr "袩褉懈胁邪褌薪褘泄 - 袛芯褋褌褍锌 泻 锌褉芯械泻褌褍 写芯谢卸械薪 锌褉械
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr "袩褉懈胁邪褌薪邪褟 - 袚褉褍锌锌褍 懈 胁泻谢褞褔褢薪薪褘械 胁 薪械褢 锌褉芯械泻褌褘 屑芯谐褍褌 胁懈写械褌褜 褌芯谢褜泻芯 褔谢械薪褘 谐褉褍锌锌褘."
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr "袩褉芯褎懈谢褜"
 
@@ -2404,6 +3294,9 @@ msgstr "袙邪褕邪 褍褔械褌薪邪褟 蟹邪锌懈褋褜 胁 薪邪褋褌芯褟褖械械 胁褉械屑褟 褟
 msgid "Profiles|your account"
 msgstr "胁邪褕邪 褍褔械褌薪邪褟 蟹邪锌懈褋褜"
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2428,9 +3321,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr "袛械褌邪谢懈 锌褉芯械泻褌邪"
 
@@ -2527,38 +3417,89 @@ msgstr "袣 褋芯卸邪谢械薪懈褞, 锌芯 胁邪褕械屑褍 蟹邪锌褉芯褋褍 锌褉芯械泻褌褘 
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr "协褌邪 褎褍薪泻褑懈芯薪邪谢褜薪芯褋褌褜 褌褉械斜褍械褌 锌芯写写械褉卸泻懈 localStorage 胁 胁邪褕械屑 斜褉邪褍蟹械褉械"
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr "袩芯 褍屑芯谢褔邪薪懈褞, Prometheus 蟹邪锌褍褋泻邪械褌褋褟 锌芯 邪写褉械褋褍 鈥榟ttp://localhost:9090鈥�. 袧械 褉械泻芯屑械薪写褍械褌褋褟 懈蟹屑械薪褟褌褜 邪写褉械褋 懈 锌芯褉褌 锌芯 褍屑芯谢褔邪薪懈褞, 褌邪泻 泻邪泻 褝褌芯 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 泻芯薪褎谢懈泻褌褍 褋 写褉褍谐懈屑 褋械褉胁懈褋邪屑懈 蟹邪锌褍褖械薪薪褘屑懈 薪邪 GitLab 褋械褉胁械褉械."
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr "袨锌褉械写械谢械薪懈械 懈 薪邪褋褌褉芯泄泻邪 屑械褌褉懈泻..."
 
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
 msgid "PrometheusService|Metrics"
 msgstr "袦械褌褉懈泻懈"
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
-msgstr "袦械褌褉懈泻懈 邪胁褌芯屑邪褌懈褔械褋泻懈 薪邪褋褌褉邪懈胁邪褞褌褋褟 懈 芯褌褋谢械卸懈胁邪褞褌褋褟 薪邪 芯褋薪芯胁械 锌芯锌褍谢褟褉薪褘褏 斜懈斜谢懈芯褌械泻 屑械褌褉懈泻."
-
 msgid "PrometheusService|Missing environment variable"
 msgstr "袩褉芯锌褍褖械薪邪 锌械褉械屑械薪薪邪褟 芯泻褉褍卸械薪懈褟"
 
-msgid "PrometheusService|Monitored"
-msgstr "袦芯薪懈褌芯褉懈薪谐 锌芯写泻谢褞褔械薪"
-
 msgid "PrometheusService|More information"
 msgstr "袛芯锌芯谢薪懈褌械谢褜薪邪褟 懈薪褎芯褉屑邪褑懈褟"
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr "袧懈 芯写薪芯泄 屑械褌褉懈泻懈 薪械 芯褌褋谢械卸懈胁邪械褌褋褟. 袛谢褟 薪邪褔邪谢邪 屑芯薪懈褌芯褉懈薪谐邪 褉邪蟹胁械褉薪懈褌械 芯泻褉褍卸械薪懈械."
+msgid "PrometheusService|New metric"
+msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr "袘邪蟹芯胁褘泄 邪写褉械褋 Prometheus API, 薪邪锌褉懈屑械褉 http://prometheus.example.com/"
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
-msgstr "袩褉芯褋屑芯褌褉 芯泻褉褍卸械薪懈泄"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
+msgstr ""
 
 msgid "Protip:"
 msgstr ""
@@ -2575,6 +3516,12 @@ msgstr "袩褉邪胁懈谢邪 袨褌锌褉邪胁泻懈"
 msgid "Push events"
 msgstr "小芯斜褘褌懈褟 芯褌锌褉邪胁泻懈"
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr "袨谐褉邪薪懈褔械薪懈褟 写谢褟 泻芯屑屑懈褌械褉邪"
 
@@ -2587,6 +3534,9 @@ msgstr "袩芯写褉芯斜薪械械"
 msgid "Readme"
 msgstr "袠薪褋褌褉褍泻褑懈褟"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "袙械褌泻懈"
 
@@ -2606,13 +3556,13 @@ msgid "Related Commits"
 msgstr "小胁褟蟹邪薪薪褘械 泻芯屑屑懈褌褘"
 
 msgid "Related Deployed Jobs"
-msgstr "小胁褟蟹邪薪薪褘械 蟹邪写邪褔懈 胁褘谐褉褍蟹泻懈"
+msgstr "小胁褟蟹邪薪薪褘械 袟邪写邪薪懈褟 袪邪蟹胁械褉褌褘胁邪薪懈褟"
 
 msgid "Related Issues"
 msgstr "小胁褟蟹邪薪薪褘械 袨斜褋褍卸写械薪懈褟"
 
 msgid "Related Jobs"
-msgstr "小胁褟蟹邪薪薪褘械 蟹邪写邪褔懈"
+msgstr "小胁褟蟹邪薪薪褘械 袟邪写邪薪懈褟"
 
 msgid "Related Merge Requests"
 msgstr "小胁褟蟹邪薪薪褘械 袟邪锌褉芯褋褘 薪邪 小谢懈褟薪懈械"
@@ -2620,6 +3570,9 @@ msgstr "小胁褟蟹邪薪薪褘械 袟邪锌褉芯褋褘 薪邪 小谢懈褟薪懈械"
 msgid "Related Merged Requests"
 msgstr "小胁褟蟹邪薪薪褘械 袙谢懈褌褘械 袟邪锌褉芯褋褘"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "袧邪锌芯屑薪懈褌褜 锌芯蟹卸械"
 
@@ -2635,9 +3588,24 @@ msgstr "校写邪谢懈褌褜 锌褉芯械泻褌"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr "袪械锌芯蟹懈褌芯褉懈泄"
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "袟邪锌褉芯褋 写芯褋褌褍锌邪"
 
@@ -2650,11 +3618,18 @@ msgstr "小斜褉芯褋懈褌褜 泻谢褞褔 写芯褋褌褍锌邪 锌褉芯胁械褉泻懈 褉邪斜芯褌芯褋
 msgid "Reset runners registration token"
 msgstr "小斜褉芯褋懈褌褜 泻谢褞褔 褉械谐懈褋褌褉邪褑懈懈 Gitlab Runners"
 
+msgid "Resolve discussion"
+msgstr "袟邪泻褉褘褌褜 写懈褋泻褍褋褋懈褞"
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
 msgid "Revert this commit"
 msgstr "袨褌屑械薪懈褌褜 褝褌芯 泻芯屑屑懈褌"
@@ -2662,6 +3637,36 @@ msgstr "袨褌屑械薪懈褌褜 褝褌芯 泻芯屑屑懈褌"
 msgid "Revert this merge request"
 msgstr "袨褌屑械薪懈褌褜 褝褌芯褌 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "SSH 袣谢褞褔懈"
 
@@ -2677,6 +3682,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "袪邪褋锌懈褋邪薪懈械 薪芯胁芯泄 褋斜芯褉芯褔薪芯泄 谢懈薪懈懈"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr "袪邪褋锌懈褋邪薪懈褟"
 
@@ -2686,6 +3694,9 @@ msgstr "袩谢邪薪懈褉芯胁邪薪懈械 小斜芯褉芯褔薪褘褏 袥懈薪懈泄"
 msgid "Scoped issue boards"
 msgstr "孝械屑邪褌懈褔械褋泻懈械 写芯褋泻懈 芯斜褋褍卸写械薪懈泄"
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "袧邪泄褌懈 胁械褌泻懈 懈 褌械谐懈"
 
@@ -2707,12 +3718,18 @@ msgstr "小械泻褍薪写 蟹邪写械褉卸泻懈 屑械卸写褍 锌芯锌褘褌泻邪屑懈 写芯褋褌褍锌
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "袙褘斜褉邪褌褜 褎芯褉屑邪褌 邪褉褏懈胁邪"
 
 msgid "Select a timezone"
 msgstr "袙褘斜芯褉 胁褉械屑械薪薪芯泄 蟹芯薪褘"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2725,6 +3742,9 @@ msgstr "袙褘斜芯褉 褑械谢械胁芯泄 胁械褌泻懈"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr "小械薪褌."
 
@@ -2737,17 +3757,35 @@ msgstr ""
 msgid "Service Templates"
 msgstr "楔邪斜谢芯薪褘 小谢褍卸斜"
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "校褋褌邪薪芯胁懈褌械 锌邪褉芯谢褜 胁 褋胁芯械屑 邪泻泻邪褍薪褌械, 褔褌芯斜褘 芯褌锌褉邪胁谢褟褌褜 懈谢懈 锌芯谢褍褔邪褌褜 泻芯写 褔械褉械蟹 %{protocol}."
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "袧邪褋褌褉芯泄泻邪 Koding"
 
-msgid "Set up auto deploy"
-msgstr "袧邪褋褌褉芯泄泻邪 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 褉邪蟹胁械褉褌褘胁邪薪懈褟"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "褍褋褌邪薪芯胁懈褌械 锌邪褉芯谢褜"
@@ -2755,6 +3793,12 @@ msgstr "褍褋褌邪薪芯胁懈褌械 锌邪褉芯谢褜"
 msgid "Settings"
 msgstr "袧邪褋褌褉芯泄泻懈"
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2764,6 +3808,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr "袩芯泻邪蟹邪褌褜 褉芯写懈褌械谢褜褋泻懈械 褋褌褉邪薪懈褑褘"
 
@@ -2773,8 +3820,9 @@ msgstr "袩芯泻邪蟹邪褌褜 褉芯写懈褌械谢褜褋泻懈械 锌芯写谐褉褍锌锌褘"
 msgid "Showing %d event"
 msgid_plural "Showing %d events"
 msgstr[0] "袩芯泻邪蟹邪薪芯 %d 褋芯斜褘褌懈械"
-msgstr[1] "袩芯泻邪蟹邪薪芯 %d 褋芯斜褘褌懈泄"
+msgstr[1] "袩芯泻邪蟹邪薪芯 %d 褋芯斜褘褌懈褟"
 msgstr[2] "袩芯泻邪蟹邪薪芯 %d 褋芯斜褘褌懈泄"
+msgstr[3] "袩芯泻邪蟹邪薪芯 %d 褋芯斜褘褌懈泄"
 
 msgid "Sidebar|Change weight"
 msgstr "袠蟹屑械薪懈褌褜 胁械褋"
@@ -2788,6 +3836,18 @@ msgstr "袨褌褋褍褌褋褌胁褍械褌"
 msgid "Sidebar|Weight"
 msgstr "袙械褋"
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr "小薪懈锌锌械褌褘"
 
@@ -2795,15 +3855,15 @@ msgid "Something went wrong on our end"
 msgstr ""
 
 msgid "Something went wrong on our end."
-msgstr "校 薪邪褋 褔褌芯-褌芯 锌芯褕谢芯 薪械 褌邪泻."
+msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
-msgstr "效褌芯-褌芯 锌芯褕谢芯 薪械 褌邪泻 锌褉懈 锌芯锌褘褌泻械 懈蟹屑械薪械薪懈褟 褋芯褋褌芯褟薪懈褟 斜谢芯泻懈褉芯胁泻懈 ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
+msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2917,6 +3977,9 @@ msgstr "袙械褋"
 msgid "Source"
 msgstr "袠褋褌芯褔薪懈泻"
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "袠褋褏芯写薪褘泄 泻芯写"
 
@@ -2926,12 +3989,21 @@ msgstr "袠褋褏芯写薪褘泄 褌械泻褋褌 薪械写芯褋褌褍锌械薪"
 msgid "Spam Logs"
 msgstr "小锌邪屑 袥芯谐懈"
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "校泻邪卸懈褌械 褋谢械写褍褞褖懈泄 URL 胁芯 胁褉械屑褟 薪邪褋褌褉芯泄泻懈 Gitlab Runner:"
 
 msgid "StarProject|Star"
 msgstr "袨褌屑械褌懈褌褜"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr "袨褌屑械褔械薪薪褘械 锌褉芯械泻褌褘"
 
@@ -2941,6 +4013,15 @@ msgstr "袧邪褔邪褌褜 %{new_merge_request} 褋 褝褌懈褏 懈蟹屑械薪械薪懈泄"
 msgid "Start the Runner!"
 msgstr "袟邪锌褍褋褌懈褌褜 GitLab Runner!"
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr "袨褋褌邪薪芯胁谢械薪"
 
@@ -2953,14 +4034,21 @@ msgstr "袩芯写谐褉褍锌锌褘"
 msgid "Switch branch/tag"
 msgstr "袩械褉械泻谢褞褔懈褌褜 胁械褌泻邪/褌械谐"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr "小懈褋褌械屑薪褘械 袨斜褉邪斜芯褌褔懈泻懈"
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "孝械谐"
-msgstr[1] "孝械谐懈"
-msgstr[2] "孝械谐懈"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "Tags"
 msgstr "孝械谐懈"
@@ -3037,6 +4125,9 @@ msgstr "蟹邪褖懈褖械薪薪褘泄"
 msgid "Target Branch"
 msgstr "袙械褌泻邪"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr "袣芯屑邪薪写邪"
 
@@ -3052,15 +4143,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "小胁褟蟹褜 褋 芯褌胁械褌胁谢械薪懈械屑 褍写邪谢械薪邪."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "小褌邪写懈褟 芯斜褋褍卸写械薪懈褟 锌芯泻邪蟹褘胁邪械褌 胁褉械屑褟, 泻芯褌芯褉芯械 锌芯褌褉械斜褍械褌褋褟 褋 屑芯屑械薪褌邪 褋芯蟹写邪薪懈褟 芯斜褋褍卸写械薪懈褟 写芯 薪邪蟹薪邪褔械薪懈褟 芯斜褋褍卸写械薪懈褞 胁械褏懈, 懈谢懈 写芯斜邪胁谢械薪懈褟 芯斜褋褍卸写械薪懈褟 薪邪 胁邪褕褍 写芯褋泻褍 蟹邪写邪褔. 袧邪褔薪懈褌械 褋芯蟹写邪胁邪褌褜 芯斜褋褍卸写械薪懈褟, 褔褌芯斜褘 褍胁懈写械褌褜 褋胁械写械薪懈褟 写谢褟 褝褌芯泄 褋褌邪写懈懈."
 
@@ -3073,12 +4173,18 @@ msgstr "袣芯谢懈褔械褋褌胁芯 锌芯锌褘褌芯泻, 泻芯褌芯褉褘械 GitLab 斜褍写械褌 锌
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr "袣芯谢懈褔械褋褌胁芯 褋斜芯械胁, 锌芯褋谢械 泻芯褌芯褉芯谐芯 Gitlab 锌芯谢薪芯褋褌褜褞 锌褉械泻褉邪褌懈褌 写芯褋褌褍锌 泻 褏褉邪薪懈谢懈褖褍. 袠蟹屑械薪械薪懈械 褋褔褢褌褔懈泻邪 \"泻芯谢懈褔械褋褌胁芯 褋斜芯械胁\" 屑芯卸械褌 斜褘褌褜 锌褉芯懈蟹胁械写械薪芯 褔械褉械蟹 邪写屑懈薪懈褋褌褉邪褌懈胁薪褘泄 懈薪褌械褉褎械泄褋: %{link_to_health_page} 懈谢懈 锌褉懈 锌芯屑芯褖懈 API %{api_documentation_link}."
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "袩褉芯懈蟹胁芯写褋褌胁械薪薪褘泄 褝褌邪锌 锌芯泻邪蟹褘胁邪械褌 芯斜褖械械 胁褉械屑褟 屑械卸写褍 褋芯蟹写邪薪懈械屑 芯斜褋褍卸写械薪懈褟 懈 褉邪蟹胁械褉褌褘胁邪薪懈械屑 泻芯写邪 胁 锌褉芯写褍泻褌懈胁薪芯泄 褋褉械写械. 袛邪薪薪褘械 斜褍写褍褌 邪胁褌芯屑邪褌懈褔械褋泻懈 写芯斜邪胁谢械薪褘 锌芯褋谢械 锌芯谢薪芯谐芯 蟹邪胁械褉褕械薪懈褟 懈写械懈."
 
@@ -3091,9 +4197,18 @@ msgstr "袛芯褋褌褍锌 泻 锌褉芯械泻褌褍 胁芯蟹屑芯卸械薪 斜械蟹 泻邪泻芯泄-谢懈斜
 msgid "The repository for this project does not exist."
 msgstr "袪械锌芯蟹懈褌芯褉懈泄 写谢褟 褝褌芯谐芯 锌褉芯械泻褌邪 薪械 褋褍褖械褋褌胁褍械褌."
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 "协褌邪锌 锌芯褋褌邪薪芯胁泻懈 锌芯泻邪蟹褘胁邪械褌 胁褉械屑褟 屑械卸写褍 褋谢懈褟薪懈械屑 \"MR\" 懈 褉邪蟹胁械褉褌褘胁邪薪懈械屑 泻芯写邪 胁 锌褉芯懈蟹胁芯写褋褌胁械薪薪芯泄 褋褉械写械. 袛邪薪薪褘械 斜褍写褍褌 邪胁褌芯屑邪褌懈褔械褋泻懈 写芯斜邪胁谢械薪褘 锌芯褋谢械 褉邪蟹胁械褉褌褘胁邪薪懈褟 胁 锌褉芯懈蟹胁芯写褋褌胁械 锌械褉胁褘泄 褉邪蟹."
 
@@ -3124,6 +4239,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr "袩褉芯斜谢械屑褘 褋 写芯褋褌褍锌芯屑 泻 Git 褏褉邪薪懈谢懈褖褍: "
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3155,7 +4273,7 @@ msgid "This is the author's first Merge Request to this project."
 msgstr "协褌芯 锌械褉胁褘泄 袟邪锌褉芯褋 薪邪 褋谢懈褟薪懈械 芯褌 邪胁褌芯褉邪 胁 褝褌芯褌 锌褉芯械泻褌."
 
 msgid "This issue is confidential"
-msgstr ""
+msgstr "协褌芯 芯斜褋褍卸写械薪懈械 褟胁谢褟械褌褋褟 泻芯薪褎懈写械薪褑懈邪谢褜薪褘屑"
 
 msgid "This issue is confidential and locked."
 msgstr "协褌芯 芯斜褋褍卸写械薪懈械 泻芯薪褎懈写械薪褑懈邪谢褜薪芯 懈 蟹邪斜谢芯泻懈褉芯胁邪薪芯."
@@ -3167,7 +4285,7 @@ msgid "This job depends on a user to trigger its process. Often they are used to
 msgstr ""
 
 msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
-msgstr ""
+msgstr "协褌芯 蟹邪写邪薪懈械 蟹邪胁懈褋懈褌 芯褌 褉邪斜芯褔懈褏 蟹邪写邪薪懈泄 薪邪 胁械褉褏薪械屑 褍褉芯胁薪械, 泻芯褌芯褉褘械 写芯谢卸薪褘 蟹邪胁械褉褕懈褌褜褋褟 褍褋锌械褕薪芯, 褔褌芯斜褘 褝褌芯 蟹邪写邪薪懈械 斜褘谢芯 蟹邪锌褍褖械薪芯"
 
 msgid "This job has not been triggered yet"
 msgstr ""
@@ -3187,12 +4305,18 @@ msgstr "协褌芯 芯蟹薪邪褔邪械褌, 褔褌芯 胁褘 薪械 屑芯卸械褌械 芯褌锌褉邪胁懈褌
 msgid "This merge request is locked."
 msgstr "袟邪锌褉芯褋 薪邪 褋谢懈褟薪懈械 蟹邪斜谢芯泻懈褉芯胁邪薪."
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr "协褌懈 褝谢械泻褌褉芯薪薪褘械 锌懈褋褜屑邪 邪胁褌芯屑邪褌懈褔械褋泻懈 锌褉械芯斜褉邪蟹褍褞褌褋褟 胁 芯斜褋褍卸写械薪懈褟 (泻芯屑屑械薪褌邪褉懈懈 褉邪褋褋褘谢邪褞褌褋褟 泻邪泻 胁械褌胁褜 芯斜褋褍卸写械薪懈褟 胁 锌芯褔褌械), 锌械褉械褔懈褋谢械薪薪褘械 蟹写械褋褜."
 
@@ -3205,6 +4329,12 @@ msgstr "袙褉械屑褟 写芯 薪邪褔邪谢邪 褉邪斜芯褌褘 薪邪写 芯斜褋褍卸写械薪懈械屑"
 msgid "Time between merge request creation and merge/close"
 msgstr "袙褉械屑褟 屑械卸写褍 褋芯蟹写邪薪懈械屑 蟹邪锌褉芯褋邪 褋谢懈褟薪懈褟 懈 褋谢懈褟薪懈械屑 / 蟹邪泻褉褘褌懈械屑"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3348,19 +4478,57 @@ msgid_plural "Time|hrs"
 msgstr[0] "褔"
 msgstr[1] "褔"
 msgstr[2] "褔"
+msgstr[3] "褔"
 
 msgid "Time|min"
 msgid_plural "Time|mins"
 msgstr[0] "屑懈薪"
 msgstr[1] "屑懈薪"
 msgstr[2] "屑懈薪"
+msgstr[3] "屑懈薪"
 
 msgid "Time|s"
 msgstr "褋"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr "袟邪谐芯谢芯胁芯泻"
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3376,21 +4544,18 @@ msgstr ""
 msgid "Total Time"
 msgstr "袨斜褖械械 胁褉械屑褟"
 
-msgid "Total issue time spent"
-msgstr "袨斜褖械械 胁褉械屑褟, 蟹邪褌褉邪褔械薪薪芯械 薪邪 芯斜褋褍卸写械薪懈械"
-
 msgid "Total test time for all commits/merges"
 msgstr "袨斜褖械械 胁褉械屑褟 褌械褋褌懈褉芯胁邪薪懈褟 褎懈泻褋邪褑懈泄/褋谢懈褟薪懈泄"
 
+msgid "Total: %{total}"
+msgstr ""
+
 msgid "Track activity with Contribution Analytics."
 msgstr "袨褌褋谢械卸懈胁邪褌褜 邪泻褌懈胁薪芯褋褌褜 褋 锌芯屑芯褖褜褞 袗薪邪谢懈褌懈泻懈 校褔邪褋褌薪懈泻芯胁."
 
 msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr "小谢械写懈褌械 蟹邪 芯斜褋褍卸写械薪懈褟屑懈, 褋谐褉褍锌锌懈褉芯胁邪薪薪褘屑懈 锌芯 褌械屑邪屑, 褋褉邪蟹褍 懈蟹 薪械褋泻芯谢褜泻懈褏 锌褉芯械泻褌芯胁 懈 褝褌邪锌芯胁"
 
-msgid "Total: %{total}"
-msgstr ""
-
 msgid "Track time with quick actions"
 msgstr ""
 
@@ -3400,24 +4565,18 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr "袙泻谢褞褔懈褌褜 小谢褍卸斜褍 袩芯写写械褉卸泻懈"
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr "袪邪蟹斜谢芯泻懈褉芯胁邪褌褜"
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
-
 msgid "Unlocked"
 msgstr "袪邪蟹斜谢芯泻懈褉芯胁邪薪芯"
 
+msgid "Unresolve discussion"
+msgstr "袩械褉械芯褌泻褉褘褌褜 写懈褋泻褍褋褋懈褞"
+
 msgid "Unstar"
 msgstr "小薪褟褌褜 芯褌屑械褌泻褍"
 
@@ -3451,6 +4610,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "泻谢懈泻薪懈褌械 写谢褟 蟹邪谐褉褍蟹泻懈"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr "袠褋锌芯谢褜蟹褍泄褌械 小谢褍卸斜褍 锌芯写写械褉卸泻懈 写谢褟 褋胁褟蟹懈 褋 胁邪褕懈屑懈 锌芯谢褜蟹芯胁邪褌械谢褟屑懈 (薪邪锌褉懈屑械褉, 写谢褟 芯褋褍褖械褋褌胁谢械薪懈褟 锌芯写写械褉卸泻懈 泻谢懈械薪褌芯胁) 褔械褉械蟹 褝谢械泻褌褉芯薪薪褍褞 锌芯褔褌褍 薪械锌芯褋褉械写褋褌胁械薪薪芯 胁 GitLab"
 
@@ -3460,21 +4625,51 @@ msgstr "袠褋锌芯谢褜蟹褍泄褌械 褋谢械写褍褞褖懈泄 褌芯泻械薪 褉械谐懈褋褌褉邪褑
 msgid "Use your global notification setting"
 msgstr "袠褋锌芯谢褜蟹褍褞褌褋褟 谐谢芯斜邪谢褜薪褘泄 薪邪褋褌褉芯泄泻懈 褍胁械写芯屑谢械薪懈泄"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr "袩褉芯褋屑芯褌褉 褎邪泄谢邪 @ "
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "袩褉芯褋屑芯褌褉械褌褜 芯褌泻褉褘褌褘泄 蟹邪锌褉芯褋 薪邪 褋谢懈褟薪懈械"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr "袩褉芯褋屑芯褌褉 蟹邪屑械薪褢薪薪芯谐芯 褎邪泄谢邪 @ "
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "袨谐褉邪薪懈褔械薪薪褘泄"
 
@@ -3499,12 +4694,21 @@ msgstr "袠薪褎芯褉屑邪褑懈褟 锌芯 褝褌邪锌褍 芯褌褋褍褌褋褌胁褍械褌."
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr "袦褘 褏芯褌懈屑 斜褘褌褜 褍胁械褉械薪褘, 褔褌芯 褝褌芯 胁褘, 锌芯卸邪谢褍泄褋褌邪, 锌芯写褌胁械褉写懈褌械, 褔褌芯 胁褘 薪械 褉芯斜芯褌."
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr "袙械斜-芯斜褉邪斜芯褌褔懈泻懈 锌芯蟹胁芯谢褟褞褌 胁邪屑 胁褘蟹褘胁邪褌褜 邪写褉械褋 URL 械褋谢懈, 薪邪锌褉懈屑械褉, 芯褌锌褉邪胁谢械薪 薪芯胁褘泄 泻芯写 懈谢懈 褋芯蟹写邪薪芯 薪芯胁芯械 芯斜褋褍卸写械薪懈械. 袙褘 屑芯卸械褌械 薪邪褋褌褉芯懈褌褜 胁械斜-芯斜褉邪斜芯褌褔懈泻懈 褌邪泻, 褔褌芯斜褘 芯薪懈 褉械邪谐懈褉芯胁邪谢懈 薪邪 芯锌褉械写械谢褢薪薪褘械 褋芯斜褘褌懈褟, 褌邪泻懈械 泻邪泻 芯褌锌褉邪胁泻懈 泻芯写邪, 芯斜褋褍卸写械薪懈褟 懈谢懈 蟹邪锌褉芯褋褘 薪邪 褋谢懈褟薪懈械. 袚褉褍锌锌芯胁褘械 胁械斜-芯斜褉邪斜芯褌褔懈泻懈 锌褉懈屑械薪褟褞褌褋褟 泻芯 胁褋械屑 锌褉芯械泻褌邪屑 胁 谐褉褍锌锌械 懈 锌芯蟹胁芯谢褟褞褌 胁邪屑 褋褌邪薪写邪褉褌懈蟹芯胁邪褌褜 褎褍薪泻褑懈芯薪邪谢褜薪芯褋褌褜 胁械斜-芯斜褉邪斜芯褌褔懈泻芯胁 写谢褟 胁褋械泄 胁邪褕械泄 谐褉褍锌锌褘."
 
 msgid "Weight"
 msgstr "袙械褋"
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr "Wiki"
 
@@ -3536,13 +4740,13 @@ msgid "WikiHistoricalPage|This is an old version of this page."
 msgstr "协褌芯 褍褋褌邪褉械胁褕邪褟 胁械褉褋懈褟 褝褌芯泄 褋褌褉邪薪懈褑褘."
 
 msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
-msgstr "袙褘 屑芯卸械褌械 褍胁懈写械褌褜 %{most_recent_link} 谢懈斜芯 锌褉芯褋屑芯褌褉械褌褜 %{history_link}."
+msgstr "袙褘 屑芯卸械褌械 褍胁懈写械褌褜 %{most_recent_link}, 谢懈斜芯 锌褉芯褋屑芯褌褉械褌褜 %{history_link}."
 
 msgid "WikiHistoricalPage|history"
-msgstr "懈褋褌芯褉懈褟"
+msgstr "懈褋褌芯褉懈褞 懈蟹屑械薪械薪懈泄"
 
 msgid "WikiHistoricalPage|most recent version"
-msgstr "锌芯褋谢械写薪褟褟 胁械褉褋懈褟"
+msgstr "锌芯褋谢械写薪褞褞 胁械褉褋懈褞"
 
 msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
 msgstr "袛芯锌芯谢薪懈褌械谢褜薪褘械 锌褉懈屑械褉褘 薪邪褏芯写褟褌褋褟 胁 %{docs_link}"
@@ -3619,22 +4823,34 @@ msgstr "小 邪薪邪谢懈褌懈泻芯泄 褍褔邪褋褌薪懈泻芯胁 胁褘 屑芯卸械褌械 懈蟹褍褔
 msgid "Withdraw Access Request"
 msgstr "袨褌屑械薪懈褌褜 蟹邪锌褉芯褋 写芯褋褌褍锌邪"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "袙褘 褋芯斜懈褉邪械褌械褋褜 褍写邪谢懈褌褜 %{group_name}. 校写邪谢械薪薪褘械 谐褉褍锌锌褘 袧袝 袦袨袚校孝 斜褘褌褜 胁芯褋褋褌邪薪芯胁谢械薪褘! 袙褘 袗袘小袨袥挟孝袧袨 褍胁械褉械薪褘?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "袙褘 褏芯褌懈褌械 褍写邪谢懈褌褜 %{project_name_with_namespace}. 校写邪谢械薪薪褘泄 锌褉芯械泻褌 袧袝 袦袨袞袝孝 斜褘褌褜 胁芯褋褋褌邪薪芯胁谢械薪! 袙褘 袗袘小袨袥挟孝袧袨 褍胁械褉械薪褘?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "袙褘 褋芯斜懈褉邪械褌械褋褜 褍写邪谢懈褌褜 褋胁褟蟹褜 芯褌胁械褌胁谢械薪懈褟 褋 懈褋褏芯写薪褘屑 锌褉芯械泻褌芯屑 %{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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3652,12 +4868,24 @@ msgstr "袙褘 薪械 屑芯卸械褌械 蟹邪锌懈褋褘胁邪褌褜 薪邪 锌芯写褔懈薪械薪薪褘械
 msgid "You cannot write to this read-only GitLab instance."
 msgstr "袙褘 薪械 屑芯卸械褌械 蟹邪锌懈褋褘胁邪褌褜 薪邪 褝褌芯褌 褝泻蟹械屑锌谢褟褉 \"褌芯谢褜泻芯 写谢褟 褔褌械薪懈褟\" 泻谢邪褋褌械褉邪 GitLab."
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "袙褘 写芯褋褌懈谐谢懈 芯谐褉邪薪懈褔械薪懈褟 胁 胁邪褕械屑 锌褉芯械泻褌械"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "袧械芯斜褏芯写懈屑芯 胁芯泄褌懈, 褔褌芯斜褘 芯褑械薪懈褌褜 锌褉芯械泻褌"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "袙邪屑 薪褍卸薪芯 褉邪蟹褉械褕械薪懈械."
 
@@ -3688,9 +4916,30 @@ msgstr "袙褘 薪械 褋屑芯卸械褌械 褉邪斜芯褌邪褌褜 褋 锌褉芯械泻褌芯屑 褔械褉械蟹
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr "袙邪褕 泻芯屑屑械薪褌邪褉懈泄 薪械 斜褍写械褌 胁懈写械薪 胁褋械屑."
 
@@ -3703,6 +4952,16 @@ msgstr "袙邪褕械 懈屑褟"
 msgid "Your projects"
 msgstr "袙邪褕懈 锌褉芯械泻褌褘"
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3712,13 +4971,31 @@ msgstr "懈屑褟 胁械褌胁懈"
 msgid "by"
 msgstr "锌芯"
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3730,7 +5007,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3745,45 +5022,136 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
-msgstr "泻芯屑屑懈褌"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
+msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
 msgid_plural "days"
-msgstr[0] "写械薪褜"
-msgstr[1] "写薪械泄"
-msgstr[2] "写薪械泄"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
 
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
+msgstr[3] ""
 
-msgid "mrWidget|Cancel automatic merge"
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
 msgstr ""
 
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr "袨褌屑械薪懈褌褜 邪胁褌芯屑邪褌懈褔械褋泻芯械 褋谢懈褟薪懈械"
+
 msgid "mrWidget|Check out branch"
 msgstr ""
 
@@ -3805,15 +5173,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3847,11 +5227,14 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
 msgid "mrWidget|Resolve conflicts"
-msgstr ""
+msgstr "袪邪蟹褉械褕懈褌褜 泻芯薪褎谢懈泻褌褘"
 
 msgid "mrWidget|Revert"
 msgstr ""
@@ -3895,12 +5278,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3921,9 +5310,10 @@ msgstr ""
 
 msgid "parent"
 msgid_plural "parents"
-msgstr[0] "懈褋褌芯褔薪懈泻"
-msgstr[1] "懈褋褌芯褔薪懈泻懈"
-msgstr[2] "懈褋褌芯褔薪懈泻懈"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "password"
 msgstr "锌邪褉芯谢褜"
@@ -3931,6 +5321,9 @@ msgstr "锌邪褉芯谢褜"
 msgid "personal access token"
 msgstr "褌芯泻械薪 写谢褟 锌械褉褋芯薪邪谢褜薪芯谐芯 写芯褋褌褍锌邪"
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3940,6 +5333,9 @@ msgstr "懈褋褏芯写薪褘泄 褌械泻褋褌"
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr "褔褌芯斜褘 锌芯屑芯褔褜 褍褔邪褋褌薪懈泻邪屑 胁蟹邪懈屑芯写械泄褋褌胁芯胁邪褌褜 褝褎褎械泻褌懈胁薪械械!"
 
@@ -3949,3 +5345,6 @@ msgstr "懈屑褟 锌芯谢褜蟹芯胁邪褌械谢褟"
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po
new file mode 100644
index 0000000000000000000000000000000000000000..6a025dfbae5ad25f14ecb8c3fa797ad48343fcdc
--- /dev/null
+++ b/locale/tr_TR/gitlab.po
@@ -0,0 +1,5288 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:35-0400\n"
+"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
+"Language-Team: Turkish\n"
+"Language: tr_TR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.com\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Language: tr\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+
+msgid " and"
+msgstr " ve"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] "%{count} kat谋l谋mc谋"
+msgstr[1] "%{count} kat谋l谋mc谋"
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{storage_name}: failed storage access attempt on host:"
+msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "(checkout the %{link} for information on how to install it)."
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr "+ %{moreCount} daha fazla"
+
+msgid "- show less"
+msgstr "- daha az g枚ster"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1st contribution!"
+msgstr "陌lk katk谋!"
+
+msgid "2FA enabled"
+msgstr ""
+
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr "K枚t眉ye Kullan谋m Raporlar谋"
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr "Eri艧im anahtarlar谋"
+
+msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr ""
+
+msgid "Account"
+msgstr "Hesap"
+
+msgid "Account and limit settings"
+msgstr ""
+
+msgid "Active"
+msgstr "Etkin"
+
+msgid "Activity"
+msgstr "Etkinlik"
+
+msgid "Add"
+msgstr "Ekle"
+
+msgid "Add Changelog"
+msgstr "De臒i艧iklik Bilgisi Ekle"
+
+msgid "Add Contribution guide"
+msgstr "Katk谋 k谋lavuzu ekle"
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add License"
+msgstr "Lisans Ekle"
+
+msgid "Add Readme"
+msgstr ""
+
+msgid "Add new directory"
+msgstr "Yeni dizin ekle"
+
+msgid "Add todo"
+msgstr "Yap谋lacaklara Ekle"
+
+msgid "AdminArea|Stop all jobs"
+msgstr "T眉m i艧leri durdur"
+
+msgid "AdminArea|Stop all jobs?"
+msgstr "T眉m i艧leri durdur?"
+
+msgid "AdminArea|Stop jobs"
+msgstr "陌艧leri durdur"
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminHealthPageLink|health page"
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "Advanced"
+msgstr "Geli艧mi艧"
+
+msgid "Advanced settings"
+msgstr "Geli艧mi艧 ayarlar"
+
+msgid "All"
+msgstr "T眉m眉"
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr "Kubernetes k眉melerini eklemeye ve y枚netmenize olanak tan谋r."
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr "Markdown 枚n izlemesi y眉klenirken hata olu艧tu"
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr "Projeler y眉klenirken bir hata olu艧tu"
+
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr "Dosya isimleri y眉klenirken bir hata olu艧tu"
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
+msgid "An error occurred while rendering KaTeX"
+msgstr "KaTeX'i i艧lerken bir hata olu艧tu"
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr "脰nizleme yay谋n谋 iletisi olu艧turulurken bir hata olu艧tu"
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr "De臒i艧iklikler y眉klenirken bir hata olu艧tu"
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr "Kullan谋c谋 ad谋 do臒rulan谋rken bir hata olu艧tu"
+
+msgid "An error occurred. Please try again."
+msgstr "Bir hata olu艧tu. L眉tfen tekrar deneyin."
+
+msgid "Any Label"
+msgstr ""
+
+msgid "Appearance"
+msgstr "G枚r眉n眉m"
+
+msgid "Applications"
+msgstr "Uygulamalar"
+
+msgid "Apr"
+msgstr "Nis"
+
+msgid "April"
+msgstr "Nisan"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Ar艧ivlenmi艧 proje! Sadece okuma yap谋labilir"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr "Emin misiniz?"
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr "#FF0000 gibi 枚zel renk ata"
+
+msgid "Assign labels"
+msgstr "Etiket tan谋mla"
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign to"
+msgstr "Ata"
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
+msgid "Assignee"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "S眉r眉kleyip b谋rakarak bir dosya ekle veya %{upload_link}"
+
+msgid "Aug"
+msgstr "A臒ustos"
+
+msgid "August"
+msgstr "A臒ustos"
+
+msgid "Authentication Log"
+msgstr "Kimlik Do臒rulama G眉nl眉臒眉"
+
+msgid "Author"
+msgstr "Yazar"
+
+msgid "Authors: %{authors}"
+msgstr "Yazarlar: %{authors}"
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
+msgstr ""
+
+msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps (Beta)"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr ""
+
+msgid "Available"
+msgstr "Kullan谋labilir"
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr "Avatar kald谋r谋lacak. Emin misiniz?"
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Downgrade"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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 "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project master or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn鈥檛 been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You鈥檙e about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Certificate fingerprint"
+msgstr ""
+
+msgid "Change Weight"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changelog"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "Check interval"
+msgstr ""
+
+msgid "Checking %{text} availability鈥�"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Choose File ..."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose file..."
+msgstr ""
+
+msgid "Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
+msgid "Choose which shards you wish to synchronize to this secondary node."
+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 "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occured while saving variables"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "CircuitBreakerApiLink|circuitbreaker api"
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add an existing Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
+msgstr ""
+
+msgid "ClusterIntegration|Applications"
+msgstr ""
+
+msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Token"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
+msgstr ""
+
+msgid "ClusterIntegration|Create on GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project ID"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
+msgid "ClusterIntegration|Install"
+msgstr ""
+
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Installed"
+msgstr ""
+
+msgid "ClusterIntegration|Installing"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Integration status"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about environments"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
+msgstr ""
+
+msgid "ClusterIntegration|More information"
+msgstr ""
+
+msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
+msgstr ""
+
+msgid "ClusterIntegration|Note:"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Project ID"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Security"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|See machine types"
+msgstr ""
+
+msgid "ClusterIntegration|See your projects"
+msgstr ""
+
+msgid "ClusterIntegration|See zones"
+msgstr ""
+
+msgid "ClusterIntegration|Service token"
+msgstr ""
+
+msgid "ClusterIntegration|Show"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes Cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Token"
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|check the pricing here"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|help page"
+msgstr ""
+
+msgid "ClusterIntegration|installing applications"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|properly configured"
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|Commit: %{commitText}"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Created"
+msgstr ""
+
+msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
+msgstr ""
+
+msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
+msgstr ""
+
+msgid "ContainerRegistry|How to use the Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Learn more about"
+msgstr ""
+
+msgid "ContainerRegistry|No tags in Container Registry for this container image."
+msgstr ""
+
+msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgstr ""
+
+msgid "ContainerRegistry|Size"
+msgstr ""
+
+msgid "ContainerRegistry|Tag"
+msgstr ""
+
+msgid "ContainerRegistry|Tag ID"
+msgstr ""
+
+msgid "ContainerRegistry|Use different image names"
+msgstr ""
+
+msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution guide"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
+msgstr ""
+
+msgid "ContributorsPage|Building repository graph."
+msgstr ""
+
+msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Control the maximum concurrency of repository backfill for this secondary node"
+msgstr ""
+
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy branch name to clipboard"
+msgstr ""
+
+msgid "Copy command to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Copy reference to clipboard"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "CreateNewFork|Fork"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Current node"
+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 "Customize colors"
+msgstr ""
+
+msgid "Cycle Analytics"
+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 "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discover GitLab Geo."
+msgstr ""
+
+msgid "Dismiss Cycle Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+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 "Downvotes"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|Open"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now."
+msgstr ""
+
+msgid "Epic will be removed! Are you sure?"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error fetching contributors data."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching usage ping data."
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error updating status for all todos."
+msgstr ""
+
+msgid "Error updating todo status."
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+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 "Expand"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fields on this page are now uneditable, you can configure"
+msgstr ""
+
+msgid "File name"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "From %{provider_title}"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Database replication lag:"
+msgstr ""
+
+msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version:"
+msgstr ""
+
+msgid "GeoNodes|Health status:"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor:"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary:"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|Local Attachments:"
+msgstr ""
+
+msgid "GeoNodes|Local LFS objects:"
+msgstr ""
+
+msgid "GeoNodes|Local job artifacts:"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Out of sync"
+msgstr ""
+
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL:"
+msgstr ""
+
+msgid "GeoNodes|Replication slots:"
+msgstr ""
+
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
+msgid "GeoNodes|Repositories:"
+msgstr ""
+
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Selective"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config:"
+msgstr ""
+
+msgid "GeoNodes|Sync settings:"
+msgstr ""
+
+msgid "GeoNodes|Synced"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
+msgid "GeoNodes|Wikis:"
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|File sync capacity"
+msgstr ""
+
+msgid "Geo|Groups to synchronize"
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Repository sync capacity"
+msgstr ""
+
+msgid "Geo|Select groups to replicate."
+msgstr ""
+
+msgid "Geo|Shards to synchronize"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git storage health information has been reset"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
+msgid "GitLab Runner section"
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "GoToYourFork|Fork"
+msgstr ""
+
+msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Share with group lock"
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Filter by name..."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|Sorry, no groups or projects matched your search"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header message"
+msgstr ""
+
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "History"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Install a Runner compatible with GitLab CI"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Issue board focus mode"
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Koding"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration was not removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes configured"
+msgstr ""
+
+msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark done"
+msgstr ""
+
+msgid "Maximum git storage failures"
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Kubernetes Cluster"
+msgstr ""
+
+msgid "New Kubernetes cluster"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "No Label"
+msgstr ""
+
+msgid "No assignee"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No labels created yet."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not allowed to merge"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+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 "Notifications"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "Number of access attempts"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pagination|Last 禄"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|芦 First"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline quota"
+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 "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project avatar in repository: %{link}"
+msgstr ""
+
+msgid "Project details"
+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 "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Masters"
+msgstr ""
+
+msgid "ProjectCreationLevel|Masters"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+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 "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
+msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Readme"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "RefSwitcher|Branches"
+msgstr ""
+
+msgid "RefSwitcher|Tags"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Registry"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Reset git storage health information"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Seconds before reseting failure information"
+msgstr ""
+
+msgid "Seconds to wait for a storage access attempt"
+msgstr ""
+
+msgid "Secret variables"
+msgstr ""
+
+msgid "Security report"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select assignee"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Selective synchronization"
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up Koding"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|No"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while fetching Dependency Scanning."
+msgstr ""
+
+msgid "Something went wrong while fetching SAST."
+msgstr ""
+
+msgid "Something went wrong while fetching the projects."
+msgstr ""
+
+msgid "Something went wrong while fetching the registry list."
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Milestone"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag."
+msgstr ""
+
+msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here..."
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The number of attempts GitLab will make to access a storage."
+msgstr ""
+
+msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 roadmap shows the progress of your epics along a timeline"
+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 ""
+
+msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset."
+msgstr ""
+
+msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised."
+msgstr ""
+
+msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
+msgstr ""
+
+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 ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no merge requests to show"
+msgstr ""
+
+msgid "There are problems accessing Git storage: "
+msgstr ""
+
+msgid "There was an error loading results"
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "This board\\'s scope is reduced"
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This is a confidential issue."
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is confidential and locked."
+msgstr ""
+
+msgid "This issue is locked."
+msgstr ""
+
+msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+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 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|in a while"
+msgstr ""
+
+msgid "Timeago|less than a minute ago"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
+msgid "Todo"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unresolve discussion"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to activate Issue weight."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload new avatar"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyPageError|You are not allowed to create wiki pages"
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
+msgstr ""
+
+msgid "WikiNewPagePlaceholder|how-to-setup"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiNewPageTitle|New Wiki Page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{page_title}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{page_title}"
+msgstr ""
+
+msgid "WikiPage|Page slug"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here..."
+msgstr ""
+
+msgid "Wiki|Create Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|Empty page"
+msgstr ""
+
+msgid "Wiki|More Pages"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Write a commit message..."
+msgstr ""
+
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
+msgid "You must sign in to star a project"
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+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 "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your comment will not be visible to the public."
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "assign yourself"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|DAST detected no alerts by analyzing the review app"
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Instances"
+msgstr ""
+
+msgid "ciReport|Learn more about whitelisting"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|SAST detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|SAST:container no vulnerabilities were found"
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Show complete code vulnerabilities report"
+msgstr ""
+
+msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
+msgstr ""
+
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove Source Branch"
+msgstr ""
+
+msgid "mrWidget|Remove source branch"
+msgstr ""
+
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The source branch has been removed"
+msgstr ""
+
+msgid "mrWidget|The source branch is being removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will be removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be removed"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|Web IDE"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|You can remove source branch now"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "password"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index f775a5117800c27448ba5678b335577c441bda54..2c7d37515315137af9bba5b9edd4859d312f50c2 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -2,15 +2,15 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 06:15-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:35-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Ukrainian\n"
 "Language: uk_UA\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
 "X-Generator: crowdin.com\n"
 "X-Crowdin-Project: gitlab-ee\n"
 "X-Crowdin-Language: uk\n"
@@ -24,36 +24,59 @@ msgid_plural "%d commits"
 msgstr[0] "%d 泻芯屑褨褌"
 msgstr[1] "%d 泻芯屑褨褌邪"
 msgstr[2] "%d 泻芯屑褨褌褨胁"
+msgstr[3] "%d 泻芯屑褨褌褨胁"
 
 msgid "%d commit behind"
 msgid_plural "%d commits behind"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "%d 泻芯屑褨褌 锌芯蟹邪写褍"
+msgstr[1] "%d 泻芯屑褨褌邪 锌芯蟹邪写褍"
+msgstr[2] "%d 泻芯屑褨褌褨胁 锌芯蟹邪写褍"
+msgstr[3] "%d 泻芯屑褨褌褨胁 锌芯蟹邪写褍"
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] "%d 械泻褋锌芯褉褌械褉"
+msgstr[1] "%d 械泻褋锌芯褉褌械褉邪"
+msgstr[2] "%d 械泻褋锌芯褉褌械褉褨胁"
+msgstr[3] "%d 械泻褋锌芯褉褌械褉褨胁"
 
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] "%d 锌褉芯斜谢械屑邪"
 msgstr[1] "%d 锌褉芯斜谢械屑懈"
 msgstr[2] "%d 锌褉芯斜谢械屑"
+msgstr[3] "%d 锌褉芯斜谢械屑"
 
 msgid "%d layer"
 msgid_plural "%d layers"
 msgstr[0] "%d 褕邪褉"
 msgstr[1] "%d 褕邪褉懈"
 msgstr[2] "%d 褕邪褉褨胁"
+msgstr[3] "%d 褕邪褉褨胁"
 
 msgid "%d merge request"
 msgid_plural "%d merge requests"
 msgstr[0] "%d 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
 msgstr[1] "%d 蟹邪锌懈褌邪 薪邪 蟹谢懈褌褌褟"
 msgstr[2] "%d 蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
+msgstr[3] "%d 蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] "%d 屑械褌褉懈泻邪"
+msgstr[1] "%d 屑械褌褉懈泻懈"
+msgstr[2] "%d 屑械褌褉懈泻"
+msgstr[3] "%d 屑械褌褉懈泻"
 
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
-msgstr[0] "%s 写芯写邪薪懈泄 泻芯屑褨褌 斜褍胁 胁懈泻谢褞褔械薪懈泄 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 锌褉芯斜谢械屑 褨蟹 褕胁懈写泻芯写褨褦褞."
-msgstr[1] "%s 写芯写邪薪懈褏 泻芯屑褨褌邪 斜褍谢懈 胁懈泻谢褞褔械薪褨 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 锌褉芯斜谢械屑 褨蟹 褕胁懈写泻芯写褨褦褞."
-msgstr[2] "%s 写芯写邪薪懈褏 泻芯屑褨褌褨胁 斜褍谢懈 胁懈泻谢褞褔械薪褨 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 锌褉芯斜谢械屑 褨蟹 褕胁懈写泻芯写褨褦褞."
+msgstr[0] "%s 写芯写邪薪懈泄 泻芯屑褨褌 斜褍胁 胁懈泻谢褞褔械薪懈泄 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 锌褉芯斜谢械屑 褨蟹 锌褉芯写褍泻褌懈胁薪褨褋褌褞."
+msgstr[1] "%s 写芯写邪薪懈褏 泻芯屑褨褌邪 斜褍谢懈 胁懈泻谢褞褔械薪褨 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 锌褉芯斜谢械屑 褨蟹 锌褉芯写褍泻褌懈胁薪褨褋褌褞."
+msgstr[2] "%s 写芯写邪薪懈褏 泻芯屑褨褌褨胁 斜褍谢懈 胁懈泻谢褞褔械薪褨 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 锌褉芯斜谢械屑 褨蟹 锌褉芯写褍泻褌懈胁薪褨褋褌褞."
+msgstr[3] "%s 写芯写邪薪懈褏 泻芯屑褨褌褨胁 斜褍谢懈 胁懈泻谢褞褔械薪褨 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 锌褉芯斜谢械屑 褨蟹 锌褉芯写褍泻褌懈胁薪褨褋褌褞."
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr "%{actionText} 褨 %{openOrClose} %{noteable}"
 
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr "%{commit_author_link} 蟹邪泻芯屑褨褌懈胁 %{commit_timeago}"
@@ -63,6 +86,13 @@ msgid_plural "%{count} participants"
 msgstr[0] "%{count} 褍褔邪褋褌薪懈泻"
 msgstr[1] "%{count} 褍褔邪褋褌薪懈泻邪"
 msgstr[2] "%{count} 褍褔邪褋褌薪懈泻褨胁"
+msgstr[3] "%{count} 褍褔邪褋褌薪懈泻褨胁"
+
+msgid "%{loadingIcon} Started"
+msgstr "%{loadingIcon} 袩芯褔邪褌芯泻"
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr "%{lock_path} 蟹邪斜谢芯泻芯胁邪薪芯 泻芯褉懈褋褌褍胁邪褔械屑 GitLab %{lock_user_id}"
 
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr "薪邪 %{number_commits_behind} 泻芯屑褨褌褨胁 锌芯蟹邪写褍 %{default_branch}, 薪邪 %{number_commits_ahead} 泻芯屑褨褌褨胁 锌芯锌械褉械写褍"
@@ -73,11 +103,15 @@ msgstr "%{number_of_failures} 胁褨写 %{maximum_failures} 薪械胁写邪褔. GitLab 薪
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "%{number_of_failures} 胁褨写 %{maximum_failures} 薪械胁写邪褔. GitLab 邪胁褌芯屑邪褌懈褔薪芯 薪械 锌芯胁褌芯褉褞胁邪褌懈屑械 褋锌褉芯斜褍. 小泻懈薪褜褌械 褨薪褎芯褉屑邪褑褨褞 褋褏芯胁懈褖邪 锌褉懈 褍褋褍薪械薪薪褨 锌褉芯斜谢械屑懈."
 
+msgid "%{openOrClose} %{noteable}"
+msgstr "%{openOrClose} %{noteable}"
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}: 褋锌褉芯斜邪 薪械胁写邪谢芯谐芯 写芯褋褌褍锌褍 写芯 褋褏芯胁懈褖邪 薪邪 褏芯褋褌褨:"
 msgstr[1] "%{storage_name}: %{failed_attempts} 薪械胁写邪谢褨 褋锌褉芯斜懈 写芯褋褌褍锌褍 写芯 褋褏芯胁懈褖邪:"
 msgstr[2] "%{storage_name}: %{failed_attempts} 薪械胁写邪谢懈褏 褋锌褉芯斜 写芯褋褌褍锌褍 写芯 褋褏芯胁懈褖邪:"
+msgstr[3] "%{storage_name}: %{failed_attempts} 薪械胁写邪谢懈褏 褋锌褉芯斜 写芯褋褌褍锌褍 写芯 褋褏芯胁懈褖邪:"
 
 msgid "%{text} is available"
 msgstr "%{text} 写芯褋褌褍锌薪懈泄"
@@ -96,6 +130,7 @@ msgid_plural "%d pipelines"
 msgstr[0] "1 泻芯薪胁械褦褉"
 msgstr[1] "%d 泻芯薪胁械褦褉邪"
 msgstr[2] "%d 泻芯薪胁械褦褉褨胁"
+msgstr[3] "%d 泻芯薪胁械褦褉褨胁"
 
 msgid "1st contribution!"
 msgstr "袩械褉褕懈泄 胁薪械褋芯泻!"
@@ -103,15 +138,30 @@ msgstr "袩械褉褕懈泄 胁薪械褋芯泻!"
 msgid "2FA enabled"
 msgstr "袛胁芯械褌邪锌薪邪 邪褍褌械薪褌懈褎褨泻邪褑褨褟 褍胁褨屑泻薪械薪邪"
 
+msgid "<strong>Removes</strong> source branch"
+msgstr "<strong>袙懈写邪谢褟褦</strong> 谐褨谢泻褍-写卸械褉械谢芯"
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "袧邪斜褨褉 谐褉邪褎褨泻褨胁 胁褨写薪芯褋薪芯 斜械蟹锌械褉械褉胁薪芯褩 褨薪褌械谐褉邪褑褨褩"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr "校 胁邪褕芯屑褍 褎芯褉泻褍 斜褍写械 褋褌胁芯褉械薪芯 薪芯胁褍 谐褨谢泻褍, 邪 褌邪泻芯卸 斜褍写械 褨薪褨褑褨泄芯胁邪薪懈泄 薪芯胁懈泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟."
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr "袩褉芯械泻褌 鈥� 褑械 屑褨褋褑械 写械 胁懈 屑芯卸械褌械 褉芯蟹屑褨褖褍胁邪褌懈 褋胁芯褩 褎邪泄谢懈 (褉械锌芯蟹懈褌芯褉褨泄), 锌谢邪薪褍胁邪褌懈 褉芯斜芯褌褍 (锌褉芯斜谢械屑懈) 褨 锌褍斜谢褨泻褍胁邪褌懈 写芯泻褍屑械薪褌邪褑褨褞 (wiki), %{among_other_things_link}."
+
+msgid "A user with write access to the source branch selected this option"
+msgstr "袣芯褉懈褋褌褍胁邪褔 褨蟹 锌褉邪胁芯屑 蟹邪锌懈褋褍 胁 谐褨谢泻褍-写卸械褉械谢芯 胁懈斜褉邪胁 褑械泄 胁邪褉褨邪薪褌"
+
 msgid "About auto deploy"
 msgstr "袩褉芯 邪胁褌芯 褉芯蟹谐芯褉褌邪薪薪褟"
 
 msgid "Abuse Reports"
 msgstr "袟胁褨褌懈 锌褉芯 蟹谢芯胁卸懈胁邪薪薪褟"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "孝芯泻械薪懈 写芯褋褌褍锌褍"
 
@@ -121,6 +171,9 @@ msgstr "袛芯褋褌褍锌 写芯 褋褏芯胁懈褖, 褖芯 胁懈泄褕谢懈 蟹 谢邪写褍, 褌懈屑褔
 msgid "Account"
 msgstr "袨斜谢褨泻芯胁懈泄 蟹邪锌懈褋"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "袗泻褌懈胁薪懈泄"
 
@@ -139,9 +192,15 @@ msgstr "袛芯写邪褌懈 泻械褉褨胁薪懈褑褌胁芯 写谢褟 褍褔邪褋薪懈泻褨胁"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr "袛芯写邪泄褌械 谐褉褍锌芯胁褨 胁械斜-谐褍泻懈 褌邪 GitLab Enterprise Edition."
 
+msgid "Add Kubernetes cluster"
+msgstr "袛芯写邪褌懈 Kubernetes-泻谢邪褋褌械褉"
+
 msgid "Add License"
 msgstr "袛芯写邪褌懈 谢褨褑械薪蟹褨褞"
 
+msgid "Add Readme"
+msgstr "袛芯写邪褌懈 褨薪褋褌褉褍泻褑褨褞"
+
 msgid "Add new directory"
 msgstr "袛芯写邪褌懈 薪芯胁懈泄 泻邪褌邪谢芯谐"
 
@@ -160,12 +219,45 @@ msgstr "袟褍锌懈薪懈褌懈 蟹邪胁写邪薪薪褟"
 msgid "AdminArea|Stopping jobs failed"
 msgstr "袟褍锌懈薪泻邪 蟹邪胁写邪薪褜 锌褉芯泄褕谢邪 薪械胁写邪谢芯"
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
-msgstr ""
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr "袟邪褉邪蟹 胁懈 蟹褍锌懈薪械褌械 胁褋褨 蟹邪胁写邪薪薪褟. 笑械 芯斜褨褉胁械 褍褋褨 蟹邪锌褍褖械薪褨 蟹邪胁写邪薪薪褟."
 
 msgid "AdminHealthPageLink|health page"
 msgstr "褋褌芯褉褨薪泻邪 褋褌邪褌褍褋褍"
 
+msgid "AdminProjects|Delete"
+msgstr "袙懈写邪谢懈褌懈"
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr "袙懈写邪谢懈褌懈 锌褉芯械泻褌 %{projectName}?"
+
+msgid "AdminProjects|Delete project"
+msgstr "袙懈写邪谢懈褌懈 锌褉芯械泻褌"
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr "袙泻邪卸褨褌褜 写芯屑械薪, 褟泻懈泄 斜褍写械 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈褋褟 胁 锌褉芯械泻褌褨 蟹邪 蟹邪屑芯胁褔褍胁邪薪薪褟屑 写谢褟 褋褌邪写褨泄 Auto Review Apps 褨 Auto Deploy."
+
+msgid "AdminUsers|Block user"
+msgstr "袟邪斜谢芯泻胁邪褌懈 泻芯褉懈褋褌褍胁邪褔邪"
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr "袙懈写邪谢懈褌懈 泻芯褉懈褋褌褍胁邪褔邪 %{username} 褌邪 泄芯谐芯 胁薪械褋泻懈?"
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr "袙懈写邪谢懈褌懈 泻芯褉懈褋褌褍胁邪褔邪 %{username}?"
+
+msgid "AdminUsers|Delete user"
+msgstr "袙懈写邪谢懈褌懈 泻芯褉懈褋褌褍胁邪褔邪"
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr "袙懈写邪谢懈褌懈 泻芯褉懈褋褌褍胁邪褔邪 褨 泄芯谐芯 胁薪械褋泻懈"
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr "袛谢褟 锌褨写褌胁械褉写卸械薪薪褟 胁胁械写褨褌褜 %{projectName}"
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr "袛谢褟 锌褨写褌胁械褉写卸械薪薪褟 胁胁械写褨褌褜 %{username}"
+
 msgid "Advanced"
 msgstr "袪芯蟹褕懈褉械薪懈泄"
 
@@ -176,53 +268,116 @@ msgid "All"
 msgstr "袙褋褨"
 
 msgid "All changes are committed"
+msgstr "袙褋褨 蟹屑褨薪懈 蟹邪泻芯屑褨褔械薪褨"
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr "袙褋褨 褎褍薪泻褑褨褩 写谢褟 薪芯胁懈褏 锌褉芯械泻褌褨胁 斜械褉褍褌褜褋褟 褨蟹 褕邪斜谢芯薪褨胁 邪斜芯 锌褨写 褔邪褋 褨屑锌芯褉褌褍胁邪薪薪褟, 邪谢械 胁懈 屑芯卸械褌械 胁懈屑懈泻邪褌懈 褩褏 锌褨蟹薪褨褕械 胁 薪邪谢邪褕褌褍胁邪薪薪褟褏 锌褉芯械泻褌褍."
+
+msgid "Allow edits from maintainers."
+msgstr "袛芯蟹胁芯谢懈褌懈 褉械写邪谐褍胁邪薪薪褟 泻芯屑邪薪写芯褞 锌褉芯械泻褌褍."
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
 msgstr ""
 
 msgid "Allows you to add and manage Kubernetes clusters."
+msgstr "袛芯蟹胁芯谢褟褦 写芯写邪胁邪褌懈 褌邪 泻械褉褍胁邪褌懈 泻谢邪褋褌械褉邪屑懈 Kubernetes."
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
 msgstr ""
 
-msgid "An error occurred previewing the blob"
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
 msgstr ""
 
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr "袣褉褨屑 褌芯谐芯, 胁懈 屑芯卸械褌械 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 %{personal_access_token_link}. 袣芯谢懈 胁懈 褋褌胁芯褉褞胁邪褌懈屑械褌械 褋胁褨泄 锌械褉褋芯薪邪谢褜薪懈泄 褌芯泻械薪 写芯褋褌褍锌褍, 胁邪屑 锌芯褌褉褨斜薪芯 斜褍写械 胁懈斜褉邪褌懈 芯斜谢邪褋褌褜 <code>褉械锌芯蟹懈褌芯褉褨褟</code>, 褖芯斜 屑懈 屑芯谐谢懈 胁褨写芯斜褉邪蟹懈褌懈 褋锌懈褋芯泻 胁邪褕懈褏 锌褍斜谢褨褔薪懈褏 褌邪 锌褉懈胁邪褌薪懈褏 褉械锌芯蟹懈褌芯褉褨褩胁, 写芯褋褌褍锌薪懈褏 写谢褟 褨屑锌芯褉褌褍."
+
+msgid "An error occurred previewing the blob"
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褨写 褔邪褋 锌芯锌械褉械写薪褜芯谐芯 锌械褉械谐谢褟写褍 芯斜'褦泻褌邪"
+
 msgid "An error occurred when toggling the notification subscription"
 msgstr "袙懈薪懈泻谢邪 锌芯屑懈谢泻邪 锌褨写 褔邪褋 蟹屑褨薪懈 锌褨写锌懈褋泻懈 薪邪 褋锌芯胁褨褖械薪薪褟"
 
 msgid "An error occurred when updating the issue weight"
 msgstr "袟斜褨泄 锌褨写 褔邪褋 芯薪芯胁谢械薪薪褟 胁邪谐懈 锌褉芯斜谢械屑懈"
 
+msgid "An error occurred while adding approver"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 写芯写邪胁邪薪薪褨 褍褔邪褋薪懈泻邪 写谢褟 蟹邪褌胁械褉写卸械薪薪褟"
+
+msgid "An error occurred while detecting host keys"
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 胁懈褟胁谢械薪薪褨 泻谢褞褔褨胁 褏芯褋褌邪"
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 胁懈屑泻薪械薪薪褨 锌芯胁褨写芯屑谢械薪薪褟 锌褉芯 褎褍薪泻褑褨褞. 袨薪芯胁褨褌褜 褋褌芯褉褨薪泻褍 褨 褋锌褉芯斜褍泄褌械 蟹薪芯胁褍."
 
 msgid "An error occurred while fetching markdown preview"
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 锌芯锌械褉械写薪褜芯屑褍 锌械褉械谐谢褟写褨 markdown"
 
 msgid "An error occurred while fetching sidebar data"
 msgstr "袙懈薪懈泻谢邪 锌芯屑懈谢泻邪 锌褨写 褔邪褋 蟹邪胁邪薪褌邪卸械薪薪褟 写邪薪懈褏 写谢褟 斜褨褔薪芯褩 锌邪薪械谢褨"
 
+msgid "An error occurred while fetching the pipeline."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 写邪薪薪懈褏 泻芯薪胁械褦褉邪."
+
 msgid "An error occurred while getting projects"
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 锌褉芯械泻褌褨胁"
+
+msgid "An error occurred while importing project"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 褨屑锌芯褉褌褨 锌褉芯械泻褌褍"
+
+msgid "An error occurred while initializing path locks"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 褨薪褨褑褨邪谢褨蟹邪褑褨褩 斜谢芯泻褍胁邪薪薪褟 褎邪泄谢芯胁懈褏 褕谢褟褏褨胁"
+
+msgid "An error occurred while loading commits"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪褨 泻芯屑褨褌褨胁"
+
+msgid "An error occurred while loading diff"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪薪褨 褉邪蟹薪懈褑褨 (diff)"
 
 msgid "An error occurred while loading filenames"
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪薪褨 褎邪泄谢褨胁"
+
+msgid "An error occurred while loading the file"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪薪褨 褎邪泄谢褍"
+
+msgid "An error occurred while making the request."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 褋褌胁芯褉械薪薪褨 蟹邪锌懈褌褍."
+
+msgid "An error occurred while removing approver"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 胁懈写邪谢械薪薪褨 褍褔邪褋薪懈泻邪 写谢褟 蟹邪褌胁械褉写卸械薪薪褟"
 
 msgid "An error occurred while rendering KaTeX"
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 褉械薪写械褉懈薪谐褍 KaTeX"
 
 msgid "An error occurred while rendering preview broadcast message"
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 锌芯锌械褉械写薪褜芯屑褍 锌械褉械谐谢褟写褨 芯谐芯谢芯褕械薪薪褟 写谢褟 泻芯褉懈褋褌褍胁邪褔褨胁"
 
 msgid "An error occurred while retrieving calendar activity"
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 泻邪谢械薪写邪褉褟 邪泻褌懈胁薪芯褋褌褨"
 
 msgid "An error occurred while retrieving diff"
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 褉褨蟹薪懈褑褨"
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹斜械褉械卸械薪薪褨 褋褌邪褌褍褋褍 锌械褉械胁懈蟹薪邪褔械薪薪褟 LDAP. 袘褍写褜 谢邪褋泻邪, 褋锌褉芯斜褍泄褌械 褖械 褉邪蟹."
+
+msgid "An error occurred while saving assignees"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹斜械褉械卸械薪薪褨 胁懈泻芯薪邪胁褑褨胁"
 
 msgid "An error occurred while validating username"
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褨写 褔邪褋 锌械褉械胁褨褉泻懈 褨屑械薪褨 泻芯褉懈褋褌褍胁邪褔邪"
 
 msgid "An error occurred. Please try again."
 msgstr "小褌邪谢邪褋褜 锌芯屑懈谢泻邪. 小锌褉芯斜褍泄褌械 褖械 褉邪蟹."
 
+msgid "Any Label"
+msgstr "袘褍写褜-褟泻邪 屑褨褌泻邪"
+
 msgid "Appearance"
 msgstr "袟芯胁薪褨褕薪褨泄 胁懈谐谢褟写"
 
@@ -241,14 +396,14 @@ msgstr "袟邪邪褉褏褨胁芯胁邪薪懈泄 锌褉芯械泻褌! 袪械锌芯蟹懈褌芯褉褨泄 写芯褋褌
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 胁懈写邪谢懈褌懈 褑械泄 褉芯蟹泻谢邪写 写谢褟 泻芯薪胁械褦褉邪?"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "袙懈 胁锌械胁薪械薪褨, 褖芯 斜邪卸邪褦褌械 褋泻邪褋褍胁邪褌懈 胁邪褕褨 蟹屑褨薪懈?"
-
 msgid "Are you sure you want to reset registration token?"
-msgstr "袙懈 胁锌械胁薪械薪褨, 褖芯 斜邪卸邪褦褌械 褋泻懈薪褍褌懈 褉械褦褋褌褉邪褑褨泄薪懈泄 褌芯泻械薪?"
+msgstr "袙懈 胁锌械胁薪械薪褨, 褖芯 斜邪卸邪褦褌械 锌械褉械谐械薪械褉褍胁邪褌懈 褉械褦褋褌褉邪褑褨泄薪懈泄 褌芯泻械薪?"
 
 msgid "Are you sure you want to reset the health check token?"
-msgstr "袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 褋泻懈薪褍褌懈 褑械泄 泻谢褞褔 锌械褉械胁褨褉泻懈 锌褉邪褑械蟹写邪褌薪芯褋褌褨?"
+msgstr "袙懈 胁锌械胁薪械薪褨, 褖芯 袙懈 褏芯褔械褌械 锌械褉械谐械薪械褉褍胁邪褌懈 褑械泄 泻谢褞褔 锌械褉械胁褨褉泻懈 锌褉邪褑械蟹写邪褌薪芯褋褌褨?"
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr "袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 褉芯蟹斜谢芯泻褍胁邪褌懈 %{path_lock_path}?"
 
 msgid "Are you sure?"
 msgstr "袙懈 胁锌械胁薪械薪褨?"
@@ -256,6 +411,9 @@ msgstr "袙懈 胁锌械胁薪械薪褨?"
 msgid "Artifacts"
 msgstr "袗褉褌械褎邪泻褌懈"
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr "袩褉懈蟹薪邪褔懈褌懈 胁谢邪褋薪懈泄 泻芯谢褨褉 褌懈锌褍 #FF0000"
 
@@ -268,6 +426,15 @@ msgstr "袩褉懈蟹薪邪褔懈褌懈 械褌邪锌"
 msgid "Assign to"
 msgstr "袩褉懈蟹薪邪褔懈褌懈"
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr "袙懈泻芯薪邪胁械褑褜"
 
@@ -289,11 +456,17 @@ msgstr "袗胁褌芯褉"
 msgid "Authors: %{authors}"
 msgstr "袗胁褌芯褉懈: %{authors}"
 
-msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
+msgid "Auto DevOps enabled"
+msgstr "Auto DevOps 褍胁褨屑泻薪械薪芯"
+
+msgid "Auto DevOps, runners and job artifacts"
 msgstr ""
 
+msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
+msgstr "袛谢褟 泻芯褉械泻褌薪芯褩 褉芯斜芯褌懈 Auto Review Apps 褌邪 Auto Deploy 薪械芯斜褏褨写械薪 %{kubernetes}."
+
 msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "袛谢褟 泻芯褉械泻褌薪芯褩 褉芯斜芯褌懈 Auto Review Apps 褌邪 Auto Deploy 薪械芯斜褏褨写薪芯 胁泻邪蟹邪褌懈 写芯屑械薪薪械 褨屑鈥櫻� 褌邪 %{kubernetes}."
 
 msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
 msgstr "袛谢褟 泻芯褉械泻褌薪芯褩 褉芯斜芯褌懈 Auto Review Apps 褌邪 Auto Deploy 薪械芯斜褏褨写薪芯 胁泻邪蟹邪褌懈 写芯屑械薪薪械 褨屑鈥櫻�."
@@ -302,7 +475,7 @@ msgid "AutoDevOps|Auto DevOps (Beta)"
 msgstr "Auto DevOps (斜械褌邪)"
 
 msgid "AutoDevOps|Auto DevOps documentation"
-msgstr "Auto DevOps 写芯泻褍屑械薪褌邪褑褨褟"
+msgstr "Auto DevOps 写芯泻褍屑械薪褌邪褑褨褩"
 
 msgid "AutoDevOps|Enable in settings"
 msgstr "袙泻谢褞褔懈褌懈 胁 薪邪谢邪褕褌褍胁邪薪薪褟褏"
@@ -313,11 +486,17 @@ msgstr "AutoDevOps 斜褍写械 邪胁褌芯屑邪褌懈褔薪芯 蟹斜懈褉邪褌懈, 褌械褋褌褍胁
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr "袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 胁 %{link_to_documentation}"
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
-msgstr "袙懈 屑芯卸械褌械 邪泻褌懈胁褍胁邪褌懈 %{link_to_settings} 写谢褟 褑褜芯谐芯 锌褉芯械泻褌褍."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr "袙懈 屑芯卸械褌械 邪胁褌芯屑邪褌懈褔薪芯 蟹斜懈褉邪褌懈 泄 褌械褋褌褍胁邪褌懈 胁邪褕 蟹邪褋褌芯褋褍薪芯泻, 褟泻褖芯 %{link_to_auto_devops_settings} 写谢褟 褑褜芯谐芯 锌褉芯械泻褌褍. 袙懈 褌邪泻芯卸 屑芯卸械褌械 泄芯谐芯 邪胁褌芯屑邪褌懈褔薪芯 褉芯蟹谐芯褉褌邪褌懈, 褟泻褖芯 %{link_to_add_kubernetes_cluster}."
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr "写芯写邪褌懈 Kubernetes-泻谢邪褋褌械褉"
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr "校胁褨屑泻薪褍褌懈 Auto DevOps (Beta)"
 
 msgid "Available"
-msgstr "袛芯褋褌褍锌薪懈泄"
+msgstr "袛芯褋褌褍锌薪芯"
 
 msgid "Avatar will be removed. Are you sure?"
 msgstr "袗胁邪褌邪褉 斜褍写械 胁懈写邪谢械薪芯. 袙懈 胁锌械胁薪械薪褨?"
@@ -325,6 +504,15 @@ msgstr "袗胁邪褌邪褉 斜褍写械 胁懈写邪谢械薪芯. 袙懈 胁锌械胁薪械薪褨?"
 msgid "Average per day: %{average}"
 msgstr "袙 褋械褉械写薪褜芯屑褍 蟹邪 写械薪褜: %{average}"
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr "袩芯褔邪褌懈 褨蟹 胁懈写褨谢械薪芯谐芯 泻芯屑褨褌褍"
+
 msgid "Billing"
 msgstr "袘褨谢褨薪谐"
 
@@ -379,14 +567,12 @@ msgstr "袨锌谢邪褔褍褦褌褜褋褟 褖芯褉褨褔薪芯 %{price_per_year}"
 msgid "BillingPlans|per user"
 msgstr "蟹邪 泻芯褉懈褋褌褍胁邪褔邪"
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "袚褨谢泻邪"
-msgstr[1] "袚褨谢泻懈"
-msgstr[2] "袚褨谢芯泻"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+msgstr[0] "袚褨谢泻邪 (%{branch_count})"
+msgstr[1] "袚褨谢泻懈 (%{branch_count})"
+msgstr[2] "袚褨谢芯泻 (%{branch_count})"
+msgstr[3] "袚褨谢芯泻 (%{branch_count})"
 
 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}"
@@ -409,6 +595,15 @@ msgstr "袩械褉械泄褌懈 胁 谐褨谢泻褍"
 msgid "Branches"
 msgstr "袚褨谢泻懈"
 
+msgid "Branches|Active"
+msgstr "袗泻褌懈胁薪褨"
+
+msgid "Branches|Active branches"
+msgstr "袗泻褌懈胁薪褨 谐褨谢泻懈"
+
+msgid "Branches|All"
+msgstr "袙褋褨"
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr "袧械 屑芯卸褍 蟹薪邪泄褌懈 HEAD-泻芯屑褨褌 写谢褟 褑褨褦褩 谐褨谢泻懈"
 
@@ -454,12 +649,39 @@ msgstr "携泻 褌褨谢褜泻懈 胁懈 锌褨写褌胁械褉写懈褌械 褨 薪邪褌懈褋薪械褌械 %{de
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr "孝褨谢褜泻懈 泻械褉褨胁薪懈泻 邪斜芯 胁谢邪褋薪懈泻 锌褉芯械泻褌褍 屑芯卸械 胁懈写邪谢懈褌懈 蟹邪褏懈褖械薪褍 谐褨谢泻褍"
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
-msgstr "袣械褉褍胁邪褌懈 蟹邪褏懈褖械薪懈屑懈 谐褨谢泻邪屑懈 屑芯卸谢懈胁芯 胁 %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr "袨谐谢褟写"
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr "袣械褉褍胁邪褌懈 蟹邪褏懈褖械薪懈屑懈 谐褨谢泻邪屑懈 屑芯卸谢懈胁芯 胁 %{project_settings_link}."
+
+msgid "Branches|Show active branches"
+msgstr "袩芯泻邪蟹褍胁邪褌懈 邪泻褌懈胁薪褨 谐褨谢泻懈"
+
+msgid "Branches|Show all branches"
+msgstr "袩芯泻邪蟹褍胁邪褌懈 胁褋褨 谐褨谢泻懈"
+
+msgid "Branches|Show more active branches"
+msgstr "袩芯泻邪蟹邪褌懈 斜褨谢褜褕械 邪泻褌懈胁薪懈褏 谐褨谢芯泻"
+
+msgid "Branches|Show more stale branches"
+msgstr "袩芯泻邪蟹邪褌懈 斜褨谢褜褕械 蟹邪褋褌邪褉褨谢懈褏 谐褨谢芯泻"
+
+msgid "Branches|Show overview of the branches"
+msgstr "袩芯泻邪蟹邪褌懈 芯锌懈褋 谐褨谢芯泻"
+
+msgid "Branches|Show stale branches"
+msgstr "袩芯泻邪蟹邪褌懈 蟹邪褋褌邪褉褨谢褨 谐褨谢泻懈"
 
 msgid "Branches|Sort by"
 msgstr "小芯褉褌褍胁邪褌懈 蟹邪"
 
+msgid "Branches|Stale"
+msgstr "袟邪褋褌邪褉褨谢褨"
+
+msgid "Branches|Stale branches"
+msgstr "袟邪褋褌邪褉褨谢褨 谐褨谢泻懈"
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr "袚褨谢泻邪 薪械 屑芯卸械 斜褍褌懈 芯薪芯胁谢械薪邪 邪胁褌芯屑邪褌懈褔薪芯, 褌芯屑褍 褖芯 胁芯薪邪 屑邪褦 褉芯蟹斜褨卸薪芯褋褌褨 褨蟹 upstream."
 
@@ -491,7 +713,7 @@ msgid "Branches|project settings"
 msgstr "袧邪谢邪褕褌褍胁邪薪薪褟褏 锌褉芯械泻褌褍"
 
 msgid "Branches|protected"
-msgstr "蟹邪褏懈褖械薪褨"
+msgstr "蟹邪褏懈褖械薪邪"
 
 msgid "Browse Directory"
 msgstr "袩械褉械谐谢褟薪褍褌懈 泻邪褌邪谢芯谐"
@@ -505,30 +727,45 @@ msgstr "袩械褉械谐谢褟写 褎邪泄谢褨胁"
 msgid "Browse files"
 msgstr "袩械褉械谐谢褟写 褎邪泄谢褨胁"
 
+msgid "Business"
+msgstr "袘褨蟹薪械褋"
+
 msgid "ByAuthor|by"
 msgstr "胁褨写"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr "CI/CD"
+
 msgid "CI/CD configuration"
 msgstr "袧邪谢邪褕褌褍胁邪薪薪褟 CI/CD"
 
+msgid "CI/CD for external repo"
+msgstr "CI/CD 写谢褟 蟹芯胁薪褨褕薪褜芯谐芯 褉械锌芯蟹懈褌芯褉褨褟"
+
 msgid "CICD|Jobs"
 msgstr "袟邪胁写邪薪薪褟"
 
 msgid "Cancel"
 msgstr "小泻邪褋褍胁邪褌懈"
 
-msgid "Cancel edit"
-msgstr "袙褨写屑褨薪懈褌懈 锌褉邪胁泻褍"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
+msgstr "袧械屑芯卸谢懈胁芯 蟹屑褨薪懈褌懈 泻械褉芯胁邪薪懈泄 泻谢邪褋褌械褉 Kubernetes"
+
+msgid "Certificate fingerprint"
 msgstr ""
 
 msgid "Change Weight"
 msgstr "袙邪谐邪 蟹屑褨薪懈"
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "袙懈斜褉邪褌懈 胁 谐褨谢褑褨"
 
@@ -542,13 +779,13 @@ msgid "ChangeTypeAction|Revert"
 msgstr "袗薪褍谢褞胁邪褌懈 泻芯屑褨褌"
 
 msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
-msgstr ""
+msgstr "笑械 褋褌胁芯褉懈褌褜 薪芯胁懈泄 泻芯屑褨褌, 褖芯斜 邪薪褍谢褞胁邪褌懈 褨褋薪褍褞褔褨 蟹屑褨薪懈."
 
 msgid "Changelog"
 msgstr "小锌懈褋芯泻 蟹屑褨薪"
 
 msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
-msgstr ""
+msgstr "袟屑褨薪懈 胁褨写芯斜褉邪卸邪褞褌褜褋褟 褌邪泻, 薪褨斜懈 <b>褉械写邪泻褑褨褟-写卸械褉械谢芯</b> 斜褍谢邪 蟹谢懈褌邪 胁 <b>褑褨谢褜芯胁褍 褉械写邪泻褑褨褞</b>."
 
 msgid "Charts"
 msgstr "袚褉邪褎褨泻懈"
@@ -575,16 +812,22 @@ msgid "Choose File ..."
 msgstr "袙懈斜械褉褨褌褜 褎邪泄谢 ..."
 
 msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
-msgstr ""
+msgstr "袙懈斜械褉褨褌褜 谐褨谢泻褍 褔懈 褌械谐 (薪邪锌褉. %{master}) 邪斜芯 胁胁械写褨褌褜 泻芯屑褨褌 (薪邪锌褉. %{sha}) 写谢褟 锌械褉械谐谢褟写褍 蟹屑褨薪 邪斜芯 写谢褟 褋褌胁芯褉械薪薪褟 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟."
 
 msgid "Choose file..."
 msgstr "袙懈斜械褉褨褌褜 褎邪泄谢..."
 
 msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "袙懈斜械褉褨褌褜 谐褉褍锌懈 写谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩 薪邪 褑械泄 胁褌芯褉懈薪薪懈泄 胁褍蟹芯谢."
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr "袙懈斜械褉褨褌褜, 褟泻褨 褉械锌芯蟹懈褌芯褉褨褩 胁懈 褏芯褔械褌械 锌褨写泻谢褞褔懈褌懈 褨 蟹邪锌褍褋褌懈褌懈 泻芯薪胁械褦褉懈 CI/CD."
+
+msgid "Choose which repositories you want to import."
+msgstr "袙懈斜械褉褨褌褜, 褟泻褨 褉械锌芯蟹懈褌芯褉褨褩 胁懈 褏芯褔械褌械 褨屑锌芯褉褌褍胁邪褌懈."
 
 msgid "Choose which shards you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "袙懈斜械褉褨褌褜 褋械谐屑械薪褌懈 写谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩 薪邪 褑械泄 胁褌芯褉懈薪薪懈泄 胁褍蟹芯谢."
 
 msgid "CiStatusLabel|canceled"
 msgstr "褋泻邪褋芯胁邪薪芯"
@@ -647,7 +890,7 @@ msgid "CiVariables|Input variable value"
 msgstr "袟薪邪褔械薪薪褟 胁褏褨写薪芯褩 蟹屑褨薪薪芯褩"
 
 msgid "CiVariables|Remove variable row"
-msgstr ""
+msgstr "袙懈写邪谢懈褌懈 褉褟写芯泻 蟹屑褨薪薪懈褏"
 
 msgid "CiVariable|* (All environments)"
 msgstr "* (袙褋褨 褋械褉械写芯胁懈褖邪)"
@@ -656,10 +899,10 @@ msgid "CiVariable|All environments"
 msgstr "袙褋褨 褋械褉械写芯胁懈褖邪"
 
 msgid "CiVariable|Create wildcard"
-msgstr ""
+msgstr "小褌胁芯褉懈褌懈 褕邪斜谢芯薪"
 
 msgid "CiVariable|Error occured while saving variables"
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹斜械褉械卸械薪薪褨 蟹屑褨薪薪懈褏"
 
 msgid "CiVariable|New environment"
 msgstr "袧芯胁械 褋械褉械写芯胁懈褖械"
@@ -668,10 +911,10 @@ msgid "CiVariable|Protected"
 msgstr "袟邪褏懈褖械薪懈泄"
 
 msgid "CiVariable|Search environments"
-msgstr ""
+msgstr "袩芯褕褍泻 褋械褉械写芯胁懈褖"
 
 msgid "CiVariable|Toggle protected"
-msgstr ""
+msgstr "袙胁褨屑泻薪褍褌懈/胁懈屑泻薪褍褌懈 蟹邪褏懈褋褌"
 
 msgid "CiVariable|Validation failed"
 msgstr "袩械褉械胁褨褉泻邪 薪械胁写邪谢邪"
@@ -679,9 +922,21 @@ msgstr "袩械褉械胁褨褉泻邪 薪械胁写邪谢邪"
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr "circuitbreaker api"
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr "袧邪褌懈褋薪褨褌褜 泻薪芯锌泻褍 薪懈卸褔械, 褖芯斜 褉芯蟹锌芯褔邪褌懈 锌褉芯褑械褋 胁褋褌邪薪芯胁谢械薪薪褟, 锌械褉械泄褕芯胁褕懈 薪邪 褋褌芯褉褨薪泻褍 Kubernetes"
+
 msgid "Click to expand text"
 msgstr "袧邪褌懈褋薪褨褌褜, 褖芯斜 褉芯蟹谐芯褉薪褍褌懈 褌械泻褋褌"
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr "袣谢芯薪褍胁邪褌懈 褉械锌芯蟹懈褌芯褉褨泄"
 
@@ -728,11 +983,14 @@ msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with Gi
 msgstr "袣械褉褍泄褌械 褋锌芯褋芯斜芯屑 褨薪褌械谐褉邪褑褨褩 胁邪褕芯谐芯 Kubernetes-泻谢邪褋褌械褉邪 蟹 GitLab"
 
 msgid "ClusterIntegration|Copy API URL"
-msgstr "小泻芯锌褨褞胁邪褌懈 URL API"
+msgstr "小泻芯锌褨褞胁邪褌懈 API URL"
 
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr "小泻芯锌褨褞胁邪褌懈 褋械褉褌懈褎褨泻邪褌 褑械薪褌褉褍 褋械褉褌懈褎褨泻邪褑褨褩"
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr "袣芯锌褨褞胁邪褌懈 IP-邪写褉械褋褍 Ingress 胁 斜褍褎械褉 芯斜屑褨薪褍"
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr "小泻芯锌褨褞胁邪褌懈 褨屑鈥櫻� Kubernetes-泻谢邪褋褌械褉邪"
 
@@ -778,12 +1036,21 @@ msgstr "袩褉芯械泻褌 Google Kubernetes Engine"
 msgid "ClusterIntegration|Helm Tiller"
 msgstr "Helm Tiller"
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr "袛谢褟 褌芯谐芯, 褖芯斜 锌芯泻邪蟹褍胁邪褌懈 褋褌邪薪 泻谢邪褋褌械褉邪, 薪邪屑 锌芯褌褉褨斜薪芯 斜褍写械 胁褋褌邪薪芯胁懈褌懈 Prometheus 薪邪 胁邪褕 泻谢邪褋褌械褉 写谢褟 蟹斜芯褉褍 薪械芯斜褏褨写薪懈褏 写邪薪懈褏."
+
 msgid "ClusterIntegration|Ingress"
 msgstr "Ingress"
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr "Ingress IP-邪写褉械褋邪"
+
 msgid "ClusterIntegration|Install"
 msgstr "袙褋褌邪薪芯胁懈褌懈"
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr "袙褋褌邪薪芯胁懈褌懈 Prometheus"
+
 msgid "ClusterIntegration|Installed"
 msgstr "袙褋褌邪薪芯胁谢械薪懈泄"
 
@@ -791,7 +1058,7 @@ msgid "ClusterIntegration|Installing"
 msgstr "袙褋褌邪薪芯胁谢械薪薪褟"
 
 msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
-msgstr ""
+msgstr "袉薪褌械谐褉邪褑褨褟 泻谢邪褋褌械褉薪芯褩 邪胁褌芯屑邪褌懈蟹邪褑褨褩 Kubernetes"
 
 msgid "ClusterIntegration|Integration status"
 msgstr "小褌邪褌褍褋 褨薪褌械谐褉邪褑褨褩"
@@ -802,6 +1069,9 @@ msgstr "Kubernetes-泻谢邪褋褌械褉"
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr "袩邪褉邪屑械褌褉懈 Kubernetes-泻谢邪褋褌械褉邪"
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr "小褌邪薪 Kubernetes-泻谢邪褋褌械褉邪"
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr "袉薪褌械谐褉邪褑褨褟 褨蟹 Kubernetes-泻谢邪褋褌械褉芯屑"
 
@@ -812,49 +1082,49 @@ msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this pro
 msgstr "袉薪褌械谐褉邪褑褨褟 褨蟹 Kubernetes-泻谢邪褋褌械褉芯屑 褍胁褨屑泻薪械薪邪 写谢褟 褑褜芯谐芯 锌褉芯械泻褌褍."
 
 msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
-msgstr ""
+msgstr "袉薪褌械谐褉邪褑褨褟 褨蟹 Kubernetes-泻谢邪褋褌械褉芯屑 褍胁褨屑泻薪械薪邪 写谢褟 褑褜芯谐芯 锌褉芯械泻褌褍. 袙懈屑泻薪械薪薪褟 褑褨褦褩 褨薪褌械谐褉邪褑褨褩 薪械 胁锌谢懈薪械 薪邪 泻谢邪褋褌械褉, 邪 谢懈褕械 褌懈屑褔邪褋芯胁芯 锌械褉械褉胁械 蟹鈥櫻斝葱叫靶叫窖� 蟹 GitLab."
 
 msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
-msgstr ""
+msgstr "Kubernetes-泻谢邪褋褌械褉 褋褌胁芯褉褞褦褌褜褋褟 薪邪 Google Kubernetes Engine..."
 
 msgid "ClusterIntegration|Kubernetes cluster name"
 msgstr "袉屑鈥櫻� Kubernetes-泻谢邪褋褌械褉邪"
 
 msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
-msgstr ""
+msgstr "Kubernetes-泻谢邪褋褌械褉 斜褍胁 褍褋锌褨褕薪芯 褋褌胁芯褉械薪懈泄 薪邪 Google Kubernetes Engine. 袨薪芯胁褨褌褜 褋褌芯褉褨薪泻褍, 褖芯斜 锌芯斜邪褔懈褌懈 锌邪褉邪屑械褌褉懈 泻谢邪褋褌械褉邪"
 
 msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
-msgstr ""
+msgstr "Kubernetes-泻谢邪褋褌械褉懈 写芯蟹胁芯谢褟褞褌褜 胁邪屑 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 Review Apps, 褉芯蟹谐芯褉褌邪褌懈 胁邪褕褨 蟹邪褋褌芯褋褍薪泻懈, 蟹邪锌褍褋泻邪褌懈 泻芯薪胁械褦褉懈 褨 斜邪谐邪褌芯 褨薪褕芯谐芯 锌褉芯褋褌懈屑 褋锌芯褋芯斜芯屑. %{link_to_help_page}"
 
 msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
-msgstr ""
+msgstr "Kubernetes-泻谢邪褋褌械褉懈 屑芯卸褍褌褜 斜褍褌懈 胁懈泻芯褉懈褋褌邪薪褨 写谢褟 褉芯蟹谐芯褉褌邪薪薪褟 蟹邪褋褌芯褋褍薪泻褨胁 褨 胁懈泻芯褉懈褋褌邪薪薪褟 Review Apps 写谢褟 褑褜芯谐芯 锌褉芯械泻褌褍"
 
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr "袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 锌褉芯 %{link_to_documentation}"
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
-msgstr "袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 锌褉芯 Kubernetes"
-
 msgid "ClusterIntegration|Learn more about environments"
 msgstr "袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 锌褉芯 褋械褉械写芯胁懈褖邪"
 
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr "袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 锌褉芯 泻芯薪褎褨谐褍褉邪褑褨褞 斜械蟹锌械泻懈"
+
 msgid "ClusterIntegration|Machine type"
 msgstr "孝懈锌 屑邪褕懈薪懈"
 
 msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
-msgstr ""
+msgstr "袙锌械胁薪褨褌褜褋褟, 褖芯 胁邪褕 芯斜谢褨泻芯胁懈泄 蟹邪锌懈褋 蟹邪写芯胁褨谢褜薪褟褦 %{link_to_requirements} 写谢褟 褋褌胁芯褉械薪薪褟 Kubernetes-泻谢邪褋褌械褉褨胁"
 
 msgid "ClusterIntegration|Manage"
 msgstr "校锌褉邪胁谢褨薪薪褟"
 
 msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
-msgstr ""
+msgstr "袣械褉褍泄褌械 胁邪褕懈屑 Kubernetes-泻谢邪褋褌械褉芯屑 蟹邪 写芯锌芯屑芯谐芯褞 %{link_gke}"
 
 msgid "ClusterIntegration|More information"
 msgstr "袛芯写邪褌泻芯胁邪 褨薪褎芯褉屑邪褑褨褟"
 
 msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
-msgstr ""
+msgstr "袛械泻褨谢褜泻邪 Kubernetes-泻谢邪褋褌械褉褨胁 写芯褋褌褍锌薪褨 胁 GitLab Enterprise Edition Premium 褌邪 Ultimate"
 
 msgid "ClusterIntegration|Note:"
 msgstr "袩褉懈屑褨褌泻邪:"
@@ -863,7 +1133,7 @@ msgid "ClusterIntegration|Number of nodes"
 msgstr "袣褨谢褜泻褨褋褌褜 胁褍蟹谢褨胁"
 
 msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
-msgstr ""
+msgstr "袙胁械写褨褌褜 褨薪褎芯褉屑邪褑褨褞 锌褉芯 写芯褋褌褍锌 写芯 褋胁芯谐芯 Kubernetes-泻谢邪褋褌械褉邪. 携泻褖芯 胁邪屑 锌芯褌褉褨斜薪邪 写芯锌芯屑芯谐邪, 胁懈 屑芯卸械褌械 锌褉芯褔懈褌邪褌懈 薪邪褕褨 %{link_to_help_page} 锌褉芯 Kubernetes"
 
 msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
 msgstr "袘褍写褜-谢邪褋泻邪 胁锌械胁薪褨褌褜褋褟, 褖芯 胁邪褕 芯斜谢褨泻芯胁懈泄 蟹邪锌懈褋 Google 蟹邪写芯胁芯谢褜薪褟褦 薪邪褋褌褍锌薪懈屑 胁懈屑芯谐邪屑:"
@@ -881,7 +1151,7 @@ msgid "ClusterIntegration|Prometheus"
 msgstr "Prometheus"
 
 msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
-msgstr ""
+msgstr "袩械褉械谐谢褟薪褜褌械 薪邪褕褍 %{link_to_help_page} 锌褉芯 褨薪褌械谐褉邪褑褨褞 褨蟹 Kubernetes."
 
 msgid "ClusterIntegration|Remove Kubernetes cluster integration"
 msgstr "袙褨写邪谢懈褌懈 褨薪褌械谐褉邪褑褨褞 褨蟹 Kubernetes-泻谢邪褋褌械褉芯屑"
@@ -890,7 +1160,7 @@ msgid "ClusterIntegration|Remove integration"
 msgstr "袙懈写邪谢懈褌懈 褨薪褌械谐褉邪褑褨褞"
 
 msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "袙懈写邪谢懈褌懈 泻芯薪褎褨谐褍褉邪褑褨褞 Kubernetes-泻谢邪褋褌械褉邪 写谢褟 褑褜芯谐芯 锌褉芯械泻褌褍. 笑械 薪械 锌褉懈蟹胁械写械 写芯 胁懈写邪谢械薪薪褟 褋邪屑芯谐芯 泻谢邪褋褌械褉邪."
 
 msgid "ClusterIntegration|Request to begin installing failed"
 msgstr "袟邪锌懈褌 锌褉芯 锌芯褔邪褌芯泻 胁褋褌邪薪芯胁谢械薪薪褟 薪械 胁懈泻芯薪邪薪芯"
@@ -898,8 +1168,11 @@ msgstr "袟邪锌懈褌 锌褉芯 锌芯褔邪褌芯泻 胁褋褌邪薪芯胁谢械薪薪褟 薪械 胁懈泻芯
 msgid "ClusterIntegration|Save changes"
 msgstr "袟斜械褉械谐褌懈 蟹屑褨薪懈"
 
+msgid "ClusterIntegration|Security"
+msgstr "袘械蟹锌械泻邪"
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
-msgstr ""
+msgstr "袩械褉械谐谢褟薪褍褌懈 褌邪 褉械写邪谐褍胁邪褌懈 锌邪褉邪屑械褌褉懈 胁邪褕芯谐芯 Kubernetes-泻谢邪褋褌械褉邪"
 
 msgid "ClusterIntegration|See machine types"
 msgstr "袩械褉械谐谢褟薪褍褌懈 褌懈锌懈 屑邪褕懈薪"
@@ -920,13 +1193,16 @@ msgid "ClusterIntegration|Something went wrong on our end."
 msgstr "些芯褋褜 锌褨褕谢芯 薪械 褌邪泻 蟹 薪邪褕芯谐芯 斜芯泻褍."
 
 msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 褋褌胁芯褉械薪薪褨 胁邪褕芯谐芯 Kubernetes-泻谢邪褋褌械褉邪 薪邪 Google Kubernetes Engine"
 
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr "袩褨写 褔邪褋 胁褋褌邪薪芯胁谢械薪薪褟 %{title} 褋褌邪谢邪褋褟 锌芯屑懈谢泻邪"
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr "小褌邪薪写邪褉褌薪邪 泻芯薪褎褨谐褍褉邪褑褨褟 泻谢邪褋褌械褉邪 薪邪写邪褦 写芯褋褌褍锌 写芯 褕懈褉芯泻芯谐芯 薪邪斜芯褉褍 褎褍薪泻褑褨泄, 薪械芯斜褏褨写薪懈褏 写谢褟 褍褋锌褨褕薪芯谐芯 褋褌胁芯褉械薪薪褟 褌邪 褉芯蟹谐芯褉褌邪薪薪褟 蟹邪褋褌芯褋褍薪泻褨胁-泻芯薪褌械泄薪械褉褨胁."
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
-msgstr ""
+msgstr "笑械泄 芯斜谢褨泻芯胁懈泄 蟹邪锌懈褋 锌芯胁懈薪械薪 屑邪褌懈 薪邪褋褌褍锌薪褨 锌褉邪胁邪 写谢褟 褋褌胁芯褉械薪薪褟 Kubernetes-泻谢邪褋褌械褉邪 胁 %{link_to_container_project}"
 
 msgid "ClusterIntegration|Toggle Kubernetes Cluster"
 msgstr "校胁褨屑泻薪褍褌懈/胁懈屑泻薪褍褌懈 Kubernetes-泻谢邪褋褌械褉"
@@ -938,7 +1214,7 @@ msgid "ClusterIntegration|Token"
 msgstr "孝芯泻械薪"
 
 msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
-msgstr ""
+msgstr "袟邪 写芯锌芯屑芯谐芯褞 锌褨写泻谢褞褔械薪芯谐芯 写芯 褑褜芯谐芯 锌褉芯械泻褌褍 Kubernetes-泻谢邪褋褌械褉邪, 胁懈 屑芯卸械褌械 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 Review Apps, 褉芯蟹谐芯褉褌邪褌懈 胁邪褕褨 锌褉芯械泻褌懈, 蟹邪锌褍褋泻邪褌懈 泻芯薪胁械褦褉懈 蟹斜褨褉泻懈 褌芯褖芯."
 
 msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
 msgstr "袙邪褕 芯斜谢褨泻芯胁懈泄 蟹邪锌懈褋 锌芯胁懈薪械薪 屑邪褌懈 %{link_to_kubernetes_engine}"
@@ -950,7 +1226,7 @@ msgid "ClusterIntegration|access to Google Kubernetes Engine"
 msgstr "写芯褋褌褍锌 写芯 Google Kubernetes Engine"
 
 msgid "ClusterIntegration|check the pricing here"
-msgstr ""
+msgstr "锌械褉械谐谢褟薪褍褌懈 褑褨薪懈 褌褍褌"
 
 msgid "ClusterIntegration|documentation"
 msgstr "写芯泻褍屑械薪褌邪褑褨褩"
@@ -970,6 +1246,12 @@ msgstr "锌褉邪胁懈谢褜薪芯 薪邪谢邪褕褌芯胁邪薪懈泄"
 msgid "Collapse"
 msgstr "袟谐芯褉薪褍褌懈"
 
+msgid "Comment and resolve discussion"
+msgstr "袟邪谢懈褕懈褌懈 泻芯屑械薪褌邪褉 褨 蟹邪胁械褉褕懈褌懈 芯斜谐芯胁芯褉械薪薪褟"
+
+msgid "Comment and unresolve discussion"
+msgstr "袟邪谢懈褕懈褌懈 泻芯屑械薪褌邪褉 褨 锌芯胁褌芯褉薪芯 胁褨写泻褉懈褌懈 芯斜谐芯胁芯褉械薪薪褟"
+
 msgid "Comments"
 msgstr "袣芯屑械薪褌邪褉褨"
 
@@ -978,6 +1260,14 @@ msgid_plural "Commits"
 msgstr[0] "袣芯屑褨褌"
 msgstr[1] "袣芯屑褨褌邪"
 msgstr[2] "袣芯屑褨褌褨胁"
+msgstr[3] "袣芯屑褨褌褨胁"
+
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] "袣芯屑褨褌 (%{commit_count})"
+msgstr[1] "袣芯屑褨褌邪 (%{commit_count})"
+msgstr[2] "袣芯屑褨褌褨胁 (%{commit_count})"
+msgstr[3] "袣芯屑褨褌褨胁 (%{commit_count})"
 
 msgid "Commit Message"
 msgstr "袣芯屑褨褌-锌芯胁褨写芯屑械谢薪薪褟"
@@ -989,7 +1279,10 @@ msgid "Commit message"
 msgstr "袣芯屑褨褌-锌芯胁褨写芯屑谢械薪薪褟"
 
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
-msgstr ""
+msgstr "小褌邪褌懈褋褌懈泻邪 泻芯屑褨褌褨胁 写谢褟 %{ref} %{start_time} - %{end_time}"
+
+msgid "Commit to %{branchName} branch"
+msgstr "袟邪泻芯屑褨褌懈褌懈 胁 谐褨谢泻褍 %{branchName}"
 
 msgid "CommitBoxTitle|Commit"
 msgstr "袣芯屑褨褌"
@@ -1004,16 +1297,16 @@ msgid "Commits feed"
 msgstr "袣邪薪邪谢 泻芯屑褨褌褨胁"
 
 msgid "Commits per day hour (UTC)"
-msgstr ""
+msgstr "袣芯屑褨褌褨胁 锌芯 谐芯写懈薪邪屑 写薪褟 (蟹邪 袚褉褨薪胁褨褔械屑)"
 
 msgid "Commits per day of month"
-msgstr ""
+msgstr "袣芯屑褨褌褨胁 锌芯 写薪褟屑 屑褨褋褟褑褟"
 
 msgid "Commits per weekday"
-msgstr ""
+msgstr "袣芯屑褨褌褨胁 锌芯 写薪褟屑 褌懈卸薪褟"
 
 msgid "Commits|An error occurred while fetching merge requests data."
-msgstr ""
+msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褨写 褔邪褋 芯褌褉懈屑邪薪薪褟 写邪薪懈褏 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟."
 
 msgid "Commits|Commit: %{commitText}"
 msgstr "袣芯屑褨褌: %{commitText}"
@@ -1022,7 +1315,7 @@ msgid "Commits|History"
 msgstr "袉褋褌芯褉褨褟"
 
 msgid "Commits|No related merge requests found"
-msgstr ""
+msgstr "袧械 蟹薪邪泄写械薪芯 锌芯胁'褟蟹邪薪懈褏 蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
 
 msgid "Committed by"
 msgstr "袣芯屑褨褌 胁褨写"
@@ -1031,14 +1324,20 @@ msgid "Compare"
 msgstr "袩芯褉褨胁薪褟褌懈"
 
 msgid "Compare Git revisions"
-msgstr ""
+msgstr "袩芯褉褨胁薪褟褌懈 Git-褉械写邪泻褑褨褩"
 
 msgid "Compare Revisions"
 msgstr "袩芯褉褨胁薪褟薪薪褟 褉械写邪泻褑褨泄"
 
-msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgid "Compare changes with the last commit"
 msgstr ""
 
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr "%{source_branch} 褨 %{target_branch} 芯写薪邪泻芯胁褨."
+
 msgid "CompareBranches|Compare"
 msgstr "袩芯褉褨胁薪褟褌懈"
 
@@ -1046,14 +1345,50 @@ msgid "CompareBranches|Source"
 msgstr "袛卸械褉械谢芯"
 
 msgid "CompareBranches|Target"
-msgstr ""
+msgstr "笑褨谢褜"
 
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr "袣芯薪褎褨写械薪褑褨泄薪褨褋褌褜"
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr "袩褨写泻谢褞褔懈褌懈"
+
+msgid "Connect all repositories"
+msgstr "袩褨写泻谢褞褔懈褌懈 胁褋褨 褉械锌芯蟹懈褌芯褉褨褩"
+
+msgid "Connect repositories from GitHub"
+msgstr "袩褨写泻谢褞褔懈褌懈 褉械锌芯蟹懈褌芯褉褨褩 蟹 GitHub"
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr "袩褨写泻谢褞褔褨褌褜 胁邪褕褨 蟹芯胁薪褨褕薪褨 褉械锌芯蟹懈褌芯褉褨褩, 褨 CI/CD 泻芯薪胁械褦褉懈 斜褍写褍褌褜 蟹邪锌褍褋泻邪褌懈褋褟 写谢褟 薪芯胁懈褏 泻芯屑褨褌褨胁. 小褌胁芯褉械薪懈泄 GitLab-锌褉芯械泻褌 斜褍写械 屑邪褌懈 谢懈褕械 CI/CD 褎褍薪褑褨褩."
+
+msgid "Connecting..."
+msgstr "袟'褦写薪邪薪薪褟..."
+
 msgid "Container Registry"
 msgstr "袪械褦褋褌褉 袣芯薪褌械泄薪械褉褨胁"
 
@@ -1099,11 +1434,17 @@ msgstr "袙懈泻芯褉懈褋褌芯胁褍泄褌械 褉褨蟹薪褨 褨屑械薪邪 芯斜褉邪蟹褨胁"
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr "袟邪 写芯锌芯屑芯谐芯褞 胁斜褍写芯胁邪薪芯谐芯 胁 GitLab 褉械褦褋褌褉褍 Docker 泻芯薪褌械泄薪械褉褨胁 泻芯卸械薪 锌褉芯械泻褌 屑芯卸械 屑邪褌懈 胁谢邪褋薪械 屑褨褋褑械 写谢褟 蟹斜械褉褨谐邪薪薪褟 Docker 芯斜褉邪蟹褨胁."
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr "袙薪械褋芯泻"
+
 msgid "Contribution guide"
 msgstr "袉薪褋褌褉褍泻褑褨褟 写谢褟 褍褔邪褋薪懈泻褨胁"
 
 msgid "Contributors"
-msgstr "袣芯薪褌褉懈斜鈥櫻幯傂狙€懈"
+msgstr "校褔邪褋薪懈泻懈"
 
 msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
 msgstr "%{startDate} 鈥� %{endDate}"
@@ -1132,11 +1473,14 @@ msgstr "小泻芯锌褨褞胁邪褌懈 URL 胁 斜褍褎械褉 芯斜屑褨薪褍"
 msgid "Copy branch name to clipboard"
 msgstr "小泻芯锌褨褞胁邪褌懈 薪邪蟹胁褍 谐褨谢泻懈 胁 斜褍褎械褉 芯斜屑褨薪褍"
 
+msgid "Copy command to clipboard"
+msgstr "小泻芯锌褨褞胁邪褌懈 泻芯屑邪薪写褍 胁 斜褍褎械褉 芯斜屑褨薪褍"
+
 msgid "Copy commit SHA to clipboard"
 msgstr "小泻芯锌褨褞胁邪褌懈 褨写械薪褌懈褎褨泻邪褌芯褉 胁 斜褍褎械褉 芯斜屑褨薪褍"
 
 msgid "Copy reference to clipboard"
-msgstr ""
+msgstr "小泻芯锌褨褞胁邪褌懈 锌芯褋懈谢邪薪薪褟 胁 斜褍褎械褉 芯斜屑褨薪褍"
 
 msgid "Create"
 msgstr "小褌胁芯褉懈褌懈"
@@ -1144,14 +1488,23 @@ msgstr "小褌胁芯褉懈褌懈"
 msgid "Create New Directory"
 msgstr "小褌胁芯褉懈褌懈 薪芯胁懈泄 泻邪褌邪谢芯谐"
 
+msgid "Create a new branch"
+msgstr "小褌胁芯褉懈褌懈 薪芯胁褍 谐褨谢泻褍"
+
+msgid "Create a new branch and merge request"
+msgstr "小褌胁芯褉懈褌懈 薪芯胁褍 谐褨谢泻褍 褨 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "小褌胁芯褉褨褌褜 褌芯泻械薪 写芯褋褌褍锌褍 写谢褟 胁邪褕芯谐芯 邪泻泻邪褍薪褌邪, 褖芯斜 胁褨写锌褉邪胁谢褟褌懈 褌邪 芯褌褉懈屑褍胁邪褌懈 褔械褉械蟹 %{protocol}."
 
+msgid "Create branch"
+msgstr "小褌胁芯褉懈褌懈 谐褨谢泻褍"
+
 msgid "Create directory"
 msgstr "小褌胁芯褉懈褌懈 泻邪褌邪谢芯谐"
 
-msgid "Create empty bare repository"
-msgstr "小褌胁芯褉懈褌懈 锌芯褉芯卸薪褨泄 褉械锌芯蟹懈褌芯褉褨泄"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr "小褌胁芯褉懈褌懈 械锌褨泻"
@@ -1159,12 +1512,18 @@ msgstr "小褌胁芯褉懈褌懈 械锌褨泻"
 msgid "Create file"
 msgstr "小褌胁芯褉懈褌懈 褎邪泄谢"
 
+msgid "Create group label"
+msgstr "小褌胁芯褉懈褌懈 屑褨褌泻褍 谐褉褍锌懈"
+
 msgid "Create lists from labels. Issues with that label appear in that list."
-msgstr ""
+msgstr "小褌胁芯褉懈褌懈 褋锌懈褋芯泻 薪邪 芯褋褌薪芯胁褨 屑褨褌芯泻. 袙 薪褜芯屑褍 斜褍写褍褌褜 锌褉芯斜谢械屑懈 蟹 褌邪泻懈屑懈 屑褨褌泻邪屑懈."
 
 msgid "Create merge request"
 msgstr "小褌胁芯褉懈褌懈 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
 
+msgid "Create merge request and branch"
+msgstr "小褌胁芯褉懈褌懈 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 褌邪 谐褨谢泻褍"
+
 msgid "Create new branch"
 msgstr "小褌胁芯褉懈褌懈 薪芯胁褍 谐褨谢泻褍"
 
@@ -1180,6 +1539,9 @@ msgstr "小褌胁芯褉懈褌懈 薪芯胁褍 屑褨褌泻褍"
 msgid "Create new..."
 msgstr "小褌胁芯褉懈褌懈..."
 
+msgid "Create project label"
+msgstr "小褌胁芯褉懈褌懈 屑褨褌泻褍 锌褉芯械泻褌褍"
+
 msgid "CreateNewFork|Fork"
 msgstr "肖芯褉泻"
 
@@ -1189,6 +1551,12 @@ msgstr "孝械谐"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "小褌胁芯褉懈褌懈 褌芯泻械薪 写谢褟 芯褋芯斜懈褋褌芯谐芯 写芯褋褌褍锌褍"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr "小褌胁芯褉褞褦 薪芯胁褍 谐褨谢泻褍 褨蟹 %{branchName}"
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr "小褌胁芯褉褞褦 薪芯胁褍 谐褨谢泻褍 褨蟹 %{branchName} 褨 锌械褉械薪邪锌褉邪胁谢褟褦 写谢褟 褋褌胁芯褉械薪薪褟 薪芯胁芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟"
+
 msgid "Creating epic"
 msgstr "小褌胁芯褉械薪薪褟 械锌褨泻褍"
 
@@ -1207,6 +1575,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "袗薪邪谢褨蟹 褑懈泻谢褍"
 
@@ -1243,6 +1614,9 @@ msgstr "谐褉褍写."
 msgid "December"
 msgstr "谐褉褍写械薪褜"
 
+msgid "Default classification label"
+msgstr "袦褨褌泻邪 泻谢邪褋懈褎褨泻邪褑褨褩 蟹邪 蟹邪屑芯胁褔褍胁邪薪薪褟屑"
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "袙懈蟹薪邪褔褌械 胁谢邪褋薪懈泄 褕邪斜谢芯薪 蟹邪 写芯锌芯屑芯谐芯褞 褋懈薪褌邪泻褋懈褋褍 cron"
 
@@ -1254,6 +1628,7 @@ msgid_plural "Deploys"
 msgstr[0] "袪芯蟹谐芯褉褌邪薪薪褟"
 msgstr[1] "袪芯蟹谐芯褉褌邪薪薪褟"
 msgstr[2] "袪芯蟹谐芯褉褌邪薪褜"
+msgstr[3] "袪芯蟹谐芯褉褌邪薪褜"
 
 msgid "Deploy Keys"
 msgstr "袣谢褞褔褨 写谢褟 褉芯蟹谐芯褉褌褍胁邪薪薪褟"
@@ -1268,7 +1643,7 @@ msgid "Details"
 msgstr "袛械褌邪谢褨"
 
 msgid "Diffs|No file name available"
-msgstr ""
+msgstr "袉屑'褟 褎邪泄谢褍 薪械 写芯褋褌褍锌薪械"
 
 msgid "Directory name"
 msgstr "袉屑'褟 泻邪褌邪谢芯谐褍"
@@ -1276,21 +1651,27 @@ msgstr "袉屑'褟 泻邪褌邪谢芯谐褍"
 msgid "Disable"
 msgstr "袙懈屑泻薪褍褌懈"
 
-msgid "Discard changes"
-msgstr "小泻邪褋褍胁邪褌懈 蟹屑褨薪懈"
+msgid "Discard draft"
+msgstr "袙懈写邪谢懈褌懈 褔械褉薪械褌泻褍"
 
 msgid "Discover GitLab Geo."
-msgstr ""
+msgstr "袙褨写泻褉懈泄褌械 GitLab Geo."
 
 msgid "Dismiss Cycle Analytics introduction box"
 msgstr "袙褨写屑褨薪懈褌懈 斜谢芯泻 胁褋褌褍锌褍 写芯 袗薪邪谢懈褌懈泻懈 笑懈泻谢褍"
 
 msgid "Dismiss Merge Request promotion"
-msgstr "袙褨写褏懈谢懈褌懈 褉械泻谢邪屑褍 写谢褟 袟邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
+msgstr "袙懈屑泻薪褍褌懈 褉械泻谢邪屑褍 写谢褟 袟邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
+
+msgid "Documentation for popular identity providers"
+msgstr ""
 
 msgid "Don't show again"
 msgstr "袧械 锌芯泻邪蟹褍胁邪褌懈 蟹薪芯胁褍"
 
+msgid "Done"
+msgstr "袚芯褌芯胁芯"
+
 msgid "Download"
 msgstr "袟邪胁邪薪褌邪卸懈褌懈"
 
@@ -1310,7 +1691,7 @@ msgid "DownloadArtifacts|Download"
 msgstr "袟邪胁邪薪褌邪卸懈褌懈"
 
 msgid "DownloadCommit|Email Patches"
-msgstr "Email-锌邪褌褔懈"
+msgstr "Email-锌邪褌褔褨"
 
 msgid "DownloadCommit|Plain Diff"
 msgstr "袩褉芯褋褌械 锌芯褉褨胁薪褟薪薪褟 (diff)"
@@ -1318,9 +1699,15 @@ msgstr "袩褉芯褋褌械 锌芯褉褨胁薪褟薪薪褟 (diff)"
 msgid "DownloadSource|Download"
 msgstr "袟邪胁邪薪褌邪卸懈褌懈"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr "袟邪锌谢邪薪芯胁邪薪邪 写邪褌邪 蟹邪胁械褉褕械薪薪褟"
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "袪械写邪谐褍胁邪褌懈"
 
@@ -1328,6 +1715,18 @@ msgid "Edit Pipeline Schedule %{id}"
 msgstr "袪械写邪谐褍胁邪褌懈 袪芯蟹泻谢邪写 袣芯薪胁械褦褉邪 %{id}"
 
 msgid "Edit files in the editor and commit changes here"
+msgstr "袪械写邪谐褍泄褌械 褎邪泄谢懈 胁 褉械写邪泻褌芯褉褨 褨 蟹邪泻芯屑褨褌褜褌械 蟹屑褨薪懈 褌褍褌"
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
 msgstr ""
 
 msgid "Emails"
@@ -1336,6 +1735,36 @@ msgstr "袗写褉械褋懈 械谢械泻褌褉芯薪薪芯褩 锌芯褕褌懈"
 msgid "Enable"
 msgstr "校胁褨屑泻薪褍褌懈"
 
+msgid "Enable Auto DevOps"
+msgstr "校胁褨屑泻薪褍褌懈 Auto DevOps"
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr "袙懈薪懈泻谢邪 锌芯屑懈谢泻邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪薪褨 褋械褉械写芯胁懈褖."
 
@@ -1390,32 +1819,44 @@ msgstr "袝锌褨泻 斜褍写械 胁懈写邪谢械薪芯! 袙懈 胁锌械胁薪械薪褨?"
 msgid "Epics"
 msgstr "袝锌褨泻懈"
 
+msgid "Epics Roadmap"
+msgstr "袩谢邪薪-谐褉邪褎褨泻 械锌褨泻褨胁"
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr "袝锌褨泻懈 写芯蟹胁芯谢褟褞褌褜 泻械褉褍胁邪褌懈 胁邪褕懈屑 锌芯褉褌褎械谢械屑 锌褉芯械泻褌褨胁 械褎械泻褌懈胁薪褨褕械 褌邪 蟹 屑械薪褕懈屑懈 蟹褍褋懈谢谢褟屑懈"
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 写邪薪懈褏 谐褨谢泻懈. 袘褍写褜 谢邪褋泻邪, 褋锌褉芯斜褍泄褌械 锌褨蟹薪褨褕械."
+
+msgid "Error committing changes. Please try again."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 泻芯屑褨褌褨 蟹屑褨薪. 袘褍写褜 谢邪褋泻邪, 褋锌褉芯斜褍泄褌械 锌褨蟹薪褨褕械."
+
 msgid "Error creating epic"
 msgstr "袩芯屑懈谢泻邪 锌褉懈 褋褌胁芯褉械薪薪褨 械锌褨泻褍"
 
 msgid "Error fetching contributors data."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 芯褌褉懈屑邪薪薪褟 写邪薪懈褏 褍褔邪褋薪懈泻褨胁."
 
 msgid "Error fetching labels."
 msgstr "袩芯屑懈谢泻邪 蟹邪胁邪薪褌邪卸械薪薪褟 屑褨褌芯泻."
 
 msgid "Error fetching network graph."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 谐褉邪褎邪 屑械褉械卸褨."
 
 msgid "Error fetching refs"
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 蟹邪胁邪薪褌邪卸械薪薪褟 refs"
 
 msgid "Error fetching usage ping data."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 写邪薪薪懈褏 锌芯 锌械褉械胁褨褉褑褨 蟹鈥櫻斝葱叫靶叫窖�."
 
 msgid "Error occurred when toggling the notification subscription"
 msgstr "小褌邪谢邪褋褟 锌芯屑懈谢泻邪 锌褨写 褔邪褋 锌褨写泻谢褞褔械薪薪褟 锌褨写锌懈褋泻懈 薪邪 褋锌芯胁褨褖械薪薪褟"
 
 msgid "Error saving label update."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹斜械褉械卸械薪薪褨 屑褨褌泻懈."
 
 msgid "Error updating status for all todos."
 msgstr "袩芯屑懈谢泻邪 芯薪芯胁谢械薪薪褟 褋褌邪褌褍褋褍 写谢褟 胁褋褨褏 蟹邪写邪褔."
@@ -1459,12 +1900,45 @@ msgstr "袨谐谢褟写 锌褉芯械泻褌褨胁"
 msgid "Explore public groups"
 msgstr "袩械褉械谐谢褟薪褍褌懈 锌褍斜谢褨褔薪褨 谐褉褍锌懈"
 
+msgid "External Classification Policy Authorization"
+msgstr "袗胁褌芯褉懈蟹邪褑褨褟 胁褨写锌芯胁褨写薪芯 写芯 蟹芯胁薪褨褕薪褜芯褩 锌芯谢褨褌懈泻懈"
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr "袟芯胁薪褨褕薪褟 邪胁褌芯褉懈蟹邪褑褨褟 蟹邪斜芯褉芯薪懈谢邪 写芯褋褌褍锌 写芯 褑褜芯谐芯 锌褉芯械泻褌褍"
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr "袦褨褌泻邪 泻谢邪褋懈褎褨泻邪褑褨褩"
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr "袦褨褌泻邪 泻谢邪褋懈褎褨泻邪褑褨褩"
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr "携泻褖芯 泻谢邪褋懈褎褨泻邪褑褨泄薪褍 屑褨褌泻褍 薪械 胁褋褌邪薪芯胁谢械薪芯, 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈屑械褌褜褋褟 褋褌邪薪写邪褉褌薪邪 屑褨褌泻邪 `%{default_label}`."
+
+msgid "Failed"
+msgstr "袧械胁写邪谢芯"
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "袧械 胁写邪谢芯褋褟 蟹屑褨薪懈褌懈 胁谢邪褋薪懈泻邪"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr "袧械 胁写邪谢芯褋褟 胁懈写邪谢懈褌懈 锌褉芯斜谢械屑褍 蟹 写芯褕泻懈, 斜褍写褜 谢邪褋泻邪, 褋锌褉芯斜褍泄褌械 褖械 褉邪蟹."
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "袧械 胁写邪谢芯褋褟 胁懈写邪谢懈褌懈 褉芯蟹泻谢邪写 泻芯薪胁械褦褉邪"
 
+msgid "Failed to update issues, please try again."
+msgstr "袧械 胁写邪谢芯褋褟 芯薪芯胁懈褌懈 锌褉芯斜谢械屑懈. 袘褍写褜 谢邪褋泻邪, 褋锌褉芯斜褍泄褌械 褖械 褉邪蟹."
+
 msgid "Feb"
 msgstr "谢褞褌."
 
@@ -1472,7 +1946,7 @@ msgid "February"
 msgstr "谢褞褌懈泄"
 
 msgid "Fields on this page are now uneditable, you can configure"
-msgstr ""
+msgstr "袩芯谢褟 薪邪 褑褨泄 褋褌芯褉褨薪褑褨 蟹邪褉邪蟹 薪械写芯褋褌褍锌薪褨 写谢褟 褉械写邪谐褍胁邪薪薪褟, 胁懈 屑芯卸械褌械 薪邪谢邪褕褌褍胁邪褌懈"
 
 msgid "File name"
 msgstr "袉屑'褟 褎邪泄谢褍"
@@ -1480,6 +1954,12 @@ msgstr "袉屑'褟 褎邪泄谢褍"
 msgid "Files"
 msgstr "肖邪泄谢懈"
 
+msgid "Files (%{human_size})"
+msgstr "肖邪泄谢懈 (%{human_size})"
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "肖褨谢褜褌褉褍胁邪褌懈 蟹邪 泻芯屑褨褌-锌芯胁褨写芯屑谢械薪薪褟屑"
 
@@ -1489,17 +1969,27 @@ msgstr "袩芯褕褍泻 锌芯 褕谢褟褏褍"
 msgid "Find file"
 msgstr "袟薪邪泄褌懈 褎邪泄谢"
 
+msgid "Finished"
+msgstr "袟邪胁械褉褕械薪芯"
+
 msgid "FirstPushedBy|First"
 msgstr "袩械褉褕懈泄"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "胁褨写锌褉邪胁谢械薪芯"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "肖芯褉泻"
 msgstr[1] "肖芯褉泻懈"
 msgstr[2] "肖芯褉泻褨胁"
+msgstr[3] "肖芯褉泻褨胁"
 
 msgid "ForkedFromProjectPath|Forked from"
 msgstr "肖芯褉泻 胁褨写"
@@ -1507,38 +1997,53 @@ msgstr "肖芯褉泻 胁褨写"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr "肖芯褉泻 褨蟹 %{project_name} (胁懈写邪谢械薪芯)"
 
+msgid "Forking in progress"
+msgstr "袙褨写斜褍胁邪褦褌褜褋褟 褋褌胁芯褉械薪薪褟 褎芯褉泻褍"
+
 msgid "Format"
 msgstr "肖芯褉屑邪褌"
 
+msgid "From %{provider_title}"
+msgstr "袟 %{provider_title}"
+
 msgid "From issue creation until deploy to production"
 msgstr "袟 屑芯屑械薪褌褍 褋褌胁芯褉械薪薪褟 锌褉芯斜谢械屑懈 写芯 褉芯蟹谐芯褉褌邪薪薪褟 薪邪 production"
 
 msgid "From merge request merge until deploy to production"
 msgstr "袙褨写 胁懈泻芯薪邪薪薪褟 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟 写芯 褉芯蟹谐芯褉褌邪薪薪褟 薪邪 production"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr "袉蟹 褋褌芯褉褨薪泻懈 写械褌邪谢械泄 Kubernetes-泻谢邪褋褌械褉邪, 胁褋褌邪薪芯胁褨褌褜 runner 蟹褨 褋锌懈褋泻褍 蟹邪褋褌芯褋褍薪泻褨胁"
+
 msgid "GPG Keys"
 msgstr "GPG 泻谢褞褔褨"
 
 msgid "Generate a default set of labels"
-msgstr ""
+msgstr "小褌胁芯褉懈褌懈 褋褌邪薪写邪褉褌薪懈泄 薪邪斜褨褉 屑褨褌泻芯泻"
 
 msgid "Geo Nodes"
 msgstr "袚械芯-袙褍蟹谢懈"
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr "袙褍蟹芯谢 薪械 锌褉邪褑褞褦 邪斜芯 蟹谢邪屑邪薪懈泄."
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr "袙褍蟹芯谢 锌褉邪褑褞褦 锌芯胁褨谢褜薪芯, 锌械褉械胁邪薪褌邪卸械薪懈泄 邪斜芯 褌褨谢褜泻懈 褖芯 胁褨写薪芯胁懈胁褋褟 锌褨褋谢褟 蟹斜芯褞."
 
+msgid "GeoNodes|Checksummed"
+msgstr "袉蟹 泻芯薪褌褉芯谢褜薪芯褞 褋褍屑芯褞"
+
 msgid "GeoNodes|Database replication lag:"
-msgstr ""
+msgstr "袟邪褌褉懈屑泻邪 褉械锌谢褨泻邪褑褨褩 斜邪蟹懈 写邪薪懈褏:"
 
 msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
+msgstr "袙褨写泻谢褞褔械薪薪褟 胁褍蟹谢邪 蟹褍锌懈薪褟褦 锌褉芯褑械褋 褋懈薪褏褉芯薪褨蟹邪褑褨褩. 袙懈 胁锌械胁薪械薪褨?"
 
 msgid "GeoNodes|Does not match the primary storage configuration"
-msgstr ""
+msgstr "袧械 胁褨写锌芯胁褨写邪褦 芯褋薪芯胁薪褨泄 泻芯薪褎褨谐褍褉邪褑褨褩 褋褏芯胁懈褖邪"
 
 msgid "GeoNodes|Failed"
 msgstr "袧械胁写邪谢芯"
@@ -1547,85 +2052,124 @@ msgid "GeoNodes|Full"
 msgstr "袩芯胁薪懈泄"
 
 msgid "GeoNodes|GitLab version does not match the primary node version"
-msgstr ""
+msgstr "袙械褉褋褨褟 GitLab 薪械 胁褨写锌芯胁褨写邪褦 胁械褉褋褨褩 芯褋薪芯胁薪芯谐芯 胁褍蟹谢邪"
 
 msgid "GeoNodes|GitLab version:"
-msgstr ""
+msgstr "袙械褉褋褨褟 GitLab:"
 
 msgid "GeoNodes|Health status:"
-msgstr ""
+msgstr "小褌邪薪 锌褉邪褑械蟹写邪褌薪芯褋褌褨:"
 
 msgid "GeoNodes|Last event ID processed by cursor:"
-msgstr ""
+msgstr "袉写械薪褌懈褎褨泻邪褌芯褉 芯褋褌邪薪薪褜芯褩 锌芯写褨褩, 芯斜褉芯斜谢械薪芯褩 泻褍褉褋芯褉芯屑:"
 
 msgid "GeoNodes|Last event ID seen from primary:"
-msgstr ""
+msgstr "袉写械薪褌懈褎褨泻邪褌芯褉 芯褋褌邪薪薪褜芯褩 锌芯写褨褩 胁懈写懈屑芯褩 褨蟹 芯褋薪芯胁薪芯谐芯:"
 
 msgid "GeoNodes|Loading nodes"
-msgstr ""
+msgstr "袟邪胁邪薪褌邪卸械薪薪褟 胁褍蟹谢褨胁"
 
 msgid "GeoNodes|Local Attachments:"
-msgstr ""
+msgstr "袥芯泻邪谢褜薪褨 胁泻谢邪写械薪薪褟 (attachments):"
 
 msgid "GeoNodes|Local LFS objects:"
-msgstr ""
+msgstr "袥芯泻邪谢褜薪褨 LFS-芯斜鈥櫻斝貉傂�:"
 
 msgid "GeoNodes|Local job artifacts:"
-msgstr ""
+msgstr "袗褉褌械褎邪泻褌懈 谢芯泻邪谢褜薪懈褏 蟹邪胁写邪薪褜:"
 
 msgid "GeoNodes|New node"
-msgstr ""
+msgstr "袧芯胁懈泄 胁褍蟹芯谢"
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr "袗褍褌械薪褌懈褎褨泻邪褑褨褞 胁褍蟹谢邪 褍褋锌褨褕薪芯 锌芯谢邪谐芯写卸械薪芯."
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr "袙褍蟹芯谢 褍褋锌褨褕薪芯 胁懈写邪谢械薪芯."
+
+msgid "GeoNodes|Not checksummed"
+msgstr "袘械蟹 泻芯薪褌褉芯谢褜薪芯褩 褋褍屑懈"
 
 msgid "GeoNodes|Out of sync"
-msgstr ""
+msgstr "袪芯蟹褋懈薪褏褉芯薪褨蟹芯胁邪薪褨"
+
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr "袙懈写邪谢械薪薪褟 胁褍蟹谢邪 蟹褍锌懈薪褟褦 锌褉芯褑械褋 褋懈薪褏褉芯薪褨蟹邪褑褨褩. 袙懈 胁锌械胁薪械薪褨?"
 
 msgid "GeoNodes|Replication slot WAL:"
-msgstr ""
+msgstr "小谢芯褌 褉械锌谢褨泻邪褑褨褩 WAL:"
 
 msgid "GeoNodes|Replication slots:"
-msgstr ""
+msgstr "小谢芯褌懈 褉械锌谢褨泻邪褑褨褩:"
+
+msgid "GeoNodes|Repositories checksummed:"
+msgstr "袪械锌芯蟹懈褌芯褉褨褩 褨蟹 泻芯薪褌褉芯谢褜薪懈屑懈 褋褍屑邪屑懈:"
 
 msgid "GeoNodes|Repositories:"
-msgstr ""
+msgstr "袪械锌芯蟹懈褌芯褉褨褩:"
+
+msgid "GeoNodes|Repository checksums verified:"
+msgstr "袪械锌芯蟹懈褌芯褉褨褩 褨蟹 锌褨写褌胁械褉写卸械薪芯褞 泻芯薪褌褉芯谢褜薪芯褞 褋褍屑芯褞:"
 
 msgid "GeoNodes|Selective"
-msgstr ""
+msgstr "袙懈斜褨褉泻芯胁褨"
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr "袩褉芯斜谢械屑邪 锌褉懈 蟹屑褨薪褨 褋褌邪褌褍褋邪 胁褍蟹谢邪"
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr "袩褉芯斜谢械屑邪 锌褉懈 胁懈写邪谢械薪薪褨 胁褍蟹谢邪"
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr "袩褉芯斜谢械屑邪 锌褉懈 锌芯谢邪谐芯写卸械薪薪褨 胁褍蟹谢邪"
 
 msgid "GeoNodes|Storage config:"
-msgstr ""
+msgstr "袣芯薪褎褨谐褍褉邪褑褨褟 褋褏芯胁懈褖邪:"
 
 msgid "GeoNodes|Sync settings:"
-msgstr ""
+msgstr "袧邪谢邪褕褌褍胁邪薪薪褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩:"
 
 msgid "GeoNodes|Synced"
-msgstr ""
+msgstr "小懈薪褏褉芯薪褨蟹芯胁邪薪芯"
 
 msgid "GeoNodes|Unused slots"
-msgstr ""
+msgstr "袧械胁懈泻芯褉懈褋褌邪薪褨 褋谢芯褌懈"
+
+msgid "GeoNodes|Unverified"
+msgstr "袧械锌褨写褌胁械褉写卸械薪褨"
 
 msgid "GeoNodes|Used slots"
-msgstr ""
+msgstr "袙懈泻芯褉懈褋褌邪薪褨 褋谢芯褌懈"
+
+msgid "GeoNodes|Verified"
+msgstr "袩褨写褌胁械褉写卸械薪褨"
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr "Wiki 褉械锌芯蟹懈褌芯褉褨褩 褨蟹 锌褨写褌胁械褉写卸械薪芯褞 泻芯薪褌褉芯谢褜薪芯褞 褋褍屑芯褞:"
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr "Wiki 褨蟹 泻芯薪褌褉芯谢褜薪芯褞 褋褍屑芯褞:"
 
 msgid "GeoNodes|Wikis:"
-msgstr ""
+msgstr "Wiki:"
 
 msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
-msgstr ""
+msgstr "袙懈 薪邪谢邪褕褌褍胁邪谢懈 Geo-胁褍蟹谢懈 褔械褉械蟹 薪械蟹邪褏懈褖械薪械 HTTP-蟹鈥櫻斝葱叫靶叫窖�. 袦懈 褉械泻芯屑械薪写褍褦屑芯 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 HTTPS."
 
 msgid "Geo|All projects"
-msgstr ""
+msgstr "袙褋褨 锌褉芯械泻褌懈"
 
 msgid "Geo|File sync capacity"
 msgstr "袩褉芯锌褍褋泻薪邪 蟹写邪褌薪褨褋褌褜 褋懈薪褏褉芯薪褨蟹邪褑褨褩 褎邪泄谢褨胁"
 
 msgid "Geo|Groups to synchronize"
-msgstr ""
+msgstr "袚褉褍锌懈 写谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩"
 
 msgid "Geo|Projects in certain groups"
-msgstr ""
+msgstr "袩褉芯械泻褌懈 胁 锌械胁薪懈褏 谐褉褍锌邪褏"
 
 msgid "Geo|Projects in certain storage shards"
-msgstr ""
+msgstr "袩褉芯械泻褌懈 胁 锌械胁薪懈褏 褋械谐屑械薪褌邪褏 褋褏芯胁懈褖"
 
 msgid "Geo|Repository sync capacity"
 msgstr "袩褉芯锌褍褋泻薪邪 蟹写邪褌薪褨褋褌褜 褋懈薪褏褉芯薪褨蟹邪褑褨褩 褉械锌芯蟹懈褌芯褉褨褩胁"
@@ -1634,23 +2178,44 @@ msgid "Geo|Select groups to replicate."
 msgstr "袙懈斜械褉褨褌褜 谐褉褍锌懈 写谢褟 褉械锌谢褨泻邪褑褨褩."
 
 msgid "Geo|Shards to synchronize"
-msgstr ""
+msgstr "小械谐屑械薪褌懈 写谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩"
+
+msgid "Git repository URL"
+msgstr "URL Git-褉械锌芯蟹懈褌芯褉褨褟"
 
 msgid "Git revision"
-msgstr ""
+msgstr "Git-褉械写邪泻褑褨褟"
 
 msgid "Git storage health information has been reset"
 msgstr "袉薪褎芯褉屑邪褑褨褟 锌褉芯 褋褌邪褌褍褋 蟹斜械褉褨谐邪薪薪褟 Git 斜褍谢邪 褋泻懈薪褍褌邪"
 
 msgid "Git version"
+msgstr "Git-胁械褉褋褨褟"
+
+msgid "GitHub import"
+msgstr "GitHub-褨屑锌芯褉褌"
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
 msgstr ""
 
 msgid "GitLab Runner section"
 msgstr "袪芯蟹写褨谢 GitLab Runner"
 
-msgid "Gitaly Servers"
+msgid "GitLab single sign on URL"
 msgstr ""
 
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr "小械褉胁械褉懈 Gitaly"
+
+msgid "Go back"
+msgstr "袩芯胁械褉薪褍褌懈褋褟"
+
 msgid "Go to your fork"
 msgstr "袩械褉械泄褌懈 写芯 胁邪褕芯谐芯 褎芯褉泻褍"
 
@@ -1661,7 +2226,25 @@ msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab ad
 msgstr "袗褍褌械薪褌懈褎褨泻邪褑褨褟 Google 薪械 %{link_to_documentation}. 袩芯锌褉芯褋褨褌褜 褋胁芯谐芯 邪写屑褨薪褨褋褌褉邪褌芯褉邪 GitLab, 褟泻褖芯 胁懈 褏芯褔械褌械 褋泻芯褉懈褋褌邪褌懈褋褟 褑懈屑 褋械褉胁褨褋芯屑."
 
 msgid "Got it!"
-msgstr ""
+msgstr "袟褉芯蟹褍屑褨谢芯!"
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr "袝锌褨泻懈 写芯蟹胁芯谢褞褌褜 胁邪屑 泻械褉褍胁邪褌懈 锌芯褉褌褎芯谢褨芯 胁邪褕懈褏 锌褉芯械泻褌褨胁 斜褨谢褜褕 械褎械泻褌懈胁薪芯 褨 蟹 屑械薪褕懈屑懈 蟹褍褋懈谢谢褟屑懈"
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr "袙褨写 %{dateWord}"
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr "袟邪胁邪薪褌邪卸械薪薪褟 锌谢邪薪褍-谐褉邪褎褨泻褍"
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr "袩褉芯斜谢械屑邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪薪褨 械锌褨泻褨胁"
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr "袛谢褟 锌械褉械谐谢褟写褍 锌谢邪薪褍-谐褉邪褎褨泻褍 写芯写邪泄褌械 蟹邪锌谢邪薪芯胁邪薪褨 写邪褌懈 锌芯褔邪褌泻褍 褌邪 蟹邪胁械褉褕械薪薪褟 写芯 芯写薪芯谐芯 蟹 胁邪褕懈褏 械锌褨泻褨胁 褍 褑褨泄 谐褉褍锌褨 邪斜芯 褩褩 锌褨写谐褉褍锌邪褏. 袙褨写芯斜褉邪卸邪褞褌褜褋褟 谢懈褕械 械锌褨泻懈 蟹邪 锌芯锌械褉械写薪褨 褌邪 薪邪褋褌褍锌薪褨 3 屑褨褋褟褑褨: 胁褨写 %{startDate} 写芯 %{endDate}."
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr "袛芯 %{dateWord}"
 
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr "袟邪斜芯褉芯薪懈褌懈 褋锌褨谢褜薪懈泄 写芯褋褌褍锌 写芯 锌褉芯械泻褌褍 胁 褉邪屑泻邪褏 %{group} 蟹 褨薪褕懈屑懈 谐褉褍锌邪屑懈"
@@ -1699,9 +2282,6 @@ msgstr "袚褉褍锌懈 薪械 蟹薪邪泄写械薪褨"
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr "袙懈 屑芯卸械褌械 泻械褉褍胁邪褌懈 锌褉邪胁邪屑懈 写芯褋褌褍锌褍 褔谢械薪褨胁 谐褉褍锌懈 屑邪褌懈 写芯褋褌褍锌 写芯 泻芯卸薪芯谐芯 锌褉芯械泻褌褍 胁 薪褨泄."
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr "小褌胁芯褉懈褌懈 锌褉芯械泻褌 褍 谐褉褍锌褨."
 
@@ -1732,6 +2312,9 @@ msgstr "袧邪 卸邪谢褜 卸芯写薪邪 谐褉褍锌锌邪 褔懈 锌褉芯械泻褌 薪械 蟹邪写芯胁
 msgid "Have your users email"
 msgstr "袝谢械泻褌褉芯薪薪邪 锌芯褕褌邪 写谢褟 蟹胁械褉褌邪薪褜 泻芯褉懈褋褌褍胁邪褔褨胁"
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "袩械褉械胁褨褉泻邪 袩褉邪褑械蟹写邪褌薪芯褋褌褨"
 
@@ -1750,11 +2333,21 @@ msgstr "袩褉芯斜谢械屑 褨蟹 蟹写芯褉芯胁'褟屑 薪械 胁懈褟胁谢械薪芯"
 msgid "HealthCheck|Unhealthy"
 msgstr "袧械蟹写芯褉芯胁懈泄"
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "小褏芯胁邪褌懈 蟹薪邪褔械薪薪褟"
+msgstr[1] "小褏芯胁邪褌懈 蟹薪邪褔械薪薪褟"
+msgstr[2] "小褏芯胁邪褌懈 蟹薪邪褔械薪褜"
+msgstr[3] "小褏芯胁邪褌懈 蟹薪邪褔械薪褜"
 
 msgid "History"
 msgstr "袉褋褌芯褉褨褟"
@@ -1762,9 +2355,39 @@ msgstr "袉褋褌芯褉褨褟"
 msgid "Housekeeping successfully started"
 msgstr "袨褔懈褖械薪薪褟 褍褋锌褨褕薪芯 褉芯蟹锌芯褔邪褌芯"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr "袩褉懈 胁懈泻芯褉懈褋褌邪薪薪褨 GitHub, 胁懈 锌芯斜邪褔懈褌械 褋褌邪褌褍褋懈 泻芯薪胁械褦褉褨胁 写谢褟 泻芯屑褨褌褨胁 褨 蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟. %{more_info_link}"
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr "携泻褖芯 褍 胁邪褋 褍卸械 褦 褎邪泄谢懈, 胁懈 屑芯卸械褌械 胁褨写锌褉邪胁懈褌懈 褩褏 蟹邪 写芯锌芯屑芯谐芯褞 %{link_to_cli} 薪懈卸褔械."
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr "携泻褖芯 胁邪褕 HTTP-褉械锌芯蟹懈褌芯褉褨泄 薪械 褦 锌褍斜谢褨褔薪懈屑, 写芯写邪泄褌械 写邪薪褨 写谢褟 邪褍褌械薪褌懈褎褨泻邪褑褨褩 写芯 URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+
+msgid "Import"
+msgstr "袉屑锌芯褉褌"
+
+msgid "Import all repositories"
+msgstr "袉屑锌芯褉褌 胁褋褨褏 褉械锌芯蟹懈褌芯褉褨褩胁"
+
+msgid "Import in progress"
+msgstr "袉屑锌芯褉褌 褌褉懈胁邪褦"
+
+msgid "Import repositories from GitHub"
+msgstr "袉屑锌芯褉褌 褉械锌芯蟹懈褌芯褉褨褩胁 蟹 GitHub"
+
 msgid "Import repository"
 msgstr "袉屑锌芯褉褌 褉械锌芯蟹懈褌芯褉褨褞"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr "袩褨写泻谢褞褔懈褌懈 褉械锌芯蟹懈褌芯褉褨褩 褨蟹"
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr "袩芯泻褉邪褖懈褌懈 写芯褕泻懈 芯斜谐芯胁芯褉械薪褜 锌褉芯斜谢械屑 蟹邪 写芯锌芯屑芯谐芯褞 胁械褉褋褨褩 GitLab Enterprise Edition."
 
@@ -1774,6 +2397,9 @@ msgstr "袩芯泻褉邪褖懈褌懈 褍锌褉邪胁谢褨薪薪褟 锌褉芯斜谢械屑邪屑懈 蟹 屑芯卸谢
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr "袩芯泻褉邪褖懈褌懈 锌芯褕褍泻 蟹邪 写芯锌芯屑芯谐芯褞 褉芯蟹褕懈褉械薪芯谐芯 谐谢芯斜邪谢褜薪芯谐芯 锌芯褕褍泻 胁 胁械褉褋褨褩 GitLab Enterprise Edition."
 
+msgid "Install Runner on Kubernetes"
+msgstr "袙褋褌邪薪芯胁懈褌懈 Runner 薪邪 Kubernetes"
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "袙褋褌邪薪芯胁褨褌褜 Runner, 褋褍屑褨褋薪懈泄 蟹 GitLab CI"
 
@@ -1782,12 +2408,16 @@ msgid_plural "Instances"
 msgstr[0] "袉薪褋褌邪薪褋"
 msgstr[1] "I薪褋褌邪薪褋懈"
 msgstr[2] "袉薪褋褌邪薪褋褨胁"
+msgstr[3] "袉薪褋褌邪薪褋褨胁"
 
 msgid "Instance does not support multiple Kubernetes clusters"
-msgstr ""
+msgstr "笑械泄 褨薪褋褌邪薪褋 薪械 锌褨写褌褉懈屑褍褦 写械泻褨谢褜泻邪 Kubernetes-泻谢邪褋褌械褉褨胁"
+
+msgid "Integrations"
+msgstr "袉薪褌械谐褉邪褑褨褩"
 
 msgid "Interested parties can even contribute by pushing commits if they want to."
-msgstr ""
+msgstr "袟邪褑褨泻邪胁谢械薪褨 褋褌芯褉芯薪懈 蟹邪 斜邪卸邪薪薪褟屑 屑芯卸褍褌褜 薪邪胁褨褌褜 褉芯斜懈褌懈 胁薪械褋泻懈 褕谢褟褏芯屑 胁褨写锌褉邪胁谢械薪薪褟 泻芯屑褨褌褨胁."
 
 msgid "Internal - The group and any internal projects can be viewed by any logged in user."
 msgstr "袙薪褍褌褉褨褕薪褟 鈥� 斜褍写褜-褟泻懈泄 邪胁褌械薪褌懈褎褨泻芯胁邪薪懈泄 泻芯褉懈褋褌褍胁邪褔 屑邪褦 写芯褋褌褍锌 写芯 褑褨褦褩 谐褉褍锌懈 褌邪 褍褋褨褏 褩褩 胁薪褍褌褉褨褕薪褨褏 锌褉芯械泻褌褨胁."
@@ -1817,7 +2447,7 @@ msgid "Issues"
 msgstr "袩褉芯斜谢械屑懈"
 
 msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
-msgstr ""
+msgstr "袩褉芯斜谢械屑懈 屑芯卸褍褌褜 斜褍褌懈 锌芯屑懈谢泻邪屑懈, 蟹邪胁写邪薪薪褟屑懈 褔懈 褨写械褟屑懈 写谢褟 芯斜谐芯胁芯褉械薪薪褟. 袣褉褨屑 褌芯谐芯, 锌褉芯斜谢械屑懈 写芯褋褌褍锌薪褨 写谢褟 锌芯褕褍泻褍 褌邪 褎褨谢褜褌褉褍胁邪薪薪褟."
 
 msgid "Jan"
 msgstr "褋褨褔."
@@ -1825,6 +2455,9 @@ msgstr "褋褨褔."
 msgid "January"
 msgstr "褋褨褔械薪褜"
 
+msgid "Jobs"
+msgstr "袟邪胁写邪薪薪褟"
+
 msgid "Jul"
 msgstr "谢懈锌."
 
@@ -1837,6 +2470,9 @@ msgstr "褔械褉."
 msgid "June"
 msgstr "褔械褉胁械薪褜"
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr "Kubernetes"
 
@@ -1844,19 +2480,22 @@ msgid "Kubernetes Cluster"
 msgstr "袣谢邪褋褌械褉 Kubernetes"
 
 msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
-msgstr ""
+msgstr "袩械褉械胁懈褖械薪薪褟 谢褨屑褨褌褍 褔邪褋褍 锌褉懈 褋褌胁芯褉械薪薪褨 Kubernetes-泻谢邪褋褌械褉邪; %{timeout}"
 
 msgid "Kubernetes cluster integration was not removed."
-msgstr ""
+msgstr "袉薪褌械谐褉邪褑褨褟 褨蟹 Kubernetes-泻谢邪褋褌械褉芯屑 薪械 斜褍谢邪 胁懈写邪谢械薪邪."
 
 msgid "Kubernetes cluster integration was successfully removed."
-msgstr ""
+msgstr "袉薪褌械谐褉邪褑褨褟 褨蟹 Kubernetes-泻谢邪褋褌械褉芯屑 斜褍谢邪 褍褋锌褨褕薪芯 胁懈写邪谢械薪邪."
 
 msgid "Kubernetes cluster was successfully updated."
-msgstr ""
+msgstr "Kubernetes-泻谢邪褋褌械褉 褍褋锌褨褕薪芯 芯薪芯胁谢械薪芯."
+
+msgid "Kubernetes configured"
+msgstr "Kubernetes 薪邪谢邪褕褌芯胁邪薪芯"
 
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
-msgstr ""
+msgstr "小锌芯褋褨斜 袉薪褌械谐褉邪褑褨褩 Kubernetes 褟泻 褋械褉胁褨褋邪 蟹邪褋褌邪褉褨胁. %{deprecated_message_content} 胁邪褕褨 Kubernetes-泻谢邪褋褌械褉懈 蟹邪 写芯锌芯屑芯谐芯褞 薪芯胁芯褩 褋褌芯褉褨薪泻懈 <a href=\"%{url}\"/>袣谢邪褋褌械褉懈 Kubernetes</a>"
 
 msgid "LFSStatus|Disabled"
 msgstr "袙懈屑泻薪械薪芯"
@@ -1864,17 +2503,36 @@ msgstr "袙懈屑泻薪械薪芯"
 msgid "LFSStatus|Enabled"
 msgstr "校胁褨屑泻薪械薪芯"
 
+msgid "Label"
+msgstr "袦褨褌泻邪"
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr "%{firstLabelName} + %{remainingLabelCount} 褖械"
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr "%{labelsString} 褨 %{remainingLabelCount} 褖械"
+
 msgid "Labels"
 msgstr "袦褨褌泻懈"
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr "袦褨褌泻懈 屑芯卸褍褌褜 斜褍褌懈 蟹邪褋褌芯褋芯胁邪薪褨 写芯 %{features}. 袚褉褍锌芯胁褨 屑褨褌泻懈 写芯褋褌褍锌薪褨 写谢褟 斜褍写褜-褟泻芯谐芯 锌褉芯械泻褌褍 胁 屑械卸邪褏 谐褉褍锌懈."
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr "袦褨褌泻懈 屑芯卸褍褌褜 斜褍褌懈 蟹邪褋褌芯褋芯胁邪薪褨 写芯 锌褉芯斜谢械屑 褌邪 蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟 写谢褟 褩褏 泻邪褌械谐芯褉懈蟹邪褑褨褩."
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
 msgstr ""
 
+msgid "Labels|Promote Label"
+msgstr "袩褨写胁懈褖懈褌懈 屑褨褌泻褍"
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "袨褋褌邪薪薪褨泄 %d 写械薪褜"
 msgstr[1] "袨褋褌邪薪薪褨褏 %d 写薪褨"
 msgstr[2] "袨褋褌邪薪薪褨褏 %d 写薪褨胁"
+msgstr[3] "袨褋褌邪薪薪褨褏 %d 写薪褨胁"
 
 msgid "Last Pipeline"
 msgstr "袨褋褌邪薪薪褨泄 袣芯薪胁械褦褉"
@@ -1903,6 +2561,12 @@ msgstr "胁"
 msgid "Learn more"
 msgstr "袛褨蟹薪邪褌懈褋褟 斜褨谢褜褕械"
 
+msgid "Learn more about Kubernetes"
+msgstr "袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 锌褉芯 Kubernetes"
+
+msgid "Learn more about protected branches"
+msgstr "袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 锌褉芯 蟹邪褏懈褖械薪褨 谐褨谢泻懈"
+
 msgid "Learn more in the"
 msgstr "袛褨蟹薪邪泄褌械褋褜 斜褨谢褜褕械"
 
@@ -1921,6 +2585,12 @@ msgstr "袟邪谢懈褕懈褌懈 锌褉芯械泻褌"
 msgid "License"
 msgstr "袥褨褑械薪蟹褨褟"
 
+msgid "List"
+msgstr "小锌懈褋芯泻"
+
+msgid "List your GitHub repositories"
+msgstr "小锌懈褋芯泻 胁邪褕懈褏 褉械锌芯蟹懈褌芯褉褨褩胁 GitHub"
+
 msgid "Loading the GitLab IDE..."
 msgstr "袟邪胁邪薪褌邪卸械薪薪褟 IDE GitLab..."
 
@@ -1930,8 +2600,8 @@ msgstr "袘谢芯泻褍胁邪薪薪褟"
 msgid "Lock %{issuableDisplayName}"
 msgstr "袟邪斜谢芯泻褍胁邪褌懈 %{issuableDisplayName}"
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
-msgstr ""
+msgid "Lock not found"
+msgstr "袘谢芯泻褍胁邪薪薪褟 薪械 蟹薪邪泄写械薪芯"
 
 msgid "Locked"
 msgstr "袟邪斜谢芯泻芯胁邪薪芯"
@@ -1939,15 +2609,30 @@ msgstr "袟邪斜谢芯泻芯胁邪薪芯"
 msgid "Locked Files"
 msgstr "袟邪斜谢芯泻芯胁邪薪褨 褎邪泄谢懈"
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr "袘谢芯泻褍胁邪薪薪褟 屑芯卸械 斜褍褌懈 蟹邪褋褌芯褋芯胁邪薪械 写芯 泻芯薪泻褉械褌薪芯谐芯 褎邪泄谢褍 邪斜芯 写懈褉械泻褌芯褉褨褩."
+
 msgid "Login"
 msgstr "袙褏褨写"
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr "袟褉芯斜褨褌褜 泻芯卸薪芯谐芯 褍褔邪褋薪懈泻邪 泻芯屑邪薪写懈 斜褨谢褜褕 锌褉芯写褍泻褌懈胁薪懈屑 薪械蟹邪谢械卸薪芯 胁褨写 泄芯谐芯 屑褨褋褑械蟹薪邪褏芯写卸械薪薪褟. GitLab Geo 褋褌胁芯褉褞褦 泻芯锌褨褩 \"褌褨谢褜泻懈 写谢褟 褔懈褌邪薪薪褟\" 胁邪褕芯谐芯 GitLab 褋械褉胁械褉邪, 褖芯斜 褋泻芯褉芯褌懈褌懈 褔邪褋 写谢褟 泻谢芯薪褍胁邪薪薪褟 褨 芯褌褉懈屑邪薪薪褟 泻芯写褍 蟹 胁械谢懈泻懈褏 褉械锌芯蟹懈褌芯褉褨褩胁."
+
+msgid "Manage all notifications"
 msgstr ""
 
+msgid "Manage group labels"
+msgstr "袣械褉褍胁邪薪薪褟 屑褨褌泻邪屑懈 谐褉褍锌懈"
+
 msgid "Manage labels"
 msgstr "袣械褉褍胁邪褌懈 屑褨褌泻邪屑懈"
 
+msgid "Manage project labels"
+msgstr "袣械褉褍胁邪薪薪褟 屑褨褌泻邪屑懈 锌褉芯械泻褌褍"
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr "斜械褉."
 
@@ -1955,7 +2640,7 @@ msgid "March"
 msgstr "斜械褉械蟹械薪褜"
 
 msgid "Mark done"
-msgstr ""
+msgstr "袩芯蟹薪邪褔懈褌懈 胁懈泻芯薪邪薪懈屑"
 
 msgid "Maximum git storage failures"
 msgstr "袦邪泻褋懈屑邪谢褜薪邪 泻褨谢褜泻褨褋褌褜 薪械胁写邪褔 胁 褋褏芯胁懈褖褨 写邪薪懈褏 git"
@@ -1969,8 +2654,8 @@ msgstr "袦械写褨邪薪邪"
 msgid "Members"
 msgstr "袣芯褉懈褋褌褍胁邪褔褨"
 
-msgid "Merge Request"
-msgstr "袟邪锌懈褌 薪邪 蟹谢懈褌褌褟"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
 
 msgid "Merge Requests"
 msgstr "袟邪锌懈褌懈 薪邪 蟹谢懈褌褌褟"
@@ -1982,14 +2667,89 @@ msgid "Merge request"
 msgstr "袟邪锌懈褌 薪邪 蟹谢懈褌褌褟"
 
 msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
-msgstr ""
+msgstr "袟邪锌懈褌 薪邪 蟹谢懈褌褌褟 鈥� 褑械 褋锌芯褋褨斜 蟹邪锌褉芯锌芯薪褍胁邪褌懈 褋胁芯褩 蟹屑褨薪懈 写芯 锌褉芯械泻褌褍 褨 芯斜谐芯胁芯褉懈褌懈 褩褏 褨蟹 褨薪褕懈屑懈"
 
 msgid "Merged"
-msgstr ""
+msgstr "袟谢懈褌芯"
 
 msgid "Messages"
 msgstr "袩芯胁褨写芯屑谢械薪薪褟"
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr "袘褨蟹薪械褋"
+
+msgid "Metrics|Create metric"
+msgstr "小褌胁芯褉懈褌懈 屑械褌褉懈泻褍"
+
+msgid "Metrics|Edit metric"
+msgstr "袪械写邪谐褍胁邪褌懈 屑械褌褉懈泻褍"
+
+msgid "Metrics|For grouping similar metrics"
+msgstr "袛谢褟 谐褉褍锌褍胁邪薪薪褟 锌芯写褨斜薪懈褏 屑械褌褉懈泻"
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr "袧邪蟹胁邪 胁械褉褌懈泻邪谢褜薪芯褩 芯褋褨 谐褉邪褎褨泻邪. 袟邪蟹胁懈褔邪泄 褑械 鈥� 芯写懈薪懈褑褨 胁懈屑褨褉褞胁邪薪薪褟. 袚芯褉懈蟹芯薪褌邪谢褜薪邪 胁褨褋褜 (胁褨褋褜 X) 蟹邪胁卸写懈 胁褨写芯斜褉邪卸邪褦 褔邪褋."
+
+msgid "Metrics|Legend label (optional)"
+msgstr "袟邪谐芯谢芯胁芯泻 谢械谐械薪写懈 (薪械芯斜芯胁鈥櫻徯沸盒拘残感�)"
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr "袦邪褦 斜褍褌懈 泻芯褉械泻褌薪懈屑 蟹邪锌懈褌芯屑 PromQL."
+
+msgid "Metrics|Name"
+msgstr "袉屑'褟"
+
+msgid "Metrics|New metric"
+msgstr "袧芯胁邪 屑械褌褉懈泻邪"
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr "袛芯泻褍屑械薪褌邪褑褨褟 锌芯 蟹邪锌懈褌邪屑 Prometheus"
+
+msgid "Metrics|Query"
+msgstr "袟邪锌懈褌"
+
+msgid "Metrics|Response"
+msgstr "袙褨写锌芯胁褨写褜"
+
+msgid "Metrics|System"
+msgstr "小懈褋褌械屑邪"
+
+msgid "Metrics|Type"
+msgstr "孝懈锌"
+
+msgid "Metrics|Unit label"
+msgstr "袨写懈薪懈褑褨 胁懈屑褨褉褞胁邪薪薪褟"
+
+msgid "Metrics|Used as a title for the chart"
+msgstr "袙懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 褟泻 蟹邪谐芯谢芯胁芯泻 谐褉邪褎褨泻邪"
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr "袙懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟, 褟泻褖芯 蟹邪锌懈褌 锌芯胁械褉褌邪褦 褦写懈薪褍 锌芯褋谢褨写芯胁薪褨褋褌褜. 携泻褖芯 卸 胁褨薪 锌芯胁械褉褌邪褦 写械泻褨谢褜泻邪, 褩褏 薪邪蟹胁懈 斜械褉褍褌褜褋褟 褨蟹 胁褨写锌芯胁褨写褨."
+
+msgid "Metrics|Y-axis label"
+msgstr "袧邪蟹胁邪 芯褋褨 Y"
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr "薪邪锌褉. HTTP-蟹邪锌懈褌懈"
+
+msgid "Metrics|e.g. Requests/second"
+msgstr "薪邪锌褉. 蟹邪锌懈褌褨胁/褋械泻"
+
+msgid "Metrics|e.g. Throughput"
+msgstr "薪邪锌褉. 锌褉芯锌褍褋泻薪邪 蟹写邪褌薪褨褋褌褜"
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr "薪邪锌褉. rate(http_requests_total[5m])"
+
+msgid "Metrics|e.g. req/sec"
+msgstr "薪邪锌褉. 蟹邪锌/褋械泻"
+
 msgid "Milestone"
 msgstr "袝褌邪锌"
 
@@ -2005,12 +2765,33 @@ msgstr "袧械 胁写邪谢芯褋褟 胁懈写邪谢懈褌懈 械褌邪锌 %{milestoneTitle}"
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr "袝褌邪锌 %{milestoneTitle} 薪械 蟹薪邪泄写械薪芯"
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr "袩褨写胁懈褖懈褌懈 %{milestoneTitle} 写芯 谐褉褍锌芯胁芯谐芯 械褌邪锌褍?"
+
+msgid "Milestones|Promote Milestone"
+msgstr "袩褨写胁懈褖懈褌懈 袝褌邪锌"
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "薪械 写芯写邪褋褌械 SSH 泻谢褞褔"
 
+msgid "Modal|Cancel"
+msgstr "小泻邪褋褍胁邪褌懈"
+
+msgid "Modal|Close"
+msgstr "袟邪泻褉懈褌懈"
+
 msgid "Monitoring"
 msgstr "袦芯薪褨褌芯褉懈薪谐"
 
+msgid "More info"
+msgstr "袛械褌邪谢褜薪褨褕械"
+
+msgid "More information"
+msgstr "袛械褌邪谢褜薪褨褕械"
+
 msgid "More information is available|here"
 msgstr "褌褍褌"
 
@@ -2024,19 +2805,20 @@ msgid "Multiple issue boards"
 msgstr "袣褨谢褜泻邪 写芯褕芯泻 芯斜谐芯胁芯褉械薪薪褟"
 
 msgid "Name new label"
-msgstr ""
+msgstr "袧邪蟹胁褨褌褜 薪芯胁褍 屑褨褌泻褍"
 
 msgid "New Issue"
 msgid_plural "New Issues"
 msgstr[0] "袧芯胁邪 锌褉芯斜谢械屑邪"
 msgstr[1] "袧芯胁褨 锌褉芯斜谢械屑懈"
 msgstr[2] "袧芯胁懈褏 锌褉芯斜谢械屑"
+msgstr[3] "袧芯胁懈褏 锌褉芯斜谢械屑"
 
 msgid "New Kubernetes Cluster"
-msgstr ""
+msgstr "袧芯胁懈泄 Kubernetes-泻谢邪褋褌械褉"
 
 msgid "New Kubernetes cluster"
-msgstr ""
+msgstr "袧芯胁懈泄 Kubernetes-泻谢邪褋褌械褉"
 
 msgid "New Pipeline Schedule"
 msgstr "袧芯胁懈泄 褉芯蟹泻谢邪写 袣芯薪胁械褦褉邪"
@@ -2083,6 +2865,9 @@ msgstr "袧芯胁邪 锌褨写谐褉褍锌邪"
 msgid "New tag"
 msgstr "袧芯胁懈泄 褌械谐"
 
+msgid "No Label"
+msgstr "袘械蟹 袦褨褌泻懈"
+
 msgid "No assignee"
 msgstr "袧械屑邪褦 胁懈泻芯薪邪胁褑褟"
 
@@ -2090,41 +2875,62 @@ msgid "No changes"
 msgstr "袧械屑邪褦 蟹屑褨薪"
 
 msgid "No connection could be made to a Gitaly Server, please check your logs!"
-msgstr ""
+msgstr "袧械屑芯卸谢懈胁芯 蟹'褦写薪邪褌懈褋褜 褨蟹 褋械褉胁械褉芯屑 Gitaly, 斜褍写褜 谢邪褋泻邪, 锌械褉械胁褨褉褌械 谢芯谐懈!"
 
 msgid "No due date"
-msgstr "袧械屑邪褦 蟹邪锌谢邪薪芯胁邪薪芯褩 写邪褌懈 蟹邪胁械褉褕械薪薪褟"
+msgstr "袧械屑邪褦"
 
 msgid "No estimate or time spent"
-msgstr ""
+msgstr "袧械屑邪褦 蟹邪锌谢邪薪芯胁邪薪芯谐芯 邪斜芯 胁懈褌褉邪褔械薪芯谐芯 褔邪褋褍"
 
 msgid "No file chosen"
 msgstr "肖邪泄谢 薪械 胁懈斜褉邪薪芯"
 
+msgid "No labels created yet."
+msgstr "袦褨褌泻懈 褖械 薪械 褋褌胁芯褉械薪褨."
+
 msgid "No repository"
 msgstr "袧械屑邪褦 褉械锌芯蟹懈褌芯褉褨褞"
 
 msgid "No schedules"
 msgstr "薪械屑邪褦 袪芯蟹泻谢邪写褨胁"
 
-msgid "No time spent"
-msgstr "袧械屑邪褦 胁懈褌褉邪褔械薪芯谐芯 褔邪褋褍"
-
 msgid "None"
-msgstr "袞芯写械薪"
+msgstr "袧械屑邪褦"
 
 msgid "Not allowed to merge"
-msgstr ""
+msgstr "袟谢懈褌褌褟 薪械 写芯锌褍褋泻邪褦褌褜褋褟"
 
 msgid "Not available"
 msgstr "袧械写芯褋褌褍锌薪懈泄"
 
+msgid "Not available for private projects"
+msgstr "袧械写芯褋褌褍锌薪芯 写谢褟 锌褉懈胁邪褌薪懈褏 锌褉芯械泻褌褨胁"
+
+msgid "Not available for protected branches"
+msgstr "袧械写芯褋褌褍锌薪芯 写谢褟 蟹邪褏懈褖械薪懈褏 谐褨谢芯泻"
+
 msgid "Not confidential"
 msgstr "袧械 泻芯薪褎褨写械薪褑褨泄薪芯"
 
 msgid "Not enough data"
 msgstr "袧械写芯褋褌邪褌薪褜芯 写邪薪懈褏"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr "袦邪泄褌械 薪邪 褍胁邪蟹褨, 褖芯 谐褨谢泻邪 master 蟹邪褏懈褖械薪邪 邪胁褌芯屑邪褌懈褔薪芯. %{link_to_protected_branches}"
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr "袩褉懈屑褨褌泻邪: 褟泻 邪写屑褨薪褨褋褌褉邪褌芯褉 胁懈 屑芯卸械褌械 薪邪谢邪褕褌褍胁邪褌懈 %{github_integration_link}, 褖芯 写芯蟹胁芯谢懈褌褜 胁褏芯写懈褌懈 褔械褉械蟹 GitHub 褨 锌褨写泻谢褞褔邪褌懈 褉械锌芯蟹懈褌芯褉褨褩 斜械蟹 褋褌胁芯褉械薪薪褟 芯褋芯斜懈褋褌芯谐芯 褌芯泻械薪褍 写芯褋褌褍锌褍."
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr "袩褉懈屑褨褌泻邪: 褟泻 邪写屑褨薪褨褋褌褉邪褌芯褉 胁懈 屑芯卸械褌械 薪邪谢邪褕褌褍胁邪褌懈 %{github_integration_link}, 褖芯 写芯蟹胁芯谢懈褌褜 胁褏芯写懈褌懈 褔械褉械蟹 GitHub 褨 褨屑锌芯褉褌褍胁邪褌懈 褉械锌芯蟹懈褌芯褉褨褩 斜械蟹 褋褌胁芯褉械薪薪褟 芯褋芯斜懈褋褌芯谐芯 褌芯泻械薪褍 写芯褋褌褍锌褍."
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr "袩褉懈屑褨褌泻邪: 蟹胁械褉薪褨褌褜褋褟 写芯 胁邪褕芯谐芯 邪写屑褨薪褨褋褌褉邪褌芯褉邪 GitLab, 褖芯斜 薪邪谢邪褕褌褍胁邪褌懈 %{github_integration_link}, 褟泻懈泄 写芯蟹胁芯谢懈褌褜 胁褏芯写懈褌懈 蟹邪 写芯锌芯屑芯谐芯褞 GitHub 褨 锌褉懈褦写薪褍胁邪褌懈 褉械锌芯蟹懈褌芯褉褨褩 斜械蟹 谐械薪械褉邪褑褨褩 锌械褉褋芯薪邪谢褜薪芯谐芯 褌芯泻械薪邪 写芯褋褌褍锌褍."
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr "袩褉懈屑褨褌泻邪: 蟹胁械褉薪褨褌褜褋褟 写芯 胁邪褕芯谐芯 邪写屑褨薪褨褋褌褉邪褌芯褉邪 GitLab, 褖芯斜 薪邪谢邪褕褌褍胁邪褌懈 %{github_integration_link}, 褟泻懈泄 写芯蟹胁芯谢懈褌褜 胁褏芯写懈褌懈 蟹邪 写芯锌芯屑芯谐芯褞 GitHub 褌邪 褨屑锌芯褉褌褍胁邪褌懈 褉械锌芯蟹懈褌芯褉褨褩 斜械蟹 谐械薪械褉邪褑褨褩 锌械褉褋芯薪邪谢褜薪芯谐芯 褌芯泻械薪邪 写芯褋褌褍锌褍."
+
 msgid "Notification events"
 msgstr "袩芯胁褨写芯屑谢械薪薪褟 锌褉芯 锌芯写褨褩"
 
@@ -2209,6 +3015,12 @@ msgstr "卸芯胁褌械薪褜"
 msgid "OfSearchInADropdown|Filter"
 msgstr "肖褨谢褜褌褉"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr "袩褨褋谢褟 褨屑锌芯褉褌褍 褉械锌芯蟹懈褌芯褉褨褩 屑芯卸褍褌褜 斜褍褌懈 胁褨写写蟹械褉泻邪谢械薪褨 褔械褉械蟹 SSH. 袛褨蟹薪邪泄褌械褋褟 斜褨谢褜褕械 %{ssh_link}"
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr "孝褨谢褜泻懈 褍褔邪褋薪懈泻懈 锌褉芯械泻褌褍 屑芯卸褍褌褜 蟹邪谢懈褕邪褌懈 泻芯屑械薪褌邪褉褨."
 
@@ -2227,12 +3039,21 @@ msgstr "袙褨写泻褉懈胁邪褦褌褜褋褟 褍 薪芯胁芯屑褍 胁褨泻薪褨"
 msgid "Options"
 msgstr "袩邪褉邪屑械褌褉懈"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr "袙 褨薪褕芯屑褍 褉邪蟹褨 褉械泻芯屑械薪写褍褦褌褜褋褟 锌芯褔邪褌懈 蟹 芯写薪芯谐芯 蟹 薪邪胁械写械薪懈褏 薪懈卸褔械 胁邪褉褨邪薪褌褨胁."
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "袨谐谢褟写"
 
 msgid "Owner"
 msgstr "袙谢邪褋薪懈泻"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr "袨褋褌邪薪薪褟 禄"
 
@@ -2245,9 +3066,21 @@ msgstr "袩芯锌械褉械写薪褟"
 msgid "Pagination|芦 First"
 msgstr "芦 袩械褉褕邪"
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "袩邪褉芯谢褜"
 
+msgid "Pending"
+msgstr "袙 芯褔褨泻褍胁邪薪薪褨"
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr "孝芯泻械薪褍 锌械褉褋芯薪邪谢褜薪芯谐芯 写芯褋褌褍锌褍"
+
 msgid "Pipeline"
 msgstr "袣芯薪胁械褦褉"
 
@@ -2327,10 +3160,55 @@ msgid "Pipelines for last year"
 msgstr "袣芯薪胁械褦褉懈 蟹邪 芯褋褌邪薪薪褨泄 褉褨泻"
 
 msgid "Pipelines|Build with confidence"
-msgstr ""
+msgstr "袙懈泻芯薪褍泄褌械 蟹斜褨褉泻懈 褨蟹 胁锌械胁薪械薪褨褋褌褞"
+
+msgid "Pipelines|CI Lint"
+msgstr "袩械褉械胁褨褉泻邪 泻芯薪褎褨谐褍褉邪褑褨褩 (CI Lint)"
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr "袨褔懈褋褌懈褌懈 泻械褕 Runner'褨胁"
 
 msgid "Pipelines|Get started with Pipelines"
-msgstr ""
+msgstr "袪芯蟹锌芯褔邪褌懈 褉芯斜芯褌褍 蟹 袣芯薪胁械褦褉邪屑懈"
+
+msgid "Pipelines|Loading Pipelines"
+msgstr "袟邪胁邪薪褌邪卸械薪薪褟 泻芯薪胁械褦褉褨胁"
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr "袣械褕 锌褉芯械泻褌褍 褍褋锌褨褕薪芯 芯褔懈褖械薪芯."
+
+msgid "Pipelines|Run Pipeline"
+msgstr "袟邪锌褍褋褌懈褌懈 袣芯薪胁械褦褉"
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯褔懈褖械薪薪褨 泻械褕邪 runner'褨胁."
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr "袙 写邪薪懈泄 褔邪褋 薪械屑邪褦 %{scope} 泻芯薪胁械褦褉褨胁."
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr "袙 写邪薪懈泄 褔邪褋 薪械屑邪褦 泻芯薪胁械褦褉褨胁."
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr "笑械泄 锌褉芯械泻褌 胁 写邪薪懈泄 褔邪褋 薪械 薪邪谢邪褕褌芯胁邪薪懈泄 写谢褟 蟹邪锌褍褋泻褍 泻芯薪胁械褦褉褨胁."
+
+msgid "Pipeline|Retry pipeline"
+msgstr "袩械褉械蟹邪锌褍褋褌懈褌懈 泻芯薪胁械褦褉"
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr "袩械褉械蟹邪锌褍褋褌懈褌懈 泻芯薪胁械褦褉 #%{pipelineId}?"
+
+msgid "Pipeline|Stop pipeline"
+msgstr "袟褍锌懈薪懈褌懈 泻芯薪胁械褦褉"
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr "袟褍锌懈薪懈褌懈 泻芯薪胁械褦褉 #%{pipelineId}?"
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr "袟邪褉邪蟹 胁懈 锌械褉械蟹邪锌褍褋褌懈褌械 泻芯薪胁械褦褉 %{pipelineId}."
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr "袟邪褉邪蟹 胁懈 蟹褍锌懈薪械褌械 泻芯薪胁械褦褉 %{pipelineId}."
 
 msgid "Pipeline|all"
 msgstr "胁褋褨"
@@ -2344,20 +3222,29 @@ msgstr "蟹褨 褋褌邪写褨褦褞"
 msgid "Pipeline|with stages"
 msgstr "蟹褨 褋褌邪写褨褟屑懈"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr "袙褨写褌胁芯褉懈褌懈"
 
 msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
-msgstr ""
+msgstr "袘褍写褜 谢邪褋泻邪, <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">褍胁褨屑泻薪褨褌褜 斜褨谢褨薪谐邪 写谢褟 芯写薪芯谐芯 蟹 胁邪褕懈褏 锌褉芯械泻褌褨胁, 褖芯斜 屑邪褌懈 屑芯卸谢懈胁褨褋褌褜 褋褌胁芯褉懈褌懈 Kubernetes-泻谢邪褋褌械褉</a>, 褨 锌芯胁褌芯褉褨褌褜 褋锌褉芯斜褍."
 
 msgid "Please solve the reCAPTCHA"
 msgstr "袘褍写褜 谢邪褋泻邪, 锌褉芯泄写褨褌褜 reCAPTCHA"
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr "袘褍写褜 谢邪褋泻邪, 锌芯褔械泻邪泄褌械 锌芯泻懈 屑懈 蟹鈥櫻斝葱窖冄斝夹狙佈� 褨蟹 胁邪褕懈屑 褉械锌芯蟹懈褌芯褉褨褦屑. 袨薪芯胁谢褞泄褌械 褋褌芯褉褨薪泻褍 蟹邪 斜邪卸邪薪薪褟屑."
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr "袘褍写褜 谢邪褋泻邪, 锌芯褔械泻邪泄褌械 锌芯泻懈 屑懈 褨屑锌芯褉褌褍褦屑芯 胁邪褕 褉械锌芯蟹懈褌芯褉褨泄. 袨薪芯胁谢褞泄褌械 褋褌芯褉褨薪泻褍 蟹邪 斜邪卸邪薪薪褟屑."
+
 msgid "Preferences"
 msgstr "袧邪谢邪褕褌褍胁邪薪薪褟"
 
 msgid "Primary"
-msgstr ""
+msgstr "袚芯谢芯胁薪懈泄"
 
 msgid "Private - Project access must be granted explicitly to each user."
 msgstr "袩褉懈胁邪褌薪懈泄 鈥� 写芯褋褌褍锌 写芯 锌褉芯械泻褌褍 锌芯胁懈薪械薪 薪邪写邪胁邪褌懈褋褟 泻芯卸薪芯屑褍 泻芯褉懈褋褌褍胁邪褔械胁褨."
@@ -2365,6 +3252,9 @@ msgstr "袩褉懈胁邪褌薪懈泄 鈥� 写芯褋褌褍锌 写芯 锌褉芯械泻褌褍 锌芯胁懈薪械薪 
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr "袩褉懈胁邪褌薪邪 鈥� 褑褞 谐褉褍锌褍 褌邪 褩褩 锌褉芯械泻褌懈 屑芯卸褍褌褜 斜邪褔懈褌懈 褌褨谢褜泻懈 褩褩 泻芯褉懈褋褌褍胁邪褔褨."
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr "袩褉懈胁邪褌薪褨 锌褉芯械泻褌懈 屑芯卸褍褌褜 斜褍褌懈 褋褌胁芯褉械薪褨 褍 胁邪褕芯屑褍 锌械褉褋芯薪邪谢褜薪芯屑褍 锌褉芯褋褌芯褉褨 褨屑械薪 蟹:"
+
 msgid "Profile"
 msgstr "袩褉芯褎褨谢褜"
 
@@ -2404,9 +3294,12 @@ msgstr "袙邪褕 芯斜谢褨泻芯胁懈泄 蟹邪锌懈褋 褦 胁谢邪褋薪懈泻芯屑 胁 褑懈褏 谐
 msgid "Profiles|your account"
 msgstr "胁邪褕 芯斜谢褨泻芯胁懈泄 蟹邪锌懈褋"
 
-msgid "Programming languages used in this repository"
+msgid "Profiling - Performance bar"
 msgstr ""
 
+msgid "Programming languages used in this repository"
+msgstr "袦芯胁懈 锌褉芯谐褉邪屑褍胁邪薪薪褟, 褖芯 胁懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 胁 褑褜芯屑褍 褉械锌芯蟹懈褌芯褉褨褩"
+
 msgid "Project '%{project_name}' is in the process of being deleted."
 msgstr "袩褉芯械泻褌 '%{project_name}' 锌械褉械斜褍胁邪褦 胁 锌褉芯褑械褋褨 胁懈写邪谢械薪薪褟."
 
@@ -2426,10 +3319,7 @@ msgid "Project avatar"
 msgstr "袗胁邪褌邪褉 锌褉芯械泻褌褍"
 
 msgid "Project avatar in repository: %{link}"
-msgstr ""
-
-msgid "Project cache successfully reset."
-msgstr "袣械褕 锌褉芯械泻褌褍 褍褋锌褨褕薪芯 褋泻懈薪褍褌芯."
+msgstr "袗胁邪褌邪褉 锌褉芯械泻褌褍 胁 褉械锌芯蟹懈褌芯褉褨褩: %{link}"
 
 msgid "Project details"
 msgstr "袛械褌邪谢褨 锌褉芯械泻褌褍"
@@ -2450,13 +3340,13 @@ msgid "ProjectActivityRSS|Subscribe"
 msgstr "袩褨写锌懈褋邪褌懈褋褟"
 
 msgid "ProjectCreationLevel|Allowed to create projects"
-msgstr ""
+msgstr "袛芯蟹胁芯谢械薪芯 褋褌胁芯褉褞胁邪褌懈 锌褉芯械泻褌懈"
 
 msgid "ProjectCreationLevel|Default project creation protection"
 msgstr ""
 
 msgid "ProjectCreationLevel|Developers + Masters"
-msgstr ""
+msgstr "袪芯蟹褉芯斜薪懈泻懈 + 袣械褉褨胁薪懈泻懈"
 
 msgid "ProjectCreationLevel|Masters"
 msgstr "袣械褉褨胁薪懈泻懈"
@@ -2527,38 +3417,89 @@ msgstr "袧邪 卸邪谢褜, 锌芯 胁邪褕芯褍 蟹邪锌懈褌褍 锌褉芯械泻褌褨胁 薪械 蟹薪邪
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr "笑褟 褎褍薪泻褑褨褟 锌芯褌褉械斜褍褦 锌褨写褌褉懈屑泻懈 localStorage 胁邪褕懈屑 斜褉邪褍蟹械褉芯屑"
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr "斜褍谢芯 蟹薪邪泄写械薪芯 %{exporters} 蟹 %{metrics}"
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr "<p class=\"text-tertiary\">袧褨褟泻懈褏 <a href=\"%{docsUrl}\">蟹邪谐邪谢褜薪懈褏 屑械褌褉懈泻</a> 薪械 蟹薪邪泄写械薪芯</p>"
+
+msgid "PrometheusService|Active"
+msgstr "袗泻褌懈胁薪懈泄"
+
+msgid "PrometheusService|Auto configuration"
+msgstr "袗胁褌芯屑邪褌懈褔薪邪 泻芯薪褎褨谐褍褉邪褑褨褟"
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr "袗胁褌芯屑邪褌懈褔薪芯 褉芯蟹谐芯褉褌邪泄褌械 褌邪 薪邪谢邪褕褌芯胁褍泄褌械 Prometheus 薪邪 胁邪褕褨 泻谢邪褋褌械褉懈 写谢褟 屑芯薪褨褌芯褉懈薪谐褍 褋械褉械写芯胁懈褖 锌褉芯械泻褌褍"
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr "袟邪 蟹邪屑芯胁褔褍胁邪薪薪褟屑, Prometheus 蟹邪锌褍褋泻邪褦褌褜褋褟 蟹邪 邪写褉械褋芯褞 鈥榟ttp://localhost:9090鈥�. 袧械 褉械泻芯屑械薪写褍褦褌褜褋褟 蟹屑褨薪褞胁邪褌懈 褑褞 邪写褉械褋褍 褨 锌芯褉褌, 斜芯 褑械 屑芯卸械 锌褉懈蟹胁械褋褌懈 写芯 泻芯薪褎谢褨泻褌褍 蟹 褨薪褕懈屑懈 褋械褉胁褨褋邪屑懈 蟹邪锌褍褖械薪懈屑懈 薪邪 GitLab 褋械褉胁械褉褨."
 
+msgid "PrometheusService|Common metrics"
+msgstr "袟邪谐邪谢褜薪褨 屑械褌褉懈泻懈"
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr "袟邪谐邪谢褜薪褨 屑械褌褉懈泻懈 邪胁褌芯屑邪褌懈褔薪芯 蟹斜懈褉邪褞褌褜褋褟 薪邪 芯褋薪芯胁褨 薪邪斜芯褉褍 屑械褌褉懈泻 胁褨写 锌芯锌褍谢褟褉薪懈褏 械泻褋锌芯褉褌械褉褨胁."
+
+msgid "PrometheusService|Custom metrics"
+msgstr "袙谢邪褋薪褨 屑械褌褉懈泻懈"
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr "袩芯褕褍泻 褌邪 薪邪谢邪褕褌褍胁邪薪薪褟 屑械褌褉懈泻..."
 
+msgid "PrometheusService|Finding custom metrics..."
+msgstr "袩芯褕褍泻 胁谢邪褋薪懈褏 屑械褌褉懈泻..."
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr "袙褋褌邪薪芯胁懈褌懈 Prometheus 薪邪 泻谢邪褋褌械褉懈"
+
+msgid "PrometheusService|Manage clusters"
+msgstr "校锌褉邪胁谢褨薪薪褟 泻谢邪褋褌械褉邪屑懈"
+
+msgid "PrometheusService|Manual configuration"
+msgstr "袪褍褔薪褨 薪邪谢邪褕褌褍胁邪薪薪褟"
+
 msgid "PrometheusService|Metrics"
 msgstr "袦械褌褉懈泻懈"
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
-msgstr "袦械褌褉懈泻懈 邪胁褌芯屑邪褌懈褔薪芯 薪邪谢邪褕褌芯胁褍褞褌褜褋褟 褌邪 泻芯薪褌褉芯谢褞褞褌褜褋褟 薪邪 芯褋薪芯胁褨 薪邪斜芯褉褍 屑械褌褉懈泻 胁褨写 锌芯锌褍谢褟褉薪懈褏 械泻褋锌芯褉褌械褉褨胁."
-
 msgid "PrometheusService|Missing environment variable"
 msgstr "袩褉芯锌褍褖械薪邪 蟹屑褨薪薪邪 褋械褉械写芯胁懈褖邪"
 
-msgid "PrometheusService|Monitored"
-msgstr "袦芯薪褨褌芯褉懈薪谐 锌褨写泻谢褞褔械薪芯"
-
 msgid "PrometheusService|More information"
 msgstr "袛芯写邪褌泻芯胁邪 褨薪褎芯褉屑邪褑褨褟"
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr "袞芯写薪褨 屑械褌褉懈泻懈 薪械 胁褨写褋谢褨写泻芯胁褍褞褌褜褋褟. 袛谢褟 锌芯褔邪褌泻褍 屑芯薪褨褌芯褉懈薪谐褍, 褉芯蟹谐芯褉薪褨褌褜 褋械褉械写芯胁懈褖械."
+msgid "PrometheusService|New metric"
+msgstr "袧芯胁邪 屑械褌褉懈泻邪"
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr "袘邪蟹芯胁邪 邪写褉械褋邪 Prometheus API, 薪邪锌褉懈泻谢邪写 http://prometheus.example.com/"
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr "Prometheus 邪胁褌芯屑邪褌懈褔薪芯 薪邪谢邪褕褌芯胁邪薪懈泄 薪邪 胁邪褕懈褏 泻谢邪褋褌械褉邪褏"
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr "笑褨 屑械褌褉懈泻懈 斜褍写褍褌褜 蟹斜懈褉邪褌懈褋褟 褌褨谢褜泻懈 锌褨褋谢褟 锌械褉褕芯谐芯 褉芯蟹谐芯褉褌邪薪薪褟 胁 褟泻芯屑褍褋褜 褋械褉懈写芯胁懈褖褨"
+
 msgid "PrometheusService|Time-series monitoring service"
+msgstr "小械褉胁褨褋 屑芯薪褨褌芯褉懈薪谐褍 褔邪褋芯胁懈褏 褉褟写褨胁"
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr "袛谢褟 屑芯卸谢懈胁芯褋褌褨 褉褍褔薪芯谐芯 薪邪谢邪褕褌褍胁邪薪薪褟, 胁懈写邪谢褨褌褜 Prometheus 褨蟹 胁邪褕懈褏 泻谢邪褋褌械褉褨胁"
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr "袛谢褟 屑芯卸谢懈胁芯褋褌褨 胁褋褌邪薪芯胁谢械薪薪褟 Prometheus 薪邪 胁邪褕褨 泻谢邪褋褌械褉懈, 写械邪泻褌懈胁褍泄褌械 褉褍褔薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 薪懈卸褔械"
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr "袨褔褨泻褍胁邪薪薪褟 锌械褉褕芯谐芯 褉芯蟹谐芯褉褌邪薪薪褟 褍 褋械褉懈写芯胁懈褖褨 写谢褟 蟹斜芯褉褍 蟹邪谐邪谢褜薪懈褏 屑械褌褉懈泻"
+
+msgid "Promote"
 msgstr ""
 
-msgid "PrometheusService|View environments"
-msgstr "袩械褉械谐谢褟写 褋械褉械写芯胁懈褖"
+msgid "Promote to Group Label"
+msgstr "袩械褉械薪械褋褌懈 屑褨褌泻褍 薪邪 褉褨胁械薪褜 谐褉褍锌懈"
+
+msgid "Promote to Group Milestone"
+msgstr ""
 
 msgid "Protip:"
 msgstr "袩褨写泻邪蟹泻邪:"
@@ -2575,11 +3516,17 @@ msgstr "袩褉邪胁懈谢邪 胁褨写锌褉邪胁谢械薪薪褟"
 msgid "Push events"
 msgstr "袩芯写褨褩 胁褨写锌褉邪胁谢械薪薪褟 (push)"
 
+msgid "Push project from command line"
+msgstr "袙懈泻芯薪邪褌懈 push 锌褉芯械泻褌褍 蟹邪 写芯锌芯屑芯谐芯褞 泻芯屑邪薪写薪芯谐芯 褉褟写泻邪"
+
+msgid "Push to create a project"
+msgstr "袧邪褌懈褋薪褨褌褜, 褖芯斜 褋褌胁芯褉懈褌懈 锌褉芯械泻褌"
+
 msgid "PushRule|Committer restriction"
 msgstr "袨斜屑械卸械薪薪褟 写谢褟 泻芯屑褨褌褌械褉邪"
 
 msgid "Quick actions can be used in the issues description and comment boxes."
-msgstr ""
+msgstr "楔胁懈写泻褨 写褨褩 屑芯卸薪邪 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 胁 芯锌懈褋邪褏 锌褉芯斜谢械屑 褨 泻芯屑械薪褌邪褉褟褏."
 
 msgid "Read more"
 msgstr "袛芯泻谢邪写薪褨褕械"
@@ -2587,6 +3534,9 @@ msgstr "袛芯泻谢邪写薪褨褕械"
 msgid "Readme"
 msgstr "袉薪褋褌褉褍泻褑褨褟"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "袚褨谢泻懈"
 
@@ -2594,7 +3544,7 @@ msgid "RefSwitcher|Tags"
 msgstr "孝械谐懈"
 
 msgid "Reference:"
-msgstr ""
+msgstr "袩芯褋懈谢邪薪薪褟:"
 
 msgid "Register / Sign In"
 msgstr "袟邪褉械褦褋褌褉褍胁邪褌懈褋褟 / 校胁褨泄褌懈"
@@ -2620,6 +3570,9 @@ msgstr "袩芯胁'褟蟹邪薪褨 蟹邪锌懈褌懈 薪邪 蟹谢懈褌褌褟"
 msgid "Related Merged Requests"
 msgstr "袩芯胁'褟蟹邪薪褨 胁懈泻芯薪邪薪褨 蟹邪锌懈褌懈"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "袧邪谐邪写邪褌懈 锌褨蟹薪褨褕械"
 
@@ -2633,11 +3586,26 @@ msgid "Remove project"
 msgstr "袙懈写邪谢懈褌懈 锌褉芯械泻褌"
 
 msgid "Repair authentication"
-msgstr ""
+msgstr "袙褨写薪芯胁懈褌懈 邪褍褌械薪褌懈褎褨泻邪褑褨褞"
+
+msgid "Repo by URL"
+msgstr "袪械锌芯蟹懈褌芯褉褨褩 锌芯 URL"
 
 msgid "Repository"
 msgstr "袪械锌芯蟹懈褌芯褉褨泄"
 
+msgid "Repository has no locks."
+msgstr "袪械锌芯蟹懈褌芯褉褨泄 薪械 屑邪褦 斜谢芯泻褍胁邪薪褜."
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "袟邪锌懈褌 写芯褋褌褍锌褍"
 
@@ -2648,13 +3616,20 @@ msgid "Reset health check access token"
 msgstr "袨薪芯胁懈褌懈 褌芯泻械薪 写芯褋褌褍锌褍 写谢褟 锌械褉械胁褨褉泻懈 锌褉邪褑械蟹写邪褌薪芯褋褌褨"
 
 msgid "Reset runners registration token"
-msgstr "小泻懈薪褍褌懈 褉械褦褋褌褉邪褑褨泄薪懈泄 褌芯泻械薪 runner-褨胁"
+msgstr "袩械褉械谐械薪械褉褍胁邪褌懈 褉械褦褋褌褉邪褑褨泄薪懈泄 褌芯泻械薪 runner-褨胁"
+
+msgid "Resolve discussion"
+msgstr "袟邪胁械褉褕懈褌懈 芯斜谐芯胁芯褉械薪薪褟"
+
+msgid "Response"
+msgstr "袙褨写锌芯胁褨写褜"
 
 msgid "Reveal value"
 msgid_plural "Reveal values"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "袩芯泻邪蟹邪褌懈 蟹薪邪褔械薪薪褟"
+msgstr[1] "袩芯泻邪蟹邪褌懈 蟹薪邪褔械薪薪褟"
+msgstr[2] "袩芯泻邪蟹邪褌懈 蟹薪邪褔械薪褜"
+msgstr[3] "袩芯泻邪蟹邪褌懈 蟹薪邪褔械薪褜"
 
 msgid "Revert this commit"
 msgstr "袗薪褍谢褞胁邪褌懈 褑械泄 泻芯屑褨褌"
@@ -2662,6 +3637,36 @@ msgstr "袗薪褍谢褞胁邪褌懈 褑械泄 泻芯屑褨褌"
 msgid "Revert this merge request"
 msgstr "袗薪褍谢褞胁邪褌懈 褑械泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr "袩谢邪薪-谐褉邪褎褨泻"
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr "袟邪锌褍褋褌懈褌懈 CI/CD 泻芯薪胁械褦褉懈 写谢褟 蟹芯胁薪褨褕薪褨褏 褉械锌芯蟹懈褌芯褉褨褩胁"
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr "袙懈泻芯薪褍褦褌褜褋褟"
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "袣谢褞褔褨 SSH"
 
@@ -2672,11 +3677,14 @@ msgid "Save pipeline schedule"
 msgstr "袟斜械褉械谐褌懈 褉芯蟹泻谢邪写 泻芯薪胁械褦褉邪"
 
 msgid "Save variables"
-msgstr ""
+msgstr "袟斜械褉械谐褌懈 蟹屑褨薪薪褨"
 
 msgid "Schedule a new pipeline"
 msgstr "袪芯蟹泻谢邪写 薪芯胁芯谐芯 泻芯薪胁械褦褉邪"
 
+msgid "Scheduled"
+msgstr "袟邪锌谢邪薪芯胁邪薪芯"
+
 msgid "Schedules"
 msgstr "袪芯蟹泻谢邪写懈"
 
@@ -2686,6 +3694,9 @@ msgstr "袩谢邪薪褍胁邪薪薪褟 泻芯薪胁械褦褉褨胁"
 msgid "Scoped issue boards"
 msgstr "孝械屑邪褌懈褔薪褨 写芯褕泻懈 锌褉芯斜谢械屑"
 
+msgid "Search"
+msgstr "袩芯褕褍泻"
+
 msgid "Search branches and tags"
 msgstr "袩芯褕褍泻 谐褨谢芯泻 褌邪 褌械谐褨胁"
 
@@ -2693,7 +3704,7 @@ msgid "Search milestones"
 msgstr "袩芯褕褍泻 械褌邪锌褨胁"
 
 msgid "Search project"
-msgstr ""
+msgstr "袩芯褕褍泻 胁 锌褉芯械泻褌褨"
 
 msgid "Search users"
 msgstr "袩芯褕褍泻 泻芯褉懈褋褌褍胁邪褔褨胁"
@@ -2705,7 +3716,10 @@ msgid "Seconds to wait for a storage access attempt"
 msgstr "袣褨谢褜泻褨褋褌褜 褋械泻褍薪写 芯褔褨泻褍胁邪薪薪褟 锌械褉械写 锌芯胁褌芯褉薪芯褞 褋锌褉芯斜芯褞 写芯褋褌褍锌褍 写芯 褋褏芯胁懈褖邪 写邪薪懈褏"
 
 msgid "Secret variables"
-msgstr ""
+msgstr "小械泻褉械褌薪褨 蟹屑褨薪薪褨"
+
+msgid "Security report"
+msgstr "袟胁褨褌 锌芯 斜械蟹锌械褑褨"
 
 msgid "Select Archive Format"
 msgstr "袙懈斜械褉褨褌褜 褎芯褉屑邪褌 邪褉褏褨胁褍"
@@ -2713,6 +3727,9 @@ msgstr "袙懈斜械褉褨褌褜 褎芯褉屑邪褌 邪褉褏褨胁褍"
 msgid "Select a timezone"
 msgstr "袙懈斜褉邪褌懈 褔邪褋芯胁懈泄 锌芯褟褋"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr "袙懈斜械褉褨褌褜 褨褋薪褍褞褔懈泄 泻谢邪褋褌械褉 Kubernetes 邪斜芯 褋褌胁芯褉褨褌褜 薪芯胁懈泄"
+
 msgid "Select assignee"
 msgstr "袙懈斜械褉褨褌褜 胁懈泻芯薪邪胁褑褟"
 
@@ -2723,7 +3740,10 @@ msgid "Select target branch"
 msgstr "袙懈斜褨褉 褑褨谢褜芯胁芯褩 谐褨谢泻懈"
 
 msgid "Selective synchronization"
-msgstr ""
+msgstr "袙懈斜褨褉泻芯胁邪 褋懈薪褏褉芯薪褨蟹邪褑褨褟"
+
+msgid "Send email"
+msgstr "袧邪写褨褋谢邪褌懈 谢懈褋褌邪"
 
 msgid "Sep"
 msgstr "胁械褉."
@@ -2735,19 +3755,37 @@ msgid "Server version"
 msgstr "袙械褉褋褨褟 褋械褉胁械褉邪"
 
 msgid "Service Templates"
-msgstr "小械褉胁褨褋 褕邪斜谢芯薪褨胁"
+msgstr "楔邪斜谢芯薪懈 褋械褉胁褨褋褨胁"
+
+msgid "Service URL"
+msgstr "小械褉胁褨褋 URL"
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
 
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "袙褋褌邪薪芯胁褨褌褜 锌邪褉芯谢褜 写谢褟 褋胁芯谐芯 芯斜谢褨泻芯胁芯谐芯 蟹邪锌懈褋褍, 褖芯斜 屑邪褌懈 屑芯卸谢懈胁褨褋褌褜 胁褨写锌褉邪胁谢褟褌懈 褌邪 芯褌褉懈屑褍胁邪褌懈 褔械褉械蟹 %{protocol}."
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr "袧邪谢邪褕褌褍胁邪薪薪褟 CI/CD"
 
 msgid "Set up Koding"
 msgstr "袧邪谢邪褕褌褍胁邪薪薪褟 Koding"
 
-msgid "Set up auto deploy"
-msgstr "袧邪谢邪褕褌褍胁邪薪薪褟 邪胁褌芯屑邪褌懈褔薪芯谐芯 褉芯蟹谐芯褉褌邪薪薪褟"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "胁褋褌邪薪芯胁懈褌懈 锌邪褉芯谢褜"
@@ -2755,14 +3793,23 @@ msgstr "胁褋褌邪薪芯胁懈褌懈 锌邪褉芯谢褜"
 msgid "Settings"
 msgstr "袧邪谢邪褕褌褍胁邪薪薪褟"
 
-msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgid "Setup a specific Runner automatically"
 msgstr ""
 
-msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
 msgstr ""
 
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr "袩褉懈 芯斜薪褍谢械薪薪褨 褏胁懈谢懈薪 泻芯薪胁械褦褉褨胁 写谢褟 褑褜芯谐芯 锌褉芯褋褌芯褉褍 褨屑械薪, 泻褨谢褜泻褨褋褌褜 胁卸械 胁懈泻芯褉懈褋褌邪薪懈褏 褏胁懈谢懈薪 斜褍写械 写芯褉褨胁薪褞胁邪褌懈 0."
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr "袨斜薪褍谢懈褌懈 褏胁懈谢懈薪懈 胁 泻芯薪胁械褦褉褨"
+
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
-msgstr ""
+msgstr "袨斜薪褍谢懈褌懈 胁懈泻芯褉懈褋褌邪薪褨 褏胁懈谢懈薪懈 胁 泻芯薪胁械褦褉褨"
+
+msgid "Show command"
+msgstr "袩芯泻邪蟹邪褌懈 泻芯屑邪薪写褍"
 
 msgid "Show parent pages"
 msgstr "袩芯泻邪蟹邪褌懈 斜邪褌褜泻褨胁褋褜泻褨 褋褌芯褉褨薪泻懈"
@@ -2775,6 +3822,7 @@ msgid_plural "Showing %d events"
 msgstr[0] "袩芯泻邪蟹邪薪芯 %d 锌芯写褨褞"
 msgstr[1] "袩芯泻邪蟹邪薪芯 %d 锌芯写褨褩"
 msgstr[2] "袩芯泻邪蟹邪薪芯 %d 锌芯写褨泄"
+msgstr[3] "袩芯泻邪蟹邪薪芯 %d 锌芯写褨泄"
 
 msgid "Sidebar|Change weight"
 msgstr "袟屑褨薪懈褌懈 胁邪谐褍"
@@ -2788,24 +3836,36 @@ msgstr "袧械屑邪褦"
 msgid "Sidebar|Weight"
 msgstr "袙邪谐邪"
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr "小薪褨锌械褌懈"
 
 msgid "Something went wrong on our end"
-msgstr ""
-
-msgid "Something went wrong on our end."
 msgstr "些芯褋褜 锌褨褕谢芯 薪械 褌邪泻 蟹 薪邪褕芯谐芯 斜芯泻褍"
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
-msgstr "些芯褋褜 锌褨褕谢芯 薪械 褌邪泻, 锌褉懈 褋锌褉芯斜褨 蟹屑褨薪懈 褋褌邪薪褍 斜谢芯泻褍胁邪薪薪褟 ${this.issuableDisplayName}"
-
 msgid "Something went wrong when toggling the button"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 锌械褉械屑懈泻邪薪薪褨 泻薪芯锌泻懈"
+
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
+msgid "Something went wrong while fetching SAST."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 SAST."
+
 msgid "Something went wrong while fetching the projects."
 msgstr "些芯褋褜 锌褨褕谢芯 薪械 褌邪泻 锌褨写 褔邪褋 芯褌褉懈屑邪薪薪褟 锌褉芯械泻褌褨胁"
 
@@ -2813,7 +3873,7 @@ msgid "Something went wrong while fetching the registry list."
 msgstr "些芯褋褜 锌褨褕谢芯 薪械 褌邪泻 锌褉懈 芯褌褉懈屑邪薪薪褨 褋锌懈褋泻褍 褨蟹 褉械褦褋褌褉褍."
 
 msgid "Something went wrong. Please try again."
-msgstr ""
+msgstr "些芯褋褜 锌褨褕谢芯 薪械 褌邪泻. 袘褍写褜 谢邪褋泻邪 褋锌褉芯斜褍泄褌械 褖械 褉邪蟹."
 
 msgid "Sort by"
 msgstr "小芯褉褌褍胁邪褌懈 蟹邪"
@@ -2917,6 +3977,9 @@ msgstr "袙邪谐邪"
 msgid "Source"
 msgstr "袛卸械褉械谢芯"
 
+msgid "Source (branch or tag)"
+msgstr "袛卸械褉械谢芯 (谐褨谢泻邪 邪斜芯 褌械谐)"
+
 msgid "Source code"
 msgstr "袣芯写"
 
@@ -2926,12 +3989,21 @@ msgstr "袛卸械褉械谢芯 薪械写芯褋褌褍锌薪械"
 msgid "Spam Logs"
 msgstr "小锌邪屑-卸褍褉薪邪谢"
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "袟邪蟹薪邪褔褌械 薪邪褋褌褍锌薪懈泄 URL 锌褨写 褔邪褋 胁褋褌邪薪芯胁谢械薪薪褟 Runner-邪:"
 
 msgid "StarProject|Star"
 msgstr "袙 芯斜褉邪薪褨"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr "袨斜褉邪薪褨 锌褉芯械泻褌懈"
 
@@ -2941,11 +4013,20 @@ msgstr "袩芯褔邪褌懈 %{new_merge_request} 蟹 褑懈屑懈 蟹屑褨薪邪屑懈"
 msgid "Start the Runner!"
 msgstr "袟邪锌褍褋褌褨褌褜 Runner!"
 
+msgid "Started"
+msgstr "袟邪锌褍褖械薪懈泄"
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr "小褌邪褌褍褋"
+
 msgid "Stopped"
 msgstr "袟褍锌懈薪械薪芯"
 
 msgid "Storage"
-msgstr ""
+msgstr "小褏芯胁懈褖械"
 
 msgid "Subgroups"
 msgstr "袩褨写谐褉褍锌懈"
@@ -2953,14 +4034,21 @@ msgstr "袩褨写谐褉褍锌懈"
 msgid "Switch branch/tag"
 msgstr "袩械褉械泄褌懈 胁 谐褨谢泻褍/褌械谐"
 
+msgid "System"
+msgstr "小懈褋褌械屑邪"
+
 msgid "System Hooks"
 msgstr "小懈褋褌械屑薪褨 谐褍泻懈"
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "孝械谐"
-msgstr[1] "孝械谐懈"
-msgstr[2] "孝械谐褨胁"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] "孝械谐 (%{tag_count})"
+msgstr[1] "孝械谐邪 (%{tag_count})"
+msgstr[2] "孝械谐褨胁 (%{tag_count})"
+msgstr[3] "孝械谐褨胁 (%{tag_count})"
 
 msgid "Tags"
 msgstr "孝械谐懈"
@@ -3037,6 +4125,9 @@ msgstr "蟹邪褏懈褖械薪懈泄"
 msgid "Target Branch"
 msgstr "笑褨谢褜芯胁邪 谐褨谢泻邪"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr "袣芯屑邪薪写邪"
 
@@ -3052,35 +4143,50 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "小褌邪写褨褟 \"袧邪锌懈褋邪薪薪褟 泻芯写褍\" 锌芯泻邪蟹褍褦 褔邪褋 胁褨写 锌械褉褕芯谐芯 泻芯屑褨褌褍 写芯 褋褌胁芯褉械薪薪褟 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 褋褌胁芯褉械薪薪褟 胁邪褕芯谐芯 锌械褉褕芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟."
+msgstr "小褌邪写褨褟 袧邪锌懈褋邪薪薪褟 泻芯写褍 锌芯泻邪蟹褍褦 褔邪褋 胁褨写 锌械褉褕芯谐芯 泻芯屑褨褌褍 写芯 褋褌胁芯褉械薪薪褟 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 褋褌胁芯褉械薪薪褟 胁邪褕芯谐芯 锌械褉褕芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟."
 
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "袣芯谢械泻褑褨褟 锌芯写褨泄 写芯写邪薪邪 写芯 写邪薪懈褏, 蟹褨斜褉邪薪懈褏 写谢褟 褑褨褦褩 褋褌邪写褨褩."
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr "袟鈥櫻斝葱叫靶叫窖� 斜褍写械 锌褉懈锌懈薪械薪芯 锌褨褋谢褟 %{timeout}. 袛谢褟 褉械锌芯蟹懈褌芯褉褨褩胁, 褟泻懈屑 锌芯褌褉褨斜薪芯 斜褨谢褜褕械 褔邪褋褍, 胁懈泻芯褉懈褋褌芯胁褍泄褌械 泻芯屑斜褨薪邪褑褨褞 clone/push."
+
 msgid "The fork relationship has been removed."
 msgstr "袟胁'褟蟹芯泻 褎芯褉泻褍 胁懈写邪谢械薪芯."
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr "袉屑锌芯褉褌 斜褍写械 锌褉懈锌懈薪械薪芯 锌褨褋谢褟 %{timeout}. 袛谢褟 褉械锌芯蟹懈褌芯褉褨褩胁, 褟泻懈屑 锌芯褌褉褨斜薪芯 斜褨谢褜褕械 褔邪褋褍, 胁懈泻芯褉懈褋褌芯胁褍泄褌械 泻芯屑斜褨薪邪褑褨褞 clone/push."
+
 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 "小褌邪写褨褟 \"袩褉芯斜谢械屑邪\" 锌芯泻邪蟹褍褦, 褋泻褨谢褜泻懈 褔邪褋褍 锌芯褌褉褨斜薪芯 胁褨写 褋褌胁芯褉械薪薪褟 锌褉芯斜谢械屑懈 写芯 胁泻谢褞褔械薪薪褟 褩褩 写芯 褟泻芯谐芯褋褜 械褌邪锌褍, 邪斜芯 写芯写邪胁邪薪薪褟 锌褉芯斜谢械屑懈 薪邪 写芯褕泻褍. 袩芯褔薪褨褌褜 褋褌胁芯褉褞胁邪褌懈 锌褉芯斜谢械屑懈, 褖芯斜 锌械褉械谐谢褟写邪褌懈 写邪薪褨 写谢褟 褑褨褦褩 褋褌邪写褨褩."
+msgstr "小褌邪写褨褟 袩褉芯斜谢械屑邪 锌芯泻邪蟹褍褦, 褋泻褨谢褜泻懈 褔邪褋褍 锌芯褌褉褨斜薪芯 胁褨写 褋褌胁芯褉械薪薪褟 锌褉芯斜谢械屑懈 写芯 胁泻谢褞褔械薪薪褟 褩褩 写芯 褟泻芯谐芯褋褜 械褌邪锌褍, 邪斜芯 写芯写邪胁邪薪薪褟 锌褉芯斜谢械屑懈 薪邪 写芯褕泻褍. 袩芯褔薪褨褌褜 褋褌胁芯褉褞胁邪褌懈 锌褉芯斜谢械屑懈, 褖芯斜 锌械褉械谐谢褟写邪褌懈 写邪薪褨 写谢褟 褑褨褦褩 褋褌邪写褨褩."
 
 msgid "The maximum file size allowed is 200KB."
-msgstr ""
+msgstr "袦邪泻褋懈屑邪谢褜薪懈泄 褉芯蟹屑褨褉 褎邪泄谢褍 鈥� 200 袣斜."
 
 msgid "The number of attempts GitLab will make to access a storage."
 msgstr "袣褨谢褜泻褨褋褌褜 褋锌褉芯斜, 褟泻褨 蟹褉芯斜懈褌褜 GitLab 写谢褟 芯褌褉懈屑邪薪薪褟 写芯褋褌褍锌褍 写芯 褋褏芯胁懈褖邪 写邪薪懈褏."
 
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
-msgstr "袣褨谢褜泻褨褋褌褜 蟹斜芯褩胁 锌褨褋谢褟 褟泻芯褩 Gitlab 锌芯胁薪褨褋褌褞 蟹邪斜谢芯泻褍褦 写芯褋褌褍锌 写芯 褋褏芯胁懈褖邪 写邪薪薪懈褏. 袥褨褔懈谢褜薪懈泻 泻褨谢褜泻芯褋褌褨 蟹斜芯褩胁 屑芯卸械 斜褍褌懈 褋泻懈薪褍褌懈泄 胁 褨薪褌械褉褎械泄褋褨 邪写屑褨薪褨褋褌褉邪褌芯褉邪 (%{link_to_health_page}), 邪斜芯 褔械褉械蟹 %{api_documentation_link}."
+msgstr "袣褨谢褜泻褨褋褌褜 蟹斜芯褩胁 锌褨褋谢褟 褟泻芯褩 Gitlab 锌芯胁薪褨褋褌褞 蟹邪斜谢芯泻褍褦 写芯褋褌褍锌 写芯 褋褏芯胁懈褖邪 写邪薪薪懈褏. 袥褨褔懈谢褜薪懈泻 泻褨谢褜泻芯褋褌褨 蟹斜芯褩胁 屑芯卸薪邪 斜褍写械 芯斜薪褍谢懈褌懈 胁 褨薪褌械褉褎械泄褋褨 邪写屑褨薪褨褋褌褉邪褌芯褉邪 (%{link_to_health_page}), 邪斜芯 褔械褉械蟹 %{api_documentation_link}."
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 "小褌邪写褨褟 \"袩谢邪薪褍胁邪薪薪褟\" 胁褨写芯斜褉邪卸邪褦褌褜褋褟 褔邪褋 胁褨写 锌芯锌械褉械写薪褜芯谐芯 泻褉芯泻褍 写芯 锌械褉褕芯谐芯 泻芯屑褨褌褍. 袛芯写邪褦褌褜褋褟 邪胁褌芯屑邪褌懈褔薪芯, 褟泻 褌褨谢褜泻懈 胁褨写锌褉邪胁懈褌褜褋褟 锌械褉褕懈泄 泻芯屑褨褌."
+msgstr "小褌邪写褨褟 袩谢邪薪褍胁邪薪薪褟 胁褨写芯斜褉邪卸邪褦 褔邪褋 胁褨写 锌芯锌械褉械写薪褜芯谐芯 泻褉芯泻褍 写芯 锌械褉褕芯谐芯 泻芯屑褨褌褍. 袛芯写邪褦褌褜褋褟 邪胁褌芯屑邪褌懈褔薪芯, 褟泻 褌褨谢褜泻懈 胁褨写锌褉邪胁懈褌褜褋褟 锌械褉褕懈泄 泻芯屑褨褌."
+
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "小褌邪写褨褟 \"Production\" 锌芯泻邪蟹褍褦 蟹邪谐邪谢褜薪懈泄 褔邪褋 屑褨卸 褋褌胁芯褉械薪薪褟屑 锌褉芯斜谢械屑懈 褌邪 褉芯蟹谐芯褉褌邪薪薪褟屑 泻芯写褍 褍 production. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 蟹邪胁械褉褕械薪薪褟 锌芯胁薪芯褩 褨写械褩 写芯 production 褑懈泻谢褍."
+msgstr "小褌邪写褨褟 Production 锌芯泻邪蟹褍褦 蟹邪谐邪谢褜薪懈泄 褔邪褋 屑褨卸 褋褌胁芯褉械薪薪褟屑 锌褉芯斜谢械屑懈 褌邪 褉芯蟹谐芯褉褌邪薪薪褟屑 泻芯写褍 褍 production. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 蟹邪胁械褉褕械薪薪褟 锌芯胁薪芯褩 褨写械褩 写芯 production 褑懈泻谢褍."
 
 msgid "The project can be accessed by any logged in user."
 msgstr "袛芯褋褌褍锌 写芯 锌褉芯械泻褌褍 屑芯卸谢懈胁懈泄 斜褍写褜-褟泻懈屑 蟹邪褉械褦褋褌褉芯胁邪薪懈屑 泻芯褉懈褋褌褍胁邪褔械屑."
@@ -3091,14 +4197,23 @@ msgstr "袛芯褋褌褍锌 写芯 锌褉芯械泻褌褍 屑芯卸谢懈胁懈泄 斜械蟹 斜褍写褜-褟泻芯
 msgid "The repository for this project does not exist."
 msgstr "袪械锌芯蟹懈褌芯褉褨泄 写谢褟 褑褜芯谐芯 锌褉芯械泻褌褍 薪械 褨褋薪褍褦."
 
+msgid "The repository for this project is empty"
+msgstr "袪械锌芯蟹懈褌芯褉褨泄 褑褜芯谐芯 锌褉芯械泻褌褍 锌芯褉芯卸薪褨泄"
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr "袪械锌芯蟹懈褌芯褉褨泄 屑邪褦 斜褍褌懈 写芯褋褌褍锌薪懈屑 褔械褉械蟹 <code>http://</code>, <code>https://</code> 邪斜芯 <code>git://</code>."
+
 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 "小褌邪写褨褟 \"袟邪褌胁械褉写卸械薪薪褟\" 锌芯泻邪蟹褍褦 褔邪褋 胁褨写 褋褌胁芯褉械薪薪褟 蟹邪锌懈褌褍 锌褉芯 芯斜'褦写薪邪薪薪褟 写芯 泄芯谐芯 胁懈泻芯薪邪薪薪褟. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 蟹邪胁械褉褕械薪薪褟 锌械褉褕芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟."
+msgstr "小褌邪写褨褟 袟邪褌胁械褉写卸械薪薪褟 锌芯泻邪蟹褍褦 褔邪褋 胁褨写 褋褌胁芯褉械薪薪褟 蟹邪锌懈褌褍 锌褉芯 芯斜'褦写薪邪薪薪褟 写芯 泄芯谐芯 胁懈泻芯薪邪薪薪褟. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 蟹邪胁械褉褕械薪薪褟 锌械褉褕芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟."
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+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 "小褌邪写褨褟 \"Staging\" 锌芯泻邪蟹褍褦 褔邪褋 屑褨卸 胁懈泻芯薪邪薪薪褟 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟 褌邪 褉芯蟹谐芯褉褌邪薪薪褟屑 泻芯写褍 褍 production. 袛邪薪褨 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪褞褌褜褋褟 锌褨褋谢褟 褉芯蟹谐芯褉褌邪薪薪褟 褍 production 胁锌械褉褕械."
+msgstr "小褌邪写褨褟 Staging 锌芯泻邪蟹褍褦 褔邪褋 屑褨卸 胁懈泻芯薪邪薪薪褟 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟 褌邪 褉芯蟹谐芯褉褌邪薪薪褟屑 泻芯写褍 褍 production. 袛邪薪褨 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪褞褌褜褋褟 锌褨褋谢褟 褉芯蟹谐芯褉褌邪薪薪褟 褍 production 胁锌械褉褕械."
 
 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 胁懈褌褉邪褔邪褦 写谢褟 胁懈泻芯薪邪薪薪褟 泻芯卸薪芯谐芯 泻芯薪胁械褦褉邪 写谢褟 胁褨写锌芯胁褨写薪芯谐芯 蟹邪锌懈褌褍 蟹谢懈褌褌褟. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 蟹邪胁械褉褕械薪薪褟 锌械褉褕芯谐芯 泻芯薪胁械褦褉邪."
+msgstr "小褌邪写褨褟 孝械褋褌褍胁邪薪薪褟 锌芯泻邪蟹褍褦 褔邪褋, 褟泻懈泄 GitLab CI 胁懈褌褉邪褔邪褦 写谢褟 胁懈泻芯薪邪薪薪褟 泻芯卸薪芯谐芯 泻芯薪胁械褦褉邪 写谢褟 胁褨写锌芯胁褨写薪芯谐芯 蟹邪锌懈褌褍 蟹谢懈褌褌褟. 袛邪薪褨 斜褍写褍褌褜 邪胁褌芯屑邪褌懈褔薪芯 写芯写邪薪褨 锌褨褋谢褟 蟹邪胁械褉褕械薪薪褟 锌械褉褕芯谐芯 泻芯薪胁械褦褉邪."
 
 msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset."
 msgstr "袣褨谢褜泻褨褋褌褜 褋械泻褍薪写, 锌褉芯褌褟谐芯屑 褟泻芯褩 GitLab 蟹斜械褉褨谐邪褦 褨薪褎芯褉屑邪褑褨褞 锌褉芯 蟹斜芯褩. 携泻褖芯 锌褉芯褌褟谐芯屑 褑褜芯谐芯 锌械褉褨芯写褍 卸芯写薪懈褏 蟹斜芯褩胁 薪械 胁褨写斜褍胁邪褦褌褜褋褟, 褨薪褎芯褉屑邪褑褨褟 锌褉芯 褌芯褔泻褍 屑芯薪褌褍胁邪薪薪褟 褋泻懈写邪褦褌褜褋褟."
@@ -3107,7 +4222,7 @@ msgid "The time in seconds GitLab will try to access storage. After this time a
 msgstr "袣褨谢褜泻褨褋褌褜 褋械泻褍薪写, 锌褉芯褌褟谐芯屑 褟泻芯褩 GitLab 薪邪屑邪谐邪褌懈屑械褌褜褋褟 芯褌褉懈屑邪褌懈 写芯褋褌褍锌 写芯 褋褏芯胁懈褖邪 写邪薪懈褏. 袩芯 蟹邪胁械褉褕械薪薪褞 褑褜芯谐芯 锌械褉褨芯写褍 斜褍写械 蟹谐械薪械褉芯胁邪薪邪 锌芯屑懈谢泻邪 锌褉芯 锌械褉械胁懈褖械薪薪褟 谢褨屑褨褌褍 褔邪褋褍."
 
 msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
-msgstr ""
+msgstr "效邪褋 褍 褋械泻褍薪写邪褏 屑褨卸 锌械褉械胁褨褉泻邪屑懈 褋褏芯胁懈褖邪. 携泻褖芯 锌芯锌械褉械写薪褟 锌械褉械胁褨褉泻邪 褨褖械 薪邪 蟹邪胁械褉褕械薪邪, GitLab 锌褉芯锌褍褋褌懈褌褜 薪邪褋褌褍锌薪褍."
 
 msgid "The time taken by each data entry gathered by that stage."
 msgstr "效邪褋, 胁懈褌褉邪褔械薪懈泄 薪邪 泻芯卸械薪 械谢械屑械薪褌, 蟹褨斜褉邪薪懈泄 薪邪 褑褨泄 褋褌邪写褨褩."
@@ -3116,31 +4231,34 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
 msgstr "小械褉械写薪褦 蟹薪邪褔械薪薪褟 胁 褉褟写泻褍. 袩褉懈泻谢邪写: 屑褨卸 3, 5, 9, 褋械褉械写薪褨屑懈 5, 屑褨卸 3, 5, 7, 8, 褋械褉械写薪褨屑懈 (5 + 7) / 2 = 6."
 
 msgid "There are no issues to show"
-msgstr ""
+msgstr "袧械屑邪褦 锌褉芯斜谢械屑 写谢褟 胁褨写芯斜褉邪卸械薪薪褟"
 
 msgid "There are no merge requests to show"
-msgstr ""
+msgstr "袧械屑邪褦 蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟 写谢褟 胁褨写芯斜褉邪卸械薪薪褟"
 
 msgid "There are problems accessing Git storage: "
 msgstr "袆 锌褉芯斜谢械屑懈 蟹 写芯褋褌褍锌芯屑 写芯 褋褏芯胁懈褖邪 git: "
 
-msgid "There was an error loading users activity calendar."
+msgid "There was an error loading results"
 msgstr ""
 
+msgid "There was an error loading users activity calendar."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪薪褨 泻邪谢械薪写邪褉褟 邪泻褌懈胁薪芯褋褌褨 泻芯褉懈褋褌褍胁邪褔褨胁."
+
 msgid "There was an error saving your notification settings."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹斜械褉械卸械薪薪褨 胁邪褕懈褏 薪邪谢邪褕褌褍胁邪薪褜 褋锌芯胁褨褖械薪褜."
 
 msgid "There was an error subscribing to this label."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 锌褨写锌懈褋褑褨 薪邪 褑褞 屑褨褌泻褍."
 
 msgid "There was an error when reseting email token."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 褋泻懈写邪薪薪褨 褌芯泻械薪邪 械谢械泻褌褉芯薪薪芯褩 锌芯褕褌懈."
 
 msgid "There was an error when subscribing to this label."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 锌褨写锌懈褋褑褨 薪邪 褑褞 屑褨褌泻褍."
 
 msgid "There was an error when unsubscribing from this label."
-msgstr ""
+msgstr "袩芯屑懈谢泻邪 锌褉懈 胁褨写锌懈褋褑褨 胁褨写 褑褨褦褩 屑褨褌泻懈."
 
 msgid "This board\\'s scope is reduced"
 msgstr "袙懈写懈屑褨褋褌褜 褑褨褦褩 写芯褕泻懈 芯斜屑械卸械薪邪"
@@ -3164,22 +4282,22 @@ msgid "This issue is locked."
 msgstr "笑褟 锌褉芯斜谢械屑邪 蟹邪斜谢芯泻芯胁邪薪邪."
 
 msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
-msgstr ""
+msgstr "笑械 蟹邪胁写邪薪薪褟 蟹邪锌褍褋泻邪褦褌褜褋褟 泻芯褉懈褋褌褍胁邪褔械屑. 效邪褋褌芯 胁芯薪懈 胁懈泻芯褉懈褋褌芯胁褍褞褌褜褋褟 写谢褟 褉芯蟹谐芯褉褌邪薪薪褟 泻芯写褍 薪邪 production"
 
 msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
-msgstr ""
+msgstr "笑械 蟹邪胁写邪薪薪褟 蟹邪谢械卸懈褌褜 胁褨写 锌芯锌械褉械写薪褨褏, 褟泻褨 锌芯胁懈薪薪褨 蟹邪胁械褉褕懈褌懈褋褟 褍褋锌褨褕薪芯 写谢褟 泄芯谐芯 蟹邪锌褍褋泻褍"
 
 msgid "This job has not been triggered yet"
-msgstr ""
+msgstr "袟邪胁写邪薪薪褟 褖械 薪械 斜褍谢芯 蟹邪锌褍褖械薪械"
 
 msgid "This job has not started yet"
-msgstr ""
+msgstr "笑械 蟹邪胁写邪薪薪褟 褖械 薪械 蟹邪锌褍褋褌懈谢芯褋褟"
 
 msgid "This job is in pending state and is waiting to be picked by a runner"
 msgstr ""
 
 msgid "This job requires a manual action"
-msgstr ""
+msgstr "袟邪胁写邪薪薪褟 胁懈屑邪谐邪褦 褉褍褔薪懈褏 写褨泄"
 
 msgid "This means you can not push code until you create an empty repository or import existing one."
 msgstr "笑械 芯蟹薪邪褔邪褦, 褖芯 胁懈 薪械 屑芯卸械褌械 胁褨写锌褉邪胁谢褟褌懈 泻芯写, 锌芯泻懈 薪械 褋褌胁芯褉懈褌械 锌芯褉芯卸薪褨泄 褉械锌芯蟹懈褌芯褉褨泄 邪斜芯 薪械 褨屑锌芯褉褌褍褦褌械 褨褋薪褍褞褔懈泄."
@@ -3187,12 +4305,18 @@ msgstr "笑械 芯蟹薪邪褔邪褦, 褖芯 胁懈 薪械 屑芯卸械褌械 胁褨写锌褉邪胁谢褟褌懈
 msgid "This merge request is locked."
 msgstr "笑械泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 蟹邪斜谢芯泻芯胁邪薪芯."
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr "笑褟 褋褌芯褉褨薪泻邪 薪械写芯褋褌褍锌薪邪, 褌芯屑褍 褖芯 胁懈 薪械 屑芯卸械褌械 锌械褉械谐谢褟写邪褌懈 褨薪褎芯褉屑邪褑褨褞 锌芯 泻褨谢褜泻芯褏 锌褉芯械泻褌邪褏."
+
 msgid "This project"
 msgstr "笑械泄 锌褉芯械泻褌"
 
 msgid "This repository"
 msgstr "笑械泄 褉械锌芯蟹懈褌芯褉褨泄"
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr "笑械 锌褉懈蟹胁械写械 写芯 胁懈写邪谢械薪薪褟 胁谢邪褋薪芯褩 屑械褌褉懈泻懈, 胁懈 胁锌械胁薪械薪褨?"
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr "笑褨 锌芯胁褨写芯屑谢械薪薪褟 械谢械泻褌褉芯薪薪芯褩 锌芯褕褌懈 邪胁褌芯屑邪褌懈褔薪芯 褋褌邪薪褍褌褜 芯斜谐芯胁芯褉械薪薪褟屑懈 锌褉芯斜谢械屑, 褟泻褨 胁褨写芯斜褉邪卸邪褌懈屑褍褌褜褋褟 褌褍褌 (锌褉懈褔芯屑褍 泻芯屑械薪褌邪褉褨 褋褌邪薪褍褌褜 褔邪褋褌懈薪芯褞 锌械褉械锌懈褋泻懈)."
 
@@ -3205,6 +4329,12 @@ msgstr "效邪褋 写芯 锌芯褔邪褌泻褍 褉芯斜芯褌懈 薪邪写 锌褉芯斜谢械屑芯褞"
 msgid "Time between merge request creation and merge/close"
 msgstr "效邪褋 屑褨卸 褋褌胁芯褉械薪薪褟屑 蟹邪锌懈褌褍 蟹谢懈褌褌褟 褨 泄芯谐芯 胁懈泻芯薪邪薪薪褟屑 邪斜芯 蟹邪泻褉懈褌褌褟屑"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr "袙褨写褋褌械卸械薪薪褟 褔邪褋褍"
 
@@ -3212,13 +4342,13 @@ msgid "Time until first merge request"
 msgstr "效邪褋 写芯 锌械褉褕芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟"
 
 msgid "TimeTrackingEstimated|Est"
-msgstr ""
+msgstr "袟邪锌谢邪薪芯胁邪薪懈泄 褔邪褋"
 
 msgid "TimeTracking|Estimated:"
-msgstr ""
+msgstr "袟邪锌谢邪薪芯胁邪薪懈泄 褔邪褋:"
 
 msgid "TimeTracking|Spent"
-msgstr ""
+msgstr "袙懈褌褉邪褔械薪芯"
 
 msgid "Timeago|%s days ago"
 msgstr "%s 写薪褨胁 褌芯屑褍"
@@ -3348,19 +4478,57 @@ msgid_plural "Time|hrs"
 msgstr[0] "谐芯写懈薪邪"
 msgstr[1] "谐芯写懈薪懈"
 msgstr[2] "谐芯写懈薪"
+msgstr[3] "谐芯写懈薪"
 
 msgid "Time|min"
 msgid_plural "Time|mins"
 msgstr[0] "褏胁懈谢懈薪邪"
 msgstr[1] "褏胁懈谢懈薪懈"
 msgstr[2] "褏胁懈谢懈薪"
+msgstr[3] "褏胁懈谢懈薪"
 
 msgid "Time|s"
 msgstr "褋械泻褍薪写(邪)"
 
+msgid "Tip:"
+msgstr "袩芯褉邪写邪:"
+
 msgid "Title"
 msgstr "袧邪蟹胁邪"
 
+msgid "To GitLab"
+msgstr "袙 GitLab"
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr "袛谢褟 锌褨写泻谢褞褔械薪薪褟 褉械锌芯蟹懈褌芯褉褨褩胁 蟹 GitHub, 胁懈 褋锌芯褔邪褌泻褍 锌芯胁懈薪薪褨 写芯蟹胁芯谢懈褌懈 Gitlab 写芯褋褌褍锌 写芯 褋锌懈褋泻褍 胁邪褕懈褏 褉械锌芯蟹懈褌芯褉褨褩胁 薪邪 GitHub:"
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr "袛谢褟 锌褉懈褦写薪邪薪薪褟 SVN-褉械锌芯蟹懈褌芯褉褨褞, 锌械褉械谐谢褟薪褜褌械 %{svn_link}."
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr "袛谢褟 褨屑锌芯褉褌褍 褉械锌芯蟹懈褌芯褉褨褩胁 蟹 GitHub, 胁懈 褋锌芯褔邪褌泻褍 锌芯胁懈薪薪褨 写芯蟹胁芯谢懈褌懈 Gitlab 写芯褋褌褍锌 写芯 褋锌懈褋泻褍 胁邪褕懈褏 褉械锌芯蟹懈褌芯褉褨褩胁 薪邪 GitHub:"
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr "袛谢褟 褨屑锌芯褉褌褍胁邪薪薪褟 SVN-褉械锌芯蟹懈褌芯褉褨褞, 锌械褉械谐谢褟薪褜褌械 %{svn_link}."
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr "些芯斜 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 谢懈褕械 褎褍薪泻褑褨褩 CI/CD 写谢褟 蟹芯胁薪褨褕薪褜芯谐芯 褉械锌芯蟹懈褌芯褉褨褞, 胁懈斜械褉褨褌褜 <strong>CI/CD 写谢褟 蟹芯胁薪褨褕薪褜芯谐芯 褉械锌芯蟹懈褌芯褉褨褞</strong>."
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr "袛谢褟 锌械褉械谐谢褟写褍 锌谢邪薪褍-谐褉邪褎褨泻褍 写芯写邪泄褌械 蟹邪锌谢邪薪芯胁邪薪褨 写邪褌懈 锌芯褔邪褌泻褍 褌邪 蟹邪胁械褉褕械薪薪褟 写芯 芯写薪芯谐芯 蟹 胁邪褕懈褏 械锌褨泻褨胁 褍 褑褨泄 谐褉褍锌褨 邪斜芯 褩褩 锌褨写谐褉褍锌邪褏. 袙褨写芯斜褉邪卸邪褞褌褜褋褟 谢懈褕械 械锌褨泻懈 蟹邪 锌芯锌械褉械写薪褨 褌邪 薪邪褋褌褍锌薪褨 3 屑褨褋褟褑褨."
+
 msgid "Todo"
 msgstr "袟邪写邪褔邪"
 
@@ -3376,53 +4544,44 @@ msgstr "小褌邪褌褍褋 锌械褉械屑懈泻邪褔邪: 校袙袉袦袣袧袝袧袨"
 msgid "Total Time"
 msgstr "袟邪谐邪谢褜薪懈泄 褔邪褋"
 
-msgid "Total issue time spent"
-msgstr "袟邪谐邪谢褜薪懈泄 胁懈褌褉邪褔械薪懈泄 褔邪褋 薪邪 锌褉芯斜谢械屑褍"
-
 msgid "Total test time for all commits/merges"
 msgstr "袟邪谐邪谢褜薪懈泄 褔邪褋, 褖芯斜 锌械褉械胁褨褉懈褌懈 胁褋褨 泻芯屑褨褌懈/蟹谢懈褌褌褟"
 
+msgid "Total: %{total}"
+msgstr "袙褋褜芯谐芯: %{total}"
+
 msgid "Track activity with Contribution Analytics."
 msgstr "袙褨写褋褌械卸褍胁邪褌懈 邪泻褌懈胁薪褨褋褌褜 蟹邪 写芯锌芯屑芯谐芯褞 袗薪邪谢褨褌懈泻懈 褍褔邪褋薪懈泻褨胁."
 
 msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr "袙褨写褋褌械卸褍泄褌械 谐褉褍锌懈 锌褉芯斜谢械屑 蟹褨 褋锌褨谢褜薪芯褞 褌械屑芯褞 蟹 褉褨蟹薪懈褏 锌褉芯械泻褌褨胁 褌邪 械褌邪锌褨胁"
 
-msgid "Total: %{total}"
-msgstr ""
-
 msgid "Track time with quick actions"
-msgstr ""
+msgstr "袙褨写褋褌械卸褍泄褌械 褔邪褋 蟹邪 写芯锌芯屑芯谐芯褞 褕胁懈写泻懈褏 写褨泄"
 
 msgid "Trigger this manual action"
-msgstr ""
+msgstr "袟邪锌褍褋褌懈褌懈 褑褞 褉褍褔薪褍 写褨褞"
 
 msgid "Turn on Service Desk"
 msgstr "袙胁褨屑泻薪褍褌懈 Service Desk"
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr "袧械胁褨写芯屑芯"
 
 msgid "Unlock"
 msgstr "袪芯蟹斜谢芯泻褍胁邪褌懈"
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
-
 msgid "Unlocked"
 msgstr "袪芯蟹斜谢芯泻芯胁邪薪芯"
 
+msgid "Unresolve discussion"
+msgstr "袩芯胁褌芯褉薪芯 胁褨写泻褉懈褌懈 芯斜谐芯胁芯褉械薪薪褟"
+
 msgid "Unstar"
 msgstr "袙懈写邪谢懈褌懈 褨蟹 芯斜褉邪薪懈褏"
 
 msgid "Up to date"
-msgstr ""
+msgstr "袗泻褌褍邪谢褜薪懈泄"
 
 msgid "Upgrade your plan to activate Advanced Global Search."
 msgstr "袩械褉械泄写褨褌褜 薪邪 胁懈褖懈泄 褌邪褉懈褎薪懈泄 锌谢邪薪 褖芯斜 邪泻褌懈胁褍胁邪褌懈 袩芯泻褉邪褖械薪懈泄 袚谢芯斜邪谢褜薪懈泄 袩芯褕褍泻."
@@ -3446,11 +4605,17 @@ msgid "Upload file"
 msgstr "袟邪胁邪薪褌邪卸懈褌懈 褎邪泄谢"
 
 msgid "Upload new avatar"
-msgstr ""
+msgstr "袟邪胁邪薪褌邪卸懈褌懈 薪芯胁懈泄 邪胁邪褌邪褉"
 
 msgid "UploadLink|click to upload"
 msgstr "袧邪褌懈褋薪褨褌褜, 褖芯斜 蟹邪胁邪薪褌邪卸懈褌懈"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr "袙懈泻芯褉懈褋褌芯胁褍泄褌械 Service Desk 写谢褟 蟹胁鈥櫻徯沸貉� 蟹 胁邪褕懈屑懈 泻芯褉懈褋褌褍胁邪褔邪屑懈 (薪邪锌褉懈泻谢邪写, 褖芯斜 蟹邪锌褉芯锌芯薪褍胁邪褌懈 泻谢褨褦薪褌褋褜泻褍 锌褨写褌褉懈屑泻褍) 褔械褉械蟹 械谢械泻褌褉芯薪薪褍 锌芯褕褌褍 斜械蟹锌芯褋械褉械写薪褜芯 褨蟹 GitLab"
 
@@ -3460,21 +4625,51 @@ msgstr "袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 褌芯泻械薪 锌褨写 褔邪褋 褍褋褌邪薪芯胁泻
 msgid "Use your global notification setting"
 msgstr "袙懈泻芯褉懈褋褌芯胁褍褞褌褜褋褟 谐谢芯斜邪谢褜薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 锌芯胁褨写芯屑谢械薪褜"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
+msgstr "袟屑褨薪薪褨 蟹邪褋褌芯褋芯胁褍褞褌褜褋褟 写芯 褋械褉械写芯胁懈褖 褔械褉械蟹 runner. 袙芯薪懈 屑芯卸褍褌褜 斜褍褌懈 蟹邪褏懈褖械薪褨, 胁 褌邪泻芯屑褍 胁懈锌邪写泻褍 胁芯薪懈 写芯褋褌褍锌薪褨 褌褨谢褜泻懈 写谢褟 蟹邪褏懈褖械薪懈褏 谐褨谢芯泻 褌邪 褌械谐褨胁. 袙懈 屑芯卸械褌械 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 蟹屑褨薪薪褨 写谢褟 锌邪褉芯谢褨胁, 褋械泻褉械褌薪懈泄 泻谢褞褔褨胁 褌芯褖芯."
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
 msgstr ""
 
+msgid "View and edit lines"
+msgstr "袩械褉械谐谢褟薪褍褌懈 褌邪 胁褨写褉械写邪谐褍胁邪褌懈 褉褟写泻懈"
+
+msgid "View epics list"
+msgstr "袩械褉械谐谢褟薪褍褌懈 褋锌懈褋芯泻 械锌褨泻褨胁"
+
 msgid "View file @ "
 msgstr "袩械褉械谐谢褟写 褎邪泄谢邪 @ "
 
+msgid "View group labels"
+msgstr "袩械褉械谐谢褟薪褍褌懈 屑褨褌泻懈 谐褉褍锌懈"
+
 msgid "View labels"
-msgstr ""
+msgstr "袩械褉械谐谢褟薪褍褌懈 屑褨褌泻懈"
 
 msgid "View open merge request"
-msgstr "袩械褉械谐谢褟写 胁褨写泻褉懈褌懈褏 蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
+msgstr "袩械褉械谐谢褟薪褍褌懈 胁褨写泻褉懈褌懈泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
+
+msgid "View project labels"
+msgstr "袩械褉械谐谢褟薪褍褌懈 屑褨褌泻懈 锌褉芯械泻褌褍"
 
 msgid "View replaced file @ "
 msgstr "袩械褉械谐谢褟写 蟹邪屑褨薪械薪芯谐芯 褎邪泄谢褍 @ "
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "袙薪褍褌褉褨褕薪褨泄"
 
@@ -3491,7 +4686,7 @@ msgid "Want to see the data? Please ask an administrator for access."
 msgstr "啸芯褔械褌械 锌芯斜邪褔懈褌懈 写邪薪褨? 袘褍写褜 谢邪褋泻邪, 锌芯锌褉芯褋懈褌褜 褍 邪写屑褨薪褨褋褌褉邪褌芯褉邪 写芯褋褌褍锌."
 
 msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
-msgstr ""
+msgstr "袦懈 薪械 蟹屑芯谐谢懈 锌械褉械胁褨褉懈褌懈, 褖芯 芯写懈薪 褨蟹 胁邪褕懈褏 锌褉芯械泻褌褨胁 胁 GCP 屑邪褦 胁胁褨屑泻薪械薪懈泄 斜褨谢褨薪谐. 袘褍写褜 谢邪褋泻邪, 褋锌褉芯斜褍泄褌械 褖械 褉邪蟹."
 
 msgid "We don't have enough data to show this stage."
 msgstr "袦懈 薪械 屑邪褦屑芯 写芯褋褌邪褌薪褜芯 写邪薪懈褏 写谢褟 胁褨写芯斜褉邪卸械薪薪褟 褑褨褦褩 褋褌邪写褨褩."
@@ -3499,12 +4694,21 @@ msgstr "袦懈 薪械 屑邪褦屑芯 写芯褋褌邪褌薪褜芯 写邪薪懈褏 写谢褟 胁褨写芯斜褉邪
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr "袦懈 褏芯褔械屑芯 斜褍褌懈 胁锌械胁薪械薪褨, 褖芯 褑械 胁懈, 斜褍写褜 谢邪褋泻邪, 锌褨写褌胁械褉写褨褌褜, 褖芯 胁懈 薪械 褉芯斜芯褌."
 
+msgid "Web IDE"
+msgstr "袙械斜-IDE"
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr "袙械斜-谐褍泻 写芯蟹胁芯谢褟褦 胁邪屑 胁懈泻谢懈泻邪褌懈 URL 褟泻褖芯, 薪邪锌褉懈泻谢邪写, 斜褍胁 胁褨写锌褉邪胁谢械薪懈泄 薪芯胁懈泄 泻芯写 邪斜芯 褋褌胁芯褉械薪芯 薪芯胁褍 锌褉芯斜谢械屑褍. 袙懈 屑芯卸械褌械 薪邪谢邪褕褌褍胁邪褌懈 泄芯谐芯 褌邪泻, 褖芯斜 胁褨薪 褉械邪谐褍胁邪胁 薪邪 锌械胁薪褨 锌芯写褨褩 (胁褨写锌褉邪胁泻懈 泻芯写褍, 锌褉芯斜谢械屑懈 邪斜芯 蟹邪锌懈褌懈 薪邪 蟹谢懈褌褌褟). 袚褉褍锌芯胁褨 胁械斜-谐褍泻懈 蟹邪褋褌芯褋芯胁褍褞褌褜褋褟 写芯 胁褋褨褏 锌褉芯械泻褌褨胁 胁 谐褉褍锌褨 褨 写芯蟹胁芯谢褟褞褌褜 胁邪屑 褋褌邪薪写邪褉褌懈蟹褍胁邪褌懈 褩褏 写谢褟 胁褋褨褦褩 胁邪褕芯褩 谐褉褍锌懈."
 
 msgid "Weight"
 msgstr "袙邪谐邪"
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr "Wiki"
 
@@ -3524,10 +4728,10 @@ msgid "WikiClone|Start Gollum and edit locally"
 msgstr "袟邪锌褍褋褌褨褌褜 Gollum 褨 褉械写邪谐褍泄褌械 谢芯泻邪谢褜薪芯"
 
 msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
-msgstr ""
+msgstr "袩芯褉邪写邪: 袙懈 屑芯卸械褌械 锌械褉械屑褨褋褌懈褌懈 褑褞 褋褌芯褉褨薪泻褍, 写芯写邪胁褕懈 褕谢褟褏 写芯 锌芯褔邪褌泻褍 蟹邪谐芯谢芯胁泻邪."
 
 msgid "WikiEdit|There is already a page with the same title in that path."
-msgstr ""
+msgstr "袙卸械 褨褋薪褍褦 褋褌芯褉褨薪泻邪 蟹 褌邪泻懈屑 褕谢褟褏芯屑 褨 蟹邪谐芯谢芯胁泻芯屑."
 
 msgid "WikiEmptyPageError|You are not allowed to create wiki pages"
 msgstr "袙懈 薪械 屑芯卸械褌械 褋褌胁芯褉褞胁邪褌懈 wiki-褋褌芯褉褨薪泻懈"
@@ -3619,32 +4823,44 @@ msgstr "袟 邪薪邪谢褨褌懈泻芯褞 褍褔邪褋薪懈泻褨胁 胁懈 屑芯卸械 胁懈胁褔邪褌懈
 msgid "Withdraw Access Request"
 msgstr "小泻邪褋褍胁邪褌懈 蟹邪锌懈褌 写芯褋褌褍锌褍"
 
+msgid "Write a commit message..."
+msgstr "袧邪锌懈褕褨褌褜 泻芯屑褨褌-锌芯胁褨写芯屑谢械薪薪褟..."
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "袙懈 褏芯褔械褌械 胁懈写邪谢懈褌懈 %{group_name}. 袙懈写邪谢械薪褨 谐褉褍锌懈 袧袝 袦袨袞袧袗 斜褍写褍 胁褨写薪芯胁懈褌懈! 袙懈 袗袘小袨袥挟孝袧袨 胁锌械胁薪械薪褨?"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "袙懈 褏芯褔械褌械 胁懈写邪谢懈褌懈 %{project_name_with_namespace}. 袙懈写邪谢械薪懈泄 锌褉芯械泻褌 袧袝 袦袨袞袝 斜褍褌懈 胁褨写薪芯胁谢械薪懈泄! 袙懈 袗袘小袨袥挟孝袧袨 胁锌械胁薪械薪褨?"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "袙懈 褏芯褔械褌械 胁懈写邪谢懈褌懈 %{project_full_name}. 袙懈写邪谢械薪懈泄 锌褉芯械泻褌 袧袝 袦袨袞袝 斜褍褌懈 胁褨写薪芯胁谢械薪懈泄! 袙懈 袗袘小袨袥挟孝袧袨 胁锌械胁薪械薪褨?"
 
 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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr "袙懈 蟹斜懈褉邪褦褌械褋褟 锌械褉械写邪褌懈 锌褉芯械泻褌 %{project_full_name} 褨薪褕芯屑褍 胁谢邪褋薪懈泻褍. 袙懈 袗袘小袨袥挟孝袧袨 胁锌械胁薪械薪褨?"
 
-msgid "You can also star a label to make it a priority label."
+msgid "You are on a read-only GitLab instance."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
 msgstr ""
 
+msgid "You can also create a project from the command line."
+msgstr "袙懈 褌邪泻芯卸 屑芯卸械褌械 褋褌胁芯褉懈褌懈 锌褉芯械泻褌 褨蟹 泻芯屑邪薪写薪芯谐芯 褉褟写泻邪."
+
+msgid "You can also star a label to make it a priority label."
+msgstr "袙懈 屑芯卸械褌械 写芯写邪褌懈 屑褨褌泻褍 胁 芯斜褉邪薪褨, 褖芯斜 蟹褉芯斜懈褌懈 褩褩 锌褉褨芯褉懈褌械褌薪芯褞."
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr "袙懈 屑芯卸械褌械 谢械谐泻芯 胁褋褌邪薪芯胁懈褌懈 Runner 薪邪 泻谢邪褋褌械褉褨 Kubernetes. %{link_to_help_page}"
+
 msgid "You can move around the graph by using the arrow keys."
-msgstr ""
+msgstr "袙懈 屑芯卸械褌械 锌械褉械褋褍胁邪褌懈褋褟 锌芯 谐褉邪褎褨泻褍 蟹邪 写芯锌芯屑芯谐芯褞 泻谢邪胁褨褕 蟹褨 褋褌褉褨谢泻邪屑懈."
 
 msgid "You can only add files when you are on a branch"
 msgstr "袙懈 屑芯卸械褌械 写芯写邪胁邪褌懈 褎邪泄谢懈 褌褨谢褜泻懈 泻芯谢懈 锌械褉械斜褍胁邪褦褌械 胁 谐褨谢褑褨"
 
 msgid "You can only edit files when you are on a branch"
-msgstr ""
+msgstr "袙懈 屑芯卸械褌械 褉械写邪谐褍胁邪褌懈 褎邪泄谢懈, 谢懈褕械 锌械褉械斜褍胁邪褞褔懈 褍 褟泻褨泄褋褜 谐褨谢褑褨"
 
 msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
 msgstr "袙懈 薪械 屑芯卸械褌械 蟹邪锌懈褋褍胁邪褌懈 薪邪 胁褌芯褉懈薪薪褨 褨薪褋褌邪薪褋懈 \"褌褨谢褜泻懈 写谢褟 褔懈褌邪薪薪褟\" GitLab Geo. 袘褍写褜 谢邪褋泻邪 胁懈泻芯褉懈褋褌芯胁褍泄褌械 %{link_to_primary_node}."
@@ -3652,12 +4868,24 @@ msgstr "袙懈 薪械 屑芯卸械褌械 蟹邪锌懈褋褍胁邪褌懈 薪邪 胁褌芯褉懈薪薪褨 褨薪褋
 msgid "You cannot write to this read-only GitLab instance."
 msgstr "袙懈 薪械 屑芯卸械褌械 蟹邪锌懈褋褍胁邪褌懈 薪邪 褑械泄 \"褌褨谢褜泻懈 写谢褟 褔懈褌邪薪薪褟\" 褨薪褋褌邪薪褋 GitLab."
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr "校 胁邪褋 薪械屑邪褦 薪械芯斜褏褨写薪懈褏 锌褉邪胁 写芯褋褌褍锌褍, 褖芯斜 锌械褉械胁懈蟹薪邪褔懈褌懈 薪邪谢邪褕褌褍胁邪薪薪褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩 LDAP-谐褉褍锌."
+
+msgid "You have no permissions"
+msgstr "校 胁邪褋 薪械屑邪褦 锌褉邪胁 写芯褋褌褍锌褍"
+
 msgid "You have reached your project limit"
 msgstr "袙懈 写芯褋褟谐谢懈 芯斜屑械卸械薪薪褟 胁 胁邪褕芯屑褍 锌褉芯械泻褌褨"
 
+msgid "You must have master access to force delete a lock"
+msgstr "校 胁邪褋 锌芯胁懈薪械薪 斜褍褌懈 写芯褋褌褍锌 薪邪 褉褨胁薪褨 泻械褉褨胁薪懈泻邪, 写谢褟 锌褉懈屑褍褋芯胁芯谐芯 胁懈写邪谢械薪薪褟 斜谢芯泻褍胁邪薪薪褟"
+
 msgid "You must sign in to star a project"
 msgstr "袧械芯斜褏褨写薪芯 褍胁褨泄褌懈, 褖芯斜 芯褑褨薪懈褌懈 锌褉芯械泻褌"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr "袛谢褟 邪泻褌懈胁邪褑褨褩 褎褍薪泻褑褨褩 袘谢芯泻褍胁邪薪薪褟 肖邪泄谢褨胁 胁邪屑 锌芯褌褉褨斜薪邪 褨薪褕邪 谢褨褑械薪蟹褨褟"
+
 msgid "You need permission."
 msgstr "袙邪屑 锌芯褌褉褨斜械薪 写芯蟹胁褨谢"
 
@@ -3686,11 +4914,32 @@ msgid "You won't be able to pull or push project code via SSH until you add an S
 msgstr "袙懈 薪械 蟹屑芯卸械褌械 胁褨写锌褉邪胁谢褟褌懈 褌邪 芯褌褉懈屑褍胁邪褌懈 泻芯写 锌褉芯械泻褌褍 褔械褉械蟹 SSH, 锌芯泻懈 薪械 写芯写邪褋褌械 胁 褋胁褨泄 锌褉芯褎褨谢褜 SSH 泻谢褞褔"
 
 msgid "You'll need to use different branch names to get a valid comparison."
+msgstr "袙邪屑 薪械芯斜褏褨写薪芯 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 褉褨蟹薪褨 褨屑械薪邪 谐褨谢芯泻 写谢褟 泻芯褉械泻褌薪芯谐芯 锌芯褉褨胁薪褟薪薪褟."
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
 msgstr ""
 
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr "袉薪褎芯褉屑邪褑褨褟 锌褉芯 胁邪褕 Kubernetes-泻谢邪褋褌械褉 胁褋械 褖械 写芯褋褌褍锌薪邪 写谢褟 褉械写邪谐褍胁邪薪薪褟 薪邪 褑褨泄 褋褌芯褉褨薪褑褨, 邪谢械 屑懈 褉邪写懈屑芯 胁懈屑泻薪褍褌懈 褨 锌芯胁褌芯褉薪芯 薪邪谢邪褕褌褍胁邪褌懈"
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
 msgstr ""
 
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr "袙邪褕褨 蟹屑褨薪懈 屑芯卸褍褌褜 斜褍褌懈 蟹邪泻芯屑褨褔械薪褨 写芯 %{branch_name}, 芯褋泻褨谢褜泻懈 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 胁褨写泻褉懈褌懈泄."
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr "袙邪褕褨 蟹屑褨薪懈 蟹邪泻芯屑褨褔械薪芯. 袣芯屑褨褌 %{commitId} %{commitStats}"
+
 msgid "Your comment will not be visible to the public."
 msgstr "袙邪褕 泻芯屑械薪褌邪褉 薪械 斜褍写械 胁懈写懈屑懈屑 写谢褟 胁褋褨褏."
 
@@ -3703,6 +4952,16 @@ msgstr "袙邪褕械 褨屑'褟"
 msgid "Your projects"
 msgstr "袙邪褕褨 锌褉芯械泻褌懈"
 
+msgid "among other things"
+msgstr "屑褨卸 褨薪褕懈屑"
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
 msgid "assign yourself"
 msgstr "锌褉懈蟹薪邪褔懈褌懈 褋械斜械"
 
@@ -3712,58 +4971,91 @@ msgstr "褨屑'褟 谐褨谢泻懈"
 msgid "by"
 msgstr "胁褨写"
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr "携泻褨褋褌褜 泻芯写褍"
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
+msgstr "DAST 薪械 胁懈褟胁懈胁 锌芯锌械褉械写卸械薪褜 锌褉懈 邪薪邪谢褨蟹褨 褑褜芯谐芯 review app"
+
+msgid "ciReport|Dependency scanning"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
-msgstr "袟邪胁邪薪褌邪卸械薪薪褟 蟹胁褨褌褍 ${type} 锌褉芯泄褕谢芯 薪械胁写邪谢芯"
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
 
-msgid "ciReport|Fixed:"
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
 msgstr ""
 
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr "袩芯屑懈谢泻邪 锌褉懈 蟹邪胁邪薪褌邪卸械薪薪褨 蟹胁褨褌褍 %{reportName}"
+
+msgid "ciReport|Fixed:"
+msgstr "袙懈锌褉邪胁谢械薪芯:"
+
 msgid "ciReport|Instances"
 msgstr "袉薪褋褌邪薪褋懈"
 
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
-msgstr ""
+msgid "ciReport|Loading %{reportName} report"
+msgstr "袟邪胁邪薪褌邪卸械薪薪褟 蟹胁褨褌褍 %{reportName}"
 
 msgid "ciReport|No changes to code quality"
-msgstr ""
+msgstr "袧械屑邪褦 蟹屑褨薪 褍 褟泻芯褋褌褨 泻芯写褍"
 
 msgid "ciReport|No changes to performance metrics"
-msgstr ""
+msgstr "袧械屑邪褦 蟹屑褨薪 褍 锌芯泻邪蟹薪懈泻邪褏 锌褉芯写褍泻褌懈胁薪芯褋褌褨"
 
 msgid "ciReport|Performance metrics"
-msgstr ""
+msgstr "袩芯泻邪蟹薪懈泻懈 锌褉芯写褍泻褌懈胁薪芯褋褌褨"
 
 msgid "ciReport|SAST"
 msgstr "SAST"
 
+msgid "ciReport|SAST detected"
+msgstr "SAST 胁懈褟胁懈胁"
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr "SAST 薪械 胁懈褟胁懈胁 薪芯胁懈褏 胁褉邪蟹谢懈胁芯褋褌械泄"
+
 msgid "ciReport|SAST detected no security vulnerabilities"
-msgstr ""
+msgstr "SAST 薪械 胁懈褟胁懈胁 卸芯写薪懈褏 胁褉邪蟹谢懈胁芯褋褌械泄"
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
-msgid "ciReport|Show complete code vulnerabilities report"
+msgid "ciReport|Security scanning"
 msgstr ""
 
-msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
+msgid "ciReport|Security scanning failed loading any results"
 msgstr ""
 
-msgid "commit"
-msgstr "泻芯屑褨褌"
+msgid "ciReport|Show complete code vulnerabilities report"
+msgstr "袩芯泻邪蟹邪褌懈 锌芯胁薪懈泄 蟹胁褨褌 锌褉芯 胁褉邪蟹谢懈胁芯褋褌褨 胁 泻芯写褨"
+
+msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
+msgstr "袧械蟹邪褌胁械褉写卸械薪褨 胁褉邪蟹谢懈胁芯褋褌褨 (褔械褉胁芯薪褨) 屑芯卸褍褌褜 斜褍褌懈 胁褨写屑褨褔械薪褨 褟泻 蟹邪褌胁械褉写卸械薪褨. %{helpLink}"
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "ciReport|no vulnerabilities"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "command line instructions"
+msgstr "褨薪褋褌褉褍泻褑褨褩 写谢褟 泻芯屑邪薪写薪芯谐芯 褉褟写泻邪"
+
+msgid "connecting"
+msgstr "蟹'褦写薪邪薪薪褟"
+
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
@@ -3771,51 +5063,139 @@ msgid_plural "days"
 msgstr[0] "写械薪褜"
 msgstr[1] "写薪褨"
 msgstr[2] "写薪褨胁"
+msgstr[3] "写薪褨胁"
+
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
 
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr "%{slash_command} 锌械褉械蟹械蟹邪锌懈褋褍褦 蟹邪锌谢邪薪芯胁邪薪懈泄 褔邪褋 芯褋褌邪薪薪褨屑 蟹薪邪褔械薪薪褟屑."
+
+msgid "here"
+msgstr "褌褍褌"
+
+msgid "importing"
+msgstr "褨屑锌芯褉褌"
+
+msgid "in progress"
 msgstr ""
 
+msgid "is invalid because there is downstream lock"
+msgstr "薪械锌褉邪胁懈谢褜薪懈泄 褔械褉械蟹 薪邪褟胁薪褨褋褌褜 斜谢芯泻褍胁邪薪褜 薪邪 薪懈卸褔懈褏 褉褨胁薪褟褏"
+
+msgid "is invalid because there is upstream lock"
+msgstr "薪械锌褉邪胁懈谢褜薪懈泄 褔械褉械蟹 薪邪褟胁薪褨褋褌褜 斜谢芯泻褍胁邪薪褜 薪邪 胁懈褖懈褏 褉褨胁薪褟褏"
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr "蟹邪斜谢芯泻芯胁邪薪芯 %{path_lock_user_name} %{created_at}"
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] "蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
 msgstr[1] "蟹邪锌懈褌懈 薪邪 蟹谢懈褌褌褟"
 msgstr[2] "蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
+msgstr[3] "蟹邪锌懈褌褨胁 薪邪 蟹谢懈褌褌褟"
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr "袘褍写褜 谢邪褋泻邪 胁褨写薪芯胁褨褌褜 褩褩 邪斜芯 胁懈泻芯褉懈褋褌芯胁褍泄褌械 褨薪褕褍 %{missingBranchName} 谐褨谢泻褍"
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr "袛芯写邪褌懈 蟹邪褌胁械褉写卸械薪薪褟"
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr "袛芯蟹胁芯谢懈褌懈 褉械写邪谐褍胁邪薪薪褟 泻芯屑邪薪写芯褞 锌褉芯械泻褌褍"
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr "袩褨写 褔邪褋 胁懈写邪谢械薪薪褟 胁邪褕芯谐芯 蟹邪褌胁械褉写卸械薪薪褟 褋褌邪谢邪褋褟 锌芯屑懈谢泻邪."
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯褌褉懈屑邪薪薪褨 写邪薪懈褏 锌褉芯 蟹邪褌胁械褉写卸械薪薪褟 写谢褟 褑褜芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟."
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr "袩芯屑懈谢泻邪 锌褉懈 芯斜褉芯斜褑褨 胁邪褕芯谐芯 蟹邪褌胁械褉写卸械薪薪褟."
+
+msgid "mrWidget|Approve"
+msgstr "袟邪褌胁械褉写懈褌懈"
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
 
 msgid "mrWidget|Cancel automatic merge"
 msgstr "小泻邪褋褍胁邪褌懈 邪胁褌芯屑邪褌懈褔薪械 蟹谢懈褌褌褟"
 
 msgid "mrWidget|Check out branch"
-msgstr ""
+msgstr "袩械褉械泄褌懈 胁 谐褨谢泻褍"
 
 msgid "mrWidget|Checking ability to merge automatically"
-msgstr ""
+msgstr "锌械褉械胁褨褉泻邪 屑芯卸谢懈胁芯褋褌褨 邪胁褌芯屑邪褌懈褔薪芯谐芯 蟹谢懈褌褌褟"
 
 msgid "mrWidget|Cherry-pick"
-msgstr ""
+msgstr "胁懈斜褨褉 (泻芯屑褨褌邪)"
 
 msgid "mrWidget|Cherry-pick this merge request in a new merge request"
-msgstr ""
+msgstr "袙懈斜褉邪褌懈 (cherry-pick) 褑械泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 胁 薪芯胁懈泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
 
 msgid "mrWidget|Closed"
-msgstr ""
+msgstr "袟邪泻褉懈褌褨"
 
 msgid "mrWidget|Closed by"
-msgstr ""
+msgstr "袟邪泻褉懈褌懈泄"
 
 msgid "mrWidget|Closes"
+msgstr "袟邪泻褉懈胁邪褦"
+
+msgid "mrWidget|Deployment statistics are not available currently"
 msgstr ""
 
 msgid "mrWidget|Did not close"
-msgstr ""
+msgstr "袧械 蟹邪泻褉懈胁"
 
 msgid "mrWidget|Email patches"
+msgstr "Email-锌邪褌褔褨"
+
+msgid "mrWidget|Failed to load deployment statistics"
 msgstr ""
 
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr "携泻褖芯 谐褨谢泻邪 %{branch} 褨褋薪褍褦 褍 胁邪褕芯屑褍 谢芯泻邪谢褜薪芯屑褍 褉械锌芯蟹懈褌芯褉褨褩, 褌芯 胁懈 屑芯卸械褌械 蟹邪褋褌芯褋褍胁邪褌懈 褑械泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 胁褉褍褔薪褍 蟹邪 写芯锌芯屑芯谐芯褞"
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr "携泻褖芯 谐褨谢泻邪 %{missingBranchName} 褨褋薪褍褦 褍 胁邪褕芯屑褍 谢芯泻邪谢褜薪芯屑褍 褉械锌芯蟹懈褌芯褉褨褩, 褌芯 胁懈 屑芯卸械褌械 蟹邪褋褌芯褋褍胁邪褌懈 褑械泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 胁褉褍褔薪褍 蟹邪 写芯锌芯屑芯谐芯褞 泻芯屑邪薪写薪芯谐芯 褉褟写泻邪"
+
+msgid "mrWidget|Loading deployment statistics"
 msgstr ""
 
 msgid "mrWidget|Mentions"
-msgstr ""
+msgstr "袟谐邪写泻懈"
 
 msgid "mrWidget|Merge"
 msgstr "袟谢懈褌褌褟"
@@ -3824,13 +5204,13 @@ msgid "mrWidget|Merge failed."
 msgstr "袟谢懈褌褌褟 锌褉芯泄褕谢芯 薪械胁写邪谢芯."
 
 msgid "mrWidget|Merge locally"
-msgstr ""
+msgstr "袟谢懈褌懈 谢芯泻邪谢褜薪芯"
 
 msgid "mrWidget|Merged by"
-msgstr ""
+msgstr "袟谢懈褌芯"
 
 msgid "mrWidget|Plain diff"
-msgstr ""
+msgstr "袩褉芯褋褌械 锌芯褉褨胁薪褟薪薪褟 (diff)"
 
 msgid "mrWidget|Refresh"
 msgstr "袨薪芯胁懈褌懈"
@@ -3839,76 +5219,85 @@ msgid "mrWidget|Refresh now"
 msgstr "袨薪芯胁懈褌懈 蟹邪褉邪蟹"
 
 msgid "mrWidget|Refreshing now"
-msgstr ""
+msgstr "袙褨写斜褍胁邪褦褌褜褋褟 芯薪芯胁谢械薪薪褟"
 
 msgid "mrWidget|Remove Source Branch"
-msgstr ""
+msgstr "袙懈写邪谢懈褌懈 谐褨谢泻褍-写卸械褉械谢芯"
 
 msgid "mrWidget|Remove source branch"
-msgstr ""
+msgstr "袙懈写邪谢懈褌懈 谐褨谢泻褍-写卸械褉械谢芯"
+
+msgid "mrWidget|Remove your approval"
+msgstr "袙懈写邪谢懈褌懈 胁邪褕械 蟹邪褌胁械褉写卸械薪薪褟"
 
 msgid "mrWidget|Request to merge"
-msgstr ""
+msgstr "袟邪锌懈褌 薪邪 蟹谢懈褌褌褟"
 
 msgid "mrWidget|Resolve conflicts"
-msgstr ""
+msgstr "袙懈褉褨褕懈褌懈 泻芯薪褎谢褨泻褌懈"
 
 msgid "mrWidget|Revert"
-msgstr ""
+msgstr "袗薪褍谢褞胁邪褌懈"
 
 msgid "mrWidget|Revert this merge request in a new merge request"
-msgstr ""
+msgstr "袗薪褍谢褞胁邪褌懈 褑械泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 蟹邪 写芯锌芯屑芯谐芯褞 薪芯胁芯谐芯 蟹邪锌懈褌褍 薪邪 蟹谢懈褌褌褟"
 
 msgid "mrWidget|Set by"
-msgstr ""
+msgstr "袙褋褌邪薪芯胁谢械薪芯"
 
 msgid "mrWidget|The changes were merged into"
-msgstr ""
+msgstr "袟屑褨薪懈 斜褍谢懈 蟹谢懈褌褨 胁"
 
 msgid "mrWidget|The changes were not merged into"
-msgstr ""
+msgstr "袟屑褨薪懈 薪械 斜褍谢懈 蟹谢懈褌褨 胁"
 
 msgid "mrWidget|The changes will be merged into"
-msgstr ""
+msgstr "袟屑褨薪懈 斜褍写褍褌褜 蟹谢懈褌褨 胁"
 
 msgid "mrWidget|The source branch has been removed"
-msgstr ""
+msgstr "袚褨谢泻褍-写卸械褉械谢芯 胁懈写邪谢械薪芯"
 
 msgid "mrWidget|The source branch is being removed"
-msgstr ""
+msgstr "袚褨谢泻邪-写卸械褉械谢芯 胁 锌褉芯褑械褋褨 胁懈写邪谢械薪薪褟"
 
 msgid "mrWidget|The source branch will be removed"
-msgstr ""
+msgstr "袚褨谢泻褍-写卸械褉械谢芯 斜褍写械 胁懈写邪谢械薪芯"
 
 msgid "mrWidget|The source branch will not be removed"
-msgstr ""
+msgstr "袚褨谢泻褍-写卸械褉械谢芯 薪械 斜褍写械 胁懈写邪谢械薪芯"
 
 msgid "mrWidget|There are merge conflicts"
-msgstr ""
+msgstr "褨褋薪褍褞褌褜 泻芯薪褎谢褨泻褌懈 锌褉懈 蟹谢懈褌褌褨"
 
 msgid "mrWidget|This merge request failed to be merged automatically"
 msgstr "袙褨写斜褍谢邪褋褟 锌芯屑懈谢泻邪 锌褉懈 邪胁褌芯屑邪褌懈褔薪芯屑褍 蟹谢懈褌褌褨 褑褜芯谐芯 蟹邪锌懈褌褍"
 
 msgid "mrWidget|This merge request is in the process of being merged"
-msgstr ""
+msgstr "袟邪锌懈褌 薪邪 蟹谢懈褌褌褟 胁 锌褉芯褑械褋褨 胁懈泻芯薪邪薪薪褟"
 
 msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr "笑械泄 锌褉芯械泻褌 蟹邪邪褉褏褨胁芯胁邪薪懈泄, 写芯褋褌褍锌 写芯 蟹邪锌懈褋褍 斜褍谢芯 胁褨写泻谢褞褔械薪芯"
+
+msgid "mrWidget|Web IDE"
 msgstr ""
 
 msgid "mrWidget|You can merge this merge request manually using the"
-msgstr ""
+msgstr "袙懈 屑芯卸械褌械 锌褉懈泄薪褟褌懈 褑械泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟 胁褉褍褔薪褍 蟹邪 写芯锌芯屑芯谐芯褞"
 
 msgid "mrWidget|You can remove source branch now"
-msgstr ""
+msgstr "孝械锌械褉 胁懈 屑芯卸械褌械 胁懈写邪谢懈褌懈 谐褨谢泻褍-写卸械褉械谢芯"
+
+msgid "mrWidget|branch does not exist."
+msgstr "谐褨谢泻邪 薪械 褨褋薪褍褦."
 
 msgid "mrWidget|command line"
-msgstr ""
+msgstr "泻芯屑邪薪写薪芯谐芯 褉褟写泻邪"
 
 msgid "mrWidget|into"
-msgstr ""
+msgstr "胁"
 
 msgid "mrWidget|to be merged automatically when the pipeline succeeds"
-msgstr ""
+msgstr "斜褍写械 蟹谢懈褌芯 邪胁褌芯屑邪褌懈褔薪芯, 泻芯谢懈 泻芯薪胁械褦褉 蟹邪胁械褉褕懈褌褜褋褟 褍褋锌褨褕薪芯"
 
 msgid "new merge request"
 msgstr "袧芯胁懈泄 蟹邪锌懈褌 薪邪 蟹谢懈褌褌褟"
@@ -3924,6 +5313,7 @@ msgid_plural "parents"
 msgstr[0] "斜邪褌褜泻褨胁褋褜泻懈泄 芯斜鈥櫻斝貉�"
 msgstr[1] "斜邪褌褜泻褨胁褋褜泻褨 芯斜鈥櫻斝貉傂�"
 msgstr[2] "斜邪褌褜泻褨胁褋褜泻懈泄 芯斜鈥櫻斝貉傃栃�"
+msgstr[3] "斜邪褌褜泻褨胁褋褜泻懈泄 芯斜鈥櫻斝貉傃栃�"
 
 msgid "password"
 msgstr "锌邪褉芯谢褜"
@@ -3931,6 +5321,9 @@ msgstr "锌邪褉芯谢褜"
 msgid "personal access token"
 msgstr "芯褋芯斜懈褋褌懈泄 褌芯泻械薪 写芯褋褌褍锌褍"
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr "胁懈写邪谢懈褌懈 蟹邪锌谢邪薪芯胁邪薪褍 写邪褌褍 蟹邪胁械褉褕械薪薪褟"
 
@@ -3938,7 +5331,10 @@ msgid "source"
 msgstr "写卸械褉械谢芯"
 
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
-msgstr ""
+msgstr "%{slash_command} 芯薪芯胁谢褞褦 褋褍屑褍 胁懈褌褉邪褔械薪芯谐芯 褔邪褋褍."
+
+msgid "this document"
+msgstr "褑械泄 写芯泻褍屑械薪褌"
 
 msgid "to help your contributors communicate effectively!"
 msgstr "褖芯斜 写芯锌芯屑芯谐褌懈 褍褔邪褋薪懈泻邪屑 械褎械泻褌懈胁薪芯 褋锌褨谢泻褍胁邪褌懈褋褟!"
@@ -3947,5 +5343,8 @@ msgid "username"
 msgstr "褨屑'褟 泻芯褉懈褋褌褍胁邪褔邪"
 
 msgid "uses Kubernetes clusters to deploy your code!"
-msgstr ""
+msgstr "胁懈泻芯褉懈褋褌芯胁褍褦 泻谢邪褋褌械褉懈 Kubernetes 写谢褟 褉芯蟹谐芯褉褌邪薪薪褟 泻芯写褍!"
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr "蟹 %{additions} 写芯写邪胁邪薪薪褟屑懈 褨 %{deletions} 胁懈写邪谢械薪薪褟屑懈."
 
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 441f080596c254195ce5515e314d7bb2e4d9598e..c25e892e568cca5ab29848b32f2ac78a39cb2076 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 03:58-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:36-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Chinese Simplified\n"
 "Language: zh_CN\n"
@@ -17,7 +17,7 @@ msgstr ""
 "X-Crowdin-File: /master/locale/gitlab.pot\n"
 
 msgid " and"
-msgstr ""
+msgstr " 鍜�"
 
 msgid "%d commit"
 msgid_plural "%d commits"
@@ -25,11 +25,15 @@ msgstr[0] "%d 娆℃彁浜�"
 
 msgid "%d commit behind"
 msgid_plural "%d commits behind"
-msgstr[0] ""
+msgstr[0] "钀藉悗 %d 涓彁浜�"
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] "%d 瀵煎嚭鍣�"
 
 msgid "%d issue"
 msgid_plural "%d issues"
-msgstr[0] ""
+msgstr[0] "%d 涓棰�"
 
 msgid "%d layer"
 msgid_plural "%d layers"
@@ -37,19 +41,32 @@ msgstr[0] "%d 灞�"
 
 msgid "%d merge request"
 msgid_plural "%d merge requests"
-msgstr[0] ""
+msgstr[0] "%d 涓悎骞惰姹�"
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] "%d 鎸囨爣"
 
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "涓烘彁楂橀〉闈㈠姞杞介€熷害鍙婃€ц兘锛屽凡鐪佺暐浜� %s 娆℃彁浜ゃ€�"
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr "%{actionText} & %{openOrClose} %{noteable}"
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
-msgstr ""
+msgstr "鐢� %{commit_author_link} 鎻愪氦浜� %{commit_timeago}"
 
 msgid "%{count} participant"
 msgid_plural "%{count} participants"
 msgstr[0] "%{count} 浣嶅弬涓庤€�"
 
+msgid "%{loadingIcon} Started"
+msgstr "%{loadingIcon} 宸插紑濮�"
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr "%{lock_path} 琚獹itLab鐢ㄦ埛 %{lock_user_id} 閿佸畾"
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr "%{number_commits_behind} 涓惤鍚� %{default_branch} 鍒嗘敮鐨勬彁浜�, %{number_commits_ahead} 鏃╄秴鍓嶇殑鎻愪氦"
 
@@ -59,6 +76,9 @@ msgstr "宸插け璐� %{number_of_failures} 娆�/鏈€澶氬厑璁稿け璐ュけ璐� %{maximum_f
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "宸插け璐� %{number_of_failures} 娆�/鏈€澶氬厑璁稿け璐� %{maximum_failures} 娆★紝GitLab 涓嶄細缁х画鑷姩閲嶈瘯銆傝鍦ㄩ棶棰樿В鍐冲悗閲嶇疆瀛樺偍鍋ュ悍淇℃伅銆�"
 
+msgid "%{openOrClose} %{noteable}"
+msgstr "%{openOrClose} %{noteable}"
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}锛氬凡 %{failed_attempts} 娆″皾璇曡闂瓨鍌ㄥけ璐ワ細"
@@ -85,15 +105,30 @@ msgstr "鏈€楂樿础鐚�"
 msgid "2FA enabled"
 msgstr "鍚敤涓ゆ楠岃瘉"
 
+msgid "<strong>Removes</strong> source branch"
+msgstr "<strong>鍒犻櫎</strong>婧愬垎鏀�"
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "鎸佺画闆嗘垚鏁版嵁鍥�"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr "灏嗗湪娲剧敓(fork)椤圭洰涓腑鍒涘缓涓€涓柊鐨勫垎鏀�, 骞跺紑鍚竴涓柊鐨勫悎骞惰姹傘€�"
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr "椤圭洰鍙互鐢ㄤ簬瀛樻斁鏂囦欢(浠撳簱)锛屽畨鎺掕鍒�(璁)锛屽苟鍙戝竷鏂囨。(wiki)锛� %{among_other_things_link}銆�"
+
+msgid "A user with write access to the source branch selected this option"
+msgstr "鍏锋湁瀵规簮鍒嗘敮鐨勫啓鍏ユ潈闄愮殑鐢ㄦ埛閫夋嫨浜嗘閫夐」"
+
 msgid "About auto deploy"
 msgstr "鍏充簬鑷姩閮ㄧ讲"
 
 msgid "Abuse Reports"
 msgstr "婊ョ敤鎶ュ憡"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "璁块棶浠ょ墝"
 
@@ -103,6 +138,9 @@ msgstr "涓烘柟渚夸慨澶嶆寕杞介棶棰橈紝璁块棶鏁呴殰瀛樺偍宸茶鏆傛椂绂佺敤銆傚湪
 msgid "Account"
 msgstr "甯愬彿"
 
+msgid "Account and limit settings"
+msgstr "甯愭埛鍜岄檺鍒惰缃�"
+
 msgid "Active"
 msgstr "鍚敤"
 
@@ -121,35 +159,74 @@ msgstr "娣诲姞璐$尞鎸囧崡"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr "娣诲姞缁刉ebhooks鍜孏itLab浼佷笟鐗堛€�"
 
+msgid "Add Kubernetes cluster"
+msgstr "娣诲姞 Kubernetes 闆嗙兢"
+
 msgid "Add License"
 msgstr "娣诲姞璁稿彲璇�"
 
+msgid "Add Readme"
+msgstr "娣诲姞鑷堪鏂囦欢"
+
 msgid "Add new directory"
 msgstr "娣诲姞鐩綍"
 
 msgid "Add todo"
-msgstr ""
+msgstr "娣诲姞寰呭姙浜嬮」"
 
 msgid "AdminArea|Stop all jobs"
-msgstr ""
+msgstr "鍋滄鎵€鏈変綔涓�"
 
 msgid "AdminArea|Stop all jobs?"
-msgstr ""
+msgstr "鍋滄鎵€鏈変綔涓氬悧锛�"
 
 msgid "AdminArea|Stop jobs"
-msgstr ""
+msgstr "鍋滄浣滀笟"
 
 msgid "AdminArea|Stopping jobs failed"
-msgstr ""
+msgstr "鍋滄浣滀笟澶辫触"
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
-msgstr ""
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr "鎮ㄥ嵆灏嗗仠姝㈡墍鏈変綔涓氥€傛墍鏈夋鍦ㄨ繍琛岀殑閮戒細琚仠姝€€�"
 
 msgid "AdminHealthPageLink|health page"
 msgstr "鍋ュ悍椤甸潰"
 
+msgid "AdminProjects|Delete"
+msgstr "鍒犻櫎"
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr "鍒犻櫎椤圭洰 %{projectName}锛�"
+
+msgid "AdminProjects|Delete project"
+msgstr "鍒犻櫎椤圭洰"
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr "涓烘瘡涓」鐩殑鑷姩瀹¢槄搴旂敤 (Auto Review Apps) 鍜岃嚜鍔ㄩ儴缃� (Auto Deploy) 闃舵鎸囧畾涓€涓粯璁や娇鐢ㄧ殑鍩熴€�"
+
+msgid "AdminUsers|Block user"
+msgstr "绂佺敤鐢ㄦ埛"
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr "鍒犻櫎鐢ㄦ埛 %{username} 浠ュ強鐩稿叧璐$尞鍚楋紵"
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr "鍒犻櫎鐢ㄦ埛 %{username}锛�"
+
+msgid "AdminUsers|Delete user"
+msgstr "鍒犻櫎鐢ㄦ埛"
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr "鍒犻櫎鐢ㄦ埛鍙婄浉鍏宠础鐚�"
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr "璇疯緭鍏� %{projectName} 鏉ョ‘璁�"
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr "璇疯緭鍏� %{username} 鏉ョ‘璁�"
+
 msgid "Advanced"
-msgstr ""
+msgstr "楂樼骇"
 
 msgid "Advanced settings"
 msgstr "楂樼骇璁剧疆"
@@ -158,53 +235,116 @@ msgid "All"
 msgstr "鍏ㄩ儴"
 
 msgid "All changes are committed"
+msgstr "鎵€鏈夋洿鏀瑰潎宸叉彁浜�"
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr "浠庢ā鏉挎垨瀵煎叆鏃朵负绌虹櫧椤圭洰灏嗗惎鐢ㄦ墍鏈夊姛鑳斤紝浣嗗彲浠ュ湪椤圭洰璁剧疆涓皢鍏剁鐢ㄣ€�"
+
+msgid "Allow edits from maintainers."
+msgstr "鍏佽涓婃父椤圭洰缁存姢浜哄憳杩涜缂栬緫銆�"
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
 msgstr ""
 
 msgid "Allows you to add and manage Kubernetes clusters."
+msgstr "杩欓噷鍙互娣诲姞鍜岀鐞� Kubernetes 闆嗙兢銆�"
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
 msgstr ""
 
-msgid "An error occurred previewing the blob"
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
 msgstr ""
 
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr "姝ゅ锛屼篃鍙互浣跨敤 %{personal_access_token_link}銆傚垱寤篜ersonal Access Token鏃讹紝鍦ㄨ寖鍥翠腑闇€閫夋嫨 <code>repo</code> 锛屼互渚挎樉绀哄彲渚涜繛鎺ョ殑鍏紑鍜岀鏈夌殑浠撳簱鍒楄〃銆�"
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr "姝ゅ锛屼篃鍙互浣跨敤 %{personal_access_token_link}銆傚垱寤篜ersonal Access Token鏃讹紝鍦ㄨ寖鍥翠腑闇€閫夋嫨 <code>repo</code> 锛屼互渚挎樉绀哄彲渚涘鍏ュ叕寮€鍜岀鏈夌殑浠撳簱鍒楄〃"
+
+msgid "An error occurred previewing the blob"
+msgstr "棰勮 blob 鏃跺嚭閿�"
+
 msgid "An error occurred when toggling the notification subscription"
 msgstr "鍒囨崲閫氱煡璁㈤槄鏃跺彂鐢熼敊璇�"
 
 msgid "An error occurred when updating the issue weight"
 msgstr "鏇存柊璁鏉冮噸鏃跺彂鐢熼敊璇�"
 
+msgid "An error occurred while adding approver"
+msgstr "娣诲姞鎵瑰噯浜烘椂鍙戠敓閿欒"
+
+msgid "An error occurred while detecting host keys"
+msgstr "妫€娴嬩富鏈哄瘑閽ユ椂鍙戠敓閿欒"
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
-msgstr ""
+msgstr "鍏抽棴鍔熻兘绐佸嚭鏄剧ず鏃跺彂鐢熼敊璇€傝鍒锋柊椤甸潰骞跺啀娆″皾璇曘€�"
 
 msgid "An error occurred while fetching markdown preview"
-msgstr ""
+msgstr "鑾峰彇 markdown 棰勮鏃跺嚭閿�"
 
 msgid "An error occurred while fetching sidebar data"
 msgstr "鑾峰彇渚ц竟鏍忔暟鎹椂鍙戠敓閿欒"
 
+msgid "An error occurred while fetching the pipeline."
+msgstr "鑾峰彇娴佹按绾挎椂鍑洪敊"
+
 msgid "An error occurred while getting projects"
-msgstr ""
+msgstr "鑾峰彇椤圭洰鏃跺彂鐢熼敊璇�"
+
+msgid "An error occurred while importing project"
+msgstr "鍦ㄥ鍏ラ」鐩椂鍙戠敓閿欒銆�"
+
+msgid "An error occurred while initializing path locks"
+msgstr "鍒濆鍖栬矾寰勯攣鏃跺彂鐢熼敊璇�"
+
+msgid "An error occurred while loading commits"
+msgstr "鍔犺浇鎻愪氦鏃跺彂鐢熼敊璇�"
+
+msgid "An error occurred while loading diff"
+msgstr "鍔犺浇宸紓鏃跺彂鐢熼敊璇�"
 
 msgid "An error occurred while loading filenames"
-msgstr ""
+msgstr "鍔犺浇鏂囦欢鍚嶆椂鍙戠敓閿欒"
+
+msgid "An error occurred while loading the file"
+msgstr "鍔犺浇鏂囦欢鏃跺彂鐢熼敊璇�"
+
+msgid "An error occurred while making the request."
+msgstr "鍙戦€佽姹傛椂鍙戠敓閿欒銆�"
+
+msgid "An error occurred while removing approver"
+msgstr "鍒犻櫎鎵瑰噯鑰呮椂鍙戠敓閿欒"
 
 msgid "An error occurred while rendering KaTeX"
-msgstr ""
+msgstr "娓叉煋KaTeX鏃跺彂鐢熼敊璇�"
 
 msgid "An error occurred while rendering preview broadcast message"
-msgstr ""
+msgstr "娓叉煋骞挎挱娑堟伅鏃跺彂鐢熼敊璇�"
 
 msgid "An error occurred while retrieving calendar activity"
-msgstr ""
+msgstr "鑾峰彇鏃ュ巻娲诲姩鏃跺彂鐢熼敊璇�"
 
 msgid "An error occurred while retrieving diff"
-msgstr ""
+msgstr "鑾峰彇宸紓鏃跺彂鐢熼敊璇�"
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr "淇濆瓨LDAP瑕嗙洊鐘舵€佹椂鍙戠敓閿欒銆傝鍐嶈瘯涓€娆°€�"
+
+msgid "An error occurred while saving assignees"
+msgstr "淇濆瓨琚寚娲句汉鏃跺嚭鐜伴敊璇€�"
 
 msgid "An error occurred while validating username"
-msgstr ""
+msgstr "楠岃瘉鐢ㄦ埛鍚嶆椂鍙戠敓閿欒"
 
 msgid "An error occurred. Please try again."
 msgstr "鍙戠敓浜嗛敊璇紝璇峰啀璇曚竴娆°€�"
 
+msgid "Any Label"
+msgstr "浠讳綍鏍囪"
+
 msgid "Appearance"
 msgstr "澶栬"
 
@@ -218,40 +358,52 @@ msgid "April"
 msgstr "鍥涙湀"
 
 msgid "Archived project! Repository is read-only"
-msgstr "椤圭洰宸插綊妗o紒瀛樺偍搴撲负鍙鐘舵€�"
+msgstr "椤圭洰宸插綊妗o紒浠撳簱涓哄彧璇荤姸鎬�"
 
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "纭畾瑕佸垹闄ゆ娴佹按绾胯鍒掑悧锛�"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "纭畾瑕佹斁寮冧慨鏀瑰悧锛�"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "纭畾瑕侀噸缃敞鍐屼护鐗屽悧锛�"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "纭畾瑕侀噸缃仴搴锋鏌ヤ护鐗屽悧锛�"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr "纭畾瑕佽В閿� %{path_lock_path} 鍚楋紵"
+
 msgid "Are you sure?"
 msgstr "纭畾鍚楋紵"
 
 msgid "Artifacts"
 msgstr "浜х墿"
 
-msgid "Assign custom color like #FF0000"
+msgid "Assertion consumer service URL"
 msgstr ""
 
+msgid "Assign custom color like #FF0000"
+msgstr "鍒嗛厤鑷畾涔夐鑹诧紝濡侳F0000"
+
 msgid "Assign labels"
-msgstr ""
+msgstr "鎸囨淳鏍囪"
 
 msgid "Assign milestone"
-msgstr ""
+msgstr "鍒嗛厤閲岀▼纰�"
 
 msgid "Assign to"
-msgstr ""
+msgstr "鍒嗛厤鍒�"
+
+msgid "Assigned Issues"
+msgstr "宸插垎閰嶈棰�"
+
+msgid "Assigned Merge Requests"
+msgstr "宸插垎閰嶅悎骞惰姹�"
+
+msgid "Assigned to :name"
+msgstr "宸插垎閰嶈嚦 :name"
 
 msgid "Assignee"
-msgstr ""
+msgstr "鎸囨淳浜�"
 
 msgid "Attach a file by drag &amp; drop or %{upload_link}"
 msgstr "鎷栨斁鏂囦欢鍒版澶勬垨鑰� %{upload_link}"
@@ -269,16 +421,22 @@ msgid "Author"
 msgstr "浣滆€�"
 
 msgid "Authors: %{authors}"
+msgstr "浣滆€咃細%{authors}"
+
+msgid "Auto DevOps enabled"
+msgstr "鍚敤Auto DevOps"
+
+msgid "Auto DevOps, runners and job artifacts"
 msgstr ""
 
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "鑷姩瀹¢槄绋嬪簭鍜岃嚜鍔ㄩ儴缃茬▼搴忛渶瑕� %{kubernetes} 鎵嶈兘姝e父宸ヤ綔銆�"
 
 msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
-msgstr ""
+msgstr "鑷姩瀹¢槄绋嬪簭鍜岃嚜鍔ㄩ儴缃茬▼搴忛渶瑕佷竴涓煙鍚嶅拰 %{kubernetes} 鎵嶈兘姝e父宸ヤ綔銆�"
 
 msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
-msgstr "鑷姩瀹℃煡绋嬪簭鍜岃嚜鍔ㄩ儴缃茬▼搴忛渶瑕佷竴涓煙鍚嶆墠鑳芥甯稿伐浣溿€�"
+msgstr "鑷姩瀹¢槄绋嬪簭鍜岃嚜鍔ㄩ儴缃茬▼搴忛渶瑕佷竴涓煙鍚嶆墠鑳芥甯稿伐浣溿€�"
 
 msgid "AutoDevOps|Auto DevOps (Beta)"
 msgstr "DevOps 鑷姩鍖�(娴嬭瘯鐗�)"
@@ -295,18 +453,33 @@ msgstr "灏嗘牴鎹瀹氫箟鐨� CI/CD 閰嶇疆鑷姩鏋勫缓銆佹祴璇曞拰閮ㄧ讲搴旂敤
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr "鎯充簡瑙f洿澶氳璁块棶 %{link_to_documentation}"
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
-msgstr "鎮ㄥ彲浠ヤ负姝ら」鐩縺娲� %{link_to_settings}銆�"
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr "濡傛灉褰撳墠椤圭洰%{link_to_auto_devops_settings}, 鍙互鑷姩鐨勬瀯寤哄拰娴嬭瘯搴旂敤銆傚鏋滃凡%{link_to_add_kubernetes_cluster}锛屽垯涔熷彲浠ュ疄鐜拌嚜鍔ㄩ儴缃层€�"
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr "娣诲姞Kubernetes缇ら泦"
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr "鍚敤Auto DevOps (Beta)"
 
 msgid "Available"
 msgstr "鍙敤鐨�"
 
 msgid "Avatar will be removed. Are you sure?"
-msgstr ""
+msgstr "鍗冲皢鍒犻櫎澶村儚銆傜‘瀹氱户缁悧锛�"
 
 msgid "Average per day: %{average}"
+msgstr "骞冲潎姣忓ぉ: %{average}"
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
 msgstr ""
 
+msgid "Begin with the selected commit"
+msgstr "浠庨€夊畾鐨勬彁浜ゅ紑濮�"
+
 msgid "Billing"
 msgstr "璐﹀崟"
 
@@ -326,7 +499,7 @@ msgid "BillingPlans|Downgrade"
 msgstr "闄嶇骇"
 
 msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
-msgstr "閫氳繃闃呰鎴戜滑鐨�%{faq_link}浜嗚В鏈夊叧姣忎釜璁″垝鐨勬洿澶氫俊鎭€�"
+msgstr "璇锋煡闃�%{faq_link} 杩涗竴姝ヤ簡瑙f瘡涓鍒掔殑鐩稿叧淇℃伅銆�"
 
 msgid "BillingPlans|Manage plan"
 msgstr "绠$悊璁″垝"
@@ -361,12 +534,9 @@ msgstr "姣忓勾鏀粯 %{price_per_year}"
 msgid "BillingPlans|per user"
 msgstr "姣忕敤鎴�"
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "鍒嗘敮"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+msgstr[0] "鍒嗘敮(%{branch_count})"
 
 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}"
@@ -389,6 +559,15 @@ msgstr "鍒囨崲鍒嗘敮"
 msgid "Branches"
 msgstr "鍒嗘敮"
 
+msgid "Branches|Active"
+msgstr "娲昏穬"
+
+msgid "Branches|Active branches"
+msgstr "娲昏穬鍒嗘敮"
+
+msgid "Branches|All"
+msgstr "鍏ㄩ儴"
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr "涓嶈兘鎵惧埌杩欎釜鍒嗘敮鐨� HEAD 鎻愪氦"
 
@@ -414,7 +593,7 @@ msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you s
 msgstr "鍒犻櫎 鈥�%{branch_name}鈥� 鍚庡皢鏃犳硶鎭㈠锛屾偍纭畾锛�"
 
 msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
-msgstr "鍒犻櫎宸插悎骞剁殑鍒嗘敮鍚庡皢鏃犳硶鎭㈠锛屾偍纭畾锛�"
+msgstr "鍒犻櫎宸插悎骞剁殑鍒嗘敮鍚庡皢鏃犳硶鎭㈠锛岀‘瀹氱户缁悧锛�"
 
 msgid "Branches|Filter by branch name"
 msgstr "鎸夊垎鏀悕绉扮瓫閫�"
@@ -434,12 +613,39 @@ msgstr "纭鎵ц %{delete_protected_branch} 鍚庡皢鏃犳硶鎾ら攢鎴栨仮澶嶃€�"
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr "鍙湁椤圭洰绠$悊鑰呮垨鎵€鏈夎€呮墠鑳藉垹闄ゅ彈淇濇姢鐨勫垎鏀紒"
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
-msgstr "鍦� %{project_settings_link} 绠$悊鍙椾繚鎶ょ殑鍒嗘敮"
+msgid "Branches|Overview"
+msgstr "姒傝"
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr "鍦� %{project_settings_link} 涓鐞嗗彈淇濇姢鐨勫垎鏀€�"
+
+msgid "Branches|Show active branches"
+msgstr "鏌ョ湅娲昏穬鍒嗘敮"
+
+msgid "Branches|Show all branches"
+msgstr "鏌ョ湅鎵€鏈夊垎鏀�"
+
+msgid "Branches|Show more active branches"
+msgstr "鏌ョ湅鏇村娲昏穬鍒嗘敮"
+
+msgid "Branches|Show more stale branches"
+msgstr "鏌ョ湅鏇村闈炴椿璺冨垎鏀�"
+
+msgid "Branches|Show overview of the branches"
+msgstr "鏌ョ湅鍒嗘敮姒傝"
+
+msgid "Branches|Show stale branches"
+msgstr "鏌ョ湅闈炴椿璺冨垎鏀�"
 
 msgid "Branches|Sort by"
 msgstr "鎺掑簭"
 
+msgid "Branches|Stale"
+msgstr "闈炴椿璺�"
+
+msgid "Branches|Stale branches"
+msgstr "闈炴椿璺冨垎鏀�"
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr "鍒嗘敮涓嶈兘鑷姩鏇存柊锛屽洜涓哄畠涓庝笂娓稿垎鏀笉涓€鑷淬€�"
 
@@ -453,25 +659,25 @@ msgid "Branches|To avoid data loss, consider merging this branch before deleting
 msgstr "涓洪伩鍏嶆暟鎹涪澶憋紝璇峰湪鍒犻櫎涔嬪墠鍚堝苟姝ゅ垎鏀€�"
 
 msgid "Branches|To confirm, type %{branch_name_confirmation}:"
-msgstr "瑕佺‘璁わ紵璇疯緭鍏� %{branch_name_confirmation} 锛�"
+msgstr "璇疯緭鍏� %{branch_name_confirmation} 鏉ョ‘璁わ細"
 
 msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
 msgstr "瑕佹斁寮冩湰鍦版洿鏀瑰苟瑕嗙洊涓婃父鐗堟湰鐨勫垎鏀紝璇峰湪姝ゅ灏嗗叾鍒犻櫎锛岀劧鍚庨€夋嫨涓婇潰鐨勨€滅珛鍗虫洿鏂扳€濄€�"
 
 msgid "Branches|You鈥檙e about to permanently delete the protected branch %{branch_name}."
-msgstr "灏嗚姘镐箙鍒犻櫎鍙椾繚鎶� %{branch_name} 鍒嗘敮銆�"
+msgstr "灏嗚姘镐箙鍒犻櫎鍙椾繚鎶ょ殑 %{branch_name} 鍒嗘敮銆�"
 
 msgid "Branches|diverged from upstream"
 msgstr "涓婃父鍒嗘敮"
 
 msgid "Branches|merged"
-msgstr "宸插悎骞剁殑"
+msgstr "宸插悎骞�"
 
 msgid "Branches|project settings"
 msgstr "椤圭洰璁剧疆"
 
 msgid "Branches|protected"
-msgstr "鍙椾繚鎶ょ殑"
+msgstr "鍙椾繚鎶�"
 
 msgid "Browse Directory"
 msgstr "娴忚鐩綍"
@@ -485,14 +691,23 @@ msgstr "娴忚鏂囦欢"
 msgid "Browse files"
 msgstr "娴忚鏂囦欢"
 
+msgid "Business"
+msgstr "涓氬姟"
+
 msgid "ByAuthor|by"
 msgstr "浣滆€�:"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr "CI/CD"
+
 msgid "CI/CD configuration"
-msgstr ""
+msgstr "CI/CD 閰嶇疆"
+
+msgid "CI/CD for external repo"
+msgstr "澶栭儴浠撳簱鐨� CI/CD"
 
 msgid "CICD|Jobs"
 msgstr "浣滀笟"
@@ -500,15 +715,21 @@ msgstr "浣滀笟"
 msgid "Cancel"
 msgstr "鍙栨秷"
 
-msgid "Cancel edit"
-msgstr "鍙栨秷缂栬緫"
+msgid "Cannot be merged automatically"
+msgstr "鏃犳硶鑷姩鍚堝苟"
 
 msgid "Cannot modify managed Kubernetes cluster"
+msgstr "鏃犳硶淇敼鎵樼鐨� Kubernetes 缇ら泦"
+
+msgid "Certificate fingerprint"
 msgstr ""
 
 msgid "Change Weight"
 msgstr "鏀瑰彉鏉冮噸"
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "閫夋嫨鍒嗘敮"
 
@@ -522,13 +743,13 @@ msgid "ChangeTypeAction|Revert"
 msgstr "杩樺師"
 
 msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
-msgstr ""
+msgstr "杩欏皢鍒涘缓涓€涓柊鐨勬彁浜�, 鏉ヨ繕鍘熺幇鏈夌殑鏇存敼銆�"
 
 msgid "Changelog"
 msgstr "鏇存柊鏃ュ織"
 
 msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
-msgstr ""
+msgstr "宸紓鏄剧ず鏂瑰紡渚�<b>婧�</b>鐗堟湰鍚堝苟鍒�<b>鐩爣</b>鐗堟湰鐨勫舰寮忋€�"
 
 msgid "Charts"
 msgstr "缁熻鍥�"
@@ -537,7 +758,7 @@ msgid "Chat"
 msgstr "鍗虫椂閫氳"
 
 msgid "Check interval"
-msgstr ""
+msgstr "妫€鏌ラ棿闅�"
 
 msgid "Checking %{text} availability鈥�"
 msgstr "姝e湪妫€鏌�%{text}鐨勫彲鐢ㄦ€�..."
@@ -552,19 +773,25 @@ msgid "Cherry-pick this merge request"
 msgstr "浼橀€夋鍚堝苟璇锋眰"
 
 msgid "Choose File ..."
-msgstr ""
+msgstr "閫夋嫨鏂囦欢 鈥︹€�"
 
 msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
-msgstr ""
+msgstr "閫夋嫨鍒嗘敮/鏍囩(渚嬪%{master})鎴栬緭鍏ユ彁浜�(渚嬪%{sha})浠ユ煡鐪嬫洿鏀瑰唴瀹规垨鍒涘缓鍚堝苟璇锋眰銆�"
 
 msgid "Choose file..."
-msgstr ""
+msgstr "閫夋嫨鏂囦欢..."
 
 msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "閫夋嫨瑕佸悓姝ュ埌姝ゆ鑺傜偣鐨勭兢缁勩€�"
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr "娓呴€夋嫨瑕佽繛鎺ュ苟杩愯 CI/CD 娴佹按绾跨殑浠g爜浠撳簱銆�"
+
+msgid "Choose which repositories you want to import."
+msgstr "閫夋嫨瑕佸鍏ョ殑浠撳簱"
 
 msgid "Choose which shards you wish to synchronize to this secondary node."
-msgstr ""
+msgstr "閫夋嫨瑕佸悓姝ュ埌姝ゆ鑺傜偣鐨勫垏鐗囥€�"
 
 msgid "CiStatusLabel|canceled"
 msgstr "宸插彇娑�"
@@ -621,76 +848,88 @@ msgid "CiStatus|running"
 msgstr "杩愯涓�"
 
 msgid "CiVariables|Input variable key"
-msgstr ""
+msgstr "杈撳叆鍙橀噺鐨勫悕绉�"
 
 msgid "CiVariables|Input variable value"
-msgstr ""
+msgstr "杈撳叆鍙橀噺鐨勫€�"
 
 msgid "CiVariables|Remove variable row"
-msgstr ""
+msgstr "鍒犻櫎鍙橀噺琛�"
 
 msgid "CiVariable|* (All environments)"
-msgstr ""
+msgstr "* (鎵€鏈夌幆澧�)"
 
 msgid "CiVariable|All environments"
-msgstr ""
+msgstr "鎵€鏈夌幆澧�"
 
 msgid "CiVariable|Create wildcard"
-msgstr ""
+msgstr "鍒涘缓閫氶厤绗�"
 
 msgid "CiVariable|Error occured while saving variables"
-msgstr ""
+msgstr "淇濆瓨鍙橀噺鏃跺彂鐢熼敊璇�"
 
 msgid "CiVariable|New environment"
-msgstr ""
+msgstr "鏂板缓鐜"
 
 msgid "CiVariable|Protected"
-msgstr ""
+msgstr "鍙椾繚鎶�"
 
 msgid "CiVariable|Search environments"
-msgstr ""
+msgstr "鎼滅储鐜"
 
 msgid "CiVariable|Toggle protected"
-msgstr ""
+msgstr "寮€鍏充繚鎶ょ姸鎬�"
 
 msgid "CiVariable|Validation failed"
-msgstr ""
+msgstr "楠岃瘉澶辫触"
 
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr "鏂矾鍣� API"
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr "鐐瑰嚮涓嬮潰鐨勬寜閽浆鍒癒ubernetes椤甸潰寮€濮嬪畨瑁呰繃绋�"
+
 msgid "Click to expand text"
-msgstr ""
+msgstr "鐐瑰嚮灞曞紑鏂囨湰"
+
+msgid "Client authentication certificate"
+msgstr "瀹㈡埛绔璇佽瘉涔�"
+
+msgid "Client authentication key"
+msgstr "瀹㈡埛绔璇佸瘑閽�"
+
+msgid "Client authentication key password"
+msgstr "瀹㈡埛绔璇佸瘑閽ュ瘑鐮�"
 
 msgid "Clone repository"
-msgstr "鍏嬮殕瀛樺偍搴�"
+msgstr "鍏嬮殕浠撳簱"
 
 msgid "Close"
 msgstr "鍏抽棴"
 
 msgid "Closed"
-msgstr ""
+msgstr "宸插叧闂�"
 
 msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
-msgstr ""
+msgstr "%{appList} 宸叉垚鍔熷畨瑁呭埌Kubernetes缇ら泦涓�"
 
 msgid "ClusterIntegration|API URL"
 msgstr "API鍦板潃"
 
 msgid "ClusterIntegration|Add Kubernetes cluster"
-msgstr ""
+msgstr "娣诲姞 Kubernetes 缇ら泦"
 
 msgid "ClusterIntegration|Add an existing Kubernetes cluster"
-msgstr ""
+msgstr "娣诲姞鐜版湁鐨凨ubernetes缇ら泦"
 
 msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
-msgstr ""
+msgstr "Kubernetes闆嗙兢闆嗘垚鐨勯珮绾ч€夐」"
 
 msgid "ClusterIntegration|Applications"
 msgstr "搴旂敤绋嬪簭"
 
 msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "纭畾瑕佸垹闄ゆKubernetes闆嗙兢鐨勯泦鎴愬悧锛熸敞鎰忚繖骞朵笉浼氬垹闄ゅ疄闄呯殑Kubernetes缇ら泦鏈韩銆�"
 
 msgid "ClusterIntegration|CA Certificate"
 msgstr "CA璇佷功"
@@ -699,13 +938,13 @@ msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
 msgstr "璇佷功鎺堟潈鍖�(PEM鏍煎紡)"
 
 msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
-msgstr ""
+msgstr "閫夋嫨濡備綍璁剧疆Kubernetes闆嗙兢闆嗘垚"
 
 msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
-msgstr ""
+msgstr "璇烽€夋嫨浣跨敤姝ubernetes缇ら泦鐨勭幆澧冦€�"
 
 msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
-msgstr ""
+msgstr "鎺у埗Kubernetes闆嗙兢涓嶨itLab闆嗘垚鏂瑰紡"
 
 msgid "ClusterIntegration|Copy API URL"
 msgstr "澶嶅埗API鍦板潃"
@@ -713,20 +952,23 @@ msgstr "澶嶅埗API鍦板潃"
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr "澶嶅埗CA璇佷功"
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr "澶嶅埗Ingress IP鍦板潃鍒板壀璐存澘"
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
-msgstr ""
+msgstr "澶嶅埗Kubernetes闆嗙兢鍚嶇О"
 
 msgid "ClusterIntegration|Copy Token"
 msgstr "澶嶅埗浠ょ墝"
 
 msgid "ClusterIntegration|Create Kubernetes cluster"
-msgstr ""
+msgstr "鍒涘缓Kubernetes缇ら泦"
 
 msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "鍦℅oogle Kubernetes寮曟搸涓婂垱寤篕ubernetes闆嗙兢"
 
 msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
-msgstr ""
+msgstr "閫氳繃GitLab鍦℅oogle Kubernetes寮曟搸涓婂垱寤篕ubernetes闆嗙兢"
 
 msgid "ClusterIntegration|Create on GKE"
 msgstr "鍦℅KE涓垱寤�"
@@ -735,13 +977,13 @@ msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
 msgstr "杈撳叆鐜版湁鐨� Kubernetes 闆嗙兢璇︾粏淇℃伅"
 
 msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
-msgstr ""
+msgstr "杈撳叆Kubernetes缇ら泦鐨勮缁嗕俊鎭�"
 
 msgid "ClusterIntegration|Environment scope"
-msgstr ""
+msgstr "鐜鑼冨洿"
 
 msgid "ClusterIntegration|GitLab Integration"
-msgstr ""
+msgstr "GitLab闆嗘垚"
 
 msgid "ClusterIntegration|GitLab Runner"
 msgstr "GitLab Runner"
@@ -758,12 +1000,21 @@ msgstr "Google Kubernetes Engine 椤圭洰"
 msgid "ClusterIntegration|Helm Tiller"
 msgstr "Helm Tiller"
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr "涓轰簡鏄剧ず闆嗙兢鐨勫仴搴风姸鍐碉紝鎮ㄧ殑闆嗙兢闇€瑕侀厤缃甈rometheus浠ユ敹闆嗘墍闇€鐨勬暟鎹€�"
+
 msgid "ClusterIntegration|Ingress"
 msgstr "鍏ュ彛"
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr "Ingress IP鍦板潃"
+
 msgid "ClusterIntegration|Install"
 msgstr "瀹夎"
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr "瀹夎Prometheus"
+
 msgid "ClusterIntegration|Installed"
 msgstr "宸插畨瑁�"
 
@@ -771,70 +1022,73 @@ msgid "ClusterIntegration|Installing"
 msgstr "瀹夎涓�"
 
 msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
-msgstr ""
+msgstr "闆嗘垚Kubernetes闆嗙兢鑷姩鍖�"
 
 msgid "ClusterIntegration|Integration status"
-msgstr ""
+msgstr "闆嗘垚鐘舵€�"
 
 msgid "ClusterIntegration|Kubernetes cluster"
-msgstr ""
+msgstr "Kubernetes 缇ら泦"
 
 msgid "ClusterIntegration|Kubernetes cluster details"
-msgstr ""
+msgstr "Kubernetes缇ら泦璇︾粏淇℃伅"
+
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr "Kubernetes闆嗙兢鍋ュ悍搴�"
 
 msgid "ClusterIntegration|Kubernetes cluster integration"
-msgstr ""
+msgstr "Kubernetes闆嗙兢闆嗘垚"
 
 msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
-msgstr ""
+msgstr "姝ら」鐩凡绂佺敤 Kubernetes 缇ら泦闆嗘垚銆�"
 
 msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
-msgstr ""
+msgstr "姝ら」鐩凡鍚敤 Kubernetes 缇ら泦闆嗘垚銆�"
 
 msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
-msgstr ""
+msgstr "姝ら」鐩凡鍚敤 Kubernetes 缇ら泦闆嗘垚銆傜鐢ㄦ闆嗘垚涓嶄細褰卞搷 Kubernetes 缇ら泦鏈韩, 鍙細鏆傛椂鍏抽棴 GitLab 涓庡叾杩炴帴銆�"
 
 msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
-msgstr ""
+msgstr "姝e湪Google Kubernetes Engine涓婂垱寤篕ubernetes缇ら泦..."
 
 msgid "ClusterIntegration|Kubernetes cluster name"
-msgstr ""
+msgstr "Kubernetes 缇ら泦鍚嶇О"
 
 msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
-msgstr ""
+msgstr "Kubernetes闆嗙兢宸插湪Google Kubernetes Engine涓婃垚鍔熷垱寤恒€傚埛鏂伴〉闈互鏌ョ湅Kubernetes缇ら泦鐨勮缁嗕俊鎭�"
 
 msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
-msgstr ""
+msgstr "閫氳繃Kubernetes 缇ら泦闆嗘垚锛屽彲浠ユ柟渚垮湴浣跨敤瀹¢槄搴旂敤銆侀儴缃插簲鐢ㄧ▼搴忋€佽繍琛屾祦姘寸嚎绛夌瓑銆�%{link_to_help_page}"
 
 msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
-msgstr ""
+msgstr "Kubernetes 缇ら泦鍙敤浜庨儴缃插簲鐢ㄧ▼搴忓拰鎻愪緵姝ら」鐩殑瀹¢槄搴旂敤"
 
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
-msgstr "浜嗚В璇︾粏%{link_to_documentation}"
-
-msgid "ClusterIntegration|Learn more about Kubernetes"
-msgstr ""
+msgstr "杩涗竴姝ヤ簡瑙�%{link_to_documentation}"
 
 msgid "ClusterIntegration|Learn more about environments"
-msgstr ""
+msgstr "杩涗竴姝ヤ簡瑙f湁鍏崇幆澧冪殑淇℃伅"
+
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr "杩涗竴姝ヤ簡瑙e畨鍏ㄧ浉鍏抽厤缃�"
 
 msgid "ClusterIntegration|Machine type"
 msgstr "鏈哄櫒绫诲瀷"
 
 msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
-msgstr ""
+msgstr "璇风‘淇濇偍鐨勫笎鎴� %{link_to_requirements} 鍙互鍒涘缓 Kubernetes 缇ら泦"
 
 msgid "ClusterIntegration|Manage"
-msgstr ""
+msgstr "绠$悊"
 
 msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
-msgstr ""
+msgstr "閫氳繃璁块棶 %{link_gke} 绠$悊 Kubernetes 缇ら泦"
 
 msgid "ClusterIntegration|More information"
-msgstr ""
+msgstr "鏇村淇℃伅"
 
 msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
-msgstr ""
+msgstr "鍦℅itLab浼佷笟楂樼骇鍜屾棗鑸扮増涓彲浠ヤ娇鐢ㄥ涓狵ubernetes闆嗙兢"
 
 msgid "ClusterIntegration|Note:"
 msgstr "娉ㄦ剰:"
@@ -843,7 +1097,7 @@ msgid "ClusterIntegration|Number of nodes"
 msgstr "鑺傜偣鏁伴噺"
 
 msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
-msgstr ""
+msgstr "璇疯緭鍏ubernetes闆嗙兢鐨勮闂俊鎭€傚闇€甯姪锛屽彲浠ラ槄璇籏ubernetes闆嗙兢鐨� %{link_to_help_page}"
 
 msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
 msgstr "璇风‘淇濇偍鐨� Google 甯愭埛绗﹀悎浠ヤ笅瑕佹眰锛�"
@@ -858,19 +1112,19 @@ msgid "ClusterIntegration|Project namespace (optional, unique)"
 msgstr "椤圭洰鍛藉悕绌洪棿(鍙€夛紝鍞竴)"
 
 msgid "ClusterIntegration|Prometheus"
-msgstr ""
+msgstr "Prometheus"
 
 msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
-msgstr ""
+msgstr "璇烽槄璇诲叧浜嶬ubernetes闆嗙兢闆嗘垚鐨�%{link_to_help_page}銆�"
 
 msgid "ClusterIntegration|Remove Kubernetes cluster integration"
-msgstr ""
+msgstr "鍒犻櫎Kubernetes闆嗙兢闆嗘垚"
 
 msgid "ClusterIntegration|Remove integration"
 msgstr "鍒犻櫎闆嗘垚"
 
 msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "浠庡綋鍓嶉」鐩腑鍒犻櫎姝ubernetes闆嗙兢鐨勯厤缃€傝鎿嶄綔骞朵笉浼氬垹闄ゅ疄闄匥ubernetes缇ら泦銆�"
 
 msgid "ClusterIntegration|Request to begin installing failed"
 msgstr "璇锋眰瀹夎澶辫触"
@@ -878,8 +1132,11 @@ msgstr "璇锋眰瀹夎澶辫触"
 msgid "ClusterIntegration|Save changes"
 msgstr "淇濆瓨鏇存敼"
 
+msgid "ClusterIntegration|Security"
+msgstr "瀹夊叏"
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
-msgstr ""
+msgstr "鏌ョ湅骞剁紪杈慘ubernetes闆嗙兢鐨勮缁嗕俊鎭�"
 
 msgid "ClusterIntegration|See machine types"
 msgstr "鍙傝鏈哄櫒绫诲瀷"
@@ -900,25 +1157,28 @@ msgid "ClusterIntegration|Something went wrong on our end."
 msgstr "鍙戠敓浜嗗唴閮ㄩ敊璇�"
 
 msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
-msgstr ""
+msgstr "鍦� Google Kubernetes Engine 涓婂垱寤篕ubernetes闆嗙兢鏃跺彂鐢熼敊璇�"
 
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr "瀹夎 %{title} 鏃跺彂鐢熸晠闅�"
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr "榛樿闆嗙兢閰嶇疆鎻愪緵浜嗘垚鍔熸瀯寤哄拰閮ㄧ讲瀹瑰櫒鍖栧簲鐢ㄦ墍闇€鐨勫ぇ閲忕浉鍏冲姛鑳姐€�"
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
-msgstr ""
+msgstr "璇ュ笎鎴烽渶鍏峰鍦ㄤ笅闈㈡寚瀹氱殑%{link_to_container_project}涓垱寤� Kubernetes闆嗙兢鐨勬潈闄�"
 
 msgid "ClusterIntegration|Toggle Kubernetes Cluster"
-msgstr ""
+msgstr "寮€鍏矺ubernetes 缇ら泦"
 
 msgid "ClusterIntegration|Toggle Kubernetes cluster"
-msgstr ""
+msgstr "寮€鍏矺ubernetes 缇ら泦"
 
 msgid "ClusterIntegration|Token"
 msgstr "浠ょ墝"
 
 msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
-msgstr ""
+msgstr "浣跨敤涓庢椤圭洰鍏宠仈鐨凨ubernetes闆嗙兢锛屽彲浠ユ柟渚垮湴浣跨敤瀹¢槄搴旂敤锛岄儴缃插簲鐢ㄧ▼搴忥紝杩愯娴佹按绾跨瓑绛夈€�"
 
 msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
 msgstr "鎮ㄧ殑甯愭埛蹇呴』鎷ユ湁%{link_to_kubernetes_engine}"
@@ -930,7 +1190,7 @@ msgid "ClusterIntegration|access to Google Kubernetes Engine"
 msgstr "璁块棶 Google Kubernetes Engine"
 
 msgid "ClusterIntegration|check the pricing here"
-msgstr ""
+msgstr "鏌ョ湅瀹氫环"
 
 msgid "ClusterIntegration|documentation"
 msgstr "鏂囨。"
@@ -948,7 +1208,13 @@ msgid "ClusterIntegration|properly configured"
 msgstr "姝g‘閰嶇疆"
 
 msgid "Collapse"
-msgstr ""
+msgstr "鏀惰捣"
+
+msgid "Comment and resolve discussion"
+msgstr "璇勮骞惰В鍐宠璁�"
+
+msgid "Comment and unresolve discussion"
+msgstr "璇勮骞跺皢璁ㄨ鍙樹负鏈喅"
 
 msgid "Comments"
 msgstr "璇勮"
@@ -957,6 +1223,10 @@ msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "鎻愪氦"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] "鎻愪氦(%{commit_count})"
+
 msgid "Commit Message"
 msgstr "鎻愪氦娑堟伅"
 
@@ -967,7 +1237,10 @@ msgid "Commit message"
 msgstr "鎻愪氦淇℃伅"
 
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
-msgstr ""
+msgstr "鎻愪氦缁熻 %{ref} %{start_time} - %{end_time}"
+
+msgid "Commit to %{branchName} branch"
+msgstr "鎻愪氦鍒� %{branchName} 鍒嗘敮"
 
 msgid "CommitBoxTitle|Commit"
 msgstr "鎻愪氦"
@@ -982,25 +1255,25 @@ msgid "Commits feed"
 msgstr "鎻愪氦鍔ㄦ€�"
 
 msgid "Commits per day hour (UTC)"
-msgstr ""
+msgstr "涓€澶╀腑姣忓皬鏃�(UTC)鎻愪氦鏁�"
 
 msgid "Commits per day of month"
-msgstr ""
+msgstr "涓€涓湀涓瘡澶╃殑鎻愪氦鏁�"
 
 msgid "Commits per weekday"
-msgstr ""
+msgstr "涓€鍛ㄤ腑姣忔棩鎻愪氦鏁�"
 
 msgid "Commits|An error occurred while fetching merge requests data."
-msgstr ""
+msgstr "鑾峰彇鍚堝苟璇锋眰鏁版嵁鏃跺嚭閿�"
 
 msgid "Commits|Commit: %{commitText}"
-msgstr ""
+msgstr "鎻愪氦鍐呭锛�%{commitText}"
 
 msgid "Commits|History"
 msgstr "鍘嗗彶"
 
 msgid "Commits|No related merge requests found"
-msgstr ""
+msgstr "鏃犵浉鍏冲悎骞惰姹�"
 
 msgid "Committed by"
 msgstr "鎻愪氦鑰咃細"
@@ -1009,29 +1282,71 @@ msgid "Compare"
 msgstr "姣旇緝"
 
 msgid "Compare Git revisions"
-msgstr ""
+msgstr "姣旇緝Git鎻愪氦鐗堟湰"
 
 msgid "Compare Revisions"
+msgstr "姣旇緝鐗堟湰"
+
+msgid "Compare changes with the last commit"
+msgstr "涓庝笂涓彁浜ゆ瘮杈冨彉鏇村唴瀹�"
+
+msgid "Compare changes with the merge request target branch"
 msgstr ""
 
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
-msgstr ""
+msgstr "%{source_branch} 鍜� %{target_branch} 鏄浉鍚岀殑"
 
 msgid "CompareBranches|Compare"
-msgstr ""
+msgstr "姣旇緝"
 
 msgid "CompareBranches|Source"
-msgstr ""
+msgstr "婧愬垎鏀�"
 
 msgid "CompareBranches|Target"
-msgstr ""
+msgstr "鐩爣鍒嗘敮"
 
 msgid "CompareBranches|There isn't anything to compare."
-msgstr ""
+msgstr "鏃犻渶姣旇緝銆�"
+
+msgid "Confidential"
+msgstr "鏈哄瘑"
 
 msgid "Confidentiality"
+msgstr "绉佸瘑鎬�"
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
 msgstr ""
 
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr "閰嶇疆鐢ㄦ埛鍒涘缓鏂板笎鎴风殑鏂瑰紡銆�"
+
+msgid "Connect"
+msgstr "杩炴帴"
+
+msgid "Connect all repositories"
+msgstr "杩炴帴鎵€鏈変粨搴�"
+
+msgid "Connect repositories from GitHub"
+msgstr "浠� Github 涓鍏ヤ唬鐮佷粨搴�"
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr "杩炴帴澶栭儴浠撳簱鍚庯紝鏂版彁浜ゅ皢浼氬惎鍔–I/CD娴佹按绾裤€備粎鍚敤CI/CD鍔熻兘鐨凣itlab椤圭洰灏嗕細琚垱寤恒€�"
+
+msgid "Connecting..."
+msgstr "姝e湪杩炴帴..."
+
 msgid "Container Registry"
 msgstr "瀹瑰櫒娉ㄥ唽"
 
@@ -1048,7 +1363,7 @@ msgid "ContainerRegistry|How to use the Container Registry"
 msgstr "濡備綍浣跨敤瀹瑰櫒娉ㄥ唽琛�"
 
 msgid "ContainerRegistry|Learn more about"
-msgstr "鍏充簬鏇村"
+msgstr "杩涗竴姝ヤ簡瑙�"
 
 msgid "ContainerRegistry|No tags in Container Registry for this container image."
 msgstr "瀹瑰櫒娉ㄥ唽琛ㄤ腑娌℃湁姝ゅ鍣ㄩ暅鍍忕殑鏍囩銆�"
@@ -1057,7 +1372,7 @@ msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload
 msgstr "鐧诲綍鍚庢偍鍙互浣跨敤閫氱敤鐨�%{build}鍜�%{push}鍛戒护鍒涘缓鍜屼笂浼犲鍣ㄩ暅鍍�"
 
 msgid "ContainerRegistry|Remove repository"
-msgstr "鍒犻櫎瀛樺偍搴�"
+msgstr "鍒犻櫎浠撳簱"
 
 msgid "ContainerRegistry|Remove tag"
 msgstr "鍒犻櫎鏍囩"
@@ -1077,6 +1392,12 @@ msgstr "浣跨敤涓嶅悓鐨勯暅鍍忓悕绉�"
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr "灏� Docker 瀹瑰櫒娉ㄥ唽琛ㄩ泦鎴愬埌 GitLab 涓紝姣忎釜椤圭洰閮藉彲浠ユ湁鍚勮嚜鐨勭┖闂存潵瀛樺偍 Docker 鐨勯暅鍍忋€�"
 
+msgid "Continuous Integration and Deployment"
+msgstr "鎸佺画闆嗘垚鍜岄儴缃�"
+
+msgid "Contribution"
+msgstr "璐$尞"
+
 msgid "Contribution guide"
 msgstr "璐$尞鎸囧崡"
 
@@ -1084,22 +1405,22 @@ msgid "Contributors"
 msgstr "璐$尞鑰�"
 
 msgid "ContributorsPage|%{startDate} 鈥� %{endDate}"
-msgstr ""
+msgstr "%{startDate} 鈥� %{endDate}"
 
 msgid "ContributorsPage|Building repository graph."
-msgstr "鏋勫缓瀛樺偍搴撳浘鏍囥€�"
+msgstr "鏋勫缓鍥捐〃涓€�"
 
 msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
-msgstr "鎻愪氦鍒�%{branch_name}锛屾帓闄ゅ悎骞舵彁浜ゃ€傞檺浜�6000娆℃彁浜ゃ€�"
+msgstr "%{branch_name} 鍒嗘敮涓婄殑鎻愪氦锛屼笉鍚悎骞舵彁浜ゃ€傞檺6000娆°€�"
 
 msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
-msgstr "璇风◢绛夌墖鍒伙紝杩欎釜椤甸潰浼氬湪鍑嗗濂芥椂鑷姩鍒锋柊銆�"
+msgstr "璇风◢鍊欙紝鍥捐〃鏋勫缓瀹屾垚鍚庨〉闈細鑷姩鍒锋柊銆�"
 
 msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
 msgstr "鎺у埗姝ゆ瑕佽妭鐐圭殑 LFS/attachment 鐨勬渶澶у苟鍙�"
 
 msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "鎺у埗姝ゆ瑕佽妭鐐圭殑瀛樺偍搴撴渶澶у苟鍙�"
+msgstr "鎺у埗姝ゆ瑕佽妭鐐圭殑浠撳簱鏈€澶у苟鍙�"
 
 msgid "Copy SSH public key to clipboard"
 msgstr "澶嶅埗 SSH 鍏挜鍒板壀璐存澘"
@@ -1108,41 +1429,59 @@ msgid "Copy URL to clipboard"
 msgstr "澶嶅埗 URL 鍒板壀璐存澘"
 
 msgid "Copy branch name to clipboard"
-msgstr ""
+msgstr "灏嗗垎鏀悕绉板鍒跺埌鍓创鏉�"
+
+msgid "Copy command to clipboard"
+msgstr "灏嗗懡浠ゅ鍒跺埌鍓创鏉�"
 
 msgid "Copy commit SHA to clipboard"
 msgstr "澶嶅埗鎻愪氦 SHA 鐨勫€煎埌鍓创鏉�"
 
 msgid "Copy reference to clipboard"
-msgstr ""
+msgstr "灏嗙储寮曞鍒跺埌鍓创鏉�"
 
 msgid "Create"
-msgstr ""
+msgstr "鍒涘缓"
 
 msgid "Create New Directory"
 msgstr "鍒涘缓鏂扮洰褰�"
 
+msgid "Create a new branch"
+msgstr "鍒涘缓涓€涓柊鍒嗘敮"
+
+msgid "Create a new branch and merge request"
+msgstr "鍒涘缓涓€涓柊鐨勫垎鏀強鍚堝苟璇锋眰"
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "鍦ㄥ笎鎴蜂笂鍒涘缓涓汉璁块棶浠ょ墝锛屼互閫氳繃 %{protocol} 鏉ユ媺鍙栨垨鎺ㄩ€併€�"
 
+msgid "Create branch"
+msgstr "鍒涘缓鍒嗘敮"
+
 msgid "Create directory"
 msgstr "鍒涘缓鐩綍"
 
-msgid "Create empty bare repository"
-msgstr "鍒涘缓绌虹殑瀛樺偍搴�"
+msgid "Create empty repository"
+msgstr "鍒涘缓绌虹殑浠撳簱"
 
 msgid "Create epic"
-msgstr "鍒涘缓EPIC"
+msgstr "鍒涘缓鍙茶瘲鏁呬簨"
 
 msgid "Create file"
 msgstr "鍒涘缓鏂囦欢"
 
+msgid "Create group label"
+msgstr "鍒涘缓缇ょ粍鏍囪"
+
 msgid "Create lists from labels. Issues with that label appear in that list."
-msgstr ""
+msgstr "浠庢爣璁板垱寤哄垪琛紝鍚湁璇ユ爣璁扮殑璁灏嗗嚭鐜板湪鐩稿簲鐨勫垪涓€�"
 
 msgid "Create merge request"
 msgstr "鍒涘缓鍚堝苟璇锋眰"
 
+msgid "Create merge request and branch"
+msgstr "鍒涘缓鍚堝苟璇锋眰鍙婂垎鏀�"
+
 msgid "Create new branch"
 msgstr "鍒涘缓鏂板垎鏀�"
 
@@ -1153,11 +1492,14 @@ msgid "Create new file"
 msgstr "鍒涘缓鏂版枃浠�"
 
 msgid "Create new label"
-msgstr ""
+msgstr "鍒涘缓鏂版爣璁�"
 
 msgid "Create new..."
 msgstr "鍒涘缓..."
 
+msgid "Create project label"
+msgstr "鍒涘缓椤圭洰鏍囪"
+
 msgid "CreateNewFork|Fork"
 msgstr "娲剧敓"
 
@@ -1167,8 +1509,14 @@ msgstr "鏍囩"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "鍒涘缓涓汉璁块棶浠ょ墝"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr "鑷�%{branchName} 鍒涘缓涓€涓柊鍒嗘敮"
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr "鑷�%{branchName} 鍒涘缓涓€涓柊鍒嗘敮锛屽苟杞埌鍒涘缓涓€涓柊鐨勫悎骞惰姹�"
+
 msgid "Creating epic"
-msgstr "鍒涘缓EPIC涓�"
+msgstr "鍒涘缓鍙茶瘲鏁呬簨涓�"
 
 msgid "Cron Timezone"
 msgstr "Cron 鏃跺尯"
@@ -1177,7 +1525,7 @@ msgid "Cron syntax"
 msgstr "Cron 璇硶"
 
 msgid "Current node"
-msgstr ""
+msgstr "褰撳墠鑺傜偣"
 
 msgid "Custom notification events"
 msgstr "鑷畾涔夐€氱煡浜嬩欢"
@@ -1185,6 +1533,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "鍛ㄦ湡鍒嗘瀽"
 
@@ -1221,6 +1572,9 @@ msgstr "鍗佷簩"
 msgid "December"
 msgstr "鍗佷簩鏈�"
 
+msgid "Default classification label"
+msgstr "榛樿鍒嗙被鏍囩"
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "浣跨敤 Cron 璇硶瀹氫箟鑷畾涔夋ā寮�"
 
@@ -1244,19 +1598,19 @@ msgid "Details"
 msgstr "璇︽儏"
 
 msgid "Diffs|No file name available"
-msgstr ""
+msgstr "娌℃湁鍙敤鐨勬枃浠跺悕"
 
 msgid "Directory name"
 msgstr "鐩綍鍚嶇О"
 
 msgid "Disable"
-msgstr ""
+msgstr "绂佺敤"
 
-msgid "Discard changes"
-msgstr "鏀惧純鏇存敼"
+msgid "Discard draft"
+msgstr "鑸嶅純鑽夌ǹ"
 
 msgid "Discover GitLab Geo."
-msgstr ""
+msgstr "鍙戠幇GitLab Geo銆�"
 
 msgid "Dismiss Cycle Analytics introduction box"
 msgstr "鍏抽棴寰幆鍒嗘瀽浠嬬粛妗�"
@@ -1264,9 +1618,15 @@ msgstr "鍏抽棴寰幆鍒嗘瀽浠嬬粛妗�"
 msgid "Dismiss Merge Request promotion"
 msgstr "鍏抽棴鍚堝苟璇锋眰涓殑淇冮攢骞垮憡"
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "涓嶅啀鏄剧ず"
 
+msgid "Done"
+msgstr "瀹屾垚"
+
 msgid "Download"
 msgstr "涓嬭浇"
 
@@ -1294,7 +1654,13 @@ msgstr "宸紓鏂囦欢"
 msgid "DownloadSource|Download"
 msgstr "涓嬭浇"
 
+msgid "Downvotes"
+msgstr "韪�"
+
 msgid "Due date"
+msgstr "鎴鏃ユ湡"
+
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
 msgstr ""
 
 msgid "Edit"
@@ -1304,12 +1670,54 @@ msgid "Edit Pipeline Schedule %{id}"
 msgstr "缂栬緫 %{id} 娴佹按绾胯鍒�"
 
 msgid "Edit files in the editor and commit changes here"
+msgstr "鍦ㄧ紪杈戝櫒涓紪杈戞枃浠跺苟鍦ㄨ繖閲屸€嬧€嬫彁浜ゅ彉鏇村唴瀹�"
+
+msgid "Editing"
+msgstr "缂栬緫"
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
 msgstr ""
 
 msgid "Emails"
 msgstr "鐢靛瓙閭欢"
 
 msgid "Enable"
+msgstr "鍚敤"
+
+msgid "Enable Auto DevOps"
+msgstr "鍚敤Auto DevOps"
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr "鍚敤骞堕厤缃甀nfluxDB鎸囨爣銆�"
+
+msgid "Enable and configure Prometheus metrics."
+msgstr "鍚敤骞堕厤缃甈rometheus鎸囨爣銆�"
+
+msgid "Enable classification control using an external service"
+msgstr "浣跨敤澶栭儴鏈嶅姟鍚敤鍒嗙被鎺у埗"
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
 msgstr ""
 
 msgid "Environments|An error occurred while fetching the environments."
@@ -1358,7 +1766,7 @@ msgid "Environments|Updated"
 msgstr "宸叉洿鏂�"
 
 msgid "Environments|You don't have any environments right now."
-msgstr "浣犺繕娌℃湁璁剧疆鐜"
+msgstr "褰撳墠鏈缃幆澧�"
 
 msgid "Epic will be removed! Are you sure?"
 msgstr "EPIC灏嗚鍒犻櫎!鏄惁纭畾锛�"
@@ -1366,38 +1774,50 @@ msgstr "EPIC灏嗚鍒犻櫎!鏄惁纭畾锛�"
 msgid "Epics"
 msgstr "EPIC"
 
+msgid "Epics Roadmap"
+msgstr "鍙茶瘲鏁呬簨璺嚎鍥�"
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
-msgstr "EPIC璁╀綘鏇存湁鏁堢巼鍦扮鐞嗕綘鐨勯」鐩粍鍚堬紝鑰屼笖涓嶈垂鍚圭伆涔嬪姏"
+msgstr "鍒╃敤鍙茶瘲鏁呬簨(Epics)锛屼骇鍝佺嚎绠$悊浼氬彉寰楁洿杞绘澗涓旀洿楂樻晥"
+
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr "妫€鏌ュ垎鏀暟鎹椂鍑洪敊銆傝鍐嶈瘯涓€娆°€�"
+
+msgid "Error committing changes. Please try again."
+msgstr "鎻愪氦鏇存敼鏃跺嚭閿欍€傝鍐嶈瘯涓€娆°€�"
 
 msgid "Error creating epic"
-msgstr "鍒涘缓EPIC鏃跺嚭閿�"
+msgstr "鍒涘缓鍙茶瘲鏁呬簨鏃跺嚭閿�"
 
 msgid "Error fetching contributors data."
-msgstr ""
+msgstr "鑾峰彇璐$尞鑰呮暟鎹椂鍑洪敊銆�"
 
 msgid "Error fetching labels."
-msgstr ""
+msgstr "鑾峰彇鏍囪鏃跺嚭閿欍€�"
 
 msgid "Error fetching network graph."
-msgstr ""
+msgstr "鑾峰彇缃戠粶鍥炬椂鍑洪敊銆�"
 
 msgid "Error fetching refs"
-msgstr ""
+msgstr "鑾峰彇refs鏃跺嚭閿欍€�"
 
 msgid "Error fetching usage ping data."
-msgstr ""
+msgstr "鑾峰彇浣跨敤鎯呭喌(usage ping) 鏁版嵁鏃跺嚭閿欍€�"
 
 msgid "Error occurred when toggling the notification subscription"
 msgstr "鍒囨崲閫氱煡璁㈤槄鏃跺彂鐢熼敊璇�"
 
 msgid "Error saving label update."
-msgstr ""
+msgstr "淇濆瓨鏍囪鏇存柊鏃跺嚭閿欍€�"
 
 msgid "Error updating status for all todos."
-msgstr ""
+msgstr "鏇存柊鎵€鏈夊緟鍔炰簨椤圭殑鐘舵€佹椂鍑洪敊銆�"
 
 msgid "Error updating todo status."
-msgstr ""
+msgstr "鏇存柊寰呭姙浜嬮」鐘舵€佹椂鍑洪敊銆�"
 
 msgid "EventFilterBy|Filter by all"
 msgstr "鍏ㄩ儴"
@@ -1427,7 +1847,7 @@ msgid "Every week (Sundays at 4:00am)"
 msgstr "姣忓懆鎵ц锛堝懆鏃ュ噷鏅� 4 鐐癸級"
 
 msgid "Expand"
-msgstr ""
+msgstr "灞曞紑"
 
 msgid "Explore projects"
 msgstr "鏌ョ湅椤圭洰"
@@ -1435,12 +1855,45 @@ msgstr "鏌ョ湅椤圭洰"
 msgid "Explore public groups"
 msgstr "鎼滅储鍏叡缇ょ粍"
 
+msgid "External Classification Policy Authorization"
+msgstr "澶栭儴鍒嗙被鏀跨瓥鎺堟潈"
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr "澶栭儴鎺堟潈鎷掔粷璁块棶姝ら」鐩�"
+
+msgid "External authorization request timeout"
+msgstr "澶栭儴鎺堟潈璇锋眰瓒呮椂"
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr "鍒嗙被鏍囩"
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr "鍒嗙被鏍囩"
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr "鏈缃垎绫绘爣绛炬椂锛屽皢浣跨敤榛樿鐨勫垎绫绘爣绛綻%{default_label}`銆�"
+
+msgid "Failed"
+msgstr "宸插け璐�"
+
+msgid "Failed Jobs"
+msgstr "澶辫触鐨勪綔涓�"
+
 msgid "Failed to change the owner"
 msgstr "鏃犳硶鍙樻洿鎵€鏈夎€�"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr "鏃犳硶浠庣湅鏉跨Щ闄ら棶棰橈紝璇烽噸璇曘€�"
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "鏃犳硶鍒犻櫎娴佹按绾胯鍒�"
 
+msgid "Failed to update issues, please try again."
+msgstr "鏇存柊璁澶辫触, 璇烽噸璇�"
+
 msgid "Feb"
 msgstr "浜�"
 
@@ -1448,7 +1901,7 @@ msgid "February"
 msgstr "浜屾湀"
 
 msgid "Fields on this page are now uneditable, you can configure"
-msgstr ""
+msgstr "褰撳墠椤甸潰涓婄殑瀛楁涓嶅彲缂栬緫锛屽彲浠ラ厤缃�"
 
 msgid "File name"
 msgstr "鏂囦欢鍚�"
@@ -1456,6 +1909,12 @@ msgstr "鏂囦欢鍚�"
 msgid "Files"
 msgstr "鏂囦欢"
 
+msgid "Files (%{human_size})"
+msgstr "鏂囦欢(%{human_size})"
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "鎸夋彁浜ゆ秷鎭繃婊�"
 
@@ -1465,12 +1924,21 @@ msgstr "鎸夎矾寰勬煡鎵�"
 msgid "Find file"
 msgstr "鏌ユ壘鏂囦欢"
 
+msgid "Finished"
+msgstr "宸插畬鎴�"
+
 msgid "FirstPushedBy|First"
 msgstr "棣栨鎺ㄩ€�"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "鎺ㄩ€佽€�:"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "娲剧敓"
@@ -1481,150 +1949,225 @@ msgstr "娲剧敓鑷�"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr "娲剧敓鑷� %{project_name} (鍒犻櫎)"
 
+msgid "Forking in progress"
+msgstr "娲剧敓(Fork)涓�"
+
 msgid "Format"
 msgstr "鏍煎紡"
 
+msgid "From %{provider_title}"
+msgstr "鏉ヨ嚜 %{provider_title}"
+
 msgid "From issue creation until deploy to production"
 msgstr "浠庡垱寤鸿棰樺埌閮ㄧ讲鑷崇敓浜х幆澧�"
 
 msgid "From merge request merge until deploy to production"
 msgstr "浠庡悎骞惰姹傝鍚堝苟鍚庡埌閮ㄧ讲鑷崇敓浜х幆澧�"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr "鍦↘ubernetes缇ら泦璇︾粏淇℃伅瑙嗗浘涓紝浠庡簲鐢ㄧ▼搴忓垪琛ㄤ腑瀹夎Runner"
+
 msgid "GPG Keys"
 msgstr "GPG 瀵嗛挜"
 
 msgid "Generate a default set of labels"
-msgstr ""
+msgstr "鐢熸垚涓€缁勯粯璁ょ殑鏍囪"
 
 msgid "Geo Nodes"
 msgstr "Geo 鑺傜偣"
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr "鑺傜偣鍑虹幇鏁呴殰鎴栨崯鍧忋€�"
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr "鑺傜偣杩愯缂撴參銆佽秴杞�, 鎴栬€呭湪鍋滄満鍚庡垰鍒氭仮澶嶃€�"
 
+msgid "GeoNodes|Checksummed"
+msgstr "宸叉牎楠�"
+
 msgid "GeoNodes|Database replication lag:"
-msgstr ""
+msgstr "鏁版嵁搴撳悓姝ユ粸鍚�"
 
 msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
+msgstr "绂佺敤鑺傜偣浼氫腑姝㈠悓姝ヨ繃绋嬨€傜‘瀹氱户缁悧锛�"
 
 msgid "GeoNodes|Does not match the primary storage configuration"
-msgstr ""
+msgstr "涓庝富瀛樺偍閰嶇疆涓嶄竴鑷�"
 
 msgid "GeoNodes|Failed"
-msgstr ""
+msgstr "澶辫触"
 
 msgid "GeoNodes|Full"
-msgstr ""
+msgstr "鍏ㄩ儴"
 
 msgid "GeoNodes|GitLab version does not match the primary node version"
-msgstr ""
+msgstr "GitLab鐗堟湰涓庝富鑺傜偣鐗堟湰涓嶄竴鑷�"
 
 msgid "GeoNodes|GitLab version:"
-msgstr ""
+msgstr "GitLab鐗堟湰锛�"
 
 msgid "GeoNodes|Health status:"
-msgstr ""
+msgstr "鍋ュ悍鐘跺喌锛�"
 
 msgid "GeoNodes|Last event ID processed by cursor:"
-msgstr ""
+msgstr "娓告爣澶勭悊鐨勬渶鍚庝簨浠禝D:"
 
 msgid "GeoNodes|Last event ID seen from primary:"
-msgstr ""
+msgstr "涓昏妭鐐逛腑鏈€鍚庝簨浠禝D:"
 
 msgid "GeoNodes|Loading nodes"
-msgstr ""
+msgstr "杞藉叆鑺傜偣"
 
 msgid "GeoNodes|Local Attachments:"
-msgstr ""
+msgstr "鏈湴闄勪欢:"
 
 msgid "GeoNodes|Local LFS objects:"
-msgstr ""
+msgstr "鏈湴LFS瀵硅薄:"
 
 msgid "GeoNodes|Local job artifacts:"
-msgstr ""
+msgstr "鏈湴浣滀笟鐢熸垚鐗�:"
 
 msgid "GeoNodes|New node"
-msgstr ""
+msgstr "鏂板缓鑺傜偣"
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr "鑺傜偣璁よ瘉宸叉垚鍔熶慨澶嶃€�"
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr "鑺傜偣宸叉垚鍔熷垹闄ゃ€�"
+
+msgid "GeoNodes|Not checksummed"
+msgstr "鏈牎楠�"
 
 msgid "GeoNodes|Out of sync"
-msgstr ""
+msgstr "涓嶅悓姝�"
+
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr "鍒犻櫎鑺傜偣浼氬仠姝㈠悓姝ャ€傜‘瀹氱户缁紵"
 
 msgid "GeoNodes|Replication slot WAL:"
-msgstr ""
+msgstr "澶嶅埗妲絎AL锛�"
 
 msgid "GeoNodes|Replication slots:"
-msgstr ""
+msgstr "澶嶅埗妲斤細"
+
+msgid "GeoNodes|Repositories checksummed:"
+msgstr "宸叉牎楠屼粨搴�:"
 
 msgid "GeoNodes|Repositories:"
-msgstr ""
+msgstr "浠撳簱:"
+
+msgid "GeoNodes|Repository checksums verified:"
+msgstr "浠撳簱鏍¢獙鍜屽凡楠岃瘉锛�"
 
 msgid "GeoNodes|Selective"
-msgstr ""
+msgstr "閫夋嫨鎬�"
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr "鏇存敼鑺傜偣鐘舵€佹椂鍙戠敓閿欒"
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr "鍒犻櫎鑺傜偣鏃跺彂鐢熼敊璇�"
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr "淇鑺傜偣鏃跺彂鐢熼敊璇�"
 
 msgid "GeoNodes|Storage config:"
-msgstr ""
+msgstr "瀛樺偍璁剧疆:"
 
 msgid "GeoNodes|Sync settings:"
-msgstr ""
+msgstr "鍚屾璁剧疆:"
 
 msgid "GeoNodes|Synced"
-msgstr ""
+msgstr "宸插悓姝�"
 
 msgid "GeoNodes|Unused slots"
-msgstr ""
+msgstr "鏈娇鐢ㄧ殑妲�"
+
+msgid "GeoNodes|Unverified"
+msgstr "鏈獙璇�"
 
 msgid "GeoNodes|Used slots"
-msgstr ""
+msgstr "宸蹭娇鐢ㄧ殑妲�"
+
+msgid "GeoNodes|Verified"
+msgstr "宸查獙璇�"
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr " Wiki鏍¢獙宸查獙璇侊細"
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr "wiki宸叉牎楠�"
 
 msgid "GeoNodes|Wikis:"
-msgstr ""
+msgstr "Wiki:"
 
 msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
-msgstr ""
+msgstr "褰撳墠Geo鑺傜偣閰嶇疆浣跨敤闈炲姞瀵嗙殑HTTP杩炴帴, 寤鸿浣跨敤HTTPS銆�"
 
 msgid "Geo|All projects"
-msgstr ""
+msgstr "鎵€鏈夐」鐩�"
 
 msgid "Geo|File sync capacity"
 msgstr "鏂囦欢鍚屾閲�"
 
 msgid "Geo|Groups to synchronize"
-msgstr ""
+msgstr "闇€鍚屾鐨勭兢缁�"
 
 msgid "Geo|Projects in certain groups"
-msgstr ""
+msgstr "鐗瑰畾缇ょ粍涓殑椤圭洰"
 
 msgid "Geo|Projects in certain storage shards"
-msgstr ""
+msgstr "鐗瑰畾瀛樺偍鐗囦腑鐨勯」鐩�"
 
 msgid "Geo|Repository sync capacity"
-msgstr "瀛樺偍搴撳悓姝ラ噺"
+msgstr "浠撳簱鍚屾閲�"
 
 msgid "Geo|Select groups to replicate."
 msgstr "閫夋嫨瑕佸鍒剁殑缇ょ粍銆�"
 
 msgid "Geo|Shards to synchronize"
-msgstr ""
+msgstr "闇€鍚屾鐨勫瓨鍌ㄧ墖"
+
+msgid "Git repository URL"
+msgstr "Git浠撳簱URL"
 
 msgid "Git revision"
-msgstr ""
+msgstr "Git鎻愪氦鐗堟湰"
 
 msgid "Git storage health information has been reset"
 msgstr "Git 瀛樺偍鍋ュ悍淇℃伅宸查噸缃�"
 
 msgid "Git version"
+msgstr "Git 鐗堟湰"
+
+msgid "GitHub import"
+msgstr "GitHub瀵煎叆"
+
+msgid "GitLab CI Linter has been moved"
+msgstr "GitLab CI Linter宸茶杞Щ"
+
+msgid "GitLab Geo"
 msgstr ""
 
 msgid "GitLab Runner section"
 msgstr "GitLab Runner"
 
-msgid "Gitaly Servers"
+msgid "GitLab single sign on URL"
 msgstr ""
 
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr "Gitaly鏈嶅姟鍣�"
+
+msgid "Go back"
+msgstr "杩斿洖"
+
 msgid "Go to your fork"
 msgstr "璺宠浆鍒版淳鐢熼」鐩�"
 
@@ -1635,7 +2178,25 @@ msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab ad
 msgstr "Google 韬唤楠岃瘉涓嶆槸%{link_to_documentation}銆傚鏋滄偍鎯充娇鐢ㄦ鏈嶅姟锛岃鍜ㄨ鎮ㄧ殑 GitLab 绠$悊鍛樸€�"
 
 msgid "Got it!"
-msgstr ""
+msgstr "浜嗚В锛�"
+
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr "鍒╃敤鍙茶瘲鏁呬簨(Epics)锛屼骇鍝佺嚎绠$悊浼氬彉寰楁洿杞绘澗涓旀洿楂樻晥"
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr "浠� %{dateWord}"
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr "杞藉叆璺嚎鍥�"
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr "璇诲彇鍙茶瘲鏁呬簨鏃跺嚭閿�"
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr "濡傞渶鏌ョ湅璺嚎鍥撅紝璇峰皢璁″垝鐨勫紑濮嬫垨缁撴潫鏃ユ湡娣诲姞鍒板綋鍓嶇兢缁勬垨鍏跺瓙缁勪腑鐨勬煇涓彶璇楁晠浜嬨€傚彧鏄剧ず杩囧幓3涓湀鍜屾帴涓嬫潵3涓湀鐨勫彶璇楁晠浜�&ndash; 浠� %{startDate} 鑷� %{endDate}."
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr "鐩村埌 %{dateWord}"
 
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr "绂佹涓庡叾浠栫兢缁勫叡浜� %{group} 涓殑椤圭洰"
@@ -1653,7 +2214,7 @@ msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can overr
 msgstr "姝よ缃凡搴旂敤浜� %{ancestor_group}銆� 鎮ㄥ彲浠ヨ鐩栨璁剧疆鎴� %{remove_ancestor_share_with_group_lock}銆�"
 
 msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
-msgstr "姝よ缃皢搴旂敤浜庢墍鏈夊瓙缁勶紝闄ら潪鐢辩粍鎵€鏈夎€呰鐩栥€傚凡缁忔湁鏉冭闂椤圭洰鐨勭兢缁勫皢缁х画璁块棶锛岄櫎闈炴墜鍔ㄧЩ闄ゃ€�"
+msgstr "姝よ缃皢搴旂敤浜庢墍鏈夊瓙缁勶紝闄ら潪鐢辩粍鎵€鏈夎€呰鐩栥€傚凡缁忔湁鏉冭闂椤圭洰鐨勭兢缁勫皢浠嶇劧鍏锋湁璁块棶鏉冮檺锛岄櫎闈炶闂潈闄愯鎵嬪姩绉婚櫎銆�"
 
 msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
 msgstr "鏃犳硶绂佺敤鐖剁粍鐨勨€滃叡浜兢缁勯攣鈥濓紝鍙湁鐖剁兢缁勭殑鎵€鏈夎€呮墠鍙互鎿嶄綔锛�"
@@ -1673,9 +2234,6 @@ msgstr "鎵句笉鍒扮兢缁�"
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr "鎮ㄥ彲浠ョ鐞嗙兢缁勬垚鍛樼殑鏉冮檺骞惰闂兢缁勪腑鐨勬瘡涓」鐩€�"
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr "鍦ㄦ缇ょ粍涓垱寤轰竴涓」鐩€�"
 
@@ -1706,6 +2264,9 @@ msgstr "瀵逛笉璧凤紝娌℃湁浠讳綍缇ょ粍鎴栭」鐩鍚堟偍鐨勬悳绱�"
 msgid "Have your users email"
 msgstr "鏈変綘鐨勭敤鎴烽偖浠�"
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "鍋ュ悍妫€鏌�"
 
@@ -1713,7 +2274,7 @@ msgid "Health information can be retrieved from the following endpoints. More in
 msgstr "鍋ュ悍淇℃伅鍙互浠庝互涓婣PI璺緞鑾峰彇銆傚闇€浜嗚В鏇村淇℃伅锛岃鏌ョ湅"
 
 msgid "HealthCheck|Access token is"
-msgstr "璁块棶浠ょ墝鏄�"
+msgstr "璁块棶浠ょ墝涓�"
 
 msgid "HealthCheck|Healthy"
 msgstr "鍋ュ悍"
@@ -1724,9 +2285,18 @@ msgstr "娌℃湁妫€娴嬪埌鍋ュ悍闂"
 msgid "HealthCheck|Unhealthy"
 msgstr "闈炲仴搴�"
 
+msgid "Help"
+msgstr "甯姪"
+
+msgid "Help page"
+msgstr "甯姪椤甸潰"
+
+msgid "Help page text and support page url."
+msgstr "甯姪椤甸潰鏂囨湰鍜屾敮鎸侀〉闈㈢綉鍧€銆�"
+
 msgid "Hide value"
 msgid_plural "Hide values"
-msgstr[0] ""
+msgstr[0] "闅愯棌鍊�"
 
 msgid "History"
 msgstr "鍘嗗彶"
@@ -1734,8 +2304,38 @@ msgstr "鍘嗗彶"
 msgid "Housekeeping successfully started"
 msgstr "宸插紑濮嬬淮鎶�"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr "濡傛灉鍚敤锛屽垯浣跨敤澶栭儴鏈嶅姟涓婄殑鍒嗙被鏍囩鏉ラ獙璇佸椤圭洰鐨勮闂潈闄愩€�"
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr "濡備娇鐢℅itHub锛孏itHub涓婄殑鎻愪氦鍜屾媺鍙栬姹�(pull request)灏嗕細鏄剧ず娴佹按绾跨姸鎬併€� %{more_info_link}"
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr "濡傛灉鏂囦欢宸插瓨鍦紝鍙互浣跨敤涓嬮潰鐨� %{link_to_cli} 鎺ㄩ€佸畠浠€�"
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr "濡傛灉HTTP浠撳簱涓嶅彲鍏紑璁块棶锛岃灏嗚韩浠介獙璇佷俊鎭坊鍔犲埌URL锛� <code>https://username:password@gitlab.company.com/group/project.git</code>."
+
+msgid "Import"
+msgstr "瀵煎叆"
+
+msgid "Import all repositories"
+msgstr "瀵煎叆鎵€鏈変粨搴�"
+
+msgid "Import in progress"
+msgstr "姝e湪瀵煎叆"
+
+msgid "Import repositories from GitHub"
+msgstr "浠� GitHub 瀵煎叆浠撳簱"
+
 msgid "Import repository"
-msgstr "瀵煎叆瀛樺偍搴�"
+msgstr "瀵煎叆浠撳簱"
+
+msgid "ImportButtons|Connect repositories from"
+msgstr "鐢ㄤ互涓嬫柟寮忚繛鎺ヤ粨搴�"
 
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr "鍗忓姪鏀硅繘 GitLab 浼佷笟鐗堢殑璁鐪嬫澘銆�"
@@ -1746,18 +2346,24 @@ msgstr "鍗忓姪鏀瑰杽GitLab 浼佷笟鐗堢殑璁绠$悊涓庢潈閲嶃€�"
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr "鍗忓姪鏀硅繘GitLab 浼佷笟鐗堢殑鎼滅储鍜岄珮绾у叏灞€鎼滅储 銆�"
 
+msgid "Install Runner on Kubernetes"
+msgstr "鍦↘ubernetes涓婂畨瑁匯unner"
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "瀹夎涓€涓笌 GitLab CI 鍏煎鐨� Runner"
 
 msgid "Instance"
 msgid_plural "Instances"
-msgstr[0] "渚嬪瓙"
+msgstr[0] "瀹炰緥"
 
 msgid "Instance does not support multiple Kubernetes clusters"
-msgstr ""
+msgstr "瀹炰緥涓嶆敮鎸佸涓狵ubernetes缇ら泦"
+
+msgid "Integrations"
+msgstr "瀵煎叆鎵€鏈変粨搴�"
 
 msgid "Interested parties can even contribute by pushing commits if they want to."
-msgstr ""
+msgstr "鐩稿叧浜哄憳鐢氳嚦鍙互閫氳繃鎺ㄩ€佹彁浜ゆ潵涓洪」鐩綔鍑鸿础鐚€�"
 
 msgid "Internal - The group and any internal projects can be viewed by any logged in user."
 msgstr "鍐呴儴 - 浠讳綍鐧诲綍鐨勭敤鎴烽兘鍙互鏌ョ湅璇ョ兢缁勫拰浠讳綍鍐呴儴椤圭洰銆�"
@@ -1787,7 +2393,7 @@ msgid "Issues"
 msgstr "璁"
 
 msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
-msgstr ""
+msgstr "璁鍙互鏄己闄凤紝浠诲姟鎴栬璁ㄨ鐨勬兂娉曘€傛澶栵紝鍙互閫氳繃鎼滅储鍜岃繃婊ゆ潵鏌ユ壘璁銆�"
 
 msgid "Jan"
 msgstr "涓€"
@@ -1795,6 +2401,9 @@ msgstr "涓€"
 msgid "January"
 msgstr "涓€鏈�"
 
+msgid "Jobs"
+msgstr "浣滀笟"
+
 msgid "Jul"
 msgstr "涓�"
 
@@ -1807,26 +2416,32 @@ msgstr "鍏�"
 msgid "June"
 msgstr "鍏湀"
 
-msgid "Kubernetes"
+msgid "Koding"
 msgstr ""
 
+msgid "Kubernetes"
+msgstr "Kubernetes"
+
 msgid "Kubernetes Cluster"
-msgstr ""
+msgstr "Kubernetes闆嗙兢"
 
 msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
-msgstr ""
+msgstr "Kubernetes闆嗙兢鍒涘缓鏃堕棿瓒呰繃瓒呮椂; %{timeout}"
 
 msgid "Kubernetes cluster integration was not removed."
-msgstr ""
+msgstr "Kubernetes闆嗙兢闆嗘垚鏈鍒犻櫎銆�"
 
 msgid "Kubernetes cluster integration was successfully removed."
-msgstr ""
+msgstr "Kubernetes闆嗙兢闆嗘垚宸叉垚鍔熷垹闄ゃ€�"
 
 msgid "Kubernetes cluster was successfully updated."
-msgstr ""
+msgstr "Kubernetes缇ら泦宸叉垚鍔熸洿鏂般€�"
+
+msgid "Kubernetes configured"
+msgstr "Kubernetes宸查厤缃�"
 
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
-msgstr ""
+msgstr "Kubernetes鏈嶅姟闆嗘垚鍗冲皢琚仠鐢ㄣ€� 璇蜂娇鐢ㄦ柊鐨� <a href=\"%{url}\"/>Kubernetes缇ら泦</a> 椤甸潰%{deprecated_message_content} Kubernetes闆嗙兢"
 
 msgid "LFSStatus|Disabled"
 msgstr "鍋滅敤"
@@ -1834,11 +2449,29 @@ msgstr "鍋滅敤"
 msgid "LFSStatus|Enabled"
 msgstr "鍚敤"
 
+msgid "Label"
+msgstr "鏍囪"
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr "%{firstLabelName} +%{remainingLabelCount} 鏇村"
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr "%{labelsString}鍜� %{remainingLabelCount} 鏇村"
+
 msgid "Labels"
-msgstr "鏍囩"
+msgstr "鏍囪"
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr "鏍囪鍙互搴旂敤浜� %{features}銆傜兢缁勬爣璁板彲鐢ㄤ簬缇ょ粍涓殑鎵€鏈夐」鐩€�"
 
 msgid "Labels can be applied to issues and merge requests to categorize them."
-msgstr ""
+msgstr "鏍囪鍙敤浜庡璁鍜屽悎骞惰姹傝繘琛屽垎绫汇€�"
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr "<span>灏嗘爣璁�</span> %{labelTitle} <span>鍗囩骇涓虹兢缁勬爣璁帮紵</span>"
+
+msgid "Labels|Promote Label"
+msgstr "鍗囩骇鏍囪"
 
 msgid "Last %d day"
 msgid_plural "Last %d days"
@@ -1869,7 +2502,13 @@ msgid "LastPushEvent|at"
 msgstr "浜�"
 
 msgid "Learn more"
-msgstr ""
+msgstr "杩涗竴姝ヤ簡瑙�"
+
+msgid "Learn more about Kubernetes"
+msgstr "杩涗竴姝ヤ簡瑙e叧浜嶬ubernetes鐨勪俊鎭�"
+
+msgid "Learn more about protected branches"
+msgstr "杩涗竴姝ヤ簡瑙d繚鎶ゅ垎鏀�"
 
 msgid "Learn more in the"
 msgstr "浜嗚В鏇村"
@@ -1889,17 +2528,23 @@ msgstr "閫€鍑洪」鐩�"
 msgid "License"
 msgstr "璁稿彲鍗忚"
 
+msgid "List"
+msgstr "鍒楄〃"
+
+msgid "List your GitHub repositories"
+msgstr "鍒楀嚭GitHub浠撳簱"
+
 msgid "Loading the GitLab IDE..."
-msgstr ""
+msgstr "鍔犺浇GitLab IDE..."
 
 msgid "Lock"
 msgstr "閿佸畾"
 
 msgid "Lock %{issuableDisplayName}"
-msgstr ""
+msgstr "閿佸畾 %{issuableDisplayName}"
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
-msgstr ""
+msgid "Lock not found"
+msgstr "鏈壘鍒伴攣"
 
 msgid "Locked"
 msgstr "宸查攣瀹�"
@@ -1907,13 +2552,28 @@ msgstr "宸查攣瀹�"
 msgid "Locked Files"
 msgstr "宸查攣瀹氭枃浠�"
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr "鍔犻攣鍙互閿佸畾鐗瑰畾鐨勬枃浠舵垨鏂囦欢澶广€�"
+
 msgid "Login"
 msgstr "鐧诲綍"
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
-msgstr ""
+msgstr "GitLab Geo 鍙互鍒涘缓 GitLab 瀹炰緥鐨勫彧璇婚暅鍍�, 浣垮緱浠庤繙绔厠闅嗗拰鎷夊彇澶у瀷浠g爜浠撳簱鐨勬椂闂村ぇ澶х缉鐭紝鎻愰珮鍥㈤槦鎴愬憳鐨勫伐浣滄晥鐜囥€�"
+
+msgid "Manage all notifications"
+msgstr "绠$悊鍏ㄩ儴閫氱煡"
+
+msgid "Manage group labels"
+msgstr "绠$悊缇ょ粍鏍囪"
 
 msgid "Manage labels"
+msgstr "绠$悊鏍囪"
+
+msgid "Manage project labels"
+msgstr "绠$悊椤圭洰鏍囪"
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
 msgstr ""
 
 msgid "Mar"
@@ -1923,7 +2583,7 @@ msgid "March"
 msgstr "涓夋湀"
 
 msgid "Mark done"
-msgstr ""
+msgstr "鏍囪涓哄凡瀹屾垚"
 
 msgid "Maximum git storage failures"
 msgstr "鏈€澶� git 瀛樺偍澶辫触"
@@ -1937,7 +2597,7 @@ msgstr "涓綅鏁�"
 msgid "Members"
 msgstr "鎴愬憳"
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1950,59 +2610,155 @@ msgid "Merge request"
 msgstr "鍚堝苟璇锋眰"
 
 msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
-msgstr ""
+msgstr "鍚堝苟璇锋眰鐢ㄤ簬鎻愬嚭瀵归」鐩殑鏇存敼涓庝粬浜鸿璁�"
 
 msgid "Merged"
-msgstr ""
+msgstr "宸插悎骞�"
 
 msgid "Messages"
 msgstr "娑堟伅"
 
+msgid "Metrics - Influx"
+msgstr "鎸囨爣 - Influx"
+
+msgid "Metrics - Prometheus"
+msgstr "鎸囨爣 - Prometheus"
+
+msgid "Metrics|Business"
+msgstr "涓氬姟"
+
+msgid "Metrics|Create metric"
+msgstr "鍒涘缓鎸囨爣"
+
+msgid "Metrics|Edit metric"
+msgstr "缂栬緫鎸囨爣"
+
+msgid "Metrics|For grouping similar metrics"
+msgstr "鐢ㄤ簬鍒嗙粍绫讳技鎸囨爣"
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr "鍥捐〃绾佃酱鐨勬爣绛俱€傞€氬父琛ㄧず缁樺埗鍗曚綅銆傛按骞宠酱锛圶杞达級涓€鑸〃绀烘椂闂淬€�"
+
+msgid "Metrics|Legend label (optional)"
+msgstr "鍥句緥鏍囩锛堝彲閫夛級"
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr "蹇呴』鏄湁鏁堢殑 PromQL 鏌ヨ銆�"
+
+msgid "Metrics|Name"
+msgstr "鍚嶇О"
+
+msgid "Metrics|New metric"
+msgstr "鍒涘缓鎸囨爣"
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr "Prometheus鏌ヨ鏂囨。"
+
+msgid "Metrics|Query"
+msgstr "鏌ヨ"
+
+msgid "Metrics|Response"
+msgstr "鍝嶅簲"
+
+msgid "Metrics|System"
+msgstr "绯荤粺"
+
+msgid "Metrics|Type"
+msgstr "绫诲瀷"
+
+msgid "Metrics|Unit label"
+msgstr "鍗曚綅鏍囩"
+
+msgid "Metrics|Used as a title for the chart"
+msgstr "鐢ㄤ綔鍥捐〃鐨勬爣棰�"
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr "鐢ㄤ簬鏌ヨ杩斿洖鍗曚釜绯诲垪鏃躲€傚鏋滆繑鍥炲涓郴鍒楋紝鐩稿簲鐨勫浘渚嬫爣绛惧皢浠庤繑鍥炴暟鎹腑閫夊彇銆�"
+
+msgid "Metrics|Y-axis label"
+msgstr "Y杞存爣绛�"
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr "渚嬪HTTP璇锋眰"
+
+msgid "Metrics|e.g. Requests/second"
+msgstr "渚嬪姣忕璇锋眰鏁�"
+
+msgid "Metrics|e.g. Throughput"
+msgstr "渚嬪鍚炲悙閲�"
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr "閫熺巼(5鍒嗛挓鍐呮墍鏈塰ttp璇锋眰)"
+
+msgid "Metrics|e.g. req/sec"
+msgstr "渚嬪姣忕璇锋眰鏁�"
+
 msgid "Milestone"
-msgstr ""
+msgstr "閲岀▼纰�"
 
 msgid "Milestones|Delete milestone"
-msgstr ""
+msgstr "鍒犻櫎閲岀▼纰�"
 
 msgid "Milestones|Delete milestone %{milestoneTitle}?"
-msgstr ""
+msgstr "鍒犻櫎閲岀▼纰� %{milestoneTitle}锛�"
 
 msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
-msgstr ""
+msgstr "鍒犻櫎閲岀▼纰� %{milestoneTitle}澶辫触"
 
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
-msgstr ""
+msgstr "鏈壘鍒伴噷绋嬬 %{milestoneTitle}"
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr "灏� %{milestoneTitle} 鍗囩骇涓虹兢缁勯噷绋嬬锛�"
+
+msgid "Milestones|Promote Milestone"
+msgstr "鍗囩骇閲岀▼纰�"
+
+msgid "Milestones|This action cannot be reversed."
+msgstr "璇ユ搷浣滄棤娉曟挙閿€銆�"
 
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "鏂板缓 SSH 鍏挜"
 
+msgid "Modal|Cancel"
+msgstr "鍙栨秷"
+
+msgid "Modal|Close"
+msgstr "鍏抽棴"
+
 msgid "Monitoring"
 msgstr "鐩戞帶"
 
+msgid "More info"
+msgstr "鏇村淇℃伅"
+
+msgid "More information"
+msgstr "鏇村淇℃伅"
+
 msgid "More information is available|here"
 msgstr "甯姪鏂囨。"
 
 msgid "Move"
-msgstr ""
+msgstr "绉诲姩"
 
 msgid "Move issue"
-msgstr ""
+msgstr "绉诲姩璁"
 
 msgid "Multiple issue boards"
 msgstr "澶氫釜璁鐪嬫澘"
 
 msgid "Name new label"
-msgstr ""
+msgstr "鍛藉悕鏂版爣璁�"
 
 msgid "New Issue"
 msgid_plural "New Issues"
 msgstr[0] "鏂板缓璁"
 
 msgid "New Kubernetes Cluster"
-msgstr ""
+msgstr "鏂板缓Kubernetes闆嗙兢"
 
 msgid "New Kubernetes cluster"
-msgstr ""
+msgstr "鏂板缓Kubernetes闆嗙兢"
 
 msgid "New Pipeline Schedule"
 msgstr "鍒涘缓娴佹按绾胯鍒�"
@@ -2017,7 +2773,7 @@ msgid "New directory"
 msgstr "鏂板缓鐩綍"
 
 msgid "New epic"
-msgstr "鏂癊PIC"
+msgstr "鏂板缓鍙茶瘲鏁呬簨"
 
 msgid "New file"
 msgstr "鏂板缓鏂囦欢"
@@ -2029,13 +2785,13 @@ msgid "New issue"
 msgstr "鏂板缓璁"
 
 msgid "New label"
-msgstr ""
+msgstr "鏂板缓鏍囪"
 
 msgid "New merge request"
 msgstr "鏂板缓鍚堝苟璇锋眰"
 
 msgid "New project"
-msgstr "鏂伴」鐩�"
+msgstr "鏂板缓椤圭洰"
 
 msgid "New schedule"
 msgstr "鏂板缓璁″垝"
@@ -2044,53 +2800,77 @@ msgid "New snippet"
 msgstr "鏂板缓浠g爜鐗囨"
 
 msgid "New subgroup"
-msgstr "鏂板瓙缇ょ粍"
+msgstr "鏂板缓瀛愮兢缁�"
 
 msgid "New tag"
 msgstr "鏂板缓鏍囩"
 
+msgid "No Label"
+msgstr "鏃犳爣璁�"
+
 msgid "No assignee"
-msgstr ""
+msgstr "鏈寚娲�"
 
 msgid "No changes"
-msgstr ""
+msgstr "鏃犲彉鏇村唴瀹�"
 
 msgid "No connection could be made to a Gitaly Server, please check your logs!"
-msgstr ""
+msgstr "鏃犳硶杩炴帴鍒癎italy鏈嶅姟鍣紝璇锋鏌ョ浉鍏虫棩蹇楋紒"
 
 msgid "No due date"
-msgstr ""
+msgstr "鏃犳埅姝㈡棩鏈�"
 
 msgid "No estimate or time spent"
-msgstr ""
+msgstr "鏃犱及绠楁垨娑堣€楃殑鏃堕棿"
 
 msgid "No file chosen"
-msgstr ""
+msgstr "鏈€夊畾浠讳綍鏂囦欢"
+
+msgid "No labels created yet."
+msgstr "灏氭湭鍒涘缓鏍囪"
 
 msgid "No repository"
-msgstr "娌℃湁瀛樺偍搴�"
+msgstr "娌℃湁浠撳簱"
 
 msgid "No schedules"
 msgstr "娌℃湁璁″垝"
 
-msgid "No time spent"
-msgstr "娌℃湁鑺辫垂鏃堕棿"
-
 msgid "None"
 msgstr "鏃�"
 
 msgid "Not allowed to merge"
-msgstr ""
+msgstr "涓嶅厑璁稿悎骞�"
 
 msgid "Not available"
 msgstr "鏁版嵁涓嶈冻"
 
+msgid "Not available for private projects"
+msgstr "瀵圭鏈夐」鐩笉鍙敤"
+
+msgid "Not available for protected branches"
+msgstr "瀵瑰彈淇濇姢鐨勫垎鏀笉鍙敤"
+
 msgid "Not confidential"
-msgstr ""
+msgstr "闈炴満瀵�"
 
 msgid "Not enough data"
 msgstr "鏁版嵁涓嶈冻"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr "璇锋敞鎰忥紝master鍒嗘敮鑷姩鍙椾繚鎶ゃ€�%{link_to_protected_branches}"
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr "鎻愮ず锛氫綔涓篏itLab绠$悊鍛橈紝鍙互閰嶇疆 %{github_integration_link}锛岃繖灏嗗厑璁搁€氳繃GitHub鐧诲綍骞跺厑璁歌繛鎺ithub浠g爜浠撳簱鑰屼笉闇€瑕佷釜浜鸿闂护鐗屻€�"
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr "鎻愮ず锛氫綔涓篏itLab绠$悊鍛橈紝鍙互閰嶇疆 %{github_integration_link}锛岃繖灏嗗厑璁搁€氳繃GitHub鐧诲綍骞跺厑璁稿鍏ithub浠g爜浠撳簱鑰屼笉闇€瑕佷釜浜鸿闂护鐗屻€�"
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr "鎻愮ず锛氬GitLab绠$悊鍛橀厤缃� %{github_integration_link}锛屽皢鍏佽閫氳繃GitHub鐧诲綍骞跺厑璁歌繛鎺ithub浠g爜浠撳簱鑰屼笉闇€瑕佷釜浜鸿闂护鐗屻€�"
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr "鎻愮ず锛氬GitLab绠$悊鍛橀厤缃� %{github_integration_link}锛屽皢鍏佽閫氳繃GitHub鐧诲綍骞跺厑璁稿鍏ithub浠g爜浠撳簱鑰屼笉闇€瑕佷釜浜鸿闂护鐗屻€�"
+
 msgid "Notification events"
 msgstr "閫氱煡浜嬩欢"
 
@@ -2149,10 +2929,10 @@ msgid "Notifications"
 msgstr "閫氱煡"
 
 msgid "Notifications off"
-msgstr ""
+msgstr "绂佺敤閫氱煡"
 
 msgid "Notifications on"
-msgstr ""
+msgstr "鍚敤閫氱煡"
 
 msgid "Nov"
 msgstr "鍗佷竴"
@@ -2164,7 +2944,7 @@ msgid "Number of access attempts"
 msgstr "灏濊瘯璁块棶娆℃暟"
 
 msgid "OK"
-msgstr ""
+msgstr "纭畾"
 
 msgid "Oct"
 msgstr "鍗�"
@@ -2175,11 +2955,17 @@ msgstr "鍗佹湀"
 msgid "OfSearchInADropdown|Filter"
 msgstr "绛涢€�"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr "浠撳簱瀵煎叆鍚庯紝鍙互閫氳繃 SSH 鎷夊彇闀滃儚銆備簡瑙f洿澶� %{ssh_link}"
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr "鍙湁椤圭洰鎴愬憳鍙互鍙戣〃璇勮銆�"
 
 msgid "Open"
-msgstr ""
+msgstr "鎵撳紑"
 
 msgid "Opened"
 msgstr "宸叉墦寮€"
@@ -2193,12 +2979,21 @@ msgstr "鎵撳紑涓€涓柊绐楀彛"
 msgid "Options"
 msgstr "鎿嶄綔"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr "鍚﹀垯锛屽缓璁偍浠庝笅闈㈢殑涓€涓€夐」寮€濮嬨€�"
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "姒傝"
 
 msgid "Owner"
 msgstr "鎵€鏈夎€�"
 
+msgid "Pages"
+msgstr "Pages"
+
 msgid "Pagination|Last 禄"
 msgstr "灏鹃〉 禄"
 
@@ -2211,9 +3006,21 @@ msgstr "涓婁竴椤�"
 msgid "Pagination|芦 First"
 msgstr "芦 棣栭〉"
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "瀵嗙爜"
 
+msgid "Pending"
+msgstr "绛夊緟澶勭悊"
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr "涓汉璁块棶鍑瘉"
+
 msgid "Pipeline"
 msgstr "娴佹按绾�"
 
@@ -2293,10 +3100,55 @@ msgid "Pipelines for last year"
 msgstr "鍘诲勾鐨勬祦姘寸嚎"
 
 msgid "Pipelines|Build with confidence"
-msgstr ""
+msgstr "鑷俊鍦版瀯寤�"
+
+msgid "Pipelines|CI Lint"
+msgstr "CI 閰嶇疆妫€鏌�(CI Lint)"
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr "娓呴櫎Runner缂撳瓨"
 
 msgid "Pipelines|Get started with Pipelines"
-msgstr ""
+msgstr "娴佹按绾垮叆闂�"
+
+msgid "Pipelines|Loading Pipelines"
+msgstr "杞藉叆娴佹按绾�"
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr "椤圭洰缂撳瓨閲嶇疆鎴愬姛銆�"
+
+msgid "Pipelines|Run Pipeline"
+msgstr "杩愯娴佹按绾�"
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr "娓呯悊runner缂撳瓨鏃跺彂鐢熼敊璇€�"
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr "褰撳墠娌℃湁 %{scope}鐨勬祦姘寸嚎銆�"
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr "褰撳墠娌℃湁娴佹按绾裤€�"
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr "姝ら」鐩綋鍓嶆湭閰嶇疆杩愯娴佹按绾裤€�"
+
+msgid "Pipeline|Retry pipeline"
+msgstr "閲嶈瘯娴佹按绾�"
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr "閲嶈瘯娴佹按绾匡純%{pipelineId}鍚楋紵"
+
+msgid "Pipeline|Stop pipeline"
+msgstr "鍋滄娴佹按绾�"
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr "鍋滄娴佹按绾匡純%{pipelineId}鍚楋紵"
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr "鍗冲皢閲嶈瘯娴佹按绾� %{pipelineId}銆�"
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr "鍗冲皢鍋滄娴佹按绾� %{pipelineId}銆�"
 
 msgid "Pipeline|all"
 msgstr "鎵€鏈�"
@@ -2310,20 +3162,29 @@ msgstr "浜庨樁娈�"
 msgid "Pipeline|with stages"
 msgstr "浜庨樁娈�"
 
-msgid "Play"
+msgid "PlantUML"
 msgstr ""
 
+msgid "Play"
+msgstr "杩愯"
+
 msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
-msgstr ""
+msgstr "璇� <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">涓烘煇涓」鐩惎鐢ㄨ璐瑰姛鑳斤紝浠ヤ究鑳藉鍒涘缓Kubernetes缇ら泦</a>锛岀劧鍚庨噸璇曘€�"
 
 msgid "Please solve the reCAPTCHA"
 msgstr "璇峰~鍐欓獙璇佺爜銆�"
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr "杩炴帴浠g爜浠撳簱涓紝璇风◢鍊欍€傚彲鍦ㄤ换鎰忔椂鍒诲埛鏂颁互鑾峰彇褰撳墠鐘舵€併€�"
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr "瀵煎叆浠g爜浠撳簱涓紝璇风◢鍊欍€傚彲鍦ㄤ换鎰忔椂鍒诲埛鏂颁互鑾峰彇褰撳墠鐘舵€併€�"
+
 msgid "Preferences"
 msgstr "鍋忓ソ璁剧疆"
 
 msgid "Primary"
-msgstr ""
+msgstr "涓昏"
 
 msgid "Private - Project access must be granted explicitly to each user."
 msgstr "绉佷汉 - 蹇呴』鍚戞瘡涓敤鎴锋槑纭巿浜堥」鐩闂潈闄愩€�"
@@ -2331,6 +3192,9 @@ msgstr "绉佷汉 - 蹇呴』鍚戞瘡涓敤鎴锋槑纭巿浜堥」鐩闂潈闄愩€�"
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr "绉佷汉 - 缇ょ粍鍙婂叾椤圭洰鍙兘鐢辨垚鍛樻煡鐪嬨€�"
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr "绉佹湁椤圭洰鍙互鍦ㄤ釜浜哄悕绉扮┖闂翠腑鍒涘缓锛�"
+
 msgid "Profile"
 msgstr "鐢ㄦ埛淇℃伅"
 
@@ -2370,9 +3234,12 @@ msgstr "鎮ㄧ殑甯愭埛鐩墠鏄繖浜涚兢缁勭殑鎵€鏈夎€咃細"
 msgid "Profiles|your account"
 msgstr "鎮ㄧ殑甯愭埛"
 
-msgid "Programming languages used in this repository"
+msgid "Profiling - Performance bar"
 msgstr ""
 
+msgid "Programming languages used in this repository"
+msgstr "褰撳墠浠g爜浠撳簱涓娇鐢ㄧ殑缂栫▼璇█"
+
 msgid "Project '%{project_name}' is in the process of being deleted."
 msgstr "椤圭洰 鈥�%{project_name}鈥� 姝e湪琚垹闄ゃ€�"
 
@@ -2389,13 +3256,10 @@ msgid "Project access must be granted explicitly to each user."
 msgstr "椤圭洰璁块棶鏉冮檺蹇呴』鏄庣‘鎺堟潈缁欐瘡涓敤鎴枫€�"
 
 msgid "Project avatar"
-msgstr ""
+msgstr "椤圭洰澶村儚"
 
 msgid "Project avatar in repository: %{link}"
-msgstr ""
-
-msgid "Project cache successfully reset."
-msgstr ""
+msgstr "椤圭洰澶村儚鍦ㄤ粨搴撲腑锛�%{link}"
 
 msgid "Project details"
 msgstr "椤圭洰璇︽儏"
@@ -2416,19 +3280,19 @@ msgid "ProjectActivityRSS|Subscribe"
 msgstr "璁㈤槄"
 
 msgid "ProjectCreationLevel|Allowed to create projects"
-msgstr ""
+msgstr "鍏佽鍒涘缓椤圭洰"
 
 msgid "ProjectCreationLevel|Default project creation protection"
-msgstr ""
+msgstr "榛樿椤圭洰鍒涘缓淇濇姢"
 
 msgid "ProjectCreationLevel|Developers + Masters"
-msgstr ""
+msgstr "Developers + Masters"
 
 msgid "ProjectCreationLevel|Masters"
-msgstr ""
+msgstr "Masters"
 
 msgid "ProjectCreationLevel|No one"
-msgstr ""
+msgstr "绂佹"
 
 msgid "ProjectFeature|Disabled"
 msgstr "鍋滅敤"
@@ -2455,7 +3319,7 @@ msgid "ProjectSettings|Contact an admin to change this setting."
 msgstr "鑱旂郴绠$悊鍛樻洿鏀规璁剧疆銆�"
 
 msgid "ProjectSettings|Only signed commits can be pushed to this repository."
-msgstr "鍙湁宸茬缃叉彁浜ゆ墠鍙互鎺ㄩ€佸埌姝ゅ瓨鍌ㄥ簱銆�"
+msgstr "鍙湁宸茬缃叉彁浜ゆ墠鍙互鎺ㄩ€佸埌姝や粨搴撱€�"
 
 msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
 msgstr "姝よ缃凡搴旂敤浜庢湇鍔″櫒绾у埆锛屽彲鐢辩鐞嗗憳瑕嗙洊銆�"
@@ -2467,7 +3331,7 @@ msgid "ProjectSettings|This setting will be applied to all projects unless overr
 msgstr "姝よ缃皢搴旂敤浜庢墍鏈夐」鐩紝闄ら潪琚鐞嗗憳瑕嗙洊銆�"
 
 msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
-msgstr "鐢ㄦ埛鍙兘閫氳繃鑷繁宸查獙璇佺殑鐢靛瓙閭欢鍦板潃灏嗘彁浜ゅ埌姝ゅ瓨鍌ㄥ簱涓€�"
+msgstr "鐢ㄦ埛鍙兘閫氳繃鑷繁宸查獙璇佺殑鐢靛瓙閭欢鍦板潃灏嗘彁浜ゅ埌姝や粨搴撲腑銆�"
 
 msgid "Projects"
 msgstr "椤圭洰"
@@ -2493,41 +3357,92 @@ msgstr "瀵逛笉璧凤紝娌℃湁鎼滅储鍒扮鍚堟潯浠剁殑椤圭洰"
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr "姝ゅ姛鑳介渶瑕佹祻瑙堝櫒鏀寔 localStorage"
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr "鎵惧埌%{exporters} 鍙� %{metrics}"
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr "<p class=\"text-tertiary\">鏃�<a href=\"%{docsUrl}\">甯哥敤鎸囨爣</a> </p>"
+
+msgid "PrometheusService|Active"
+msgstr "鍚敤"
+
+msgid "PrometheusService|Auto configuration"
+msgstr "鑷姩閰嶇疆"
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr "鑷姩閮ㄧ讲鍜岄厤缃甈rometheus鍒伴泦缇ゆ潵鐩戞祴椤圭洰鐨勭幆澧�"
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr "榛樿鎯呭喌涓嬶紝Prometheus 渚﹀惉 鈥榟ttp://localhost:9090鈥欍€備笉寤鸿鏇存敼榛樿鍦板潃鍜岀鍙o紝鍥犱负杩欏彲鑳戒細褰卞搷鎴栧啿绐佸湪 GitLab 鏈嶅姟鍣ㄤ笂杩愯鐨勫叾浠栨湇鍔°€�"
 
+msgid "PrometheusService|Common metrics"
+msgstr "甯哥敤鎸囨爣"
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr "甯哥敤鎸囨爣浼氭牴鎹簲鐢ㄥ箍娉涚殑瀵煎嚭鍣ㄦ寚鏍囧簱鑷姩鐩戞帶銆�"
+
+msgid "PrometheusService|Custom metrics"
+msgstr "鑷畾涔夋寚鏍�"
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr "鏌ユ壘鍜岄厤缃寚鏍�..."
 
+msgid "PrometheusService|Finding custom metrics..."
+msgstr "鏌ユ壘鑷畾涔夋寚鏍�..."
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr "鍦ㄧ兢闆嗕笂瀹夎Prometheus"
+
+msgid "PrometheusService|Manage clusters"
+msgstr "绠$悊闆嗙兢"
+
+msgid "PrometheusService|Manual configuration"
+msgstr "鎵嬪姩閰嶇疆"
+
 msgid "PrometheusService|Metrics"
 msgstr "鎸囨爣"
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
-msgstr "鎸囨爣浼氭牴鎹寚瀹氱殑鎸囨爣搴撹嚜鍔ㄩ厤缃拰鐩戞帶銆�"
-
 msgid "PrometheusService|Missing environment variable"
 msgstr "娌℃湁鐜鍙橀噺"
 
-msgid "PrometheusService|Monitored"
-msgstr "鐩戞祴"
-
 msgid "PrometheusService|More information"
 msgstr "鏇村鐨勪俊鎭�"
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr "娌℃湁鐩戞祴鎸囨爣銆傝寮€濮嬬洃娴嬶紝璇烽儴缃插埌鐜涓€�"
+msgid "PrometheusService|New metric"
+msgstr "鏂板缓鎸囨爣"
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr "Prometheus API 鍦板潃锛屼緥濡� http://prometheus.example.com/"
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr "Prometheus姝e湪琚兢闆嗚嚜鍔ㄧ鐞�"
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr "鍦ㄩ娆¢儴缃插埌鏌愮幆澧冧箣鍚�, 杩欎簺鎸囨爣鎵嶄細琚洃鎺�"
+
 msgid "PrometheusService|Time-series monitoring service"
-msgstr ""
+msgstr "鏃堕棿搴忓垪鐩戞帶鏈嶅姟"
 
-msgid "PrometheusService|View environments"
-msgstr "鏌ョ湅鐜"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr "濡傞渶鍚敤鎵嬪姩閰嶇疆锛岃浠庣兢闆嗕腑鍗歌浇Prometheus"
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr "濡傞渶鍦ㄧ兢闆嗕笂鍚敤Prometheus鐨勫畨瑁咃紝璇峰彇娑堜互涓嬬殑鎵嬪姩閰嶇疆"
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr "绛夊緟棣栨閮ㄧ讲鍒扮幆澧冧互鏌ユ壘甯哥敤鎸囨爣"
+
+msgid "Promote"
+msgstr "鍗囩骇"
+
+msgid "Promote to Group Label"
+msgstr "鍗囩骇鍒扮兢缁勬爣璁�"
+
+msgid "Promote to Group Milestone"
+msgstr "鍗囩骇鍒扮兢缁勯噷绋嬬"
 
 msgid "Protip:"
-msgstr ""
+msgstr "涓撳鎻愮ず锛�"
 
 msgid "Public - The group and any public projects can be viewed without any authentication."
 msgstr "鍏紑 - 缇ょ粍鍜屼换浣曞叕鍏遍」鐩彲浠ュ湪娌℃湁浠讳綍韬唤楠岃瘉鐨勬儏鍐典笅鏌ョ湅銆�"
@@ -2541,18 +3456,27 @@ msgstr "鎺ㄩ€佽鍒�"
 msgid "Push events"
 msgstr "鎺ㄩ€佷簨浠�"
 
+msgid "Push project from command line"
+msgstr "浠庡懡浠よ鎺ㄩ€侀」鐩�"
+
+msgid "Push to create a project"
+msgstr "閫氳繃鎺ㄩ€佸垱寤洪」鐩�"
+
 msgid "PushRule|Committer restriction"
 msgstr "鎻愪氦闄愬埗"
 
 msgid "Quick actions can be used in the issues description and comment boxes."
-msgstr ""
+msgstr "蹇€熸搷浣滃彲鐢ㄤ簬璁鎻忚堪鍜岃瘎璁烘銆�"
 
 msgid "Read more"
-msgstr "浜嗚В鏇村"
+msgstr "杩涗竴姝ヤ簡瑙�"
 
 msgid "Readme"
 msgstr "鑷堪鏂囦欢"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "鍒嗘敮"
 
@@ -2560,10 +3484,10 @@ msgid "RefSwitcher|Tags"
 msgstr "鏍囩"
 
 msgid "Reference:"
-msgstr ""
+msgstr "寮曠敤锛�"
 
 msgid "Register / Sign In"
-msgstr ""
+msgstr "娉ㄥ唽/鐧诲綍"
 
 msgid "Registry"
 msgstr "娉ㄥ唽琛�"
@@ -2586,23 +3510,41 @@ msgstr "鐩稿叧鐨勫悎骞惰姹�"
 msgid "Related Merged Requests"
 msgstr "鐩稿叧宸插悎骞剁殑鍚堝苟璇锋眰"
 
+msgid "Related merge requests"
+msgstr "鐩稿叧鍚堝苟璇锋眰"
+
 msgid "Remind later"
 msgstr "绋嶅悗鎻愰啋"
 
 msgid "Remove"
-msgstr ""
+msgstr "鍒犻櫎"
 
 msgid "Remove avatar"
-msgstr ""
+msgstr "鍒犻櫎澶村儚"
 
 msgid "Remove project"
 msgstr "鍒犻櫎椤圭洰"
 
 msgid "Repair authentication"
-msgstr ""
+msgstr "淇璁よ瘉"
+
+msgid "Repo by URL"
+msgstr "浠嶶RL瀵煎叆浠撳簱"
 
 msgid "Repository"
-msgstr "瀛樺偍搴�"
+msgstr "浠撳簱"
+
+msgid "Repository has no locks."
+msgstr "褰撳墠浠撳簱鏃犲姞閿佹枃浠躲€�"
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
 
 msgid "Request Access"
 msgstr "鐢宠鏉冮檺"
@@ -2616,9 +3558,15 @@ msgstr "閲嶇疆鍋ュ悍妫€鏌ヨ闂护鐗�"
 msgid "Reset runners registration token"
 msgstr "閲嶇疆 Runner 娉ㄥ唽浠ょ墝"
 
+msgid "Resolve discussion"
+msgstr "瑙e喅璁ㄨ"
+
+msgid "Response"
+msgstr "鍝嶅簲"
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
-msgstr[0] ""
+msgstr[0] "鏄剧ず鍊�"
 
 msgid "Revert this commit"
 msgstr "杩樺師姝ゆ彁浜�"
@@ -2626,6 +3574,36 @@ msgstr "杩樺師姝ゆ彁浜�"
 msgid "Revert this merge request"
 msgstr "杩樺師姝ゅ悎骞惰姹�"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr "妫€鏌�"
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr "璺嚎鍥�"
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr "浣跨敤澶栭儴浠撳簱鐨凜I/CD娴佹按绾�"
+
+msgid "Runners"
+msgstr "Runner"
+
+msgid "Running"
+msgstr "杩愯涓�"
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "SSH 瀵嗛挜"
 
@@ -2636,11 +3614,14 @@ msgid "Save pipeline schedule"
 msgstr "淇濆瓨娴佹按绾胯鍒�"
 
 msgid "Save variables"
-msgstr ""
+msgstr "淇濆瓨鍙橀噺"
 
 msgid "Schedule a new pipeline"
 msgstr "鏂板缓娴佹按绾胯鍒�"
 
+msgid "Scheduled"
+msgstr "宸插姞鍏ユ棩绋�"
+
 msgid "Schedules"
 msgstr "鏃ョ▼"
 
@@ -2650,17 +3631,20 @@ msgstr "娴佹按绾胯鍒�"
 msgid "Scoped issue boards"
 msgstr "璁鐪嬫澘鑼冨洿"
 
+msgid "Search"
+msgstr "鎼滅储"
+
 msgid "Search branches and tags"
 msgstr "鎼滅储鍒嗘敮鍜屾爣绛�"
 
 msgid "Search milestones"
-msgstr ""
+msgstr "鎼滅储閲岀▼纰�"
 
 msgid "Search project"
-msgstr ""
+msgstr "鎼滅储椤圭洰"
 
 msgid "Search users"
-msgstr ""
+msgstr "鎼滅储鐢ㄦ埛"
 
 msgid "Seconds before reseting failure information"
 msgstr "閲嶇疆澶辫触淇℃伅绛夊緟鏃堕棿(绉�)"
@@ -2669,7 +3653,10 @@ msgid "Seconds to wait for a storage access attempt"
 msgstr "绛夊緟瀛樺偍璁块棶灏濊瘯鏃堕棿(绉�)"
 
 msgid "Secret variables"
-msgstr ""
+msgstr "鍔犲瘑鍙橀噺"
+
+msgid "Security report"
+msgstr "瀹夊叏鎶ュ憡"
 
 msgid "Select Archive Format"
 msgstr "閫夋嫨涓嬭浇鏍煎紡"
@@ -2677,17 +3664,23 @@ msgstr "閫夋嫨涓嬭浇鏍煎紡"
 msgid "Select a timezone"
 msgstr "閫夋嫨鏃跺尯"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr "閫夋嫨涓€涓棦鏈夌殑Kubernetes闆嗙兢鎴栬€呭垱寤轰竴涓柊鐨�"
+
 msgid "Select assignee"
-msgstr ""
+msgstr "閫夋嫨鎸囨淳浜�"
 
 msgid "Select branch/tag"
-msgstr ""
+msgstr "閫夋嫨鍒嗘敮/鏍囩"
 
 msgid "Select target branch"
 msgstr "閫夋嫨鐩爣鍒嗘敮"
 
 msgid "Selective synchronization"
-msgstr ""
+msgstr "閫夋嫨鎬у悓姝�"
+
+msgid "Send email"
+msgstr "鍙戦€佺數瀛愰偖浠�"
 
 msgid "Sep"
 msgstr "涔�"
@@ -2696,22 +3689,40 @@ msgid "September"
 msgstr "涔濇湀"
 
 msgid "Server version"
-msgstr ""
+msgstr "鏈嶅姟鍣ㄧ増鏈�"
 
 msgid "Service Templates"
 msgstr "鏈嶅姟妯℃澘"
 
+msgid "Service URL"
+msgstr "鏈嶅姟URL"
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr "浼氳瘽鏈夋晥鏈燂紝椤圭洰闄愬埗鍙婇檮浠跺ぇ灏忋€�"
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "涓鸿处鍙峰垱寤轰竴涓敤浜庢帹閫佹垨鎷夊彇鐨� %{protocol} 瀵嗙爜銆�"
 
-msgid "Set up CI/CD"
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr "璁惧畾缂虹渷鍙婂彈闄愬彲瑙佹€х骇鍒€傞厤缃鍏ユ潵婧愬強git璁块棶鍗忚銆�"
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
 msgstr ""
 
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr "璁惧畾鐢ㄦ埛鐧诲綍鐨勬潯浠躲€傚惎鐢ㄥ己鍒跺弻閲嶈璇併€�"
+
+msgid "Set up CI/CD"
+msgstr "閰嶇疆 CI/CD"
+
 msgid "Set up Koding"
 msgstr "璁剧疆 Koding"
 
-msgid "Set up auto deploy"
-msgstr "璁剧疆鑷姩閮ㄧ讲"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "璁剧疆瀵嗙爜"
@@ -2719,14 +3730,23 @@ msgstr "璁剧疆瀵嗙爜"
 msgid "Settings"
 msgstr "璁剧疆"
 
-msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgid "Setup a specific Runner automatically"
+msgstr "鑷姩鍒涘缓鐙韩Runner"
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
 msgstr ""
 
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr "閫氳繃閲嶇疆姝ゅ懡鍚嶇┖闂寸殑娴佹按绾垮垎閽熸暟锛屽綋鍓嶄娇鐢ㄧ殑鍒嗛挓鏁板皢琚綊闆躲€�"
+
 msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
-msgstr ""
+msgstr "閲嶇疆娴佹按绾垮垎閽熸暟"
 
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
-msgstr ""
+msgstr "閲嶇疆宸茬敤娴佹按绾垮垎閽熸暟"
+
+msgid "Show command"
+msgstr "鏄剧ず鍛戒护"
 
 msgid "Show parent pages"
 msgstr "鏌ョ湅涓婄骇椤甸潰"
@@ -2748,25 +3768,37 @@ msgid "Sidebar|None"
 msgstr "鏃�"
 
 msgid "Sidebar|Weight"
-msgstr "瀹藉害"
+msgstr "鏉冮噸"
+
+msgid "Sign-in restrictions"
+msgstr "鐧诲綍闄愬埗"
+
+msgid "Sign-up restrictions"
+msgstr "娉ㄥ唽闄愬埗"
+
+msgid "Size and domain settings for static websites"
+msgstr "闈欐€佺綉绔欑殑澶у皬鍜屽煙璁剧疆"
+
+msgid "Slack application"
+msgstr ""
 
 msgid "Snippets"
 msgstr "浠g爜鐗囨"
 
 msgid "Something went wrong on our end"
-msgstr ""
+msgstr "鍑洪敊浜嗭紝鎶辨瓑銆�"
 
 msgid "Something went wrong on our end."
-msgstr "鍙戠敓浜嗛敊璇€�"
+msgstr "鍑洪敊浜嗭紝鎶辨瓑銆�"
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
-msgstr ""
+msgid "Something went wrong when toggling the button"
+msgstr "鐐瑰嚮鎸夐挳鏃跺嚭閿�"
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
-msgstr "璇曞浘鏀瑰彉 ${this.issuableDisplayName} 鐨勯攣瀹氱姸鎬佹椂鍑洪敊浜�"
+msgid "Something went wrong while fetching Dependency Scanning."
+msgstr "璇诲彇渚濊禆鎵弿缁撴灉鏃跺彂鐢熼敊璇€�"
 
-msgid "Something went wrong when toggling the button"
-msgstr ""
+msgid "Something went wrong while fetching SAST."
+msgstr "璇诲彇SAST 鏃跺嚭閿欍€�"
 
 msgid "Something went wrong while fetching the projects."
 msgstr "鎷夊彇椤圭洰鏃跺彂鐢熼敊璇€�"
@@ -2775,7 +3807,7 @@ msgid "Something went wrong while fetching the registry list."
 msgstr "鎷夊彇娉ㄥ唽琛ㄥ垪琛ㄦ椂鍙戠敓閿欒銆�"
 
 msgid "Something went wrong. Please try again."
-msgstr ""
+msgstr "鍑虹幇閿欒銆傝閲嶈瘯銆�"
 
 msgid "Sort by"
 msgstr "鎺掑簭"
@@ -2799,13 +3831,13 @@ msgid "SortOptions|Due soon"
 msgstr "鍗冲皢鎴"
 
 msgid "SortOptions|Label priority"
-msgstr "鏍囩浼樺厛"
+msgstr "鏍囪浼樺厛"
 
 msgid "SortOptions|Largest group"
 msgstr "鏈€澶х兢缁�"
 
 msgid "SortOptions|Largest repository"
-msgstr "鏈€澶у瓨鍌ㄥ簱"
+msgstr "鏈€澶т粨搴�"
 
 msgid "SortOptions|Last created"
 msgstr "鏈€杩戝垱寤�"
@@ -2879,6 +3911,9 @@ msgstr "鏉冮噸"
 msgid "Source"
 msgstr "婧�"
 
+msgid "Source (branch or tag)"
+msgstr "婧�(鍒嗘敮鎴栨爣绛�)"
+
 msgid "Source code"
 msgstr "婧愪唬鐮�"
 
@@ -2888,12 +3923,21 @@ msgstr "婧愪笉鍙敤"
 msgid "Spam Logs"
 msgstr "鍨冨溇淇℃伅鏃ュ織"
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "鍦� Runner 璁剧疆鏃舵寚瀹氫互涓� URL锛�"
 
 msgid "StarProject|Star"
 msgstr "鏄熸爣"
 
+msgid "Starred Projects"
+msgstr "宸叉槦鏍囬」鐩�"
+
+msgid "Starred Projects' Activity"
+msgstr "宸叉槦鏍囬」鐩殑娲诲姩"
+
 msgid "Starred projects"
 msgstr "宸叉槦鏍囬」鐩�"
 
@@ -2903,11 +3947,20 @@ msgstr "鐢辨鏇存敼 %{new_merge_request}"
 msgid "Start the Runner!"
 msgstr "鍚姩 Runner!"
 
+msgid "Started"
+msgstr "宸插惎鍔�"
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr "鐘舵€�"
+
 msgid "Stopped"
 msgstr "宸插仠姝�"
 
 msgid "Storage"
-msgstr ""
+msgstr "瀛樺偍"
 
 msgid "Subgroups"
 msgstr "瀛愮兢缁�"
@@ -2915,12 +3968,18 @@ msgstr "瀛愮兢缁�"
 msgid "Switch branch/tag"
 msgstr "鍒囨崲鍒嗘敮/鏍囩"
 
+msgid "System"
+msgstr "绯荤粺"
+
 msgid "System Hooks"
 msgstr "绯荤粺閽╁瓙"
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "鏍囩"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] "鏍囩(%{tag_count})"
 
 msgid "Tags"
 msgstr "鏍囩"
@@ -2956,10 +4015,10 @@ msgid "TagsPage|Filter by tag name"
 msgstr "鏍规嵁鏍囩鍚嶇О杩囨护"
 
 msgid "TagsPage|New Tag"
-msgstr "鏂版爣绛�"
+msgstr "鏂板缓鏍囩"
 
 msgid "TagsPage|New tag"
-msgstr "鏂版爣绛�"
+msgstr "鏂板缓鏍囩"
 
 msgid "TagsPage|Optionally, add a message to the tag."
 msgstr "(鍙€�)娣诲姞涓€鏉℃秷鎭埌鏍囩銆�"
@@ -2980,7 +4039,7 @@ msgid "TagsPage|Tags"
 msgstr "鏍囩"
 
 msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
-msgstr "鏍囩鍏锋湁鍦ㄦ彁浜ゅ巻鍙蹭笂鏍囪鐗瑰畾鎻愪氦鐨勮兘鍔�"
+msgstr "浣跨敤鏍囩锛屽彲浠ユ爣璁版彁浜ゅ巻鍙蹭笂鐨勭壒瀹氱偣涓洪噸瑕佹彁浜�"
 
 msgid "TagsPage|This tag has no release notes."
 msgstr "姝ゆ爣绛炬病鏈夊彂琛岃鏄庛€�"
@@ -2997,6 +4056,9 @@ msgstr "宸蹭繚鎶�"
 msgid "Target Branch"
 msgstr "鐩爣鍒嗘敮"
 
+msgid "Target branch"
+msgstr "鐩爣鍒嗘敮"
+
 msgid "Team"
 msgstr "鍥㈤槦"
 
@@ -3004,13 +4066,16 @@ msgid "Thanks! Don't show me this again"
 msgstr "璋㈣阿 ! 璇蜂笉瑕佸啀鏄剧ず"
 
 msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
-msgstr "GitLab 涓殑楂樼骇鍏ㄥ眬鎼滅储鍔熻兘鏄潪甯稿己澶х殑鎼滅储鏈嶅姟銆傛偍鍙互鎼滅储鍏朵粬鍥㈤槦鐨勪唬鐮佷互甯姪鎮ㄥ畬鍠勮嚜宸遍」鐩腑鐨勪唬鐮併€備粠鑰岄伩鍏嶅垱寤洪噸澶嶇殑浠g爜鍜屾氮璐规椂闂淬€�"
+msgstr "GitLab 涓殑楂樼骇鍏ㄥ眬鎼滅储鏄竴椤瑰姛鑳藉己澶х殑鎼滅储鏈嶅姟锛屾湁鍔╀簬鑺傜害椤圭洰寮€鍙戞椂闂淬€傛偍鍙互鎼滅储鍏朵粬鍥㈤槦鐨勪唬鐮佷腑瀵硅嚜宸遍」鐩湁甯姪鐨勯儴鍒嗗姞浠ュ埄鐢紝浠庤€岄伩鍏嶅垱寤洪噸澶嶄唬鐮佸拰娴垂鏃堕棿銆�"
 
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
-msgstr ""
+msgstr "璁璺熻釜鐢ㄤ簬绠$悊闇€瑕佹敼杩涙垨鑰呰В鍐崇殑闂"
 
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
-msgstr ""
+msgstr "璁璺熻釜鐢ㄤ簬绠$悊闇€瑕佹敼杩涙垨鑰呰В鍐崇殑闂銆傝娉ㄥ唽鎴栫櫥褰曞悗涓哄綋鍓嶉」鐩垱寤鸿棰樸€�"
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr "鍦ㄩ渶瑕佺浉浜� TLS 涓庡閮ㄦ巿鏉冩湇鍔¢€氫俊鏃朵娇鐢ㄧ殑 X509 璇佷功銆傚鏋滀繚鐣欎负绌�, 鍒欏湪璁块棶 HTTPS 鏃朵粛鐒堕獙璇佹湇鍔″櫒璇佷功銆�"
 
 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 "缂栫爜闃舵姒傝堪浜嗕粠绗竴娆℃彁浜ゅ埌鍒涘缓鍚堝苟璇锋眰鐨勬椂闂淬€傚垱寤虹涓€涓悎骞惰姹傚悗锛屾暟鎹皢鑷姩娣诲姞鍒版澶勩€�"
@@ -3018,14 +4083,20 @@ msgstr "缂栫爜闃舵姒傝堪浜嗕粠绗竴娆℃彁浜ゅ埌鍒涘缓鍚堝苟璇锋眰鐨勬椂闂淬€�
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "涓庤闃舵鐩稿叧鐨勪簨浠堕泦鍚堛€�"
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr "璇ヨ繛鎺ュ皢鍦� %{timeout}鍚庤秴鏃躲€傚浜庨渶瑕侀暱浜庤鏃堕棿鎵嶈兘瀵煎叆鐨勪粨搴擄紝璇蜂娇鐢ㄥ厠闅�/鎺ㄩ€佺粍鍚堛€�"
+
 msgid "The fork relationship has been removed."
 msgstr "娲剧敓鍏崇郴宸茶鍒犻櫎銆�"
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr "璇ュ鍏ヨ繃绋嬪皢鍦� %{timeout}鍚庤秴鏃躲€傚浜庨渶瑕侀暱浜庤鏃堕棿鎵嶈兘瀵煎叆鐨勪粨搴擄紝璇蜂娇鐢ㄥ厠闅�/鎺ㄩ€佺粍鍚堛€�"
+
 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 maximum file size allowed is 200KB."
-msgstr ""
+msgstr "鏂囦欢澶у皬闄愬埗涓� 200KB銆�"
 
 msgid "The number of attempts GitLab will make to access a storage."
 msgstr "GitLab 璁块棶瀛樺偍鐨勬鏁般€�"
@@ -3033,12 +4104,18 @@ msgstr "GitLab 璁块棶瀛樺偍鐨勬鏁般€�"
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr "GitLab 灏嗗畬鍏ㄩ樆姝㈣闂瓨鍌ㄧ殑鏁呴殰娆℃暟銆傚彲浠ュ湪绠$悊鐣岄潰%{link_to_health_page}鎴栦娇鐢�%{api_documentation_link}閲嶇疆鏁呴殰娆℃暟銆�"
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr "瑙e瘑绉侀挜鎵€闇€鐨勫瘑鐮佺煭璇€傝椤逛负鍙€夐」, 骞朵笖鍐呭琚姞瀵嗗瓨鍌ㄣ€�"
+
 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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "鐢熶骇闃舵姒傝堪浜嗕粠鍒涘缓涓€涓棰樺埌灏嗕唬鐮侀儴缃插埌鐢熶骇鐜鐨勬€绘椂闂淬€傚綋瀹屾垚鎯虫硶鍒伴儴缃茬敓浜х殑寰幆锛屾暟鎹皢鑷姩娣诲姞鍒版澶勩€�"
 
@@ -3049,11 +4126,20 @@ msgid "The project can be accessed without any authentication."
 msgstr "璇ラ」鐩厑璁镐换浣曚汉璁块棶銆�"
 
 msgid "The repository for this project does not exist."
-msgstr "姝ら」鐩殑瀛樺偍搴撲笉瀛樺湪銆�"
+msgstr "姝ら」鐩殑浠撳簱涓嶅瓨鍦ㄣ€�"
+
+msgid "The repository for this project is empty"
+msgstr "璇ラ」鐩殑浠撳簱鏄┖鐨�"
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr "璇ヤ粨搴撳繀椤诲彲閫氳繃<code>http://</code>, <code>https://</code> 鎴� <code>git://</code>杩涜璁块棶銆�"
 
 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 roadmap shows the progress of your epics along a timeline"
+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 "棰勫彂甯冮樁娈垫杩颁簡浠庡悎骞惰姹傝鍚堝苟鍒伴儴缃茶嚦鐢熶骇鐜鐨勬€绘椂闂淬€傞娆¢儴缃插埌鐢熶骇鐜鍚庯紝鏁版嵁灏嗚嚜鍔ㄦ坊鍔犲埌姝ゅ銆�"
 
@@ -3067,7 +4153,7 @@ msgid "The time in seconds GitLab will try to access storage. After this time a
 msgstr "GitLab 灏嗗皾璇曡闂瓨鍌ㄧ殑鏃堕棿(绉�)銆傚湪姝ゆ椂闂翠箣鍚庡皢寮曞彂瓒呮椂閿欒銆�"
 
 msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
-msgstr ""
+msgstr "瀛樺偍妫€鏌ヤ箣闂寸殑鏃堕棿闂撮殧锛堢锛夈€傚涓婃妫€鏌ュ皻鏈畬鎴愶紝GitLab灏嗚烦杩囧綋鍓嶆鏌ャ€�"
 
 msgid "The time taken by each data entry gathered by that stage."
 msgstr "璇ラ樁娈垫瘡鏉℃暟鎹墍鑺辩殑鏃堕棿"
@@ -3076,46 +4162,49 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
 msgstr "涓綅鏁版槸涓€涓暟鍒椾腑鏈€涓棿鐨勫€笺€備緥濡傚湪 3銆�5銆�9 涔嬮棿锛屼腑浣嶆暟鏄� 5銆傚湪 3銆�5銆�7銆�8 涔嬮棿锛屼腑浣嶆暟鏄� 锛�5 + 7锛�/ 2 = 6銆�"
 
 msgid "There are no issues to show"
-msgstr ""
+msgstr "褰撳墠鏃犺棰�"
 
 msgid "There are no merge requests to show"
-msgstr ""
+msgstr "褰撳墠鏃犲悎骞惰姹�"
 
 msgid "There are problems accessing Git storage: "
 msgstr "璁块棶 Git 瀛樺偍鏃跺嚭鐜伴棶棰橈細"
 
+msgid "There was an error loading results"
+msgstr "鍔犺浇缁撴灉鏃跺嚭閿�"
+
 msgid "There was an error loading users activity calendar."
-msgstr ""
+msgstr "鍔犺浇鐢ㄦ埛娲诲姩鏃ュ巻鏃跺嚭閿欍€�"
 
 msgid "There was an error saving your notification settings."
-msgstr ""
+msgstr "淇濆瓨閫氱煡璁剧疆鏃跺彂鐢熼敊璇€�"
 
 msgid "There was an error subscribing to this label."
-msgstr ""
+msgstr "璁㈤槄姝ゆ爣璁版椂鍑洪敊銆�"
 
 msgid "There was an error when reseting email token."
-msgstr ""
+msgstr "閲嶇疆鐢靛瓙閭欢浠ょ墝鏃跺嚭閿欍€�"
 
 msgid "There was an error when subscribing to this label."
-msgstr ""
+msgstr "璁㈤槄姝ゆ爣璁版椂鍑洪敊銆�"
 
 msgid "There was an error when unsubscribing from this label."
-msgstr ""
+msgstr "鍙栨秷璁㈤槄姝ゆ爣璁版椂鍑洪敊銆�"
 
 msgid "This board\\'s scope is reduced"
 msgstr "杩欎釜鐪嬫澘鐨勮寖鍥寸缉灏忎簡"
 
 msgid "This directory"
-msgstr ""
+msgstr "褰撳墠鐩綍"
 
 msgid "This is a confidential issue."
 msgstr "杩欐槸涓€涓満瀵嗚棰樸€�"
 
 msgid "This is the author's first Merge Request to this project."
-msgstr "杩欐槸浣滆€呬负椤圭洰璐$尞鐨勭涓€涓悎骞惰姹傘€�"
+msgstr "杩欐槸浣滆€呬负褰撳墠椤圭洰璐$尞鐨勭涓€涓悎骞惰姹傘€�"
 
 msgid "This issue is confidential"
-msgstr ""
+msgstr "褰撳墠闂涓虹瀵嗛棶棰�"
 
 msgid "This issue is confidential and locked."
 msgstr "杩欎釜鏄満瀵嗕笖宸查攣瀹氱殑璁銆�"
@@ -3124,34 +4213,40 @@ msgid "This issue is locked."
 msgstr "姝よ棰樺凡閿佸畾銆�"
 
 msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
-msgstr ""
+msgstr "褰撳墠浣滀笟闇€瑕佺敤鎴疯Е鍙戝叾杩囩▼銆傛绫讳綔涓氶€氬父鐢ㄤ簬灏嗕唬鐮侀儴缃插埌鐢熶骇鐜"
 
 msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
-msgstr ""
+msgstr "褰撳墠浣滀笟闇€鍦ㄤ笂绾т綔涓氭垚鍔熷悗鎵嶅彲琚惎鍔ㄣ€�"
 
 msgid "This job has not been triggered yet"
-msgstr ""
+msgstr "浣滀笟杩樻湭琚Е鍙�"
 
 msgid "This job has not started yet"
-msgstr ""
+msgstr "浣滀笟杩樻湭寮€濮�"
 
 msgid "This job is in pending state and is waiting to be picked by a runner"
-msgstr ""
+msgstr "浣滀笟鎸傝捣涓紝绛夊緟杩涘叆闃熷垪"
 
 msgid "This job requires a manual action"
-msgstr ""
+msgstr "浣滀笟闇€鎵嬪伐鎿嶄綔"
 
 msgid "This means you can not push code until you create an empty repository or import existing one."
-msgstr "鍦ㄥ垱寤轰竴涓┖鐨勫瓨鍌ㄥ簱鎴栧鍏ョ幇鏈夊瓨鍌ㄥ簱涔嬪墠锛屽皢鏃犳硶鎺ㄩ€佷唬鐮併€�"
+msgstr "鍦ㄥ垱寤轰竴涓┖鐨勪粨搴撴垨瀵煎叆鐜版湁浠撳簱涔嬪墠锛屽皢鏃犳硶鎺ㄩ€佷唬鐮併€�"
 
 msgid "This merge request is locked."
 msgstr "姝ゅ悎骞惰姹傚凡閿佸畾銆�"
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr "姝ら〉闈笉鍙敤锛屾偍鏃犳潈璺ㄩ」鐩槄璇荤浉鍏充俊鎭€�"
+
 msgid "This project"
-msgstr ""
+msgstr "褰撳墠椤圭洰"
 
 msgid "This repository"
-msgstr ""
+msgstr "褰撳墠浠撳簱"
+
+msgid "This will delete the custom metric, Are you sure?"
+msgstr "姝ゆ搷浣滃皢鍒犻櫎鑷畾涔夋寚鏍囷紝纭畾缁х画鍚楋紵"
 
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr "杩欎簺鐢靛瓙閭欢鑷姩鐢熸垚涓洪棶棰�(璇勮鐢熸垚涓虹數瀛愰偖浠跺璇�)鍦ㄨ繖閲屽垪鍑恒€�"
@@ -3165,20 +4260,26 @@ msgstr "寮€濮嬭繘琛岀紪鐮佸墠鐨勬椂闂�"
 msgid "Time between merge request creation and merge/close"
 msgstr "浠庡垱寤哄悎骞惰姹傚埌琚悎骞舵垨鍏抽棴鐨勬椂闂�"
 
-msgid "Time tracking"
+msgid "Time between updates and capacity settings."
 msgstr ""
 
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr "GitLab绛夊緟澶栭儴鏈嶅姟鐨勫搷搴旀椂闂达紙绉掞級銆傚綋鏈嶅姟娌℃湁鍙婃椂鍝嶅簲鏃讹紝璁块棶灏嗚鎷掔粷銆�"
+
+msgid "Time tracking"
+msgstr "宸ユ椂缁熻"
+
 msgid "Time until first merge request"
 msgstr "鍒涘缓绗竴涓悎骞惰姹備箣鍓嶇殑鏃堕棿"
 
 msgid "TimeTrackingEstimated|Est"
-msgstr ""
+msgstr "棰勮"
 
 msgid "TimeTracking|Estimated:"
-msgstr ""
+msgstr "棰勮:"
 
 msgid "TimeTracking|Spent"
-msgstr ""
+msgstr "宸茬敤:"
 
 msgid "Timeago|%s days ago"
 msgstr " %s 澶╁墠"
@@ -3314,71 +4415,98 @@ msgstr[0] "鍒嗛挓"
 msgid "Time|s"
 msgstr "绉�"
 
+msgid "Tip:"
+msgstr "鎻愮ず锛�"
+
 msgid "Title"
 msgstr "鏍囬"
 
-msgid "Todo"
+msgid "To GitLab"
+msgstr "鍒癎itLab"
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr "鍙互浣跨敤 %{personal_access_token_link}杩炴帴GitHub浠撳簱銆傚綋鍒涘缓涓汉璁块棶浠ょ墝鏃讹紝闇€瑕侀€夋嫨 <code>repo</code> 鑼冨洿锛屼互鏄剧ず鍙緵杩炴帴鐨勫叕鍏卞拰绉佹湁鐨勪粨搴撳垪琛ㄣ€�"
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr "瑕佽繛鎺itHub浠撳簱锛岄鍏堥渶瑕佹巿鏉僄itLab璁块棶鍒楄〃涓殑GitHub浠撳簱锛�"
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr "瑕佽繛鎺VN浠撳簱锛岃鏌ョ湅 %{svn_link}銆�"
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr "鍙互浣跨敤 %{personal_access_token_link}瀵煎叆GitHub浠撳簱銆傚綋鍒涘缓涓汉璁块棶浠ょ墝鏃讹紝闇€瑕侀€夋嫨 <code>repo</code> 鑼冨洿锛屼互鏄剧ず鍙鍏ョ殑鍏叡鍜岀鏈夌殑浠撳簱鍒楄〃銆�"
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr "瑕佸鍏itHub浠撳簱锛岄鍏堥渶瑕佹巿鏉僄itLab璁块棶鍒楄〃涓殑GitHub浠撳簱锛�"
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr "瑕佸鍏VN浠撳簱锛岃鏌ョ湅 %{svn_link}銆�"
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr "瑕佷粎涓哄閮ㄤ粨搴撲娇鐢–I / CD鍔熻兘鏃讹紝璇烽€夋嫨</strong>浣跨敤澶栭儴浠撳簱杩愯CI/CD<strong>銆�"
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
 msgstr ""
 
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr "濡傞渶楠岃瘉GitLab CI璁剧疆锛岃璁块棶褰撳墠椤圭洰鐨�'CI/CD 鈫� 娴佹按绾�'锛岀劧鍚庣偣鍑�'CI Lint'鎸夐挳銆�"
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr "濡傞渶鏌ョ湅璺嚎鍥撅紝璇峰皢璁″垝鐨勫紑濮嬫垨缁撴潫鏃ユ湡娣诲姞鍒板綋鍓嶇兢缁勬垨鍏跺瓙缁勪腑鐨勬煇涓彶璇楁晠浜嬨€傚彧鏄剧ず杩囧幓3涓湀鍜屾帴涓嬫潵3涓湀鐨勫彶璇楁晠浜嬨€�"
+
+msgid "Todo"
+msgstr "寰呭姙浜嬮」"
+
 msgid "Toggle sidebar"
-msgstr ""
+msgstr "鍒囨崲杈规爮"
 
 msgid "ToggleButton|Toggle Status: OFF"
-msgstr ""
+msgstr "鍒囨崲鐘舵€侊細鍏抽棴"
 
 msgid "ToggleButton|Toggle Status: ON"
-msgstr ""
+msgstr "鍒囨崲鐘舵€侊細寮€鍚�"
 
 msgid "Total Time"
 msgstr "鎬绘椂闂�"
 
-msgid "Total issue time spent"
-msgstr "璁鑺辫垂鏃堕棿鎬昏"
-
 msgid "Total test time for all commits/merges"
 msgstr "鎵€鏈夋彁浜ゅ拰鍚堝苟鐨勬€绘祴璇曟椂闂�"
 
+msgid "Total: %{total}"
+msgstr "鎬昏锛�%{total}"
+
 msgid "Track activity with Contribution Analytics."
 msgstr "璺熻釜娲诲姩涓庤础鐚殑鍒嗘瀽銆�"
 
 msgid "Track groups of issues that share a theme, across projects and milestones"
-msgstr "鍦ㄩ」鐩拰閲岀▼纰戜箣闂磋窡韪叡浜富棰樼殑璁缁�"
-
-msgid "Total: %{total}"
-msgstr ""
+msgstr "鍦ㄤ笉鍚岄」鐩拰閲岀▼纰戜腑璺熻釜鍏锋湁鍚屼竴涓婚鐨勮棰樼粍"
 
 msgid "Track time with quick actions"
-msgstr ""
+msgstr "浣跨敤蹇嵎鎿嶄綔鏉ョ粺璁″伐鏃�"
 
 msgid "Trigger this manual action"
-msgstr ""
+msgstr "瑙﹀彂姝ゆ墜鍔ㄦ搷浣�"
 
 msgid "Turn on Service Desk"
 msgstr "鎵撳紑鏈嶅姟鍙�"
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
-msgstr ""
+msgstr "鏈煡鐨�"
 
 msgid "Unlock"
 msgstr "瑙i攣"
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
-
 msgid "Unlocked"
 msgstr "宸茶В閿�"
 
+msgid "Unresolve discussion"
+msgstr "寰呰В鍐崇殑璁ㄨ"
+
 msgid "Unstar"
 msgstr "鍙栨秷鏄熸爣"
 
 msgid "Up to date"
-msgstr ""
+msgstr "宸叉槸鏈€鏂�"
 
 msgid "Upgrade your plan to activate Advanced Global Search."
 msgstr "鍗囩骇鎮ㄧ殑鏂规浠ュ惎鐢ㄩ珮绾у叏灞€鎼滅储銆�"
@@ -3402,11 +4530,17 @@ msgid "Upload file"
 msgstr "涓婁紶鏂囦欢"
 
 msgid "Upload new avatar"
-msgstr ""
+msgstr "涓婁紶鏂板ご鍍�"
 
 msgid "UploadLink|click to upload"
 msgstr "鐐瑰嚮涓婁紶"
 
+msgid "Upvotes"
+msgstr "椤�"
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr "浣跨敤鏈嶅姟鍙板湪GitLab鍐呴儴閫氳繃鐢靛瓙閭欢涓庣敤鎴疯仈绯伙紙渚嬪鎻愪緵瀹㈡埛鏀寔锛�"
 
@@ -3416,21 +4550,51 @@ msgstr "鍦ㄥ畨瑁呰繃绋嬩腑浣跨敤浠ヤ笅娉ㄥ唽浠ょ墝锛�"
 msgid "Use your global notification setting"
 msgstr "浣跨敤鍏ㄥ眬閫氱煡璁剧疆"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
+msgstr "鍙橀噺閫氳繃runner浣滅敤浜庣幆澧冧腑銆傚彲灏嗗彉閲忛檺鍒朵负浠呭彈淇濇姢鐨勫垎鏀垨鏍囩鍙互璁块棶銆傚彲浠ヤ娇鐢ㄥ彉閲忔潵淇濆瓨瀵嗙爜銆佸瘑閽ユ垨浠讳綍鍏朵粬鍐呭銆�"
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
 msgstr ""
 
+msgid "View and edit lines"
+msgstr "鏌ョ湅鍜岀紪杈戣"
+
+msgid "View epics list"
+msgstr "鏌ョ湅鍙茶瘲鏁呬簨鍒楄〃"
+
 msgid "View file @ "
 msgstr "娴忚鏂囦欢 @ "
 
+msgid "View group labels"
+msgstr "鏌ョ湅缇ょ粍鏍囪"
+
 msgid "View labels"
-msgstr ""
+msgstr "鏌ョ湅鏍囪"
 
 msgid "View open merge request"
 msgstr "鏌ョ湅寰呭鐞嗙殑鍚堝苟璇锋眰"
 
+msgid "View project labels"
+msgstr "鏌ョ湅椤圭洰鏍囪"
+
 msgid "View replaced file @ "
 msgstr "鏌ョ湅鏇挎崲鏂囦欢 @ "
 
+msgid "Visibility and access controls"
+msgstr "鍙鎬т笌璁块棶鎺у埗"
+
 msgid "VisibilityLevel|Internal"
 msgstr "鍐呴儴"
 
@@ -3447,7 +4611,7 @@ msgid "Want to see the data? Please ask an administrator for access."
 msgstr "鏉冮檺涓嶈冻銆傚闇€鏌ョ湅鐩稿叧鏁版嵁锛岃鍚戠鐞嗗憳鐢宠鏉冮檺銆�"
 
 msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
-msgstr ""
+msgstr "鏃犳硶楠岃瘉鎮ㄥ湪 GCP 涓婄殑鏌愪釜椤圭洰鏄惁鍚敤浜嗚璐广€傝閲嶈瘯銆�"
 
 msgid "We don't have enough data to show this stage."
 msgstr "璇ラ樁娈电殑鏁版嵁涓嶈冻锛屾棤娉曟樉绀恒€�"
@@ -3455,12 +4619,21 @@ msgstr "璇ラ樁娈电殑鏁版嵁涓嶈冻锛屾棤娉曟樉绀恒€�"
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr "鎴戜滑瑕佺‘瀹氫綘鏄笉鏄満鍣ㄤ汉銆�"
 
+msgid "Web IDE"
+msgstr "Web IDE"
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr "濡傛灉鏈夋柊鐨勬帹閫佹垨鏂扮殑璁锛學ebhook灏嗚嚜鍔ㄨЕ鍙戞偍璁剧疆URL銆� 鎮ㄥ彲浠ラ厤缃� Webhook 鏉ョ洃鍚壒瀹氫簨浠讹紝濡傛帹閫併€佽棰樻垨鍚堝苟璇锋眰銆� 缇ょ粍 Webhook 灏嗛€傜敤浜庡洟闃熶腑鐨勬墍鏈夐」鐩紝骞跺厑璁告偍璁剧疆鏁翠釜鍥㈤槦涓殑 Webhook 銆�"
 
 msgid "Weight"
 msgstr "鏉冮噸"
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr "灏哢RL淇濈暀涓虹┖鐧芥椂锛屼粛鍙寚瀹氬垎绫绘爣绛撅紝鑰屾棤闇€绂佺敤璺ㄩ」鐩姛鑳芥垨鎵ц澶栭儴鎺堟潈妫€鏌ャ€�"
+
 msgid "Wiki"
 msgstr "Wiki"
 
@@ -3480,10 +4653,10 @@ msgid "WikiClone|Start Gollum and edit locally"
 msgstr "鍚姩 Gollum 骞跺湪鏈湴缂栬緫"
 
 msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
-msgstr ""
+msgstr "鎻愮ず锛氬彲浠ラ€氳繃灏嗚矾寰勬坊鍔犲埌鏍囬寮€澶存潵绉诲姩姝ら〉闈€€�"
 
 msgid "WikiEdit|There is already a page with the same title in that path."
-msgstr ""
+msgstr "鍦ㄨ璺緞涓凡缁忔湁涓€涓叿鏈夌浉鍚屾爣棰樼殑椤甸潰銆�"
 
 msgid "WikiEmptyPageError|You are not allowed to create wiki pages"
 msgstr "鎮ㄤ笉鑳藉垱寤� wiki 椤甸潰"
@@ -3575,32 +4748,44 @@ msgstr "閫氳繃璐$尞鍒嗘瀽锛屾偍鍙互鍒嗘瀽鎮ㄧ殑缁勭粐鍙婂叾鎴愬憳鐨勮棰樸€�
 msgid "Withdraw Access Request"
 msgstr "鍙栨秷鏉冮檺鐢宠"
 
+msgid "Write a commit message..."
+msgstr "濉啓鎻愪氦淇℃伅..."
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "鍗冲皢鍒犻櫎 %{group_name}銆傚凡鍒犻櫎鐨勭兢缁勬棤娉曟仮澶嶏紒纭畾缁х画鍚楋紵"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "鍗冲皢瑕佸垹闄� %{project_name_with_namespace}銆傚凡鍒犻櫎鐨勯」鐩棤娉曟仮澶嶏紒纭畾缁х画鍚楋紵"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "灏嗚鍒犻櫎 %{project_full_name}銆傚垹闄ゅ悗灏嗘棤娉曟仮澶嶏紒纭畾鎵ц姝ゆ搷浣滐紵"
 
 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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr "灏嗚鎶� %{project_full_name} 杞Щ缁欏彟涓€涓墍鏈夎€呫€傜‘瀹氭墽琛屾鎿嶄綔锛�"
+
+msgid "You are on a read-only GitLab instance."
+msgstr "褰撳墠姝e湪璁块棶鍙 GitLab 瀹炰緥銆�"
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr "褰撳墠姝e湪璁块棶Geo娆�(鍙)鑺傜偣銆傚闇€杩涜浠讳綍鍐欏叆鎿嶄綔锛屽繀椤昏闂�%{primary_node}銆�"
+
+msgid "You can also create a project from the command line."
+msgstr "鍙互浣跨敤鍛戒护琛屾潵鍒涘缓椤圭洰銆�"
 
 msgid "You can also star a label to make it a priority label."
-msgstr ""
+msgstr "鍙互閫氳繃涓烘爣璁拌缃槦鏍囨潵鎻愰珮鍏朵紭鍏堢骇銆�"
 
-msgid "You can move around the graph by using the arrow keys."
-msgstr ""
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr "鍙互杞绘澗鍦板湪Kubernetes缇ら泦涓婂畨瑁匯unner銆� %{link_to_help_page}"
 
 msgid "You can move around the graph by using the arrow keys."
-msgstr ""
+msgstr "鍙互浣跨敤鏂瑰悜閿Щ鍔ㄥ浘褰€€�"
 
 msgid "You can only add files when you are on a branch"
 msgstr "鍙兘鍦ㄥ垎鏀笂娣诲姞鏂囦欢"
 
 msgid "You can only edit files when you are on a branch"
-msgstr ""
+msgstr "鍙兘鍦ㄥ垎鏀笂缂栬緫鏂囦欢"
 
 msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
 msgstr "鎮ㄤ笉鑳藉啓鍏ュ彧璇荤殑杈呭姪 GitLab Geo 瀹炰緥銆傝鏀圭敤%{link_to_primary_node}銆�"
@@ -3608,12 +4793,24 @@ msgstr "鎮ㄤ笉鑳藉啓鍏ュ彧璇荤殑杈呭姪 GitLab Geo 瀹炰緥銆傝鏀圭敤%{link_to_pr
 msgid "You cannot write to this read-only GitLab instance."
 msgstr "鎮ㄤ笉鑳藉啓鍏ヨ繖涓彧璇荤殑 GitLab 瀹炰緥銆�"
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr "娌℃湁瓒冲鏉冮檺鏉ヤ慨鏀筁DAP缁勫悓姝ヤ腑鐨勮缃€�"
+
+msgid "You have no permissions"
+msgstr "娌℃湁鏉冮檺"
+
 msgid "You have reached your project limit"
 msgstr "鎮ㄥ凡杈惧埌椤圭洰鏁伴噺闄愬埗"
 
+msgid "You must have master access to force delete a lock"
+msgstr "蹇呴』鎷ユ湁 master 鏉冮檺鎵嶈兘寮哄埗瑙i櫎閿佸畾"
+
 msgid "You must sign in to star a project"
 msgstr "蹇呴』鐧诲綍鎵嶈兘瀵归」鐩姞鏄熸爣"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr "闇€瑕佷娇鐢ㄤ笌褰撳墠涓嶅悓鐨勮鍙�(license)鎵嶈兘鍚敤FileLocks鍔熻兘"
+
 msgid "You need permission."
 msgstr "闇€瑕佺浉鍏崇殑鏉冮檺銆�"
 
@@ -3642,10 +4839,31 @@ msgid "You won't be able to pull or push project code via SSH until you add an S
 msgstr "鍦ㄦ偍鐨勪釜浜鸿祫鏂欎腑娣诲姞SSH瀵嗛挜涔嬪墠锛屾偍涓嶈兘閫氳繃SSH鏉ユ媺鍙栨垨鎺ㄩ€侀」鐩唬鐮併€�"
 
 msgid "You'll need to use different branch names to get a valid comparison."
-msgstr ""
+msgstr "闇€瑕佷娇鐢ㄤ笉鍚岀殑鍒嗘敮鎵嶈兘杩涜鏈夋晥鐨勬瘮杈冦€�"
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr "鎮ㄦ敹鍒拌繖灏佺數瀛愰偖浠舵槸鍥犱负浣犲湪 %{host} 鎷ユ湁甯愭埛銆� %{manage_notifications_link} &middot; %{help_link}"
+
+msgid "Your Groups"
+msgstr "鎮ㄧ殑缇ょ粍"
 
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
-msgstr ""
+msgstr "鍦ㄦ椤甸潰涓婄殑Kubernetes缇ら泦淇℃伅浠嶅彲缂栬緫锛屼絾寤鸿鎮ㄧ鐢ㄥ苟閲嶆柊閰嶇疆"
+
+msgid "Your Projects (default)"
+msgstr "鎮ㄧ殑椤圭洰 (榛樿鍊�)"
+
+msgid "Your Projects' Activity"
+msgstr "鎮ㄧ殑椤圭洰娲诲姩"
+
+msgid "Your Todos"
+msgstr "鎮ㄧ殑寰呭姙浜嬮」"
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr "鍚堝苟璇锋眰宸插紑鍚紝鍙互鎻愪氦鍙樻洿鍒�%{branch_name}銆�"
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr "鏇存敼宸叉彁浜ゃ€傛彁浜� %{commitId} %{commitStats}"
 
 msgid "Your comment will not be visible to the public."
 msgstr "鎮ㄧ殑璇勮灏嗕笉浼氬叕寮€鏄剧ず銆�"
@@ -3659,8 +4877,15 @@ msgstr "鎮ㄧ殑鍚嶅瓧"
 msgid "Your projects"
 msgstr "鎮ㄧ殑椤圭洰"
 
+msgid "among other things"
+msgstr "闄ゅ叾浠栦簨椤瑰"
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] "鍙�%d涓慨澶嶇殑婕忔礊"
+
 msgid "assign yourself"
-msgstr ""
+msgstr "鍒嗛厤缁欒嚜宸�"
 
 msgid "branch name"
 msgstr "鍒嗘敮鍚嶇О"
@@ -3668,199 +4893,321 @@ msgstr "鍒嗘敮鍚嶇О"
 msgid "by"
 msgstr "鏉ヨ嚜"
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr "%{type} 鏈彂鐜版柊鐨勫畨鍏ㄦ紡娲�"
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr "%{type} 鏈彂鐜板畨鍏ㄦ紡娲�"
+
 msgid "ciReport|Code quality"
-msgstr ""
+msgstr "浠g爜璐ㄩ噺"
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
-msgstr ""
+msgstr "DAST鍦ㄥ闃呭簲鐢ㄤ腑鏈娴嬪埌鍛婅"
 
-msgid "ciReport|Failed to load ${type} report"
-msgstr ""
+msgid "ciReport|Dependency scanning"
+msgstr "渚濊禆鍏崇郴鎵弿"
+
+msgid "ciReport|Dependency scanning detected"
+msgstr "渚濊禆鍏崇郴鎵弿妫€娴嬪埌"
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr "渚濊禆鍏崇郴鎵弿鏈娴嬪埌鏂扮殑瀹夊叏婕忔礊"
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr "渚濊禆鍏崇郴鎵弿鏈娴嬪埌瀹夊叏婕忔礊"
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr "鏃犳硶鍔犺浇 %{reportName} 鎶ュ憡"
 
 msgid "ciReport|Fixed:"
-msgstr ""
+msgstr "澶辫触:"
 
 msgid "ciReport|Instances"
-msgstr ""
+msgstr "瀹炰緥"
 
 msgid "ciReport|Learn more about whitelisting"
-msgstr ""
+msgstr "杩涗竴姝ヤ簡瑙e叧浜庣櫧鍚嶅崟鐨勪俊鎭�"
 
-msgid "ciReport|Loading ${type} report"
-msgstr ""
+msgid "ciReport|Loading %{reportName} report"
+msgstr "杞藉叆%{reportName} 鎶ュ憡"
 
 msgid "ciReport|No changes to code quality"
-msgstr ""
+msgstr "浠g爜璐ㄩ噺鏃犲彉鍖�"
 
 msgid "ciReport|No changes to performance metrics"
-msgstr ""
+msgstr "鎬ц兘鎸囨爣鏃犲彉鍖�"
 
 msgid "ciReport|Performance metrics"
-msgstr ""
+msgstr "鎬ц兘鎸囨爣"
 
 msgid "ciReport|SAST"
-msgstr ""
+msgstr "SAST"
+
+msgid "ciReport|SAST detected"
+msgstr "SAST妫€娴嬪埌"
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr "SAST鏈彂鐜版柊鐨勫畨鍏ㄦ紡娲�"
 
 msgid "ciReport|SAST detected no security vulnerabilities"
-msgstr ""
+msgstr "SAST鏈彂鐜板畨鍏ㄦ紡娲�"
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
-msgstr ""
+msgstr "SAST:container鏈彂鐜版紡娲�"
+
+msgid "ciReport|Security scanning"
+msgstr "瀹夊叏鎵弿"
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr "瀹夊叏鎵弿鏃犳硶鍔犺浇浠讳綍缁撴灉"
 
 msgid "ciReport|Show complete code vulnerabilities report"
-msgstr ""
+msgstr "鏄剧ず瀹屾暣鐨勪唬鐮佹紡娲炴姤鍛�"
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
-msgstr ""
+msgstr "鏈壒鍑嗙殑婕忔礊 (绾㈣壊) 鍙互鏍囪涓哄凡鎵瑰噯銆� %{helpLink}"
 
-msgid "commit"
-msgstr "鎻愪氦"
+msgid "ciReport|no vulnerabilities"
+msgstr "鏈娴嬪埌瀹夊叏婕忔礊"
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
-msgstr ""
+msgid "command line instructions"
+msgstr "鍛戒护琛屾寚鍗�"
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
-msgstr ""
+msgid "connecting"
+msgstr "杩炴帴涓�"
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr "鏃犳硶璇诲彇绉侀挜锛屽瘑鐮佺煭璇槸鍚︽纭紵"
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "澶�"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] "妫€娴嬪埌%d涓畨鍏ㄦ紡娲炲凡淇"
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] "妫€娴嬪埌%d涓柊鐨勫畨鍏ㄦ紡娲�"
+
+msgid "detected no vulnerabilities"
+msgstr "鏈娴嬪埌瀹夊叏婕忔礊"
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
-msgstr ""
+msgstr "鏈€鍚庝竴娆�%{slash_command} 鍛戒护灏嗘洿鏂伴璁℃椂闂淬€�"
+
+msgid "here"
+msgstr "姝ゅ"
+
+msgid "importing"
+msgstr "瀵煎叆涓�"
+
+msgid "in progress"
+msgstr "杩涜涓�"
+
+msgid "is invalid because there is downstream lock"
+msgstr "鍥犱笅娓搁攣瀹氳€屾棤鏁�"
+
+msgid "is invalid because there is upstream lock"
+msgstr "鍥犱笂娓搁攣瀹氳€屾棤鏁�"
+
+msgid "is not a valid X509 certificate."
+msgstr "涓嶆槸鏈夋晥鐨刋509璇佷功銆�"
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr "琚� %{path_lock_user_name} 浜� %{created_at} 閿佸畾"
 
 msgid "merge request"
 msgid_plural "merge requests"
-msgstr[0] ""
+msgstr[0] "鍚堝苟璇锋眰"
 
-msgid "mrWidget|Cancel automatic merge"
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr "璇锋仮澶嶆鍒嗘敮鎴栦娇鐢ㄥ叾浠栫殑 %{missingBranchName} 鍒嗘敮"
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr "%{metricsLinkStart} 鍐呭瓨 %{metricsLinkEnd} 鍗犵敤 %{emphasisStart} 涓嬮檷 %{emphasisEnd}锛屼粠 %{memoryFrom}MB 鍒� %{memoryTo}MB"
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr "%{metricsLinkStart} 鍐呭瓨 %{metricsLinkEnd} 鍗犵敤 %{emphasisStart} 涓婂崌 %{emphasisEnd}锛屼粠 %{memoryFrom}MB 鍒� %{memoryTo}MB"
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr "%{metricsLinkStart} 鍐呭瓨 %{metricsLinkEnd} 鍗犵敤 %{emphasisStart} 鏃犲彉鍖� %{emphasisEnd}锛� 淇濇寔鍦� %{memoryFrom}MB"
+
+msgid "mrWidget|Add approval"
+msgstr "澧炲姞鎵瑰噯"
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr "鍏佽涓婃父椤圭洰缁存姢浜哄憳杩涜缂栬緫銆�"
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr "鍒犻櫎鎵瑰噯鏃跺彂鐢熼敊璇€�"
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr "璇诲彇姝ゅ悎骞惰姹傜殑鎵瑰噯鏁版嵁鏃跺彂鐢熼敊璇€�"
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr "鎻愪氦鎵瑰噯鏃跺彂鐢熼敊璇€�"
+
+msgid "mrWidget|Approve"
+msgstr "鎵瑰噯"
+
+msgid "mrWidget|Approved"
 msgstr ""
 
+msgid "mrWidget|Approved by"
+msgstr "鎵瑰噯浜�:"
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr "鍙栨秷鑷姩鍚堝苟"
+
 msgid "mrWidget|Check out branch"
-msgstr ""
+msgstr "妫€鍑哄垎鏀�"
 
 msgid "mrWidget|Checking ability to merge automatically"
-msgstr ""
+msgstr "妫€鏌ユ槸鍚﹀彲浠ヨ嚜鍔ㄥ悎骞�"
 
 msgid "mrWidget|Cherry-pick"
-msgstr ""
+msgstr "浼橀€�"
 
 msgid "mrWidget|Cherry-pick this merge request in a new merge request"
-msgstr ""
+msgstr "閫氳繃鏂扮殑鍚堝苟璇锋眰涓紭閫夋鍚堝苟璇锋眰"
 
 msgid "mrWidget|Closed"
-msgstr ""
+msgstr "宸插叧闂�"
 
 msgid "mrWidget|Closed by"
-msgstr ""
+msgstr "鍏抽棴:"
 
 msgid "mrWidget|Closes"
-msgstr ""
+msgstr "鍏抽棴"
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr "閮ㄧ讲缁熻淇℃伅褰撳墠涓嶅彲鐢�"
 
 msgid "mrWidget|Did not close"
-msgstr ""
+msgstr "娌℃湁鍏抽棴"
 
 msgid "mrWidget|Email patches"
-msgstr ""
+msgstr "閫氳繃鐢靛瓙閭欢鍙戝嚭琛ヤ竵"
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr "鏃犳硶鍔犺浇閮ㄧ讲缁熻淇℃伅"
 
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
-msgstr ""
+msgstr "濡傛灉 %{branch} 鍒嗘敮瀛樺湪浜庢湰鍦颁粨搴撲腑锛屽垯鍙互鎵嬪姩鍚堝苟璇ュ悎骞惰姹傘€傞渶浣跨敤"
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr "濡傛灉 %{missingBranchName} 鍒嗘敮瀛樺湪浜庢湰鍦颁粨搴撲腑锛屽垯鍙互閫氳繃浠ヤ笅鍛戒护琛屾墜鍔ㄥ悎骞惰鍚堝苟璇锋眰銆�"
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr "鍔犺浇閮ㄧ讲缁熻淇℃伅涓�"
 
 msgid "mrWidget|Mentions"
-msgstr ""
+msgstr "鎻愬強"
 
 msgid "mrWidget|Merge"
-msgstr ""
+msgstr "鍚堝苟"
 
 msgid "mrWidget|Merge failed."
-msgstr ""
+msgstr "鍚堝苟澶辫触銆�"
 
 msgid "mrWidget|Merge locally"
-msgstr ""
+msgstr "鏈湴鍚堝苟"
 
 msgid "mrWidget|Merged by"
-msgstr ""
+msgstr "鍚堝苟:"
 
 msgid "mrWidget|Plain diff"
-msgstr ""
+msgstr "鏂囨湰宸紓"
 
 msgid "mrWidget|Refresh"
-msgstr ""
+msgstr "鍒锋柊"
 
 msgid "mrWidget|Refresh now"
-msgstr ""
+msgstr "绔嬪嵆鍒锋柊"
 
 msgid "mrWidget|Refreshing now"
-msgstr ""
+msgstr "绔嬪嵆鍒锋柊"
 
 msgid "mrWidget|Remove Source Branch"
-msgstr ""
+msgstr "鍒犻櫎婧愬垎鏀�"
 
 msgid "mrWidget|Remove source branch"
-msgstr ""
+msgstr "鍒犻櫎婧愬垎鏀�"
+
+msgid "mrWidget|Remove your approval"
+msgstr "鍒犻櫎鎮ㄧ殑鎵瑰噯"
 
 msgid "mrWidget|Request to merge"
-msgstr ""
+msgstr "璇锋眰鍚堝苟"
 
 msgid "mrWidget|Resolve conflicts"
-msgstr ""
+msgstr "瑙e喅鍐茬獊"
 
 msgid "mrWidget|Revert"
-msgstr ""
+msgstr "杩樺師"
 
 msgid "mrWidget|Revert this merge request in a new merge request"
-msgstr ""
+msgstr "閫氳繃鏂扮殑鍚堝苟璇锋眰涓繕鍘熸鍚堝苟璇锋眰"
 
 msgid "mrWidget|Set by"
-msgstr ""
+msgstr "璁剧疆:"
 
 msgid "mrWidget|The changes were merged into"
-msgstr ""
+msgstr "鏇存敼宸插悎骞跺埌"
 
 msgid "mrWidget|The changes were not merged into"
-msgstr ""
+msgstr "鏇存敼鏈悎骞跺埌"
 
 msgid "mrWidget|The changes will be merged into"
-msgstr ""
+msgstr "鏇存敼灏嗚鍚堝苟鍒�"
 
 msgid "mrWidget|The source branch has been removed"
-msgstr ""
+msgstr "婧愬垎鏀凡琚垹闄�"
 
 msgid "mrWidget|The source branch is being removed"
-msgstr ""
+msgstr "婧愬垎鏀鍦ㄨ鍒犻櫎"
 
 msgid "mrWidget|The source branch will be removed"
-msgstr ""
+msgstr "婧愬垎鏀皢琚垹闄�"
 
 msgid "mrWidget|The source branch will not be removed"
-msgstr ""
+msgstr "婧愬垎鏀笉浼氳鍒犻櫎"
 
 msgid "mrWidget|There are merge conflicts"
-msgstr ""
+msgstr "瀛樺湪鍚堝苟鍐茬獊"
 
 msgid "mrWidget|This merge request failed to be merged automatically"
-msgstr ""
+msgstr "璇ュ悎骞惰姹傛湭鑳借嚜鍔ㄥ悎骞�"
 
 msgid "mrWidget|This merge request is in the process of being merged"
-msgstr ""
+msgstr "璇ュ悎骞惰姹傛鍦ㄨ鍚堝苟"
 
 msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr "璇ラ」鐩凡瀛樻。锛岀姝㈠啓鍏�"
+
+msgid "mrWidget|Web IDE"
 msgstr ""
 
 msgid "mrWidget|You can merge this merge request manually using the"
-msgstr ""
+msgstr "鍙互鎵嬪姩鍚堝苟姝ゅ悎骞惰姹傦紝浣跨敤浠ヤ笅"
 
 msgid "mrWidget|You can remove source branch now"
-msgstr ""
+msgstr "褰撳墠宸插彲浠ュ垹闄ゆ簮鍒嗘敮"
+
+msgid "mrWidget|branch does not exist."
+msgstr "鍒嗘敮涓嶅瓨鍦�"
 
 msgid "mrWidget|command line"
-msgstr ""
+msgstr "鍛戒护琛�"
 
 msgid "mrWidget|into"
-msgstr ""
+msgstr "鍏�"
 
 msgid "mrWidget|to be merged automatically when the pipeline succeeds"
-msgstr ""
+msgstr "娴佹按绾挎垚鍔熸椂鑷姩鍚堝苟"
 
 msgid "new merge request"
 msgstr "鏂板缓鍚堝苟璇锋眰"
@@ -3869,7 +5216,7 @@ msgid "notification emails"
 msgstr "閫氱煡閭欢"
 
 msgid "or"
-msgstr ""
+msgstr "鎴�"
 
 msgid "parent"
 msgid_plural "parents"
@@ -3881,14 +5228,20 @@ msgstr "瀵嗙爜"
 msgid "personal access token"
 msgstr "涓汉璁块棶浠ょ墝"
 
+msgid "private key does not match certificate."
+msgstr "绉侀挜涓庤瘉涔︿笉鍖归厤銆�"
+
 msgid "remove due date"
-msgstr ""
+msgstr "鍒犻櫎鎴鏃ユ湡"
 
 msgid "source"
 msgstr "婧�"
 
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
-msgstr ""
+msgstr "%{slash_command} 灏嗕細鏇存柊娑堣€楃殑鎬绘椂闀裤€�"
+
+msgid "this document"
+msgstr "姝ゆ枃妗�"
 
 msgid "to help your contributors communicate effectively!"
 msgstr "甯姪鎮ㄧ殑璐$尞鑰呰繘琛屾湁鏁堟矡閫氾紒"
@@ -3897,5 +5250,8 @@ msgid "username"
 msgstr "鐢ㄦ埛鍚�"
 
 msgid "uses Kubernetes clusters to deploy your code!"
-msgstr ""
+msgstr "浣跨敤 Kubernetes 闆嗙兢鏉ラ儴缃蹭唬鐮侊紒"
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr "鍏� %{additions} 鏉℃柊澧�, %{deletions} 鏉″垹闄�."
 
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index c79a46c93f7cee5afd3401d49e52326a8549346e..6bfcae6aa91c49894522350ab9523e9e59aaa393 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 03:58-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:39-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Chinese Traditional, Hong Kong\n"
 "Language: zh_HK\n"
@@ -27,6 +27,10 @@ msgid "%d commit behind"
 msgid_plural "%d commits behind"
 msgstr[0] ""
 
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+
 msgid "%d issue"
 msgid_plural "%d issues"
 msgstr[0] ""
@@ -39,10 +43,17 @@ msgid "%d merge request"
 msgid_plural "%d merge requests"
 msgstr[0] ""
 
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "鐐烘彁楂橀爜闈㈠姞杓夐€熷害鍙婃€ц兘锛屽凡鐪佺暐浜� %s 娆℃彁浜ゃ€�"
 
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{commit_author_link} authored %{commit_timeago}"
 msgstr ""
 
@@ -50,6 +61,12 @@ msgid "%{count} participant"
 msgid_plural "%{count} participants"
 msgstr[0] ""
 
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -59,6 +76,9 @@ msgstr "宸插け鏁� %{number_of_failures} 娆★紝鏈€澶уけ鏁� %{maximum_failures} 
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "宸插け鏁� %{number_of_failures} 娆★紝鏈€澶уけ鏁� %{maximum_failures} 娆★紝GitLab涓嶆渻閲嶈│銆傜暥鍟忛瑙f焙鏅傞噸缃瓨鍎蹭俊鎭€�"
 
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}锛氬凡瑷晱姝や富姗熷け鏁� %{failed_attempts} 娆�"
@@ -85,15 +105,30 @@ msgstr ""
 msgid "2FA enabled"
 msgstr ""
 
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "鐩搁棞鎸佺簩闆嗘垚鐨勫湒鍍忛泦鍚�"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
 msgid "About auto deploy"
 msgstr "闂滄柤鑷嫊閮ㄧ讲"
 
 msgid "Abuse Reports"
 msgstr ""
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr ""
 
@@ -103,6 +138,9 @@ msgstr "鍥犳仮寰╁畨瑁濓紝瑷晱鏁呴殰瀛樺劜宸茶鏆檪绂佺敤銆傚湪鍟忛瑙f焙
 msgid "Account"
 msgstr ""
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "鍟熺敤"
 
@@ -121,9 +159,15 @@ msgstr "娣诲姞璨㈢嵒鎸囧崡"
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Add Kubernetes cluster"
+msgstr ""
+
 msgid "Add License"
 msgstr "娣诲姞瑷卞彲璀�"
 
+msgid "Add Readme"
+msgstr ""
+
 msgid "Add new directory"
 msgstr "娣诲姞鏂扮洰閷�"
 
@@ -142,12 +186,45 @@ msgstr ""
 msgid "AdminArea|Stopping jobs failed"
 msgstr ""
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
 msgstr ""
 
 msgid "AdminHealthPageLink|health page"
 msgstr ""
 
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
 msgid "Advanced"
 msgstr ""
 
@@ -160,9 +237,33 @@ msgstr "鍏ㄩ儴"
 msgid "All changes are committed"
 msgstr ""
 
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "Allow edits from maintainers."
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
 msgid "Allows you to add and manage Kubernetes clusters."
 msgstr ""
 
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
 msgid "An error occurred previewing the blob"
 msgstr ""
 
@@ -172,6 +273,12 @@ msgstr ""
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
 msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
 msgstr ""
 
@@ -181,12 +288,36 @@ msgstr ""
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -199,12 +330,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr ""
 
+msgid "Any Label"
+msgstr ""
+
 msgid "Appearance"
 msgstr ""
 
@@ -223,21 +363,24 @@ msgstr "姝告獢闋呯洰锛佸瓨鍎插韩鐐哄彧璁€"
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "纰哄畾瑕佸埅闄ゆ娴佹按绶氳▓鍔冨棊锛�"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "纰哄畾瑕佹斁妫勪慨鏀瑰棊锛�"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "纰哄畾瑕侀噸缃ɑ鍐婁护鐗屽棊锛�"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "纰哄畾瑕侀噸缃仴搴锋鏌ヤ护鐗屽棊锛�"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr "纰哄畾鍡庯紵"
 
 msgid "Artifacts"
 msgstr ""
 
+msgid "Assertion consumer service URL"
+msgstr ""
+
 msgid "Assign custom color like #FF0000"
 msgstr ""
 
@@ -250,6 +393,15 @@ msgstr ""
 msgid "Assign to"
 msgstr ""
 
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
+msgstr ""
+
 msgid "Assignee"
 msgstr ""
 
@@ -271,6 +423,12 @@ msgstr ""
 msgid "Authors: %{authors}"
 msgstr ""
 
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
 msgstr ""
 
@@ -295,7 +453,13 @@ msgstr ""
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr ""
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
 msgstr ""
 
 msgid "Available"
@@ -307,6 +471,15 @@ msgstr ""
 msgid "Average per day: %{average}"
 msgstr ""
 
+msgid "Background Color"
+msgstr ""
+
+msgid "Background jobs"
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
 msgid "Billing"
 msgstr ""
 
@@ -361,12 +534,9 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "鍒嗘敮"
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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}"
@@ -389,6 +559,15 @@ msgstr "鍒囨彌鍒嗘敮"
 msgid "Branches"
 msgstr "鍒嗘敮"
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr ""
 
@@ -434,12 +613,39 @@ msgstr ""
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr ""
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
 msgstr ""
 
 msgid "Branches|Sort by"
 msgstr ""
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -485,30 +691,45 @@ msgstr "鐎忚鏂囦欢"
 msgid "Browse files"
 msgstr "鐎忚鏂囦欢"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "浣滆€�:"
 
 msgid "CI / CD"
 msgstr ""
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
 msgstr ""
 
+msgid "CI/CD for external repo"
+msgstr ""
+
 msgid "CICD|Jobs"
 msgstr ""
 
 msgid "Cancel"
 msgstr "鍙栨秷"
 
-msgid "Cancel edit"
-msgstr "鍙栨秷缂栬緫"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "鎸戦伕鍒板垎鏀�"
 
@@ -563,6 +784,12 @@ msgstr ""
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -659,9 +886,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr ""
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr ""
 
@@ -713,6 +952,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -758,12 +1000,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -782,6 +1033,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -812,10 +1066,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -878,6 +1132,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -905,6 +1162,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -950,6 +1210,12 @@ msgstr ""
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr "瑭曡珫"
 
@@ -957,6 +1223,10 @@ msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "鎻愪氦"
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+
 msgid "Commit Message"
 msgstr ""
 
@@ -969,6 +1239,9 @@ msgstr "鎻愪氦淇℃伅"
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "鎻愪氦"
 
@@ -1014,6 +1287,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1029,9 +1308,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr ""
 
@@ -1077,6 +1392,12 @@ msgstr ""
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr ""
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "璨㈢嵒鎸囧崡"
 
@@ -1110,6 +1431,9 @@ msgstr "瑜囪=URL鍒板壀璨兼澘"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "瑜囪=鎻愪氦 SHA 鍒板壀璨兼澘"
 
@@ -1122,14 +1446,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "鍓靛缓鏂扮洰閷�"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "鍦ㄥ赋鎴朵笂鍓靛缓鍊嬩汉瑷晱浠ょ墝锛屼互閫氶亷 %{protocol} 渚嗘媺鍙栨垨鎺ㄩ€併€�"
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "鍓靛缓鐩寗"
 
-msgid "Create empty bare repository"
-msgstr "鍓靛缓绌虹殑瀛樺劜搴�"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1137,12 +1470,18 @@ msgstr ""
 msgid "Create file"
 msgstr ""
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "鍓靛缓鍚堜降璜嬫眰"
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr ""
 
@@ -1158,6 +1497,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "鍓靛缓..."
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "娲剧敓"
 
@@ -1167,6 +1509,12 @@ msgstr "妯欑堡"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "鍓靛缓鍊嬩汉瑷晱浠ょ墝"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1185,6 +1533,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "閫辨湡鍒嗘瀽"
 
@@ -1221,6 +1572,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "浣跨敤 Cron 瑾炴硶瀹氱京鑷畾缇╂ā寮�"
 
@@ -1252,8 +1606,8 @@ msgstr "鐩寗鍚嶇ū"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
-msgstr "鏀炬鏇存敼"
+msgid "Discard draft"
+msgstr ""
 
 msgid "Discover GitLab Geo."
 msgstr ""
@@ -1264,9 +1618,15 @@ msgstr ""
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "涓嶅啀椤ず"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "涓嬭級"
 
@@ -1294,10 +1654,16 @@ msgstr "宸暟鏂囦欢"
 msgid "DownloadSource|Download"
 msgstr "涓嬭級"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
-msgid "Edit"
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
+msgid "Edit"
 msgstr "绶ㄨ集"
 
 msgid "Edit Pipeline Schedule %{id}"
@@ -1306,12 +1672,54 @@ msgstr "绶ㄨ集 %{id} 娴佹按绶氳▓鍔�"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr ""
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1366,9 +1774,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1435,12 +1855,45 @@ msgstr ""
 msgid "Explore public groups"
 msgstr ""
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "鐒℃硶璁婃洿鎵€鏈夎€�"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "鐒℃硶鍒櫎娴佹按绶氳▓鍔�"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1456,6 +1909,12 @@ msgstr ""
 msgid "Files"
 msgstr "鏂囦欢"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "鎸夋彁浜ゆ秷鎭亷婵�"
 
@@ -1465,12 +1924,21 @@ msgstr "鎸夎矾寰戞煡鎵�"
 msgid "Find file"
 msgstr "鏌ユ壘鏂囦欢"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "棣栨鎺ㄩ€�"
 
 msgid "FirstPushedBy|pushed by"
 msgstr "鎺ㄩ€佽€�:"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "娲剧敓"
@@ -1481,15 +1949,24 @@ msgstr "娲剧敓鑷�"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr ""
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr ""
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr "寰炲壍寤鸿椤屽埌閮ㄧ讲鍒扮敓鐢㈢挵澧�"
 
 msgid "From merge request merge until deploy to production"
 msgstr "寰炲悎浣佃珛姹傜殑鍚堜降鍒伴儴缃茶嚦鐢熺敘鐠板"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr ""
 
@@ -1499,12 +1976,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1550,21 +2033,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1577,9 +2087,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1610,6 +2132,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1619,12 +2144,30 @@ msgstr "Git 瀛樺劜鍋ュ悍淇℃伅宸查噸缃�"
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr "GitLab Runner 浠嬬垂"
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "璺宠綁鍒版淳鐢熼爡鐩�"
 
@@ -1637,6 +2180,24 @@ msgstr ""
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr ""
 
@@ -1673,9 +2234,6 @@ msgstr ""
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr ""
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr ""
 
@@ -1706,6 +2264,9 @@ msgstr ""
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "鍋ュ悍妾㈡煡 (Health Check)"
 
@@ -1724,6 +2285,15 @@ msgstr "娌掓湁妾㈡脯鍒板仴搴峰晱椤�"
 msgid "HealthCheck|Unhealthy"
 msgstr "涓嶈壇"
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1734,9 +2304,39 @@ msgstr ""
 msgid "Housekeeping successfully started"
 msgstr "宸查枊濮嬬董璀�"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "灏庡叆瀛樺劜搴�"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1746,6 +2346,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "瀹夎澹瑰€嬭垏 GitLab CI 鍏煎鐨� Runner"
 
@@ -1756,6 +2359,9 @@ msgstr[0] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1795,6 +2401,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1807,6 +2416,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1825,6 +2437,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1834,12 +2449,30 @@ msgstr "鍋滅敤"
 msgid "LFSStatus|Enabled"
 msgstr "鍟熺敤"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr ""
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "鏈€杩� %d 澶�"
@@ -1871,6 +2504,12 @@ msgstr "鍦�"
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "浜嗚В鏇村"
 
@@ -1889,6 +2528,12 @@ msgstr "閫€鍑洪爡鐩�"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1898,7 +2543,7 @@ msgstr ""
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1907,15 +2552,30 @@ msgstr ""
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr ""
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1937,7 +2597,7 @@ msgstr "涓綅鏁�"
 msgid "Members"
 msgstr ""
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1958,6 +2618,81 @@ msgstr ""
 msgid "Messages"
 msgstr ""
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1973,12 +2708,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "娣诲姞澹瑰€� SSH 鍏懓"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr ""
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr "骞姪鏂囨獢"
 
@@ -2049,6 +2805,9 @@ msgstr ""
 msgid "New tag"
 msgstr "鏂板妯欑堡"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2067,15 +2826,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "娌掓湁瀛樺劜搴�"
 
 msgid "No schedules"
 msgstr "娌掓湁瑷堝妰"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr ""
 
@@ -2085,12 +2844,33 @@ msgstr ""
 msgid "Not available"
 msgstr "涓嶅彲鐢�"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "鏁告摎涓嶈冻"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "閫氱煡浜嬩欢"
 
@@ -2175,6 +2955,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr "绡╅伕"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr ""
 
@@ -2193,12 +2979,21 @@ msgstr ""
 msgid "Options"
 msgstr "鎿嶄綔"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr ""
 
 msgid "Owner"
 msgstr "鎵€鏈夎€�"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr ""
 
@@ -2211,9 +3006,21 @@ msgstr ""
 msgid "Pagination|芦 First"
 msgstr ""
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr ""
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "娴佹按绶�"
 
@@ -2295,9 +3102,54 @@ msgstr ""
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "鎵€鏈�"
 
@@ -2310,6 +3162,9 @@ msgstr "鏂奸殠娈�"
 msgid "Pipeline|with stages"
 msgstr "鏂奸殠娈�"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2319,6 +3174,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr ""
 
@@ -2331,6 +3192,9 @@ msgstr ""
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr ""
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr ""
 
@@ -2370,6 +3234,9 @@ msgstr ""
 msgid "Profiles|your account"
 msgstr ""
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2394,9 +3261,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr "灏堟瑭虫儏"
 
@@ -2493,37 +3357,88 @@ msgstr ""
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr ""
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2541,6 +3456,12 @@ msgstr ""
 msgid "Push events"
 msgstr "鎺ㄩ€佷簨浠� (push event) "
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2553,6 +3474,9 @@ msgstr "浜嗚В鏇村"
 msgid "Readme"
 msgstr "鑷堪鏂囦欢"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "鍒嗘敮"
 
@@ -2586,6 +3510,9 @@ msgstr "鐩搁棞鐨勫悎浣佃珛姹�"
 msgid "Related Merged Requests"
 msgstr "鐩搁棞宸插悎浣电殑鍚堜降璜嬫眰"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "绋嶅緦鎻愰啋"
 
@@ -2601,9 +3528,24 @@ msgstr "鍒櫎闋呯洰"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr "瀛樺劜搴�"
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "鐢宠珛娆婇檺"
 
@@ -2616,6 +3558,12 @@ msgstr "閲嶇疆鍋ュ悍妾㈡煡瑷晱浠ょ墝"
 msgid "Reset runners registration token"
 msgstr "閲嶇疆 Runner 瑷诲唺浠ょ墝"
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2626,6 +3574,36 @@ msgstr "閭勫師姝ゆ彁浜�"
 msgid "Revert this merge request"
 msgstr "閭勫師姝ゅ悎浣佃珛姹�"
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr ""
 
@@ -2641,6 +3619,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "鏂板缓娴佹按绶氳▓鍔�"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr ""
 
@@ -2650,6 +3631,9 @@ msgstr "娴佹按绶氳▓鍔�"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "鎼滅储鍒嗘敮鍜屾绫�"
 
@@ -2671,12 +3655,18 @@ msgstr ""
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "閬告搰涓嬭級鏍煎紡"
 
 msgid "Select a timezone"
 msgstr "閬告搰鏅傚崁"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2689,6 +3679,9 @@ msgstr "閬告搰鐩鍒嗘敮"
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2701,17 +3694,35 @@ msgstr ""
 msgid "Service Templates"
 msgstr ""
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "鐐鸿超铏熸坊鍔犲9鍊嬬敤鏂兼帹閫佹垨鎷夊彇鐨� %{protocol} 瀵嗙⒓銆�"
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "瑷疆 Koding"
 
-msgid "Set up auto deploy"
-msgstr "瑷疆鑷嫊閮ㄧ讲"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "瑷疆瀵嗙⒓"
@@ -2719,6 +3730,12 @@ msgstr "瑷疆瀵嗙⒓"
 msgid "Settings"
 msgstr ""
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2728,6 +3745,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr ""
 
@@ -2750,6 +3770,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr ""
 
@@ -2759,13 +3791,13 @@ msgstr ""
 msgid "Something went wrong on our end."
 msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2879,6 +3911,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "婧愪唬纰�"
 
@@ -2888,12 +3923,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr ""
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "鍦� Runner 瑷疆鏅傛寚瀹氫互涓� URL锛�"
 
 msgid "StarProject|Star"
 msgstr "鏄熸"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr ""
 
@@ -2903,6 +3947,15 @@ msgstr "鐢辨鏇存敼 %{new_merge_request}"
 msgid "Start the Runner!"
 msgstr "閬嬩綔 Runner!"
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2915,12 +3968,18 @@ msgstr ""
 msgid "Switch branch/tag"
 msgstr "鍒囨彌鍒嗘敮/妯欑堡"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr ""
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "妯欑堡"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
 
 msgid "Tags"
 msgstr "妯欑堡"
@@ -2997,6 +4056,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "鐩鍒嗘敮"
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr "鍦橀殜"
 
@@ -3012,15 +4074,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "绶ㄧ⒓闅庢姒傝堪浜嗗緸绗9娆℃彁浜ゅ埌鍓靛缓鍚堜降璜嬫眰鐨勬檪闁撱€傚壍寤虹澹瑰€嬪悎浣佃珛姹傚緦锛屾暩鎿氬皣鑷嫊娣诲姞鍒版铏曘€�"
 
 msgid "The collection of events added to the data gathered for that stage."
 msgstr "鑸囪┎闅庢鐩搁棞鐨勪簨浠躲€�"
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "娲剧敓闂滀總宸茶鍒櫎銆�"
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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 "璀伴闅庢姒傝堪浜嗗緸鍓靛缓璀伴鍒板皣璀伴娣诲姞鍒拌绋嬬鎴栬椤岀湅鏉挎墍鑺辫不鐨勬檪闁撱€傚壍寤虹澹瑰€嬭椤屽緦锛屾暩鎿氬皣鑷嫊娣诲姞鍒版铏�.銆�"
 
@@ -3033,12 +4104,18 @@ msgstr ""
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr ""
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "鐢熺敘闅庢姒傝堪浜嗗緸鍓靛缓璀伴鍒板皣浠g⒓閮ㄧ讲鍒扮敓鐢㈢挵澧冪殑鏅傞枔銆傜暥瀹屾垚瀹屾暣鐨勬兂娉曞埌閮ㄧ讲鐢熺敘锛屾暩鎿氬皣鑷嫊娣诲姞鍒版铏曘€�"
 
@@ -3051,9 +4128,18 @@ msgstr "瑭查爡鐩厑瑷变换浣曚汉瑷晱銆�"
 msgid "The repository for this project does not exist."
 msgstr "姝ら爡鐩殑瀛樺劜搴笉瀛樺湪銆�"
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "瑭曞闅庢姒傝堪浜嗗緸鍓靛缓鍚堜降璜嬫眰鍒板悎浣电殑鏅傞枔銆傜暥鍓靛缓绗9鍊嬪悎浣佃珛姹傚緦锛屾暩鎿氬皣鑷嫊娣诲姞鍒版铏曘€�"
 
+msgid "The roadmap shows the progress of your epics along a timeline"
+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 "闋愮櫦甯冮殠娈垫杩颁簡鍚堜降璜嬫眰鐨勫悎浣靛埌閮ㄧ讲浠g⒓鍒扮敓鐢㈢挵澧冪殑绺芥檪闁撱€傜暥棣栨閮ㄧ讲鍒扮敓鐢㈢挵澧冨緦锛屾暩鎿氬皣鑷嫊娣诲姞鍒版铏曘€�"
 
@@ -3084,6 +4170,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr "瑷晱 Git 瀛樺劜鏅傚嚭鐝惧晱椤岋細"
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3147,12 +4236,18 @@ msgstr "鍦ㄥ壍寤哄9鍊嬬┖鐨勫瓨鍎插韩鎴栧皫鍏ョ従鏈夊瓨鍎插韩涔嬪墠锛屾偍灏囩劇
 msgid "This merge request is locked."
 msgstr ""
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3165,6 +4260,12 @@ msgstr "闁嬪閫茶绶ㄧ⒓鍓嶇殑鏅傞枔"
 msgid "Time between merge request creation and merge/close"
 msgstr "寰炲壍寤哄悎浣佃珛姹傚埌琚悎浣垫垨闂滈枆鐨勬檪闁�"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3314,9 +4415,45 @@ msgstr[0] "鍒嗛悩"
 msgid "Time|s"
 msgstr "绉�"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3332,19 +4469,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "绺芥檪闁�"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "鎵€鏈夋彁浜ゅ拰鍚堜降鐨勭附娓│鏅傞枔"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3356,22 +4490,16 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr ""
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgid "Unlocked"
 msgstr ""
 
-msgid "Unlocked"
+msgid "Unresolve discussion"
 msgstr ""
 
 msgid "Unstar"
@@ -3407,6 +4535,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "榛炴搳涓婂偝"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3416,21 +4550,51 @@ msgstr "鍦ㄥ畨瑁濋亷绋嬩腑浣跨敤浠ヤ笅瑷诲唺浠ょ墝锛�"
 msgid "Use your global notification setting"
 msgstr "浣跨敤鍏ㄥ眬閫氱煡瑷疆"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr ""
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "鏌ョ湅闁嬪暉鐨勫悎涓﹁珛姹�"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr ""
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "鍏ч儴"
 
@@ -3455,12 +4619,21 @@ msgstr "瑭查殠娈电殑鏁告摎涓嶈冻锛岀劇娉曢’绀恒€�"
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr ""
 
@@ -3575,22 +4748,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "鍙栨秷娆婇檺鐢宠"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "鍗冲皣鍒櫎 %{group_name}銆傚凡鍒櫎鐨勭兢绲勭劇娉曟仮寰╋紒纰哄畾绻肩簩鍡庯紵"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "鍗冲皣瑕佸埅闄� %{project_name_with_namespace}銆傚凡鍒櫎鐨勯爡鐩劇娉曟仮瑜囷紒纰哄畾绻肩簩鍡庯紵"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "鍗冲皣鍒櫎鑸囨簮闋呯洰 %{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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3608,12 +4793,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr ""
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "鎮ㄥ凡閬斿埌闋呯洰鏁搁噺闄愬埗"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "蹇呴爤鐧婚寗鎵嶈兘灏嶉爡鐩姞鏄熸"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "闇€瑕佺浉闂滅殑娆婇檺銆�"
 
@@ -3644,9 +4841,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr ""
 
@@ -3659,6 +4877,13 @@ msgstr "鎮ㄧ殑鍚嶅瓧"
 msgid "Your projects"
 msgstr ""
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3668,13 +4893,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3686,7 +4929,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3701,38 +4944,121 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "澶�"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3757,15 +5083,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3799,6 +5137,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3847,12 +5188,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3881,6 +5228,9 @@ msgstr ""
 msgid "personal access token"
 msgstr ""
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3890,6 +5240,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3899,3 +5252,6 @@ msgstr ""
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 635f5c6c44953e5156f33786d08e7ccdfc17ebb9..553050d06a153d9af538276adb1cca4b4411aa1c 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitlab-ee\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-07 11:38-0600\n"
-"PO-Revision-Date: 2018-02-12 03:58-0500\n"
+"POT-Creation-Date: 2018-04-04 19:35+0200\n"
+"PO-Revision-Date: 2018-04-05 03:39-0400\n"
 "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
 "Language-Team: Chinese Traditional\n"
 "Language: zh_TW\n"
@@ -17,7 +17,7 @@ msgstr ""
 "X-Crowdin-File: /master/locale/gitlab.pot\n"
 
 msgid " and"
-msgstr ""
+msgstr " 鍜�"
 
 msgid "%d commit"
 msgid_plural "%d commits"
@@ -25,11 +25,15 @@ msgstr[0] "%d 鍊嬫洿鍕� (commit)"
 
 msgid "%d commit behind"
 msgid_plural "%d commits behind"
-msgstr[0] ""
+msgstr[0] "钀藉緦 %d 鍊嬫洿鍕曠磤閷�"
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] "%d 灏庡嚭"
 
 msgid "%d issue"
 msgid_plural "%d issues"
-msgstr[0] ""
+msgstr[0] "%d 鍊嬭椤�"
 
 msgid "%d layer"
 msgid_plural "%d layers"
@@ -37,19 +41,32 @@ msgstr[0] "%d 鍊嬪湒灞�"
 
 msgid "%d merge request"
 msgid_plural "%d merge requests"
-msgstr[0] ""
+msgstr[0] "%d 鍊嬪悎浣佃珛姹�"
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] "%d 鎸囨"
 
 msgid "%s additional commit has been omitted to prevent performance issues."
 msgid_plural "%s additional commits have been omitted to prevent performance issues."
 msgstr[0] "鍥犳晥鑳借€冮噺锛屽凡闅辫棌 %s 鍊嬫洿鍕� (commit)銆�"
 
-msgid "%{commit_author_link} authored %{commit_timeago}"
+msgid "%{actionText} & %{openOrClose} %{noteable}"
 msgstr ""
 
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr "鐢� %{commit_author_link} 鎻愪氦鏂� %{commit_timeago}"
+
 msgid "%{count} participant"
 msgid_plural "%{count} participants"
 msgstr[0] "%{count} 鍙冭垏鑰�"
 
+msgid "%{loadingIcon} Started"
+msgstr "%{loadingIcon} 闁嬪"
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr "%{lock_path} 琚娇鐢ㄨ€� %{lock_user_id} 閹栧畾"
+
 msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
 msgstr ""
 
@@ -59,12 +76,15 @@ msgstr "鐩墠宸插け鏁� %{number_of_failures} 娆°€侴itLab 鍏佽ū鍦� %{maximum_f
 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
 msgstr "宸插け鏁� %{number_of_failures} / %{maximum_failures} 娆★紝GitLab 灏囦笉鍐嶈嚜鍕曢噸瑭︺€傝珛鍦ㄧ⒑瑾嶅晱椤岃В姹哄緦鎵嬪嫊閲嶇疆鍎插瓨绌洪枔璩囪▕銆�"
 
+msgid "%{openOrClose} %{noteable}"
+msgstr "%{openOrClose} %{noteable}"
+
 msgid "%{storage_name}: failed storage access attempt on host:"
 msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
 msgstr[0] "%{storage_name}锛氬凡瀛樺彇姝や富姗熷け鏁� %{failed_attempts} 娆�"
 
 msgid "%{text} is available"
-msgstr ""
+msgstr "%{text} 鍙敤"
 
 msgid "(checkout the %{link} for information on how to install it)."
 msgstr "(濡備綍瀹夎璜嬪弮闁� %{link})"
@@ -85,15 +105,30 @@ msgstr "绗竴娆″崝浣�"
 msgid "2FA enabled"
 msgstr "宸插暉鐢ㄩ洐閲嶈獚璀�"
 
+msgid "<strong>Removes</strong> source branch"
+msgstr "<strong>鍒櫎</strong>渚嗘簮鍒嗘敮"
+
 msgid "A collection of graphs regarding Continuous Integration"
 msgstr "鎸佺簩鏁村悎 (CI) 鐩搁棞鐨勫湒琛�"
 
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr "灏囨渻鍐嶅壍寤轰竴鍊嬫柊鐨勫垎鏀紝涓﹀缓绔嬩竴鍊嬫柊鐨勫悎浣佃珛姹傘€�"
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr "涓€鍊嬪皥妗堟彁渚涗簡浠ヤ笅鍔熻兘锛屽瓨鏀句綘鐨勬枃浠�(瀛樺劜搴�)锛岃▓鍔冧綘鐨勫伐浣�(璀伴)锛屼甫鐧煎竷浣犵殑鏂囦欢(缍熀)锛� %{among_other_things_link}銆�"
+
+msgid "A user with write access to the source branch selected this option"
+msgstr "涓€鍊嬫湁瀛樺彇鍘熷鍒嗘敮娆婇檺鐨勪娇鐢ㄨ€咃紝閬告搰浜嗘闋呯洰"
+
 msgid "About auto deploy"
 msgstr "闂滄柤鑷嫊閮ㄧ讲"
 
 msgid "Abuse Reports"
 msgstr "婵敤鍫卞憡"
 
+msgid "Abuse reports"
+msgstr ""
+
 msgid "Access Tokens"
 msgstr "瀛樺彇鎲戣瓑 (access token)"
 
@@ -103,6 +138,9 @@ msgstr "宸叉毇鏅傚仠鐢ㄥけ鏁楃殑 Git 鍎插瓨绌洪枔銆傜暥鍎插瓨绌洪枔鎭㈠京姝e父
 msgid "Account"
 msgstr "甯宠櫉"
 
+msgid "Account and limit settings"
+msgstr ""
+
 msgid "Active"
 msgstr "鍟熺敤"
 
@@ -110,7 +148,7 @@ msgid "Activity"
 msgstr "娲诲嫊"
 
 msgid "Add"
-msgstr ""
+msgstr "澧炲姞"
 
 msgid "Add Changelog"
 msgstr "鏂板鏇存柊鏃ヨ獙"
@@ -119,38 +157,77 @@ msgid "Add Contribution guide"
 msgstr "鏂板鍗斾綔鎸囧崡"
 
 msgid "Add Group Webhooks and GitLab Enterprise Edition."
-msgstr ""
+msgstr "鍟熺敤缇ょ祫 Webhooks 鍙� GitLab 浼佹キ鐗堛€�"
+
+msgid "Add Kubernetes cluster"
+msgstr "澧炲姞 Kubernetes 鍙㈤泦"
 
 msgid "Add License"
 msgstr "鏂板鎺堟瑠姊濇"
 
+msgid "Add Readme"
+msgstr "澧炲姞瑾槑妾旀"
+
 msgid "Add new directory"
 msgstr "鏂板鐩寗"
 
 msgid "Add todo"
-msgstr ""
+msgstr "澧炲姞寰呰睛浜嬮爡"
 
 msgid "AdminArea|Stop all jobs"
-msgstr ""
+msgstr "鍋滄鎵€鏈変换鍕�"
 
 msgid "AdminArea|Stop all jobs?"
-msgstr ""
+msgstr "瑕佸仠姝㈡墍鏈変换鍕欏棊锛�"
 
 msgid "AdminArea|Stop jobs"
-msgstr ""
+msgstr "鍋滄浠诲嫏"
 
 msgid "AdminArea|Stopping jobs failed"
-msgstr ""
+msgstr "鍋滄浠诲嫏澶辨晽"
 
-msgid "AdminArea|You鈥檙e about to stop all jobs. This will halt all current jobs that are running."
-msgstr ""
+msgid "AdminArea|You鈥檙e about to stop all jobs.This will halt all current jobs that are running."
+msgstr "鎮ㄥ皣鍋滄鎵€鏈変换鍕欙紝閫欏皣鏈冩毇鍋滄墍鏈夋鍦ㄩ亱琛岀殑浠诲嫏銆�"
 
 msgid "AdminHealthPageLink|health page"
 msgstr "绯荤当鐙€鎱�"
 
-msgid "Advanced"
+msgid "AdminProjects|Delete"
+msgstr "鍒櫎"
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr "鍒櫎灏堟 %{projectName} 锛�"
+
+msgid "AdminProjects|Delete project"
+msgstr "鍒櫎灏堟"
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
 msgstr ""
 
+msgid "AdminUsers|Block user"
+msgstr "灏侀帠浣跨敤鑰�"
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr "鍒櫎浣跨敤鑰� %{username} 鍙婂叾璨㈢嵒锛�"
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr "鍒櫎浣跨敤鑰� %{username} 锛�"
+
+msgid "AdminUsers|Delete user"
+msgstr "鍒櫎浣跨敤鑰�"
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr "鍒櫎浣跨敤鑰呭強鍏惰并鐛�"
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr "璜嬭几鍏� %{projectName} 浠ラ€茶纰鸿獚"
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr "璜嬭几鍏� %{username} 浠ラ€茶纰鸿獚"
+
+msgid "Advanced"
+msgstr "閫查殠瑷疆"
+
 msgid "Advanced settings"
 msgstr "閫查殠瑷畾"
 
@@ -158,35 +235,89 @@ msgid "All"
 msgstr "鍏ㄩ儴"
 
 msgid "All changes are committed"
+msgstr "鎵€鏈夋敼璁婇兘宸茬稉鎻愪氦"
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr "寰炴ā鏉垮缓绔嬫垨灏庡叆灏堟鏅傚皣鍟熺敤鎵€鏈夊姛鑳斤紝鎮ㄥ彲浠ュ湪灏堟瑷疆涓皣鍏堕棞闁夈€�"
+
+msgid "Allow edits from maintainers."
+msgstr "鍏佽ū缍浜哄摗绶ㄨ集銆�"
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
 msgstr ""
 
 msgid "Allows you to add and manage Kubernetes clusters."
+msgstr "鍏佽ū鎮ㄥ鍔犲拰绠$悊Kubernetes鍙㈤泦銆�"
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
 msgstr ""
 
-msgid "An error occurred previewing the blob"
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
 msgstr ""
 
-msgid "An error occurred when toggling the notification subscription"
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr "鎴栬€咃紝浣犲彲浠ヤ娇鐢� %{personal_access_token_link}銆傜暥浣犲缓绔嬩綘鐨勫€嬩汉瀛樺彇娆婃潠鏅傦紝浣犲皣闇€瑕侀伕鎿�<code>妾旀搴�</code>绡勫湇锛屾墍浠ユ垜鍊戝皣鏈冮’绀轰綘鍏枊鍙婄浜虹殑妾旀搴竻鍠€茶閫g祼銆�"
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
 msgstr ""
 
+msgid "An error occurred previewing the blob"
+msgstr "闋愯 blob 妾旀鏅傜櫦鐢熼尟瑾�"
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr "鍒囨彌瑷傞柋閫氱煡鏅傜櫦鐢熼尟瑾�"
+
 msgid "An error occurred when updating the issue weight"
 msgstr ""
 
-msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgid "An error occurred while adding approver"
 msgstr ""
 
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr "瑙i櫎浜珮椤ず鏅傜櫦鐢熼尟瑾わ紝璜嬮噸鏂版暣鐞嗛爜闈㈠啀娆″槜瑭︺€�"
+
 msgid "An error occurred while fetching markdown preview"
 msgstr ""
 
 msgid "An error occurred while fetching sidebar data"
 msgstr ""
 
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
 msgid "An error occurred while getting projects"
 msgstr ""
 
+msgid "An error occurred while importing project"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading commits"
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
 msgid "An error occurred while loading filenames"
 msgstr ""
 
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
 msgid "An error occurred while rendering KaTeX"
 msgstr ""
 
@@ -199,12 +330,21 @@ msgstr ""
 msgid "An error occurred while retrieving diff"
 msgstr ""
 
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr "鍎插瓨琚寚娲句汉鏅傜櫦鐢熼尟瑾�"
+
 msgid "An error occurred while validating username"
 msgstr ""
 
 msgid "An error occurred. Please try again."
 msgstr "鐧肩敓閷锛岃珛鍐嶈│涓€娆°€�"
 
+msgid "Any Label"
+msgstr "浠绘剰妯欑堡"
+
 msgid "Appearance"
 msgstr "澶栬"
 
@@ -212,10 +352,10 @@ msgid "Applications"
 msgstr "鎳夌敤绋嬪紡"
 
 msgid "Apr"
-msgstr ""
+msgstr "鍥涙湀"
 
 msgid "April"
-msgstr ""
+msgstr "鍥涙湀"
 
 msgid "Archived project! Repository is read-only"
 msgstr "姝ゅ皥妗堝凡灏佸瓨锛佹獢妗堝韩 (repository) 鐐哄敮璁€鐙€鎱�"
@@ -223,44 +363,56 @@ msgstr "姝ゅ皥妗堝凡灏佸瓨锛佹獢妗堝韩 (repository) 鐐哄敮璁€鐙€鎱�"
 msgid "Are you sure you want to delete this pipeline schedule?"
 msgstr "纰哄畾瑕佸埅闄ゆ娴佹按绶� (pipeline) 鎺掔▼鍡庯紵"
 
-msgid "Are you sure you want to discard your changes?"
-msgstr "纰哄畾瑕佹斁妫勪慨鏀瑰棊锛�"
-
 msgid "Are you sure you want to reset registration token?"
 msgstr "纰哄畾瑕侀噸缃ɑ鍐婃啈璀� (registration token) 鍡庯紵"
 
 msgid "Are you sure you want to reset the health check token?"
 msgstr "纰哄畾瑕侀噸缃仴搴锋鏌ュ瓨鍙栨啈璀� (access token) 鍡庯紵"
 
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
 msgid "Are you sure?"
 msgstr "纰哄畾鍡庯紵"
 
 msgid "Artifacts"
+msgstr "鐢㈢墿"
+
+msgid "Assertion consumer service URL"
 msgstr ""
 
 msgid "Assign custom color like #FF0000"
-msgstr ""
+msgstr "鑷畾缇╅鑹诧紝渚嬪 #FF0000"
 
 msgid "Assign labels"
-msgstr ""
+msgstr "鎸囨淳妯欑堡"
 
 msgid "Assign milestone"
-msgstr ""
+msgstr "鎸囨淳閲岀▼纰�"
 
 msgid "Assign to"
+msgstr "鎸囨淳绲�"
+
+msgid "Assigned Issues"
 msgstr ""
 
-msgid "Assignee"
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to :name"
 msgstr ""
 
+msgid "Assignee"
+msgstr "鎸囨淳浜�"
+
 msgid "Attach a file by drag &amp; drop or %{upload_link}"
 msgstr "鎷栨斁妾旀鍒版铏曟垨鑰� %{upload_link}"
 
 msgid "Aug"
-msgstr ""
+msgstr "鍏湀"
 
 msgid "August"
-msgstr ""
+msgstr "鍏湀"
 
 msgid "Authentication Log"
 msgstr "鐧诲叆绱€閷�"
@@ -269,6 +421,12 @@ msgid "Author"
 msgstr "浣滆€�"
 
 msgid "Authors: %{authors}"
+msgstr "浣滆€咃細%{authors}"
+
+msgid "Auto DevOps enabled"
+msgstr "鍟熷嫊鑷嫊 DevOps"
+
+msgid "Auto DevOps, runners and job artifacts"
 msgstr ""
 
 msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
@@ -295,21 +453,36 @@ msgstr "灏囨牴鎿氳ō瀹氱殑鐨� CI / CD 娴佺▼鑷嫊寤烘銆佹脯瑭﹀拰閮ㄧ讲鎳夌敤
 msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
 msgstr "浜嗚В鏇村鏂� %{link_to_documentation}"
 
-msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
-msgstr "浣犲彲浠ョ偤姝ゅ皥妗堝暉鍕� %{link_to_settings}"
+msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
+msgstr ""
+
+msgid "AutoDevOps|add a Kubernetes cluster"
+msgstr ""
+
+msgid "AutoDevOps|enable Auto DevOps (Beta)"
+msgstr "鍟熺敤鑷嫊 DevOps (娓│鐗�)"
 
 msgid "Available"
-msgstr ""
+msgstr "鑳介亱鍋氱殑"
 
 msgid "Avatar will be removed. Are you sure?"
-msgstr ""
+msgstr "澶ч牠璨煎皣琚埅闄ゃ€備綘纰哄畾鍡庯紵"
 
 msgid "Average per day: %{average}"
+msgstr "骞冲潎姣忓ぉ锛�%{average}"
+
+msgid "Background Color"
 msgstr ""
 
-msgid "Billing"
+msgid "Background jobs"
 msgstr ""
 
+msgid "Begin with the selected commit"
+msgstr "寰為伕瀹氱殑璁婃洿绱€閷勯枊濮�"
+
+msgid "Billing"
+msgstr "甯冲柈"
+
 msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
 msgstr ""
 
@@ -361,12 +534,9 @@ msgstr ""
 msgid "BillingPlans|per user"
 msgstr ""
 
-msgid "Begin with the selected commit"
-msgstr ""
-
-msgid "Branch"
-msgid_plural "Branches"
-msgstr[0] "鍒嗘敮 (branch) "
+msgid "Branch (%{branch_count})"
+msgid_plural "Branches (%{branch_count})"
+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 "宸插缓绔嬪垎鏀� (branch) <strong>%{branch_name}</strong> 銆傚闇€瑷畾鑷嫊閮ㄧ讲锛� 鍦ㄩ伕鎿囧悎閬╃殑 GitLab CI Yaml 妯℃澘寰岋紝璜嬮€佷氦 (commit) 鎮ㄧ殑绶ㄨ集鍏у銆�%{link_to_autodeploy_doc}"
@@ -389,6 +559,15 @@ msgstr "鍒囨彌鍒嗘敮 (branch)"
 msgid "Branches"
 msgstr "鍒嗘敮 (branch) "
 
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr "鍏ㄩ儴"
+
 msgid "Branches|Cant find HEAD commit for this branch"
 msgstr "鎵句笉鍒版鍒嗘敮鐨� HEAD 鏇村嫊銆�"
 
@@ -434,12 +613,39 @@ msgstr "涓€鏃︿綘纰鸿獚涓︽寜涓� %{delete_protected_branch} 涔嬪緦锛屾鍕曚綔
 msgid "Branches|Only a project master or owner can delete a protected branch"
 msgstr "鍙湁灏堟绠$悊鑰呮垨鎿佹湁鑰呮墠鑳藉埅闄よ淇濊鐨勫垎鏀€�"
 
-msgid "Branches|Protected branches can be managed in %{project_settings_link}"
-msgstr "鍦� %{project_settings_link} 绠$悊鍙椾繚璀风殑鍒嗘敮"
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
 
 msgid "Branches|Sort by"
 msgstr "鎺掑簭鑷�"
 
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
 msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
 msgstr ""
 
@@ -485,13 +691,22 @@ msgstr "鐎忚妾旀"
 msgid "Browse files"
 msgstr "鐎忚妾旀"
 
+msgid "Business"
+msgstr ""
+
 msgid "ByAuthor|by"
 msgstr "浣滆€咃細"
 
 msgid "CI / CD"
 msgstr "CI / CD"
 
+msgid "CI/CD"
+msgstr ""
+
 msgid "CI/CD configuration"
+msgstr "CI/CD 瑷畾"
+
+msgid "CI/CD for external repo"
 msgstr ""
 
 msgid "CICD|Jobs"
@@ -500,15 +715,21 @@ msgstr "浣滄キ"
 msgid "Cancel"
 msgstr "鍙栨秷"
 
-msgid "Cancel edit"
-msgstr "鍙栨秷绶ㄨ集"
+msgid "Cannot be merged automatically"
+msgstr ""
 
 msgid "Cannot modify managed Kubernetes cluster"
 msgstr ""
 
+msgid "Certificate fingerprint"
+msgstr ""
+
 msgid "Change Weight"
 msgstr ""
 
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
 msgid "ChangeTypeActionLabel|Pick into branch"
 msgstr "鎸戦伕鍒板垎鏀� (branch) "
 
@@ -552,17 +773,23 @@ msgid "Cherry-pick this merge request"
 msgstr "鎸戦伕姝ゅ悎浣佃珛姹� (merge request) "
 
 msgid "Choose File ..."
-msgstr ""
+msgstr "閬告搰妾旀鈰�"
 
 msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
 msgstr ""
 
 msgid "Choose file..."
-msgstr ""
+msgstr "閬告搰妾旀鈰�"
 
 msgid "Choose which groups you wish to synchronize to this secondary node."
 msgstr ""
 
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which repositories you want to import."
+msgstr ""
+
 msgid "Choose which shards you wish to synchronize to this secondary node."
 msgstr ""
 
@@ -659,9 +886,21 @@ msgstr ""
 msgid "CircuitBreakerApiLink|circuitbreaker api"
 msgstr "鏂疯矾鍣� (circuitbreaker) API"
 
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
 msgid "Click to expand text"
 msgstr ""
 
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
 msgid "Clone repository"
 msgstr "瑜囪=锛坈lone锛夋獢妗堝韩锛坮epository锛�"
 
@@ -713,6 +952,9 @@ msgstr ""
 msgid "ClusterIntegration|Copy CA Certificate"
 msgstr ""
 
+msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgstr ""
+
 msgid "ClusterIntegration|Copy Kubernetes cluster name"
 msgstr ""
 
@@ -758,12 +1000,21 @@ msgstr ""
 msgid "ClusterIntegration|Helm Tiller"
 msgstr ""
 
+msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgstr ""
+
 msgid "ClusterIntegration|Ingress"
 msgstr ""
 
+msgid "ClusterIntegration|Ingress IP Address"
+msgstr ""
+
 msgid "ClusterIntegration|Install"
 msgstr ""
 
+msgid "ClusterIntegration|Install Prometheus"
+msgstr ""
+
 msgid "ClusterIntegration|Installed"
 msgstr ""
 
@@ -782,6 +1033,9 @@ msgstr ""
 msgid "ClusterIntegration|Kubernetes cluster details"
 msgstr ""
 
+msgid "ClusterIntegration|Kubernetes cluster health"
+msgstr ""
+
 msgid "ClusterIntegration|Kubernetes cluster integration"
 msgstr ""
 
@@ -812,10 +1066,10 @@ msgstr ""
 msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
 msgstr "瀛哥繏鏇村鏈夐棞鏂�%{link_to_documentation}"
 
-msgid "ClusterIntegration|Learn more about Kubernetes"
+msgid "ClusterIntegration|Learn more about environments"
 msgstr ""
 
-msgid "ClusterIntegration|Learn more about environments"
+msgid "ClusterIntegration|Learn more about security configuration"
 msgstr ""
 
 msgid "ClusterIntegration|Machine type"
@@ -878,6 +1132,9 @@ msgstr ""
 msgid "ClusterIntegration|Save changes"
 msgstr ""
 
+msgid "ClusterIntegration|Security"
+msgstr ""
+
 msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
 msgstr ""
 
@@ -905,6 +1162,9 @@ msgstr ""
 msgid "ClusterIntegration|Something went wrong while installing %{title}"
 msgstr ""
 
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
 msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
 msgstr ""
 
@@ -950,6 +1210,12 @@ msgstr "瑷畾姝g⒑"
 msgid "Collapse"
 msgstr ""
 
+msgid "Comment and resolve discussion"
+msgstr ""
+
+msgid "Comment and unresolve discussion"
+msgstr ""
+
 msgid "Comments"
 msgstr "鐣欒█"
 
@@ -957,6 +1223,10 @@ msgid "Commit"
 msgid_plural "Commits"
 msgstr[0] "鏇村嫊瑷橀寗 (commit) "
 
+msgid "Commit (%{commit_count})"
+msgid_plural "Commits (%{commit_count})"
+msgstr[0] ""
+
 msgid "Commit Message"
 msgstr "鏇村嫊瑷婃伅"
 
@@ -969,6 +1239,9 @@ msgstr "鏇村嫊瑾槑 (commit) "
 msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
 msgstr ""
 
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
 msgid "CommitBoxTitle|Commit"
 msgstr "閫佷氦"
 
@@ -1014,6 +1287,12 @@ msgstr ""
 msgid "Compare Revisions"
 msgstr ""
 
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
 msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
 msgstr ""
 
@@ -1029,9 +1308,45 @@ msgstr ""
 msgid "CompareBranches|There isn't anything to compare."
 msgstr ""
 
+msgid "Confidential"
+msgstr ""
+
 msgid "Confidentiality"
 msgstr ""
 
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Sidekiq job throttling."
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure storage path and circuit breaker settings."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
 msgid "Container Registry"
 msgstr "Container Registry"
 
@@ -1077,6 +1392,12 @@ msgstr "浣跨敤涓嶅悓鐨勬槧鍍忔獢鍚嶇ū"
 msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
 msgstr "灏� Docker Container Registry 鏁村悎鍒� GitLab 涓緦锛屾瘡鍊嬪皥妗堥兘鍙互鏈夎嚜宸辩殑绌洪枔渚嗗劜瀛� Docker 鐨勬槧鍍忔獢"
 
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
 msgid "Contribution guide"
 msgstr "鍗斾綔鎸囧崡"
 
@@ -1110,6 +1431,9 @@ msgstr "瑜囪=缍插潃鍒板壀璨肩翱"
 msgid "Copy branch name to clipboard"
 msgstr ""
 
+msgid "Copy command to clipboard"
+msgstr ""
+
 msgid "Copy commit SHA to clipboard"
 msgstr "瑜囪=鏇村嫊瑷橀寗 (commit) 鐨� SHA 鍊煎埌鍓布绨�"
 
@@ -1122,14 +1446,23 @@ msgstr ""
 msgid "Create New Directory"
 msgstr "寤虹珛鏂扮洰閷�"
 
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new branch and merge request"
+msgstr ""
+
 msgid "Create a personal access token on your account to pull or push via %{protocol}."
 msgstr "寤虹珛鍊嬩汉瀛樺彇鎲戣瓑 (access token) 浠ヤ娇鐢� %{protocol} 渚嗕笂鍌� (push) 鎴栦笅杓� (pull) 銆�"
 
+msgid "Create branch"
+msgstr ""
+
 msgid "Create directory"
 msgstr "寤虹珛鐩寗"
 
-msgid "Create empty bare repository"
-msgstr "寤虹珛涓€鍊嬫柊鐨� bare repository"
+msgid "Create empty repository"
+msgstr ""
 
 msgid "Create epic"
 msgstr ""
@@ -1137,12 +1470,18 @@ msgstr ""
 msgid "Create file"
 msgstr "鏂板妾旀"
 
+msgid "Create group label"
+msgstr ""
+
 msgid "Create lists from labels. Issues with that label appear in that list."
 msgstr ""
 
 msgid "Create merge request"
 msgstr "鐧煎嚭鍚堜降璜嬫眰 (merge request) "
 
+msgid "Create merge request and branch"
+msgstr ""
+
 msgid "Create new branch"
 msgstr "鏂板鍒嗘敮锛坆ranch锛�"
 
@@ -1158,6 +1497,9 @@ msgstr ""
 msgid "Create new..."
 msgstr "寤虹珛..."
 
+msgid "Create project label"
+msgstr ""
+
 msgid "CreateNewFork|Fork"
 msgstr "鍒嗘敮 (fork) "
 
@@ -1167,6 +1509,12 @@ msgstr "寤虹珛妯欑堡"
 msgid "CreateTokenToCloneLink|create a personal access token"
 msgstr "寤虹珛鍊嬩汉瀛樺彇鎲戣瓑 (access token)"
 
+msgid "Creates a new branch from %{branchName}"
+msgstr ""
+
+msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
+msgstr ""
+
 msgid "Creating epic"
 msgstr ""
 
@@ -1185,6 +1533,9 @@ 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 "Customize colors"
+msgstr ""
+
 msgid "Cycle Analytics"
 msgstr "閫辨湡鍒嗘瀽"
 
@@ -1221,6 +1572,9 @@ msgstr ""
 msgid "December"
 msgstr ""
 
+msgid "Default classification label"
+msgstr ""
+
 msgid "Define a custom pattern with cron syntax"
 msgstr "浣跨敤 Cron 瑾炴硶鑷▊鎺掔▼"
 
@@ -1252,8 +1606,8 @@ msgstr "鐩寗鍚嶇ū"
 msgid "Disable"
 msgstr ""
 
-msgid "Discard changes"
-msgstr "鏀炬淇敼"
+msgid "Discard draft"
+msgstr ""
 
 msgid "Discover GitLab Geo."
 msgstr ""
@@ -1264,9 +1618,15 @@ msgstr "闂滈枆寰挵鍒嗘瀽浠嬬垂瑕栫獥"
 msgid "Dismiss Merge Request promotion"
 msgstr ""
 
+msgid "Documentation for popular identity providers"
+msgstr ""
+
 msgid "Don't show again"
 msgstr "涓嶅啀椤ず"
 
+msgid "Done"
+msgstr ""
+
 msgid "Download"
 msgstr "涓嬭級"
 
@@ -1294,9 +1654,15 @@ msgstr "宸暟妾� (diff)"
 msgid "DownloadSource|Download"
 msgstr "涓嬭級鍘熷纰�"
 
+msgid "Downvotes"
+msgstr ""
+
 msgid "Due date"
 msgstr ""
 
+msgid "During this process, you鈥檒l be asked for URLs from GitLab鈥檚 side. Use the URLs shown below."
+msgstr ""
+
 msgid "Edit"
 msgstr "绶ㄨ集"
 
@@ -1306,12 +1672,54 @@ msgstr "绶ㄨ集 %{id} 娴佹按绶� (pipeline) 鎺掔▼"
 msgid "Edit files in the editor and commit changes here"
 msgstr ""
 
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch intergration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
 msgid "Emails"
 msgstr "闆诲瓙閮典欢"
 
 msgid "Enable"
 msgstr ""
 
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
 msgid "Environments|An error occurred while fetching the environments."
 msgstr ""
 
@@ -1366,9 +1774,21 @@ msgstr ""
 msgid "Epics"
 msgstr ""
 
+msgid "Epics Roadmap"
+msgstr ""
+
 msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
 msgstr ""
 
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error checking branch data. Please try again."
+msgstr ""
+
+msgid "Error committing changes. Please try again."
+msgstr ""
+
 msgid "Error creating epic"
 msgstr ""
 
@@ -1435,12 +1855,45 @@ msgstr "鐎忚灏堟"
 msgid "Explore public groups"
 msgstr "鎼滃皨鍏枊鐨勭兢绲�"
 
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification Label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
 msgid "Failed to change the owner"
 msgstr "鐒℃硶璁婃洿鎵€鏈夋瑠"
 
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
 msgid "Failed to remove the pipeline schedule"
 msgstr "鐒℃硶鍒櫎娴佹按绶� (pipeline) 鎺掔▼"
 
+msgid "Failed to update issues, please try again."
+msgstr ""
+
 msgid "Feb"
 msgstr ""
 
@@ -1456,6 +1909,12 @@ msgstr "妾旀鍚嶇ū"
 msgid "Files"
 msgstr "妾旀"
 
+msgid "Files (%{human_size})"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
 msgid "Filter by commit message"
 msgstr "浠ユ洿鍕曡鏄庣閬�"
 
@@ -1465,12 +1924,21 @@ msgstr "浠ヨ矾寰戞悳灏�"
 msgid "Find file"
 msgstr "鎼滃皨妾旀"
 
+msgid "Finished"
+msgstr ""
+
 msgid "FirstPushedBy|First"
 msgstr "棣栨鎺ㄩ€� (push) "
 
 msgid "FirstPushedBy|pushed by"
 msgstr "鎺ㄩ€佽€� (push) 锛�"
 
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
 msgid "Fork"
 msgid_plural "Forks"
 msgstr[0] "鍒嗘敮 (fork) "
@@ -1481,15 +1949,24 @@ msgstr "鍒嗘敮 (fork) 鑷�"
 msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
 msgstr "寰� %{project_name} Fork. (deleted)"
 
+msgid "Forking in progress"
+msgstr ""
+
 msgid "Format"
 msgstr "鏍煎紡"
 
+msgid "From %{provider_title}"
+msgstr ""
+
 msgid "From issue creation until deploy to production"
 msgstr "寰炶椤� (issue) 寤虹珛鐩村埌閮ㄧ讲鑷崇嚐閬嬬挵澧�"
 
 msgid "From merge request merge until deploy to production"
 msgstr "寰炶珛姹傝鍚堜降寰� (merge request merged) 鐩村埌閮ㄧ讲鑷崇嚐閬嬬挵澧�"
 
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
 msgid "GPG Keys"
 msgstr "GPG 閲戦懓"
 
@@ -1499,12 +1976,18 @@ msgstr ""
 msgid "Geo Nodes"
 msgstr ""
 
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
 msgid "GeoNodeSyncStatus|Node is failing or broken."
 msgstr ""
 
 msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
 msgstr ""
 
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
 msgid "GeoNodes|Database replication lag:"
 msgstr ""
 
@@ -1550,21 +2033,48 @@ msgstr ""
 msgid "GeoNodes|New node"
 msgstr ""
 
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
 msgid "GeoNodes|Out of sync"
 msgstr ""
 
+msgid "GeoNodes|Removing a node stops the sync process. Are you sure?"
+msgstr ""
+
 msgid "GeoNodes|Replication slot WAL:"
 msgstr ""
 
 msgid "GeoNodes|Replication slots:"
 msgstr ""
 
+msgid "GeoNodes|Repositories checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Repositories:"
 msgstr ""
 
+msgid "GeoNodes|Repository checksums verified:"
+msgstr ""
+
 msgid "GeoNodes|Selective"
 msgstr ""
 
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
 msgid "GeoNodes|Storage config:"
 msgstr ""
 
@@ -1577,9 +2087,21 @@ msgstr ""
 msgid "GeoNodes|Unused slots"
 msgstr ""
 
+msgid "GeoNodes|Unverified"
+msgstr ""
+
 msgid "GeoNodes|Used slots"
 msgstr ""
 
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksums verified:"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed:"
+msgstr ""
+
 msgid "GeoNodes|Wikis:"
 msgstr ""
 
@@ -1610,6 +2132,9 @@ msgstr ""
 msgid "Geo|Shards to synchronize"
 msgstr ""
 
+msgid "Git repository URL"
+msgstr ""
+
 msgid "Git revision"
 msgstr ""
 
@@ -1619,12 +2144,30 @@ msgstr "Git 鍎插瓨绌洪枔鍋ュ悍鎸囨暩宸查噸缃�"
 msgid "Git version"
 msgstr ""
 
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
 msgid "GitLab Runner section"
 msgstr "GitLab Runner"
 
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
 msgid "Gitaly Servers"
 msgstr ""
 
+msgid "Go back"
+msgstr ""
+
 msgid "Go to your fork"
 msgstr "鍓嶅線鎮ㄧ殑鍒嗘敮 (fork) "
 
@@ -1637,6 +2180,24 @@ msgstr "Google 韬唤椹楄瓑涓嶆槸 %{link_to_documentation}銆傚鏋滄偍鎯充娇鐢�
 msgid "Got it!"
 msgstr ""
 
+msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Loading roadmap"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
 msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
 msgstr "绂佹鑸囧叾浠栫兢绲勫叡浜� %{group} 涓殑灏堟"
 
@@ -1673,9 +2234,6 @@ msgstr "鎵句笉鍒扮兢绲�"
 msgid "GroupsEmptyState|You can manage your group member鈥檚 permissions and access to each project in the group."
 msgstr "浣犲彲浠ョ鐞嗙兢绲勫収鎵€鏈夋垚鍝$殑姣忓€嬪皥妗堢殑瀛樺彇娆婇檺"
 
-msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
-msgstr ""
-
 msgid "GroupsTree|Create a project in this group."
 msgstr "鍦ㄦ缇ょ祫寤虹珛鏂扮殑灏堟"
 
@@ -1706,6 +2264,9 @@ msgstr "涓嶅ソ鎰忔€濓紝娌掓湁鎼滃皨鍒颁换浣曠鍚堟浠剁殑缇ょ祫鎴栧皥妗�"
 msgid "Have your users email"
 msgstr ""
 
+msgid "Header message"
+msgstr ""
+
 msgid "Health Check"
 msgstr "鍋ュ悍妾㈡煡"
 
@@ -1724,6 +2285,15 @@ msgstr "娌掓湁妾㈡脯鍒板仴搴峰晱椤�"
 msgid "HealthCheck|Unhealthy"
 msgstr "涓嶈壇"
 
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
 msgid "Hide value"
 msgid_plural "Hide values"
 msgstr[0] ""
@@ -1734,9 +2304,39 @@ msgstr "姝峰彶"
 msgid "Housekeeping successfully started"
 msgstr "宸查枊濮嬬董璀�"
 
+msgid "Identity provider single sign on URL"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you鈥檒l see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
 msgid "Import repository"
 msgstr "鍖叆妾旀搴� (repository)"
 
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
 msgid "Improve Issue boards with GitLab Enterprise Edition."
 msgstr ""
 
@@ -1746,6 +2346,9 @@ msgstr ""
 msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
 msgstr ""
 
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
 msgid "Install a Runner compatible with GitLab CI"
 msgstr "瀹夎鑸� GitLab CI 鐩稿鐨� Runner"
 
@@ -1756,6 +2359,9 @@ msgstr[0] ""
 msgid "Instance does not support multiple Kubernetes clusters"
 msgstr ""
 
+msgid "Integrations"
+msgstr ""
+
 msgid "Interested parties can even contribute by pushing commits if they want to."
 msgstr ""
 
@@ -1795,6 +2401,9 @@ msgstr ""
 msgid "January"
 msgstr ""
 
+msgid "Jobs"
+msgstr ""
+
 msgid "Jul"
 msgstr ""
 
@@ -1807,6 +2416,9 @@ msgstr ""
 msgid "June"
 msgstr ""
 
+msgid "Koding"
+msgstr ""
+
 msgid "Kubernetes"
 msgstr ""
 
@@ -1825,6 +2437,9 @@ msgstr ""
 msgid "Kubernetes cluster was successfully updated."
 msgstr ""
 
+msgid "Kubernetes configured"
+msgstr ""
+
 msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
 msgstr ""
 
@@ -1834,12 +2449,30 @@ msgstr "鍋滅敤"
 msgid "LFSStatus|Enabled"
 msgstr "鍟熺敤"
 
+msgid "Label"
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
 msgid "Labels"
 msgstr "妯欑堡"
 
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
 msgid "Labels can be applied to issues and merge requests to categorize them."
 msgstr ""
 
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
 msgstr[0] "鏈€杩� %d 澶�"
@@ -1871,6 +2504,12 @@ msgstr "鏂�"
 msgid "Learn more"
 msgstr ""
 
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about protected branches"
+msgstr ""
+
 msgid "Learn more in the"
 msgstr "浜嗚В鏇村"
 
@@ -1889,6 +2528,12 @@ msgstr "閫€鍑哄皥妗�"
 msgid "License"
 msgstr ""
 
+msgid "List"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
 msgid "Loading the GitLab IDE..."
 msgstr ""
 
@@ -1898,7 +2543,7 @@ msgstr "閹栧畾"
 msgid "Lock %{issuableDisplayName}"
 msgstr ""
 
-msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgid "Lock not found"
 msgstr ""
 
 msgid "Locked"
@@ -1907,15 +2552,30 @@ msgstr "閹栧畾"
 msgid "Locked Files"
 msgstr ""
 
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
 msgid "Login"
 msgstr "鐧诲叆"
 
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
 msgid "Manage labels"
 msgstr ""
 
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage your group鈥檚 membership while adding another level of security with SAML."
+msgstr ""
+
 msgid "Mar"
 msgstr ""
 
@@ -1937,7 +2597,7 @@ msgstr "涓綅鏁�"
 msgid "Members"
 msgstr "鎴愬摗"
 
-msgid "Merge Request"
+msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
 msgstr ""
 
 msgid "Merge Requests"
@@ -1958,6 +2618,81 @@ msgstr ""
 msgid "Messages"
 msgstr "鍏憡"
 
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics|Business"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the chart's vertical axis. Usually the type of the unit being charted. The horizontal axis (X-axis) always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|Name"
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Query"
+msgstr ""
+
+msgid "Metrics|Response"
+msgstr ""
+
+msgid "Metrics|System"
+msgstr ""
+
+msgid "Metrics|Type"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
 msgid "Milestone"
 msgstr ""
 
@@ -1973,12 +2708,33 @@ msgstr ""
 msgid "Milestones|Milestone %{milestoneTitle} was not found"
 msgstr ""
 
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
 msgid "MissingSSHKeyWarningLink|add an SSH key"
 msgstr "鏂板 SSH 閲戦懓"
 
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
 msgid "Monitoring"
 msgstr "鐩f帶"
 
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
 msgid "More information is available|here"
 msgstr "鍋ュ悍妾㈡煡"
 
@@ -2049,6 +2805,9 @@ msgstr "鏂板瓙缇ょ祫"
 msgid "New tag"
 msgstr "鏂板妯欑堡"
 
+msgid "No Label"
+msgstr ""
+
 msgid "No assignee"
 msgstr ""
 
@@ -2067,15 +2826,15 @@ msgstr ""
 msgid "No file chosen"
 msgstr ""
 
+msgid "No labels created yet."
+msgstr ""
+
 msgid "No repository"
 msgstr "鎵句笉鍒版獢妗堝韩 (repository)"
 
 msgid "No schedules"
 msgstr "娌掓湁鎺掔▼"
 
-msgid "No time spent"
-msgstr ""
-
 msgid "None"
 msgstr "鐒�"
 
@@ -2085,12 +2844,33 @@ msgstr ""
 msgid "Not available"
 msgstr "鐒℃硶浣跨敤"
 
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
 msgid "Not confidential"
 msgstr ""
 
 msgid "Not enough data"
 msgstr "璩囨枡涓嶈冻"
 
+msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
 msgid "Notification events"
 msgstr "浜嬩欢閫氱煡"
 
@@ -2175,6 +2955,12 @@ msgstr ""
 msgid "OfSearchInADropdown|Filter"
 msgstr "绡╅伕"
 
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Online IDE integration settings."
+msgstr ""
+
 msgid "Only project members can comment."
 msgstr "鍙湁缇ょ祫鎴愬摗鎵嶈兘鐣欒█銆�"
 
@@ -2193,12 +2979,21 @@ msgstr "鏂兼柊瑕栫獥闁嬪暉"
 msgid "Options"
 msgstr "閬搁爡"
 
+msgid "Otherwise it is recommended you start with one of the options below."
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
 msgid "Overview"
 msgstr "绺借"
 
 msgid "Owner"
 msgstr "鎵€鏈夋瑠"
 
+msgid "Pages"
+msgstr ""
+
 msgid "Pagination|Last 禄"
 msgstr "鏈€鏈爜 禄"
 
@@ -2211,9 +3006,21 @@ msgstr "涓婁竴闋�"
 msgid "Pagination|芦 First"
 msgstr "芦 绗竴闋�"
 
+msgid "Part of merge request changes"
+msgstr ""
+
 msgid "Password"
 msgstr "瀵嗙⒓"
 
+msgid "Pending"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
 msgid "Pipeline"
 msgstr "娴佹按绶� (pipeline) "
 
@@ -2295,9 +3102,54 @@ msgstr "鍘诲勾鐨勬祦姘寸窔"
 msgid "Pipelines|Build with confidence"
 msgstr ""
 
+msgid "Pipelines|CI Lint"
+msgstr "CI Lint"
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr "娓呴櫎閬嬭鍣ㄥ揩鍙�"
+
 msgid "Pipelines|Get started with Pipelines"
 msgstr ""
 
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Retry pipeline"
+msgstr ""
+
+msgid "Pipeline|Retry pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to retry pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|You鈥檙e about to stop pipeline %{pipelineId}."
+msgstr ""
+
 msgid "Pipeline|all"
 msgstr "鎵€鏈�"
 
@@ -2310,6 +3162,9 @@ msgstr "鏂奸殠娈�"
 msgid "Pipeline|with stages"
 msgstr "鏂奸殠娈�"
 
+msgid "PlantUML"
+msgstr ""
+
 msgid "Play"
 msgstr ""
 
@@ -2319,6 +3174,12 @@ msgstr ""
 msgid "Please solve the reCAPTCHA"
 msgstr ""
 
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
 msgid "Preferences"
 msgstr "鍋忓ソ瑷畾"
 
@@ -2331,6 +3192,9 @@ msgstr "绉佹湁 - 灏堟娆婇檺蹇呴爤涓€涓€鎸囨淳绲︽瘡鍊嬩娇鐢ㄨ€�"
 msgid "Private - The group and its projects can only be viewed by members."
 msgstr "绉佹湁 - 缇ょ祫鍙婃棗涓嬪皥妗堝彧鑳借瑭茬兢绲勬垚鍝℃煡鐪�"
 
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
 msgid "Profile"
 msgstr "鍊嬩汉璩囨枡"
 
@@ -2370,6 +3234,9 @@ msgstr "浣犵殑甯宠櫉鐩墠鎿佹湁閫欎簺缇ょ祫锛�"
 msgid "Profiles|your account"
 msgstr "浣犵殑甯宠櫉"
 
+msgid "Profiling - Performance bar"
+msgstr ""
+
 msgid "Programming languages used in this repository"
 msgstr ""
 
@@ -2394,9 +3261,6 @@ msgstr ""
 msgid "Project avatar in repository: %{link}"
 msgstr ""
 
-msgid "Project cache successfully reset."
-msgstr ""
-
 msgid "Project details"
 msgstr "灏堟绱扮瘈"
 
@@ -2493,37 +3357,88 @@ msgstr "鎶辨瓑锛屾矑鏈夌鍚堟悳灏嬫浠剁殑灏堟"
 msgid "ProjectsDropdown|This feature requires browser localStorage support"
 msgstr "姝ゅ姛鑳介渶瑕佺€忚鍣ㄦ敮鎻� localStorage"
 
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project鈥檚 environments"
+msgstr ""
+
 msgid "PrometheusService|By default, Prometheus listens on 鈥榟ttp://localhost:9090鈥�. It鈥檚 not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
 msgstr ""
 
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
 msgid "PrometheusService|Finding and configuring metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics"
+msgid "PrometheusService|Finding custom metrics..."
 msgstr ""
 
-msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
+msgid "PrometheusService|Install Prometheus on clusters"
 msgstr ""
 
-msgid "PrometheusService|Missing environment variable"
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
 msgstr ""
 
-msgid "PrometheusService|Monitored"
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
 msgstr ""
 
 msgid "PrometheusService|More information"
 msgstr ""
 
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
+msgid "PrometheusService|New metric"
 msgstr ""
 
 msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
 msgstr ""
 
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
 msgid "PrometheusService|Time-series monitoring service"
 msgstr ""
 
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
 msgstr ""
 
 msgid "Protip:"
@@ -2541,6 +3456,12 @@ msgstr ""
 msgid "Push events"
 msgstr "鎺ㄩ€� (push) 浜嬩欢"
 
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
 msgid "PushRule|Committer restriction"
 msgstr ""
 
@@ -2553,6 +3474,9 @@ msgstr "鐬В鏇村"
 msgid "Readme"
 msgstr "瑾槑妾�"
 
+msgid "Real-time features"
+msgstr ""
+
 msgid "RefSwitcher|Branches"
 msgstr "鍒嗘敮 (branch) "
 
@@ -2586,6 +3510,9 @@ msgstr "鐩搁棞鐨勫悎浣佃珛姹� (merge request) "
 msgid "Related Merged Requests"
 msgstr "鐩搁棞宸插悎浣电殑璜嬫眰"
 
+msgid "Related merge requests"
+msgstr ""
+
 msgid "Remind later"
 msgstr "绋嶅緦鎻愰啋"
 
@@ -2601,9 +3528,24 @@ msgstr "鍒櫎灏堟"
 msgid "Repair authentication"
 msgstr ""
 
+msgid "Repo by URL"
+msgstr ""
+
 msgid "Repository"
 msgstr "妾旀搴� (repository)"
 
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror settings"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
 msgid "Request Access"
 msgstr "鐢宠珛娆婇檺"
 
@@ -2616,6 +3558,12 @@ msgstr "閲嶇疆鍋ュ悍妾㈡煡瀛樺彇鎲戣瓑 (access token)"
 msgid "Reset runners registration token"
 msgstr "閲嶇疆 Runner 瑷诲唺鎲戣瓑 (registration token)"
 
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -2626,6 +3574,36 @@ msgstr "閭勫師姝ゆ洿鍕曡閷� (commit)"
 msgid "Revert this merge request"
 msgstr "閭勫師姝ゅ悎浣佃珛姹� (merge request) "
 
+msgid "Review the process for configuring service providers in your identity provider 鈥� in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "SAML Single Sign On"
+msgstr ""
+
+msgid "SAML Single Sign On Settings"
+msgstr ""
+
+msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
 msgid "SSH Keys"
 msgstr "SSH 閲戦懓"
 
@@ -2641,6 +3619,9 @@ msgstr ""
 msgid "Schedule a new pipeline"
 msgstr "寤虹珛娴佹按绶� (pipeline) 鎺掔▼"
 
+msgid "Scheduled"
+msgstr ""
+
 msgid "Schedules"
 msgstr "鎺掔▼"
 
@@ -2650,6 +3631,9 @@ msgstr "娴佹按绶� (pipeline) 鎺掔▼"
 msgid "Scoped issue boards"
 msgstr ""
 
+msgid "Search"
+msgstr ""
+
 msgid "Search branches and tags"
 msgstr "鎼滃皨鍒嗘敮 (branch) 鍜屾绫�"
 
@@ -2671,12 +3655,18 @@ msgstr "绛夊緟瀛樺彇鍎插瓨绌洪枔鐨勫槜瑭︽檪闁擄紙绉掞級"
 msgid "Secret variables"
 msgstr ""
 
+msgid "Security report"
+msgstr ""
+
 msgid "Select Archive Format"
 msgstr "閬告搰涓嬭級鏍煎紡"
 
 msgid "Select a timezone"
 msgstr "閬告搰鏅傚崁"
 
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
 msgid "Select assignee"
 msgstr ""
 
@@ -2689,6 +3679,9 @@ msgstr "閬告搰鐩鍒嗘敮 (branch) "
 msgid "Selective synchronization"
 msgstr ""
 
+msgid "Send email"
+msgstr ""
+
 msgid "Sep"
 msgstr ""
 
@@ -2701,17 +3694,35 @@ msgstr ""
 msgid "Service Templates"
 msgstr "鏈嶅嫏绡勬湰"
 
+msgid "Service URL"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
 msgid "Set a password on your account to pull or push via %{protocol}."
 msgstr "璜嬪厛瑷畾瀵嗙⒓锛屾墠鑳戒娇鐢� %{protocol} 渚嗕笂鍌� (push) 鎴栦笅杓� (pull) 銆�"
 
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
 msgid "Set up CI/CD"
 msgstr ""
 
 msgid "Set up Koding"
 msgstr "瑷畾 Koding"
 
-msgid "Set up auto deploy"
-msgstr "瑷畾鑷嫊閮ㄧ讲"
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
 
 msgid "SetPasswordToCloneLink|set a password"
 msgstr "瑷畾瀵嗙⒓"
@@ -2719,6 +3730,12 @@ msgstr "瑷畾瀵嗙⒓"
 msgid "Settings"
 msgstr "瑷畾"
 
+msgid "Setup a specific Runner automatically"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
 msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
 msgstr ""
 
@@ -2728,6 +3745,9 @@ msgstr ""
 msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
 msgstr ""
 
+msgid "Show command"
+msgstr ""
+
 msgid "Show parent pages"
 msgstr "椤ず涓婂堡闋侀潰"
 
@@ -2750,6 +3770,18 @@ msgstr ""
 msgid "Sidebar|Weight"
 msgstr ""
 
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
 msgid "Snippets"
 msgstr "鏂囧瓧鐗囨"
 
@@ -2757,15 +3789,15 @@ msgid "Something went wrong on our end"
 msgstr ""
 
 msgid "Something went wrong on our end."
-msgstr "鐧肩敓浜嗛尟瑾ゃ€�"
+msgstr ""
 
-msgid "Something went wrong trying to change the confidentiality of this issue"
+msgid "Something went wrong when toggling the button"
 msgstr ""
 
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong while fetching Dependency Scanning."
 msgstr ""
 
-msgid "Something went wrong when toggling the button"
+msgid "Something went wrong while fetching SAST."
 msgstr ""
 
 msgid "Something went wrong while fetching the projects."
@@ -2879,6 +3911,9 @@ msgstr ""
 msgid "Source"
 msgstr ""
 
+msgid "Source (branch or tag)"
+msgstr ""
+
 msgid "Source code"
 msgstr "鍘熷纰�"
 
@@ -2888,12 +3923,21 @@ msgstr ""
 msgid "Spam Logs"
 msgstr "鍨冨溇瑷婃伅瑷橀寗"
 
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
 msgid "Specify the following URL during the Runner setup:"
 msgstr "鍦ㄥ畨瑁� Runner 鏅傛寚瀹氫互涓� URL锛�"
 
 msgid "StarProject|Star"
 msgstr "鏀惰棌"
 
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
 msgid "Starred projects"
 msgstr "鏄熸闋呯洰"
 
@@ -2903,6 +3947,15 @@ msgstr "浠ラ€欎簺鏀瑰嫊寤虹珛涓€鍊嬫柊鐨� %{new_merge_request} "
 msgid "Start the Runner!"
 msgstr "鍟熷嫊 Runner!"
 
+msgid "Started"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
 msgid "Stopped"
 msgstr ""
 
@@ -2915,12 +3968,18 @@ msgstr "瀛愮兢绲�"
 msgid "Switch branch/tag"
 msgstr "鍒囨彌鍒嗘敮 (branch) 鎴栨绫�"
 
+msgid "System"
+msgstr ""
+
 msgid "System Hooks"
 msgstr "绯荤当閴ゅ瓙"
 
-msgid "Tag"
-msgid_plural "Tags"
-msgstr[0] "妯欑堡"
+msgid "System header and footer:"
+msgstr ""
+
+msgid "Tag (%{tag_count})"
+msgid_plural "Tags (%{tag_count})"
+msgstr[0] ""
 
 msgid "Tags"
 msgstr "妯欑堡"
@@ -2997,6 +4056,9 @@ msgstr ""
 msgid "Target Branch"
 msgstr "鐩鍒嗘敮 (branch) "
 
+msgid "Target branch"
+msgstr ""
+
 msgid "Team"
 msgstr "鍦橀殜"
 
@@ -3012,15 +4074,24 @@ msgstr ""
 msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
 msgstr ""
 
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+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 "瑭查殠娈典腑鐨勭浉闂滀簨浠堕泦鍚堛€�"
 
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
 msgid "The fork relationship has been removed."
 msgstr "鍒嗘敮鑸囦富骞归枔鐨勯棞鑱� (fork relationship) 宸茶鍒櫎銆�"
 
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+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) 涓墍鑺辩殑鏅傞枔銆傚缓绔嬬涓€鍊嬭椤屽緦锛岃硣鏂欏皣鑷嫊濉叆銆�"
 
@@ -3033,12 +4104,18 @@ msgstr "GitLab 瀛樺彇鍎插瓨绌洪枔鐨勫槜瑭︽鏁搞€�"
 msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
 msgstr "GitLab 灏囬樆鎿嬪瓨鍙栧け鏁楃殑娆℃暩銆傚湪绠$悊鑰呬粙闈腑鍙互閲嶇疆澶辨晽娆℃暩: %{link_to_health_page} 鎴栦娇鐢� %{api_documentation_link}銆�"
 
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+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 "瑷堝妰闅庢椤ず寰炴洿鍕曡閷� (commit) 琚帓绋嬭嚦绗竴鍊嬫帹閫佺殑鏅傞枔銆傜涓€娆℃帹閫佷箣寰岋紝璩囨枡灏囪嚜鍕曞~鍏ャ€�"
 
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+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 "鐕熼亱闅庢椤ず寰炲缓绔嬭椤� (issue) 鍒伴儴缃茬▼寮忎笂绶氭墍鑺辩殑鏅傞枔銆傚畬鎴愬緸鐧兼兂鍒颁笂绶氱殑瀹屾暣闁嬬櫦閫辨湡寰岋紝璩囨枡灏囪嚜鍕曞~鍏ャ€�"
 
@@ -3051,9 +4128,18 @@ msgstr "鏈皥妗堝彲璁撲换浣曚汉瀛樺彇"
 msgid "The repository for this project does not exist."
 msgstr "鏈皥妗堟矑鏈夋獢妗堝韩 (repository) "
 
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+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 "瑜囬柋闅庢椤ず寰炲悎浣佃珛姹� (merge request) 寤虹珛寰岃嚦琚悎浣电殑鏅傞枔銆傜暥寤虹珛绗竴鍊嬪悎浣佃珛姹� (merge request) 寰岋紝璩囨枡灏囪嚜鍕曞~鍏ャ€�"
 
+msgid "The roadmap shows the progress of your epics along a timeline"
+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) 琚悎浣靛緦鑷抽儴缃茬嚐閬嬬殑鏅傞枔銆傜暥绗竴娆¢儴缃茬嚐閬嬪緦锛岃硣鏂欏皣鑷嫊濉叆"
 
@@ -3084,6 +4170,9 @@ msgstr ""
 msgid "There are problems accessing Git storage: "
 msgstr "瀛樺彇 Git 鍎插瓨绌洪枔鏅傚嚭鐝惧晱椤岋細"
 
+msgid "There was an error loading results"
+msgstr ""
+
 msgid "There was an error loading users activity calendar."
 msgstr ""
 
@@ -3147,12 +4236,18 @@ msgstr "閫欎唬琛ㄥ湪鎮ㄥ缓绔嬩竴鍊嬬┖鐨勬獢妗堝韩 (repository) 鎴栨槸鍖叆涓€
 msgid "This merge request is locked."
 msgstr "閫欏€嬪悎浣佃珛姹傚凡琚帠瀹氥€�"
 
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
 msgid "This project"
 msgstr ""
 
 msgid "This repository"
 msgstr ""
 
+msgid "This will delete the custom metric, Are you sure?"
+msgstr ""
+
 msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
 msgstr ""
 
@@ -3165,6 +4260,12 @@ msgstr "璀伴 (issue) 绛夊緟闁嬪瀵︿綔鐨勬檪闁�"
 msgid "Time between merge request creation and merge/close"
 msgstr "鍚堜降璜嬫眰 (merge request) 寰炲缓绔嬪埌琚悎浣垫垨鏄棞闁夌殑鏅傞枔"
 
+msgid "Time between updates and capacity settings."
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr "GitLab 绛夊緟澶栭儴鏈嶅嫏鐨勫洖鎳夋檪闁擄紙绉掞級銆傜暥鏈嶅嫏娌掓湁鍦ㄦ檪闁撳収鍥炴噳鏅傦紝瀛樺彇灏囪鎷掔禃銆�"
+
 msgid "Time tracking"
 msgstr ""
 
@@ -3314,9 +4415,45 @@ msgstr[0] "鍒嗛悩"
 msgid "Time|s"
 msgstr "绉�"
 
+msgid "Tip:"
+msgstr ""
+
 msgid "Title"
 msgstr ""
 
+msgid "To GitLab"
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD 鈫� Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown."
+msgstr ""
+
 msgid "Todo"
 msgstr ""
 
@@ -3332,19 +4469,16 @@ msgstr ""
 msgid "Total Time"
 msgstr "绺芥檪闁�"
 
-msgid "Total issue time spent"
-msgstr ""
-
 msgid "Total test time for all commits/merges"
 msgstr "鍚堜降 (merge) 鑸囨洿鍕曡閷� (commit) 鐨勭附娓│鏅傞枔"
 
-msgid "Track activity with Contribution Analytics."
+msgid "Total: %{total}"
 msgstr ""
 
-msgid "Track groups of issues that share a theme, across projects and milestones"
+msgid "Track activity with Contribution Analytics."
 msgstr ""
 
-msgid "Total: %{total}"
+msgid "Track groups of issues that share a theme, across projects and milestones"
 msgstr ""
 
 msgid "Track time with quick actions"
@@ -3356,24 +4490,18 @@ msgstr ""
 msgid "Turn on Service Desk"
 msgstr ""
 
-msgid "Type %{value} to confirm:"
-msgstr ""
-
-msgid "Unable to reset project cache."
-msgstr ""
-
 msgid "Unknown"
 msgstr ""
 
 msgid "Unlock"
 msgstr "瑙i帠"
 
-msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
-
 msgid "Unlocked"
 msgstr "宸茶В閹�"
 
+msgid "Unresolve discussion"
+msgstr ""
+
 msgid "Unstar"
 msgstr "鍙栨秷鏀惰棌"
 
@@ -3407,6 +4535,12 @@ msgstr ""
 msgid "UploadLink|click to upload"
 msgstr "榛炴搳涓婂偝"
 
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
 msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
 msgstr ""
 
@@ -3416,21 +4550,51 @@ msgstr "鍦ㄥ畨瑁濋亷绋嬩腑浣跨敤姝よɑ鍐婃啈璀� (registration token)锛�"
 msgid "Use your global notification setting"
 msgstr "浣跨敤鍏ㄥ煙閫氱煡瑷畾"
 
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
 msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
 msgstr ""
 
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "View and edit lines"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
 msgid "View file @ "
 msgstr "鐎忚妾旀 @ "
 
+msgid "View group labels"
+msgstr ""
+
 msgid "View labels"
 msgstr ""
 
 msgid "View open merge request"
 msgstr "鏌ョ湅姝ゅ垎鏀殑鍚堜降璜嬫眰 (merge request)"
 
+msgid "View project labels"
+msgstr ""
+
 msgid "View replaced file @ "
 msgstr "鐎忚宸叉浛鎻涙獢妗� @ "
 
+msgid "Visibility and access controls"
+msgstr ""
+
 msgid "VisibilityLevel|Internal"
 msgstr "鍏ч儴"
 
@@ -3455,12 +4619,21 @@ msgstr "鍥犺┎闅庢鐨勮硣鏂欎笉瓒宠€岀劇娉曢’绀虹浉闂滆硣瑷�"
 msgid "We want to be sure it is you, please confirm you are not a robot."
 msgstr ""
 
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
 msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
 msgstr ""
 
 msgid "Weight"
 msgstr ""
 
+msgid "When leaving the URL blank, classification labels can still be specified whitout disabling cross project features or performing external authorization checks."
+msgstr ""
+
 msgid "Wiki"
 msgstr "Wiki"
 
@@ -3575,22 +4748,34 @@ msgstr ""
 msgid "Withdraw Access Request"
 msgstr "鍙栨秷娆婇檺鐢宠珛"
 
+msgid "Write a commit message..."
+msgstr ""
+
 msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
 msgstr "灏囪鍒櫎 %{group_name}銆傝鍒櫎鐨勭兢绲勭劇娉曞京鍘燂紒鐪熺殑銆岀⒑瀹氥€嶈閫欓杭鍋氬棊锛�"
 
-msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "灏囪鍒櫎 %{project_name_with_namespace}銆傝鍒櫎鐨勫皥妗堢劇娉曞京鍘燂紒鐪熺殑銆岀⒑瀹氥€嶈閫欓杭鍋氬棊锛�"
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! 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 "灏囪鍒櫎鏈垎鏀皥妗堣垏涓诲构 %{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 are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the %{primary_node}."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
 
 msgid "You can also star a label to make it a priority label."
 msgstr ""
 
-msgid "You can move around the graph by using the arrow keys."
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
 msgstr ""
 
 msgid "You can move around the graph by using the arrow keys."
@@ -3608,12 +4793,24 @@ msgstr ""
 msgid "You cannot write to this read-only GitLab instance."
 msgstr "鎮ㄤ笉鑳戒慨鏀归€欏€嬪敮璁€鐨� GitLab 涓绘銆�"
 
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
 msgid "You have reached your project limit"
 msgstr "鎮ㄥ凡閬斿埌灏堟鏁搁噺闄愬埗"
 
+msgid "You must have master access to force delete a lock"
+msgstr ""
+
 msgid "You must sign in to star a project"
 msgstr "蹇呴爤鐧诲叆鎵嶈兘鏀惰棌灏堟"
 
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
 msgid "You need permission."
 msgstr "闇€瑕佹瑠闄愭墠鑳介€欓杭鍋氥€�"
 
@@ -3644,9 +4841,30 @@ msgstr ""
 msgid "You'll need to use different branch names to get a valid comparison."
 msgstr ""
 
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
 msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
 msgstr ""
 
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
 msgid "Your comment will not be visible to the public."
 msgstr "浣犵殑鐣欒█灏囦笉鏈冭鍏枊銆�"
 
@@ -3659,6 +4877,13 @@ msgstr "鎮ㄧ殑鍚嶅瓧"
 msgid "Your projects"
 msgstr "浣犵殑瑷堝妰"
 
+msgid "among other things"
+msgstr ""
+
+msgid "and %d fixed vulnerability"
+msgid_plural "and %d fixed vulnerabilities"
+msgstr[0] ""
+
 msgid "assign yourself"
 msgstr ""
 
@@ -3668,13 +4893,31 @@ msgstr ""
 msgid "by"
 msgstr ""
 
+msgid "ciReport|%{type} detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{type} detected no security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|Code quality"
 msgstr ""
 
 msgid "ciReport|DAST detected no alerts by analyzing the review app"
 msgstr ""
 
-msgid "ciReport|Failed to load ${type} report"
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no new security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Dependency scanning detected no security vulnerabilities"
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
 msgstr ""
 
 msgid "ciReport|Fixed:"
@@ -3686,7 +4929,7 @@ msgstr ""
 msgid "ciReport|Learn more about whitelisting"
 msgstr ""
 
-msgid "ciReport|Loading ${type} report"
+msgid "ciReport|Loading %{reportName} report"
 msgstr ""
 
 msgid "ciReport|No changes to code quality"
@@ -3701,38 +4944,121 @@ msgstr ""
 msgid "ciReport|SAST"
 msgstr ""
 
+msgid "ciReport|SAST detected"
+msgstr ""
+
+msgid "ciReport|SAST detected no new security vulnerabilities"
+msgstr ""
+
 msgid "ciReport|SAST detected no security vulnerabilities"
 msgstr ""
 
 msgid "ciReport|SAST:container no vulnerabilities were found"
 msgstr ""
 
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
 msgid "ciReport|Show complete code vulnerabilities report"
 msgstr ""
 
 msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}"
 msgstr ""
 
-msgid "commit"
+msgid "ciReport|no vulnerabilities"
+msgstr ""
+
+msgid "command line instructions"
 msgstr ""
 
-msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgid "connecting"
 msgstr ""
 
-msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgid "could not read private key, is the passphrase correct?"
 msgstr ""
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "澶�"
 
+msgid "detected %d fixed vulnerability"
+msgid_plural "detected %d fixed vulnerabilities"
+msgstr[0] ""
+
+msgid "detected %d new vulnerability"
+msgid_plural "detected %d new vulnerabilities"
+msgstr[0] ""
+
+msgid "detected no vulnerabilities"
+msgstr ""
+
 msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
 msgstr ""
 
+msgid "here"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in progress"
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
 msgid "merge request"
 msgid_plural "merge requests"
 msgstr[0] ""
 
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows edits from maintainers"
+msgstr ""
+
+msgid "mrWidget|An error occured while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occured while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occured while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approved"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
 msgid "mrWidget|Cancel automatic merge"
 msgstr ""
 
@@ -3757,15 +5083,27 @@ msgstr ""
 msgid "mrWidget|Closes"
 msgstr ""
 
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
 msgid "mrWidget|Did not close"
 msgstr ""
 
 msgid "mrWidget|Email patches"
 msgstr ""
 
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
 msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
 msgstr ""
 
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
 msgid "mrWidget|Mentions"
 msgstr ""
 
@@ -3799,6 +5137,9 @@ msgstr ""
 msgid "mrWidget|Remove source branch"
 msgstr ""
 
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
 msgid "mrWidget|Request to merge"
 msgstr ""
 
@@ -3847,12 +5188,18 @@ msgstr ""
 msgid "mrWidget|This project is archived, write access has been disabled"
 msgstr ""
 
+msgid "mrWidget|Web IDE"
+msgstr ""
+
 msgid "mrWidget|You can merge this merge request manually using the"
 msgstr ""
 
 msgid "mrWidget|You can remove source branch now"
 msgstr ""
 
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
 msgid "mrWidget|command line"
 msgstr ""
 
@@ -3881,6 +5228,9 @@ msgstr "瀵嗙⒓"
 msgid "personal access token"
 msgstr "绉佷汉瀛樺彇鎲戣瓑 (access token)"
 
+msgid "private key does not match certificate."
+msgstr ""
+
 msgid "remove due date"
 msgstr ""
 
@@ -3890,6 +5240,9 @@ msgstr ""
 msgid "spendCommand|%{slash_command} will update the sum of the time spent."
 msgstr ""
 
+msgid "this document"
+msgstr ""
+
 msgid "to help your contributors communicate effectively!"
 msgstr ""
 
@@ -3899,3 +5252,6 @@ msgstr "浣跨敤鑰呭悕绋�"
 msgid "uses Kubernetes clusters to deploy your code!"
 msgstr ""
 
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
diff --git a/package.json b/package.json
index cbad55b4c854568f672bdd1256fd325e868fde1c..45bea12fd9bcfd9daef5f16d9a9479fab2e5c750 100644
--- a/package.json
+++ b/package.json
@@ -5,14 +5,18 @@
     "eslint": "eslint --max-warnings 0 --ext .js,.vue .",
     "eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .",
     "eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .",
-    "karma": "karma start config/karma.config.js --single-run",
-    "karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run",
+    "karma": "karma start --single-run true config/karma.config.js",
+    "karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
     "karma-start": "karma start config/karma.config.js",
-    "svg": "node config/svg.config.js",
+    "prettier-staged": "node ./scripts/frontend/prettier.js",
+    "prettier-staged-save": "node ./scripts/frontend/prettier.js save",
+    "prettier-all": "node ./scripts/frontend/prettier.js check-all",
+    "prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
     "webpack": "webpack --config config/webpack.config.js",
     "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
   },
   "dependencies": {
+    "@gitlab-org/gitlab-svgs": "^1.18.0",
     "autosize": "^4.0.0",
     "axios": "^0.17.1",
     "babel-core": "^6.26.0",
@@ -91,10 +95,10 @@
     "worker-loader": "^1.1.0"
   },
   "devDependencies": {
-    "@gitlab-org/gitlab-svgs": "^1.8.0",
     "axios-mock-adapter": "^1.10.0",
     "babel-eslint": "^8.0.2",
     "babel-plugin-istanbul": "^4.1.5",
+    "commander": "^2.15.1",
     "eslint": "^3.18.0",
     "eslint-config-airbnb-base": "^10.0.1",
     "eslint-import-resolver-webpack": "^0.8.3",
@@ -104,6 +108,7 @@
     "eslint-plugin-jasmine": "^2.1.0",
     "eslint-plugin-promise": "^3.5.0",
     "eslint-plugin-vue": "^4.0.1",
+    "ignore": "^3.3.7",
     "istanbul": "^0.4.5",
     "jasmine-core": "^2.9.0",
     "jasmine-jquery": "^2.1.1",
@@ -115,7 +120,7 @@
     "karma-sourcemap-loader": "^0.3.7",
     "karma-webpack": "2.0.7",
     "nodemon": "^1.15.1",
-    "prettier": "1.9.2",
+    "prettier": "1.11.1",
     "webpack-dev-server": "^2.11.2"
   }
 }
diff --git a/public/robots.txt b/public/robots.txt
index 123272a9834ef428917dbb0f1bc3099737eea78a..1f9d42f4adc9862306a232e69fb4e8b27c30a5ab 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -20,6 +20,7 @@ Disallow: /projects/new
 Disallow: /groups/new
 Disallow: /groups/*/edit
 Disallow: /users
+Disallow: /help
 
 # Global snippets
 User-Agent: *
diff --git a/qa/README.md b/qa/README.md
index 3a99a30d379f1cae51e101499075651d8999eae0..a4b4398645e0096f9e2a6730c4d3b723b481ce47 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -26,7 +26,7 @@ and corresponding views / partials / selectors in CE / EE.
 
 Whenever `qa:selectors` job fails in your merge request, you are supposed to
 fix [page objects](qa/page/README.md). You should also trigger end-to-end tests
-using `package-qa` manual action, to test if everything works fine.
+using `package-and-qa` manual action, to test if everything works fine.
 
 ## How can I use it?
 
diff --git a/qa/qa.rb b/qa/qa.rb
index 7220af5088ebec0936a745e61ab22512d8f792ec..fff99a1d31b5a9c1f0b7065825a6cc35adef8993 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -31,6 +31,7 @@ module QA
       autoload :Project, 'qa/factory/resource/project'
       autoload :MergeRequest, 'qa/factory/resource/merge_request'
       autoload :DeployKey, 'qa/factory/resource/deploy_key'
+      autoload :Branch, 'qa/factory/resource/branch'
       autoload :SecretVariable, 'qa/factory/resource/secret_variable'
       autoload :Runner, 'qa/factory/resource/runner'
       autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
@@ -90,6 +91,10 @@ module QA
       autoload :OAuth, 'qa/page/main/oauth'
     end
 
+    module Settings
+      autoload :Common, 'qa/page/settings/common'
+    end
+
     module Menu
       autoload :Main, 'qa/page/menu/main'
       autoload :Side, 'qa/page/menu/side'
@@ -128,6 +133,7 @@ module QA
         autoload :Repository, 'qa/page/project/settings/repository'
         autoload :CICD, 'qa/page/project/settings/ci_cd'
         autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
+        autoload :ProtectedBranches, 'qa/page/project/settings/protected_branches'
         autoload :SecretVariables, 'qa/page/project/settings/secret_variables'
         autoload :Runners, 'qa/page/project/settings/runners'
         autoload :MergeRequest, 'qa/page/project/settings/merge_request'
@@ -150,7 +156,10 @@ module QA
     end
 
     module Admin
-      autoload :Settings, 'qa/page/admin/settings'
+      module Settings
+        autoload :RepositoryStorage, 'qa/page/admin/settings/repository_storage'
+        autoload :Main, 'qa/page/admin/settings/main'
+      end
     end
 
     module Mattermost
diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb
index afaa96b45416850da33731bc39115f00c73afc7a..7a532ce534b50de7c24784cd1d76fb1b7f59979c 100644
--- a/qa/qa/factory/base.rb
+++ b/qa/qa/factory/base.rb
@@ -22,7 +22,7 @@ module QA
 
           factory.fabricate!(*args)
 
-          return Factory::Product.populate!(factory)
+          break Factory::Product.populate!(factory)
         end
       end
 
diff --git a/qa/qa/factory/resource/branch.rb b/qa/qa/factory/resource/branch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d0ef142e90d7da2194e4d000b4265ad5dcfb702c
--- /dev/null
+++ b/qa/qa/factory/resource/branch.rb
@@ -0,0 +1,73 @@
+module QA
+  module Factory
+    module Resource
+      class Branch < Factory::Base
+        attr_accessor :project, :branch_name, :allow_to_push, :protected
+
+        dependency Factory::Resource::Project, as: :project do |project|
+          project.name = 'protected-branch-project'
+        end
+
+        product :name do
+          Page::Project::Settings::Repository.act do
+            expand_protected_branches(&:last_branch_name)
+          end
+        end
+
+        product :push_allowance do
+          Page::Project::Settings::Repository.act do
+            expand_protected_branches(&:last_push_allowance)
+          end
+        end
+
+        def initialize
+          @branch_name = 'test/branch'
+          @allow_to_push = true
+          @protected = false
+        end
+
+        def fabricate!
+          project.visit!
+
+          Factory::Repository::Push.fabricate! do |resource|
+            resource.project = project
+            resource.file_name = 'kick-off.txt'
+            resource.commit_message = 'First commit'
+          end
+
+          branch = Factory::Repository::Push.fabricate! do |resource|
+            resource.project = project
+            resource.file_name = 'README.md'
+            resource.commit_message = 'Add readme'
+            resource.branch_name = "master:#{@branch_name}"
+          end
+
+          Page::Project::Show.act { wait_for_push }
+
+          # The upcoming process will make it access the Protected Branches page,
+          # select the already created branch and protect it according
+          # to `allow_to_push` variable.
+          return branch unless @protected
+
+          Page::Menu::Side.act do
+            click_repository_settings
+          end
+
+          Page::Project::Settings::Repository.perform do |setting|
+            setting.expand_protected_branches do |page|
+              page.select_branch(branch_name)
+
+              if allow_to_push
+                page.allow_devs_and_masters_to_push
+              else
+                page.allow_no_one_to_push
+              end
+
+              page.protect_branch
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/qa/qa/factory/settings/hashed_storage.rb b/qa/qa/factory/settings/hashed_storage.rb
index 13ce2435fe4152d6772022a587813621a085b7c7..c69ebed3c6b1cb1c9534dc6381d3bf0fb97c25fb 100644
--- a/qa/qa/factory/settings/hashed_storage.rb
+++ b/qa/qa/factory/settings/hashed_storage.rb
@@ -9,9 +9,11 @@ module QA
           Page::Menu::Main.act { go_to_admin_area }
           Page::Menu::Admin.act { go_to_settings }
 
-          Page::Admin::Settings.act do
-            enable_hashed_storage
-            save_settings
+          Page::Admin::Settings::Main.perform do |setting|
+            setting.expand_repository_storage do |page|
+              page.enable_hashed_storage
+              page.save_settings
+            end
           end
 
           QA::Page::Menu::Main.act { sign_out }
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index b3150e8f3faa9636cdc19b9068d4004c47b030e1..2f9f06ba277fccdb710638229d9161603487cd04 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -1,11 +1,14 @@
 require 'cgi'
 require 'uri'
+require 'open3'
 
 module QA
   module Git
     class Repository
       include Scenario::Actable
 
+      attr_reader :push_error
+
       def self.perform(*args)
         Dir.mktmpdir do |dir|
           Dir.chdir(dir) { super }
@@ -65,7 +68,8 @@ module QA
       end
 
       def push_changes(branch = 'master')
-        `git push #{@uri.to_s} #{branch} #{suppress_output}`
+        # capture3 returns stdout, stderr and status.
+        _, @push_error, _ = Open3.capture3("git push #{@uri} #{branch} #{suppress_output}")
       end
 
       def commits
diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md
index 83710606d7c78a98e2ac1c8987dbe92aa526bf38..d38223f690d2757bf5ab087f8147dc4bb2861ab2 100644
--- a/qa/qa/page/README.md
+++ b/qa/qa/page/README.md
@@ -40,7 +40,7 @@ the time it would take to build packages and test everything.
 That is why when someone changes `t.text_field :login` to
 `t.text_field :username` in the _new session_ view we won't know about this
 change until our GitLab QA nightly pipeline fails, or until someone triggers
-`package-qa` action in their merge request.
+`package-and-qa` action in their merge request.
 
 Obviously such a change would break all tests. We call this problem a _fragile
 tests problem_.
diff --git a/qa/qa/page/admin/settings.rb b/qa/qa/page/admin/settings.rb
deleted file mode 100644
index 1f646103e7f2e05b5abcfab4809a997710317294..0000000000000000000000000000000000000000
--- a/qa/qa/page/admin/settings.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module QA
-  module Page
-    module Admin
-      class Settings < Page::Base
-        view 'app/views/admin/application_settings/_form.html.haml' do
-          element :form_actions, '.form-actions'
-          element :submit, "submit 'Save'"
-          element :repository_storage, '%legend Repository Storage'
-          element :hashed_storage,
-            'Create new projects using hashed storage paths'
-        end
-
-        def enable_hashed_storage
-          scroll_to 'legend', text: 'Repository Storage'
-          check 'Create new projects using hashed storage paths'
-        end
-
-        def save_settings
-          scroll_to '.form-actions' do
-            click_button 'Save'
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/qa/qa/page/admin/settings/main.rb b/qa/qa/page/admin/settings/main.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e7c1220c967769c0862a27d77fc2e1df1d1d8041
--- /dev/null
+++ b/qa/qa/page/admin/settings/main.rb
@@ -0,0 +1,21 @@
+module QA
+  module Page
+    module Admin
+      module Settings
+        class Main < Page::Base
+          include QA::Page::Settings::Common
+
+          view 'app/views/admin/application_settings/show.html.haml' do
+            element :advanced_settings_section, 'Repository storage'
+          end
+
+          def expand_repository_storage(&block)
+            expand_section('Repository storage') do
+              RepositoryStorage.perform(&block)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/qa/qa/page/admin/settings/repository_storage.rb b/qa/qa/page/admin/settings/repository_storage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b4a1344216e2128560e056fe25e7cebaffce2c55
--- /dev/null
+++ b/qa/qa/page/admin/settings/repository_storage.rb
@@ -0,0 +1,23 @@
+module QA
+  module Page
+    module Admin
+      module Settings
+        class RepositoryStorage < Page::Base
+          view 'app/views/admin/application_settings/_repository_storage.html.haml' do
+            element :submit, "submit 'Save changes'"
+            element :hashed_storage,
+              'Create new projects using hashed storage paths'
+          end
+
+          def enable_hashed_storage
+            check 'Create new projects using hashed storage paths'
+          end
+
+          def save_settings
+            click_button 'Save changes'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index d215518d316a185683f3abf2474ce2505b187542..89125bd2e5971feb407f9e3939dc9d5b0c56ede0 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -29,7 +29,7 @@ module QA
           filter_by_name(name)
 
           wait(reload: false) do
-            return false if page.has_content?('Sorry, no groups or projects matched your search')
+            break false if page.has_content?('Sorry, no groups or projects matched your search')
 
             page.has_link?(name)
           end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 35875487da8fb832d9d4fda69226f4025bf28063..166861e6c4af589dabcb6cc8cf9cbcb02e0164bf 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -2,8 +2,9 @@ module QA
   module Page
     module MergeRequest
       class Show < Page::Base
-        view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js' do
+        view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do
           element :merge_button
+          element :fast_forward_message, 'Fast-forward merge without a merge commit'
         end
 
         view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do
@@ -12,19 +13,19 @@ module QA
 
         view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue' do
           element :mr_rebase_button
-          element :fast_forward_nessage, "Fast-forward merge is not possible"
+          element :no_fast_forward_message, 'Fast-forward merge is not possible'
         end
 
         def rebase!
-          wait(reload: false) do
-            click_element :mr_rebase_button
+          click_element :mr_rebase_button
 
-            has_text?("The source branch HEAD has recently changed.")
+          wait(reload: false) do
+            has_text?('Fast-forward merge without a merge commit')
           end
         end
 
         def fast_forward_possible?
-          !has_text?("Fast-forward merge is not possible")
+          !has_text?('Fast-forward merge is not possible')
         end
 
         def has_merge_button?
@@ -34,10 +35,10 @@ module QA
         end
 
         def merge!
-          wait(reload: false) do
-            click_element :merge_button
+          click_element :merge_button
 
-            has_text?("The changes were merged into")
+          wait(reload: false) do
+            has_text?('The changes were merged into')
           end
         end
       end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index b183552d46c7a7a444b1ea53d2ab6e25623f027e..ec61c47b3bb06ccbcf11487ecaa51e4982af515a 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -20,14 +20,14 @@ module QA::Page
 
       def running?
         within('.ci-header-container') do
-          return page.has_content?('running')
+          page.has_content?('running')
         end
       end
 
       def has_build?(name, status: :success)
         within('.pipeline-graph') do
           within('.ci-job-component', text: name) do
-            return has_selector?(".ci-status-icon-#{status}")
+            has_selector?(".ci-status-icon-#{status}")
           end
         end
       end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index 99be21bbe89120dbfe10eddf9bcb83cbc31db272..17a1bc904e4e02c97d54f01fadfe625f32c8dedd 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -1,4 +1,4 @@
-module QA
+module QA # rubocop:disable Naming/FileName
   module Page
     module Project
       module Settings
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index 319cb1045b6116338678ba01b53f2fb25e84cd8f..874fb381554a5d35099f44c3c672f0a4dc6cfb79 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -3,6 +3,8 @@ module QA
     module Project
       module Settings
         module Common
+          include QA::Page::Settings::Common
+
           def self.included(base)
             base.class_eval do
               view 'app/views/projects/edit.html.haml' do
@@ -10,24 +12,6 @@ module QA
               end
             end
           end
-
-          # Click the Expand button present in the specified section
-          #
-          # @param [String] name present in the container in the DOM
-          def expand_section(name)
-            page.within('#content-body') do
-              page.within('section', text: name) do
-                # Because it is possible to click the button before the JS toggle code is bound
-                wait(reload: false) do
-                  click_button 'Expand' unless first('button', text: 'Collapse')
-
-                  page.has_content?('Collapse')
-                end
-
-                yield if block_given?
-              end
-            end
-          end
         end
       end
     end
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f356340112425f9471b01b39bd47f16c8bdca6f8
--- /dev/null
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -0,0 +1,69 @@
+module QA
+  module Page
+    module Project
+      module Settings
+        class ProtectedBranches < Page::Base
+          view 'app/views/projects/protected_branches/shared/_dropdown.html.haml' do
+            element :protected_branch_select
+            element :protected_branch_dropdown
+          end
+
+          view 'app/views/projects/protected_branches/_create_protected_branch.html.haml' do
+            element :allowed_to_push_select
+            element :allowed_to_push_dropdown
+          end
+
+          view 'app/views/projects/protected_branches/shared/_branches_list.html.haml' do
+            element :protected_branches_list
+          end
+
+          view 'app/views/projects/protected_branches/shared/_protected_branch.html.haml' do
+            element :protected_branch_name
+          end
+
+          def select_branch(branch_name)
+            click_element :protected_branch_select
+
+            within_element(:protected_branch_dropdown) do
+              click_on branch_name
+            end
+          end
+
+          def allow_no_one_to_push
+            allow_to_push('No one')
+          end
+
+          def allow_devs_and_masters_to_push
+            allow_to_push('Developers + Masters')
+          end
+
+          def protect_branch
+            click_on 'Protect'
+          end
+
+          def last_branch_name
+            within_element(:protected_branches_list) do
+              all('.qa-protected-branch-name').last
+            end
+          end
+
+          def last_push_allowance
+            within_element(:protected_branches_list) do
+              all('.qa-allowed-to-push').last
+            end
+          end
+
+          private
+
+          def allow_to_push(text)
+            click_element :allowed_to_push_select
+
+            within_element(:allowed_to_push_dropdown) do
+              click_on text
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index 22362164a1acc5c46970f1bd348eb8627966b88c..30900e74e90fe5d0d1553ec3ebbfbfac2a9031cc 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -14,6 +14,12 @@ module QA
               DeployKeys.perform(&block)
             end
           end
+
+          def expand_protected_branches(&block)
+            expand_section('Protected Branches') do
+              ProtectedBranches.perform(&block)
+            end
+          end
         end
       end
     end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 0c7ad46d36b29ae77841d88b7d5b9d74a244649c..c7e7ece792d779fc456d15d0d6b48eb0d534904e 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -21,6 +21,11 @@ module QA
           element :new_issue_link, "link_to 'New issue', new_project_issue_path(@project)"
         end
 
+        view 'app/views/shared/_ref_switcher.html.haml' do
+          element :branches_select
+          element :branches_dropdown
+        end
+
         def choose_repository_clone_http
           choose_repository_clone('HTTP', 'http')
         end
@@ -44,6 +49,18 @@ module QA
           find('.qa-project-name').text
         end
 
+        def switch_to_branch(branch_name)
+          find_element(:branches_select).click
+
+          within_element(:branches_dropdown) do
+            click_on branch_name
+          end
+        end
+
+        def last_commit_content
+          find_element(:commit_content).text
+        end
+
         def new_merge_request
           wait(reload: true) do
             has_css?(element_selector_css(:create_merge_request))
diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a683a6829d56e973ce677b30d151a8e1c41b4dfa
--- /dev/null
+++ b/qa/qa/page/settings/common.rb
@@ -0,0 +1,25 @@
+module QA
+  module Page
+    module Settings
+      module Common
+        # Click the Expand button present in the specified section
+        #
+        # @param [String] name present in the container in the DOM
+        def expand_section(name)
+          page.within('#content-body') do
+            page.within('section', text: name) do
+              # Because it is possible to click the button before the JS toggle code is bound
+              wait(reload: false) do
+                click_button 'Expand' unless first('button', text: 'Collapse')
+
+                page.has_content?('Collapse')
+              end
+
+              yield if block_given?
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/qa/qa/scenario/bootable.rb b/qa/qa/scenario/bootable.rb
index d6de4d404c86dff675744cd1cdeeafee58417d82..dd12ea6d49275194749b4da229e8b1d797bd7d1c 100644
--- a/qa/qa/scenario/bootable.rb
+++ b/qa/qa/scenario/bootable.rb
@@ -23,7 +23,7 @@ module QA
 
           arguments.parse!(argv)
 
-          self.perform(**Runtime::Scenario.attributes)
+          self.perform(Runtime::Scenario.attributes, *arguments.default_argv)
         end
 
         private
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index 341998af1609f15a9fa34eac4eedc607fdea3772..d21a9d529972a8ee081d50193558ff2ba4cdc2c8 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -4,7 +4,7 @@ module QA
       def self.perform(*args)
         new.tap do |scenario|
           yield scenario if block_given?
-          return scenario.perform(*args)
+          break scenario.perform(*args)
         end
       end
 
diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb
index 0af9afd1ea4f5afb0df8b9b16a456f43cf15f6fb..567e5fd6ccab5fc2b09e68cbe39c8248bc59a419 100644
--- a/qa/qa/scenario/test/instance.rb
+++ b/qa/qa/scenario/test/instance.rb
@@ -11,7 +11,7 @@ module QA
 
         tags :core
 
-        def perform(address, *files)
+        def perform(address, *rspec_options)
           Runtime::Scenario.define(:gitlab_address, address)
 
           ##
@@ -22,9 +22,9 @@ module QA
           Specs::Runner.perform do |specs|
             specs.tty = true
             specs.tags = self.class.focus
-            specs.files =
-              if files.any?
-                files
+            specs.options =
+              if rspec_options.any?
+                rspec_options
               else
                 File.expand_path('../../specs/features', __dir__)
               end
diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb
index d939f52ab16e93823b09c183a3eb5c4a2bc136eb..13bfad28b0ba15496e0c54637e94800f7c84bd98 100644
--- a/qa/qa/scenario/test/integration/mattermost.rb
+++ b/qa/qa/scenario/test/integration/mattermost.rb
@@ -9,10 +9,10 @@ module QA
         class Mattermost < Test::Instance
           tags :core, :mattermost
 
-          def perform(address, mattermost, *files)
+          def perform(address, mattermost, *rspec_options)
             Runtime::Scenario.define(:mattermost_address, mattermost)
 
-            super(address, *files)
+            super(address, *rspec_options)
           end
         end
       end
diff --git a/qa/qa/specs/features/repository/protected_branches_spec.rb b/qa/qa/specs/features/repository/protected_branches_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..88fa4994e32b3c1c8f09d9b3c540db47a74805c3
--- /dev/null
+++ b/qa/qa/specs/features/repository/protected_branches_spec.rb
@@ -0,0 +1,63 @@
+module QA
+  feature 'branch protection support', :core do
+    given(:branch_name) { 'protected-branch' }
+    given(:commit_message) { 'Protected push commit message' }
+    given(:project) do
+      Factory::Resource::Project.fabricate! do |resource|
+        resource.name = 'protected-branch-project'
+      end
+    end
+    given(:location) do
+      Page::Project::Show.act do
+        choose_repository_clone_http
+        repository_location
+      end
+    end
+
+    before do
+      Runtime::Browser.visit(:gitlab, Page::Main::Login)
+      Page::Main::Login.act { sign_in_using_credentials }
+    end
+
+    scenario 'user is able to protect a branch' do
+      protected_branch = Factory::Resource::Branch.fabricate! do |resource|
+        resource.branch_name = branch_name
+        resource.project = project
+        resource.allow_to_push = true
+        resource.protected = true
+      end
+
+      expect(protected_branch.name).to have_content(branch_name)
+      expect(protected_branch.push_allowance).to have_content('Developers + Masters')
+    end
+
+    scenario 'users without authorization cannot push to protected branch' do
+      Factory::Resource::Branch.fabricate! do |resource|
+        resource.branch_name = branch_name
+        resource.project = project
+        resource.allow_to_push = false
+        resource.protected = true
+      end
+
+      project.visit!
+
+      Git::Repository.perform do |repository|
+        repository.location = location
+        repository.use_default_credentials
+
+        repository.act do
+          clone
+          configure_identity('GitLab QA', 'root@gitlab.com')
+          checkout('protected-branch')
+          commit_file('README.md', 'readme content', 'Add a readme')
+          push_changes('protected-branch')
+        end
+
+        expect(repository.push_error)
+          .to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
+        expect(repository.push_error)
+          .to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
+      end
+    end
+  end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 752e3e60b8c3d6ef71d0493d872be55400622f08..f8f6fe6559932e9879d12219f68ce2947f302d94 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -3,19 +3,19 @@ require 'rspec/core'
 module QA
   module Specs
     class Runner < Scenario::Template
-      attr_accessor :tty, :tags, :files
+      attr_accessor :tty, :tags, :options
 
       def initialize
         @tty = false
         @tags = []
-        @files = [File.expand_path('./features', __dir__)]
+        @options = [File.expand_path('./features', __dir__)]
       end
 
       def perform
         args = []
         args.push('--tty') if tty
         tags.to_a.each { |tag| args.push(['-t', tag.to_s]) }
-        args.push(files)
+        args.push(options)
 
         Runtime::Browser.configure!
 
diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb
index 02822d7d18f3a97cf3a2871d56b79a6614cb0c96..559576499046319353ad0a1a63a11fa0a1cfde66 100644
--- a/qa/spec/page/validator_spec.rb
+++ b/qa/spec/page/validator_spec.rb
@@ -30,7 +30,7 @@ describe QA::Page::Validator do
     let(:view) { spy('view') }
 
     before do
-      allow(QA::Page::Admin::Settings)
+      allow(QA::Page::Admin::Settings::Main)
         .to receive(:views).and_return([view])
     end
 
diff --git a/qa/spec/scenario/test/instance_spec.rb b/qa/spec/scenario/test/instance_spec.rb
index bd09c28e924bcc43fc4f27d897fa3eedb3560f63..a74a9538be8cf98c92dd0681aea00c26a0183e5d 100644
--- a/qa/spec/scenario/test/instance_spec.rb
+++ b/qa/spec/scenario/test/instance_spec.rb
@@ -29,7 +29,7 @@ describe QA::Scenario::Test::Instance do
       it 'should call runner with default arguments' do
         subject.perform("test")
 
-        expect(runner).to have_received(:files=)
+        expect(runner).to have_received(:options=)
           .with(File.expand_path('../../../qa/specs/features', __dir__))
       end
     end
@@ -38,7 +38,7 @@ describe QA::Scenario::Test::Instance do
       it 'should call runner with paths' do
         subject.perform('test', 'path1', 'path2')
 
-        expect(runner).to have_received(:files=).with(%w[path1 path2])
+        expect(runner).to have_received(:options=).with(%w[path1 path2])
       end
     end
   end
diff --git a/rubocop/cop/avoid_break_from_strong_memoize.rb b/rubocop/cop/avoid_break_from_strong_memoize.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9b436118db3e7a1e6862fad93e68e9bffdc64f9a
--- /dev/null
+++ b/rubocop/cop/avoid_break_from_strong_memoize.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module RuboCop
+  module Cop
+    # Checks for break inside strong_memoize blocks.
+    # For more information see: https://gitlab.com/gitlab-org/gitlab-ce/issues/42889
+    #
+    # @example
+    #   # bad
+    #   strong_memoize(:result) do
+    #     break if something
+    #
+    #     do_an_heavy_calculation
+    #   end
+    #
+    #   # good
+    #   strong_memoize(:result) do
+    #     next if something
+    #
+    #     do_an_heavy_calculation
+    #   end
+    #
+    class AvoidBreakFromStrongMemoize < RuboCop::Cop::Cop
+      MSG = 'Do not use break inside strong_memoize, use next instead.'
+
+      def on_block(node)
+        block_body = node.body
+
+        return unless block_body
+        return unless node.method_name == :strong_memoize
+
+        block_body.each_node(:break) do |break_node|
+          next if container_block_for(break_node) != node
+
+          add_offense(break_node)
+        end
+      end
+
+      private
+
+      def container_block_for(current_node)
+        current_node = current_node.parent until current_node.type == :block && current_node.method_name == :strong_memoize
+
+        current_node
+      end
+    end
+  end
+end
diff --git a/rubocop/cop/avoid_return_from_blocks.rb b/rubocop/cop/avoid_return_from_blocks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..40b2aed019f1077b9ffd682831fa196d89bf2d0a
--- /dev/null
+++ b/rubocop/cop/avoid_return_from_blocks.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module RuboCop
+  module Cop
+    # Checks for return inside blocks.
+    # For more information see: https://gitlab.com/gitlab-org/gitlab-ce/issues/42889
+    #
+    # @example
+    #   # bad
+    #   call do
+    #     return if something
+    #
+    #     do_something_else
+    #   end
+    #
+    #   # good
+    #   call do
+    #     break if something
+    #
+    #     do_something_else
+    #   end
+    #
+    class AvoidReturnFromBlocks < RuboCop::Cop::Cop
+      MSG = 'Do not return from a block, use next or break instead.'
+      DEF_METHODS = %i[define_method lambda].freeze
+      WHITELISTED_METHODS = %i[each each_filename times loop].freeze
+
+      def on_block(node)
+        block_body = node.body
+
+        return unless block_body
+        return unless top_block?(node)
+
+        block_body.each_node(:return) do |return_node|
+          next if parent_blocks(node, return_node).all?(&method(:whitelisted?))
+
+          add_offense(return_node)
+        end
+      end
+
+      private
+
+      def top_block?(node)
+        current_node = node
+        top_block = nil
+
+        while current_node && current_node.type != :def
+          top_block = current_node if current_node.type == :block
+          current_node = current_node.parent
+        end
+
+        top_block == node
+      end
+
+      def parent_blocks(node, current_node)
+        blocks = []
+
+        until node == current_node || def?(current_node)
+          blocks << current_node if current_node.type == :block
+          current_node = current_node.parent
+        end
+
+        blocks << node if node == current_node && !def?(node)
+        blocks
+      end
+
+      def def?(node)
+        node.type == :def || node.type == :defs ||
+          (node.type == :block && DEF_METHODS.include?(node.method_name))
+      end
+
+      def whitelisted?(block_node)
+        WHITELISTED_METHODS.include?(block_node.method_name)
+      end
+    end
+  end
+end
diff --git a/rubocop/cop/gitlab/httparty.rb b/rubocop/cop/gitlab/httparty.rb
new file mode 100644
index 0000000000000000000000000000000000000000..215f18b6993f2ebe3fce105720b9dca21cec3bdf
--- /dev/null
+++ b/rubocop/cop/gitlab/httparty.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helpers'
+
+module RuboCop
+  module Cop
+    module Gitlab
+      class HTTParty < RuboCop::Cop::Cop
+        include SpecHelpers
+
+        MSG_SEND = <<~EOL.freeze
+          Avoid calling `HTTParty` directly. Instead, use the Gitlab::HTTP
+          wrapper. To allow request to localhost or the private network set
+          the option :allow_local_requests in the request call.
+        EOL
+
+        MSG_INCLUDE = <<~EOL.freeze
+          Avoid including `HTTParty` directly. Instead, use the Gitlab::HTTP
+          wrapper. To allow request to localhost or the private network set
+          the option :allow_local_requests in the request call.
+        EOL
+
+        def_node_matcher :includes_httparty?, <<~PATTERN
+          (send nil? :include (const nil? :HTTParty))
+        PATTERN
+
+        def_node_matcher :httparty_node?, <<~PATTERN
+          (send (const nil? :HTTParty)...)
+        PATTERN
+
+        def on_send(node)
+          return if in_spec?(node)
+
+          add_offense(node, location: :expression, message: MSG_SEND) if httparty_node?(node)
+          add_offense(node, location: :expression, message: MSG_INCLUDE) if includes_httparty?(node)
+        end
+
+        def autocorrect(node)
+          if includes_httparty?(node)
+            autocorrect_includes_httparty(node)
+          else
+            autocorrect_httparty_node(node)
+          end
+        end
+
+        def autocorrect_includes_httparty(node)
+          lambda do |corrector|
+            corrector.remove(node.source_range)
+          end
+        end
+
+        def autocorrect_httparty_node(node)
+          _, method_name, *arg_nodes = *node
+
+          replacement = "Gitlab::HTTP.#{method_name}(#{arg_nodes.map(&:source).join(', ')})"
+
+          lambda do |corrector|
+            corrector.replace(node.source_range, replacement)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/rubocop/cop/migration/safer_boolean_column.rb b/rubocop/cop/migration/safer_boolean_column.rb
index dc5c55df6fb102ffaf616824c038f619347072d0..a7d922c752f946de3771563d8a1b330630e141e9 100644
--- a/rubocop/cop/migration/safer_boolean_column.rb
+++ b/rubocop/cop/migration/safer_boolean_column.rb
@@ -61,7 +61,7 @@ module RuboCop
           return true unless opts
 
           each_hash_node_pair(opts) do |key, value|
-            return value == 'nil' if key == :default
+            break value == 'nil' if key == :default
           end
         end
 
@@ -69,7 +69,7 @@ module RuboCop
           return true unless opts
 
           each_hash_node_pair(opts) do |key, value|
-            return value != 'false' if key == :null
+            break value != 'false' if key == :null
           end
         end
 
diff --git a/rubocop/cop/rspec/factories_in_migration_specs.rb b/rubocop/cop/rspec/factories_in_migration_specs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0c5aa838a2cb93ddbe221c27076875fd675657e6
--- /dev/null
+++ b/rubocop/cop/rspec/factories_in_migration_specs.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helpers'
+
+module RuboCop
+  module Cop
+    module RSpec
+      # This cop checks for the usage of factories in migration specs
+      #
+      # @example
+      #
+      #   # bad
+      #   let(:user) { create(:user) }
+      #
+      #   # good
+      #   let(:users) { table(:users) }
+      #   let(:user) { users.create!(name: 'User 1', username: 'user1') }
+      class FactoriesInMigrationSpecs < RuboCop::Cop::Cop
+        include SpecHelpers
+
+        MESSAGE = "Don't use FactoryBot.%s in migration specs, use `table` instead.".freeze
+        FORBIDDEN_METHODS = %i[build build_list create create_list].freeze
+
+        def_node_search :forbidden_factory_usage?, <<~PATTERN
+          (send {(const nil? :FactoryBot) nil?} {#{FORBIDDEN_METHODS.map(&:inspect).join(' ')}} ...)
+        PATTERN
+
+        # Following is what node.children looks like on a match:
+        # - Without FactoryBot namespace: [nil, :build, s(:sym, :user)]
+        # - With FactoryBot namespace: [s(:const, nil, :FactoryBot), :build, s(:sym, :user)]
+        def on_send(node)
+          return unless in_migration_spec?(node)
+          return unless forbidden_factory_usage?(node)
+
+          method = node.children[1]
+
+          add_offense(node, location: :expression, message: MESSAGE % method)
+        end
+      end
+    end
+  end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 9110237c5380133df2ca106eee1d03e244881e35..f05990232abe7095a91db64f1ad8e26a038baced 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,6 +1,10 @@
+# rubocop:disable Naming/FileName
 require_relative 'cop/gitlab/module_with_instance_variables'
 require_relative 'cop/gitlab/predicate_memoization'
+require_relative 'cop/gitlab/httparty'
 require_relative 'cop/include_sidekiq_worker'
+require_relative 'cop/avoid_return_from_blocks'
+require_relative 'cop/avoid_break_from_strong_memoize'
 require_relative 'cop/line_break_around_conditional_block'
 require_relative 'cop/migration/add_column'
 require_relative 'cop/migration/add_concurrent_foreign_key'
@@ -19,4 +23,5 @@ require_relative 'cop/migration/update_column_in_batches'
 require_relative 'cop/migration/update_large_table'
 require_relative 'cop/project_path_helper'
 require_relative 'cop/rspec/env_assignment'
+require_relative 'cop/rspec/factories_in_migration_specs'
 require_relative 'cop/sidekiq_options_queue'
diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb
index a702a083958a16043cbc7cb4870b20e38a399324..6c0f0193b1ac242337a73827423755963877886a 100644
--- a/rubocop/spec_helpers.rb
+++ b/rubocop/spec_helpers.rb
@@ -6,7 +6,18 @@ module RuboCop
     def in_spec?(node)
       path = node.location.expression.source_buffer.name
 
-      !SPEC_HELPERS.include?(File.basename(path)) && path.start_with?(File.join(Dir.pwd, 'spec'))
+      !SPEC_HELPERS.include?(File.basename(path)) &&
+        path.start_with?(File.join(Dir.pwd, 'spec'), File.join(Dir.pwd, 'ee', 'spec'))
+    end
+
+    # Returns true if the given node originated from a migration spec.
+    def in_migration_spec?(node)
+      path = node.location.expression.source_buffer.name
+
+      in_spec?(node) &&
+        path.start_with?(
+          File.join(Dir.pwd, 'spec', 'migrations'),
+          File.join(Dir.pwd, 'ee', 'spec', 'migrations'))
     end
   end
 end
diff --git a/scripts/codequality b/scripts/codequality
deleted file mode 100755
index 2f3ccef7d2d21399b93a5392b94683d1b3afa6a7..0000000000000000000000000000000000000000
--- a/scripts/codequality
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-
-set -eo pipefail
-
-code_path=$(pwd)
-
-# docker run --tty will merge stderr and stdout, we don't need this on CI or
-# it will break codequality json file
-[ "$CI" != "" ] || docker_tty="--tty"
-
-# The codebase and instructions for the following image can be found at https://gitlab.com/gitlab-org/codeclimate-rubocop/wikis/home
-docker pull dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1 > /dev/null
-docker tag dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1 codeclimate/codeclimate-rubocop:gitlab-codeclimate-rubocop-0-52-1 > /dev/null
-
-exec docker run --rm $docker_tty --env CODECLIMATE_CODE="$code_path" \
-	--volume "$code_path":/code \
-	--volume /var/run/docker.sock:/var/run/docker.sock \
-	--volume /tmp/cc:/tmp/cc \
-	"codeclimate/codeclimate:${CODECLIMATE_VERSION:-0.71.1}" "$@"
diff --git a/scripts/frontend/frontend_script_utils.js b/scripts/frontend/frontend_script_utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..e42b912d359cfa05831766fa0cf4e6416a5b70aa
--- /dev/null
+++ b/scripts/frontend/frontend_script_utils.js
@@ -0,0 +1,24 @@
+const execFileSync = require('child_process').execFileSync;
+
+const exec = (command, args) => {
+  const options = {
+    cwd: process.cwd(),
+    env: process.env,
+    encoding: 'utf-8',
+  };
+  return execFileSync(command, args, options);
+};
+
+const execGitCmd = args =>
+  exec('git', args)
+    .trim()
+    .toString()
+    .split('\n');
+
+module.exports = {
+  getStagedFiles: fileExtensionFilter => {
+    const gitOptions = ['diff', '--name-only', '--cached', '--diff-filter=ACMRTUB'];
+    if (fileExtensionFilter) gitOptions.push(...fileExtensionFilter);
+    return execGitCmd(gitOptions);
+  },
+};
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
new file mode 100644
index 0000000000000000000000000000000000000000..39de77bc333f628f94091adb72225eefbe972043
--- /dev/null
+++ b/scripts/frontend/prettier.js
@@ -0,0 +1,117 @@
+const glob = require('glob');
+const prettier = require('prettier');
+const fs = require('fs');
+const path = require('path');
+const prettierIgnore = require('ignore')();
+
+const getStagedFiles = require('./frontend_script_utils').getStagedFiles;
+
+const mode = process.argv[2] || 'check';
+const shouldSave = mode === 'save' || mode === 'save-all';
+const allFiles = mode === 'check-all' || mode === 'save-all';
+
+const config = {
+  patterns: ['**/*.js', '**/*.vue', '**/*.scss'],
+  /*
+   * The ignore patterns below are just to reduce search time with glob, as it includes the
+   * folders with the most ignored assets, the actual `.prettierignore` will be used later on
+   */
+  ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'],
+  parsers: {
+    js: 'babylon',
+    vue: 'vue',
+    scss: 'css',
+  },
+};
+
+/*
+ * Unfortunately the prettier API does not expose support for `.prettierignore` files, they however
+ * use the ignore package, so we do the same. We simply cannot use the glob package, because
+ * gitignore style is not compatible with globs ignore style.
+ */
+prettierIgnore.add(
+  fs
+    .readFileSync(path.join(__dirname, '../../', '.prettierignore'))
+    .toString()
+    .trim()
+    .split(/\r?\n/)
+);
+
+const availableExtensions = Object.keys(config.parsers);
+
+console.log(`Loading ${allFiles ? 'All' : 'Staged'} Files ...`);
+
+const stagedFiles = allFiles ? null : getStagedFiles(availableExtensions.map(ext => `*.${ext}`));
+
+if (stagedFiles) {
+  if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) {
+    console.log('No matching staged files.');
+    return;
+  }
+  console.log(`Matching staged Files : ${stagedFiles.length}`);
+}
+
+let didWarn = false;
+let didError = false;
+
+let files;
+if (allFiles) {
+  const ignore = config.ignore;
+  const patterns = config.patterns;
+  const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
+  files = glob.sync(globPattern, { ignore }).filter(f => allFiles || stagedFiles.includes(f));
+} else {
+  files = stagedFiles.filter(f => availableExtensions.includes(f.split('.').pop()));
+}
+
+files = prettierIgnore.filter(files);
+
+if (!files.length) {
+  console.log('No Files found to process with Prettier');
+  return;
+}
+
+console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`);
+
+prettier
+  .resolveConfig('.')
+  .then(options => {
+    console.log('Found options : ', options);
+    files.forEach(file => {
+      try {
+        const fileExtension = file.split('.').pop();
+        Object.assign(options, {
+          parser: config.parsers[fileExtension],
+        });
+
+        const input = fs.readFileSync(file, 'utf8');
+
+        if (shouldSave) {
+          const output = prettier.format(input, options);
+          if (output !== input) {
+            fs.writeFileSync(file, output, 'utf8');
+            console.log(`Prettified : ${file}`);
+          }
+        } else if (!prettier.check(input, options)) {
+          if (!didWarn) {
+            console.log(
+              '\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n'
+            );
+            didWarn = true;
+          }
+          console.log(`Prettify Manually : ${file}`);
+        }
+      } catch (error) {
+        didError = true;
+        console.log(`\n\nError with ${file}: ${error.message}`);
+      }
+    });
+
+    if (didWarn || didError) {
+      process.exit(1);
+    }
+  })
+  .catch(e => {
+    console.log(`Error on loading the Config File: ${e.message}`);
+    process.exit(1);
+  });
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index e5242fee32b18ff6935030886ef340f72c6ea0d9..178b209aacf04f55f67885ccf10beda319cd5885 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -3,7 +3,7 @@
 cd "$(dirname "$0")/.."
 
 # Use long options (e.g. --header instead of -H) for curl examples in documentation.
-echo 'Checking for curl short options...'
+echo '=> Checking for cURL short options...'
 grep --extended-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/ >/dev/null 2>&1
 if [ $? == 0 ]
 then
@@ -15,7 +15,7 @@ fi
 
 # Ensure that the CHANGELOG.md does not contain duplicate versions
 DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^## .+' CHANGELOG.md | sed -E 's| \(.+\)||' | sort -r | uniq -d)
-echo 'Checking for CHANGELOG.md duplicate entries...'
+echo '=> Checking for CHANGELOG.md duplicate entries...'
 if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ]
 then
   echo '鉁� ERROR: Duplicate versions in CHANGELOG.md:' >&2
@@ -25,7 +25,7 @@ fi
 
 # Make sure no files in doc/ are executable
 EXEC_PERM_COUNT=$(find doc/ app/ -type f -perm 755 | wc -l)
-echo 'Checking for executable permissions...'
+echo '=> Checking for executable permissions...'
 if [ "${EXEC_PERM_COUNT}" -ne 0 ]
 then
   echo '鉁� ERROR: Executable permissions should not be used in documentation! Use `chmod 644` to the files in question:' >&2
@@ -33,5 +33,33 @@ then
   exit 1
 fi
 
+# Do not use 'README.md', instead use 'index.md'
+# Number of 'README.md's as of 2018-03-26
+NUMBER_READMES_CE=42
+NUMBER_READMES_EE=46
+FIND_READMES=$(find doc/ -name "README.md" | wc -l)
+echo '=> Checking for new README.md files...'
+if [ "${CI_PROJECT_NAME}" == 'gitlab-ce' ]
+then
+  if [ ${FIND_READMES} -ne ${NUMBER_READMES_CE} ]
+  then
+    echo
+    echo '  鉁� ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
+    echo '  https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents'
+    echo
+    exit 1
+  fi
+elif [ "${CI_PROJECT_NAME}" == 'gitlab-ee' ]
+then
+  if [ ${FIND_READMES} -ne $NUMBER_READMES_EE ]
+  then
+    echo
+    echo '  鉁� ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
+    echo '  https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents'
+    echo
+    exit 1
+  fi
+fi
+
 echo "鉁� Linting passed"
 exit 0
diff --git a/scripts/prune-old-flaky-specs b/scripts/prune-old-flaky-specs
new file mode 100755
index 0000000000000000000000000000000000000000..f7451fbd4282c9eaa369ddde3f06215606405e2e
--- /dev/null
+++ b/scripts/prune-old-flaky-specs
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+
+# lib/rspec_flaky/flaky_examples_collection.rb is requiring
+# `active_support/hash_with_indifferent_access`, and we install the `activesupport`
+# gem manually on the CI
+require 'rubygems'
+
+require_relative '../lib/rspec_flaky/report'
+
+report_file = ARGV.shift
+unless report_file
+  puts 'usage: prune-old-flaky-specs <report-file> <new-report-file>'
+  exit 1
+end
+
+new_report_file = ARGV.shift || report_file
+report = RspecFlaky::Report.load(report_file)
+puts "Loading #{report_file}..."
+puts "Current report has #{report.size} entries."
+
+new_report = report.prune_outdated
+
+puts "New report has #{new_report.size} entries: #{report.size - new_report.size} entries older than 90 days were removed."
+puts "Saved #{new_report_file}." if new_report.write(new_report_file)
diff --git a/scripts/trigger-build-docs b/scripts/trigger-build-docs
index a270823b8570b7ccca8c095c6cad614e3518215f..c9aaba91aa03f746ac0d8451f8e736b7aa9e41cd 100755
--- a/scripts/trigger-build-docs
+++ b/scripts/trigger-build-docs
@@ -7,7 +7,7 @@ require 'gitlab'
 #
 Gitlab.configure do |config|
   config.endpoint       = 'https://gitlab.com/api/v4'
-  config.private_token  = ENV["DOCS_API_TOKEN"]  # GitLab Docs bot access token which has only Developer access to gitlab-docs
+  config.private_token  = ENV["DOCS_API_TOKEN"] # GitLab Docs bot access token with Developer access to gitlab-docs
 end
 
 #
@@ -24,20 +24,40 @@ def docs_branch
   # The maximum string length a file can have on a filesystem (ext4)
   # is 63 characters. Let's use something smaller to be 100% sure.
   max = 42
-  # Prefix the remote branch with 'preview-' in order to avoid
-  # name conflicts in the rare case the branch name already
+  # Prefix the remote branch with the slug of the project in order
+  # to avoid name conflicts in the rare case the branch name already
   # exists in the docs repo and truncate to max length.
   "#{slug}-#{ENV["CI_COMMIT_REF_SLUG"]}"[0...max]
 end
 
 #
-# Create a remote branch in gitlab-docs
+# Create a remote branch in gitlab-docs and immediately cancel the pipeline
+# to avoid race conditions, since a triggered pipeline will also run right
+# after the branch creation. This only happens the very first time a branch
+# is created and will be skipped in subsequent runs. Read more in
+# https://gitlab.com/gitlab-com/gitlab-docs/issues/154.
 #
 def create_remote_branch
   Gitlab.create_branch(GITLAB_DOCS_REPO, docs_branch, 'master')
-  puts "Remote branch '#{docs_branch}' created"
+  puts "=> Remote branch '#{docs_branch}' created"
+
+  pipelines = nil
+
+  # Wait until the pipeline is started
+  loop do
+    sleep 1
+    puts "=> Waiting for pipeline to start..."
+    pipelines = Gitlab.pipelines(GITLAB_DOCS_REPO, { ref: docs_branch })
+    break if pipelines.any?
+  end
+
+  # Get the first pipeline ID which should be the only one for the branch
+  pipeline_id = pipelines.first.id
+
+  # Cancel the pipeline
+  Gitlab.cancel_pipeline(GITLAB_DOCS_REPO, pipeline_id)
 rescue Gitlab::Error::BadRequest
-  puts "Remote branch '#{docs_branch}' already exists"
+  puts "=> Remote branch '#{docs_branch}' already exists"
 end
 
 #
@@ -45,7 +65,7 @@ end
 #
 def remove_remote_branch
   Gitlab.delete_branch(GITLAB_DOCS_REPO, docs_branch)
-  puts "Remote branch '#{docs_branch}' deleted"
+  puts "=> Remote branch '#{docs_branch}' deleted"
 end
 
 #
@@ -78,18 +98,22 @@ def trigger_pipeline
   # The review app URL
   app_url = "http://#{docs_branch}.#{ENV["DOCS_REVIEW_APPS_DOMAIN"]}/#{slug}"
 
-  # Create the pipeline
-  puts "=> Triggering a pipeline..."
+  # Create the cross project pipeline using CI_JOB_TOKEN
   pipeline = Gitlab.run_trigger(GITLAB_DOCS_REPO, ENV["CI_JOB_TOKEN"], docs_branch, { param_name => ENV["CI_COMMIT_REF_NAME"] })
 
-  puts "=> Pipeline created:"
+  puts "=> Follow the status of the triggered pipeline:"
   puts ""
   puts "https://gitlab.com/gitlab-com/gitlab-docs/pipelines/#{pipeline.id}"
   puts ""
-  puts "=> Preview your changes live at:"
+  puts "=> In a few minutes, you will be able to preview your changes under the following URL:"
   puts ""
   puts app_url
   puts ""
+  puts "=> For more information, read the documentation"
+  puts "=> https://docs.gitlab.com/ee/development/writing_documentation.html#previewing-the-changes-live"
+  puts ""
+  puts "=> If something doesn't work, drop a line in the #docs chat channel."
+  puts ""
 end
 
 #
diff --git a/scripts/trigger-build-omnibus b/scripts/trigger-build-omnibus
index 85ea4aa74ac437b9ee74160fed52901a3b3966ba..95f35b44f5af319ea7f261db5c0d2776d4977949 100755
--- a/scripts/trigger-build-omnibus
+++ b/scripts/trigger-build-omnibus
@@ -9,6 +9,7 @@ module Omnibus
 
   class Trigger
     TOKEN = ENV['BUILD_TRIGGER_TOKEN']
+    TRIGGERER = ENV['CI_PROJECT_NAME']
 
     def initialize
       @uri = URI("https://gitlab.com/api/v4/projects/#{CGI.escape(Omnibus::PROJECT_PATH)}/trigger/pipeline")
@@ -32,7 +33,7 @@ module Omnibus
     private
 
     def ee?
-      File.exist?('CHANGELOG-EE.md')
+      TRIGGERER == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md')
     end
 
     def env_params
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index cc1b1e5039ec7e9e7e87eda995d08e18e939f36c..b4fc2aa326f96d2472a0cc1ab0e49109b2f1987c 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -72,11 +72,10 @@ describe Admin::ApplicationSettingsController do
       expect(ApplicationSetting.current.restricted_visibility_levels).to eq([10, 20])
     end
 
-    it 'falls back to defaults when settings are omitted' do
-      put :update, application_setting: {}
+    it 'updates the restricted_visibility_levels when empty array is passed' do
+      put :update, application_setting: { restricted_visibility_levels: [] }
 
       expect(response).to redirect_to(admin_application_settings_path)
-      expect(ApplicationSetting.current.default_project_visibility).to eq(Gitlab::VisibilityLevel::PRIVATE)
       expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty
     end
   end
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index d5a3c250f31dc3e488db315f96c7106f48294ba7..cc200b9fed9557fe181db91c16d5bda178d58595 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -31,5 +31,15 @@ describe Admin::ProjectsController do
       expect(response.body).not_to match(pending_delete_project.name)
       expect(response.body).to match(project.name)
     end
+
+    it 'does not have N+1 queries', :use_clean_rails_memory_store_caching, :request_store do
+      get :index
+
+      control_count = ActiveRecord::QueryRecorder.new { get :index }.count
+
+      create(:project)
+
+      expect { get :index }.not_to exceed_query_limit(control_count)
+    end
   end
 end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index b7257fac608f3a3149e8d14eac6361eacbac2d77..fb6d82d7de397cdd52a785ba3637f89c1d9752b5 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -246,7 +246,7 @@ describe AutocompleteController do
           expect(json_response.size).to eq(1)
 
           expect(json_response.first['id']).to eq authorized_project.id
-          expect(json_response.first['name_with_namespace']).to eq authorized_project.name_with_namespace
+          expect(json_response.first['name_with_namespace']).to eq authorized_project.full_name
         end
       end
     end
@@ -267,7 +267,7 @@ describe AutocompleteController do
           expect(json_response.size).to eq(1)
 
           expect(json_response.first['id']).to eq authorized_search_project.id
-          expect(json_response.first['name_with_namespace']).to eq authorized_search_project.name_with_namespace
+          expect(json_response.first['name_with_namespace']).to eq authorized_search_project.full_name
         end
       end
     end
diff --git a/spec/controllers/concerns/checks_collaboration_spec.rb b/spec/controllers/concerns/checks_collaboration_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1bd764290aeeca8fb5aef6986f85469741cb09c6
--- /dev/null
+++ b/spec/controllers/concerns/checks_collaboration_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe ChecksCollaboration do
+  include ProjectForksHelper
+
+  let(:helper) do
+    fake_class = Class.new(ApplicationController) do
+      include ChecksCollaboration
+    end
+
+    fake_class.new
+  end
+
+  describe '#can_collaborate_with_project?' do
+    let(:user) { create(:user) }
+    let(:project) { create(:project, :public) }
+
+    before do
+      allow(helper).to receive(:current_user).and_return(user)
+      allow(helper).to receive(:can?) do |user, ability, subject|
+        Ability.allowed?(user, ability, subject)
+      end
+    end
+
+    it 'is true if the user can push to the project'  do
+      project.add_developer(user)
+
+      expect(helper.can_collaborate_with_project?(project)).to be_truthy
+    end
+
+    it 'is true when the user can push to a branch of the project' do
+      fake_access = double('Gitlab::UserAccess')
+      expect(fake_access).to receive(:can_push_to_branch?).with('a-branch').and_return(true)
+      expect(Gitlab::UserAccess).to receive(:new).with(user, project: project).and_return(fake_access)
+
+      expect(helper.can_collaborate_with_project?(project, ref: 'a-branch')).to be_truthy
+    end
+
+    context 'when the user has forked the project' do
+      before do
+        fork_project(project, user, namespace: user.namespace)
+      end
+
+      it 'is true' do
+        expect(helper.can_collaborate_with_project?(project)).to be_truthy
+      end
+
+      it 'is false when the project is archived' do
+        project.archived = true
+
+        expect(helper.can_collaborate_with_project?(project)).to be_falsy
+      end
+    end
+  end
+end
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f4c99ea4064f33f1866e8e8a6326f45190054bad
--- /dev/null
+++ b/spec/controllers/concerns/send_file_upload_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe SendFileUpload do
+  let(:uploader_class) do
+    Class.new(GitlabUploader) do
+      include ObjectStorage::Concern
+
+      storage_options Gitlab.config.uploads
+
+      private
+
+      # user/:id
+      def dynamic_segment
+        File.join(model.class.to_s.underscore, model.id.to_s)
+      end
+    end
+  end
+
+  let(:controller_class) do
+    Class.new do
+      include SendFileUpload
+    end
+  end
+
+  let(:object) { build_stubbed(:user) }
+  let(:uploader) { uploader_class.new(object, :file) }
+
+  describe '#send_upload' do
+    let(:controller) { controller_class.new }
+    let(:temp_file) { Tempfile.new('test') }
+
+    subject { controller.send_upload(uploader) }
+
+    before do
+      FileUtils.touch(temp_file)
+    end
+
+    after do
+      FileUtils.rm_f(temp_file)
+    end
+
+    context 'when local file is used' do
+      before do
+        uploader.store!(temp_file)
+      end
+
+      it 'sends a file' do
+        expect(controller).to receive(:send_file).with(uploader.path, anything)
+
+        subject
+      end
+    end
+
+    context 'when remote file is used' do
+      before do
+        stub_uploads_object_storage(uploader: uploader_class)
+        uploader.object_store = ObjectStorage::Store::REMOTE
+        uploader.store!(temp_file)
+      end
+
+      context 'and proxying is enabled' do
+        before do
+          allow(Gitlab.config.uploads.object_store).to receive(:proxy_download) { true }
+        end
+
+        it 'sends a file' do
+          headers = double
+          expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/)
+          expect(controller).to receive(:headers) { headers }
+          expect(controller).to receive(:head).with(:ok)
+
+          subject
+        end
+      end
+
+      context 'and proxying is disabled' do
+        before do
+          allow(Gitlab.config.uploads.object_store).to receive(:proxy_download) { false }
+        end
+
+        it 'sends a file' do
+          expect(controller).to receive(:redirect_to).with(/#{uploader.path}/)
+
+          subject
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index 97c2c3fb940d076558a5877fa1e9c21ce51b28fc..3458d679107982882f7ba3dcd95aafe1fc1db171 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -11,9 +11,11 @@ describe DashboardController do
 
   describe 'GET issues' do
     it_behaves_like 'issuables list meta-data', :issue, :issues
+    it_behaves_like 'issuables requiring filter', :issues
   end
 
   describe 'GET merge requests' do
     it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
+    it_behaves_like 'issuables requiring filter', :merge_requests
   end
 end
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0f5bde620069845168ae345a362bf64d71310f44
--- /dev/null
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe Groups::BoardsController do
+  let(:group) { create(:group) }
+  let(:user)    { create(:user) }
+
+  before do
+    group.add_master(user)
+    sign_in(user)
+  end
+
+  describe 'GET index' do
+    it 'creates a new board when group does not have one' do
+      expect { list_boards }.to change(group.boards, :count).by(1)
+    end
+
+    context 'when format is HTML' do
+      it 'renders template' do
+        list_boards
+
+        expect(response).to render_template :index
+        expect(response.content_type).to eq 'text/html'
+      end
+
+      context 'with unauthorized user' do
+        before do
+          allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
+          allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
+        end
+
+        it 'returns a not found 404 response' do
+          list_boards
+
+          expect(response).to have_gitlab_http_status(404)
+          expect(response.content_type).to eq 'text/html'
+        end
+      end
+    end
+
+    context 'when format is JSON' do
+      it 'return an array with one group board' do
+        create(:board, group: group)
+
+        list_boards format: :json
+
+        parsed_response = JSON.parse(response.body)
+
+        expect(response).to match_response_schema('boards')
+        expect(parsed_response.length).to eq 1
+      end
+
+      context 'with unauthorized user' do
+        before do
+          allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
+          allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
+        end
+
+        it 'returns a not found 404 response' do
+          list_boards format: :json
+
+          expect(response).to have_gitlab_http_status(404)
+          expect(response.content_type).to eq 'application/json'
+        end
+      end
+    end
+
+    def list_boards(format: :html)
+      get :index, group_id: group, format: format
+    end
+  end
+
+  describe 'GET show' do
+    let!(:board) { create(:board, group: group) }
+
+    context 'when format is HTML' do
+      it 'renders template' do
+        read_board board: board
+
+        expect(response).to render_template :show
+        expect(response.content_type).to eq 'text/html'
+      end
+
+      context 'with unauthorized user' do
+        before do
+          allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
+          allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
+        end
+
+        it 'returns a not found 404 response' do
+          read_board board: board
+
+          expect(response).to have_gitlab_http_status(404)
+          expect(response.content_type).to eq 'text/html'
+        end
+      end
+    end
+
+    context 'when format is JSON' do
+      it 'returns project board' do
+        read_board board: board, format: :json
+
+        expect(response).to match_response_schema('board')
+      end
+
+      context 'with unauthorized user' do
+        before do
+          allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
+          allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
+        end
+
+        it 'returns a not found 404 response' do
+          read_board board: board, format: :json
+
+          expect(response).to have_gitlab_http_status(404)
+          expect(response.content_type).to eq 'application/json'
+        end
+      end
+    end
+
+    context 'when board does not belong to group' do
+      it 'returns a not found 404 response' do
+        another_board = create(:board)
+
+        read_board board: another_board
+
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+
+    def read_board(board:, format: :html)
+      get :show, group_id: group,
+                 id: board.to_param,
+                 format: format
+    end
+  end
+end
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index c639ad32ec639ea1169271715f932835e9c3f0e5..5f0e8c5eca977c88d166a20fdc0708010a747922 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -3,72 +3,125 @@ require 'spec_helper'
 describe OmniauthCallbacksController do
   include LoginHelpers
 
-  let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: provider) }
-  let(:provider) { :github }
+  let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
 
   before do
-    mock_auth_hash(provider.to_s, 'my-uid', user.email)
+    mock_auth_hash(provider.to_s, extern_uid, user.email)
     stub_omniauth_provider(provider, context: request)
   end
 
-  it 'allows sign in' do
-    post provider
+  context 'when the user is on the last sign in attempt' do
+    let(:extern_uid) { 'my-uid' }
 
-    expect(request.env['warden']).to be_authenticated
-  end
+    before do
+      user.update(failed_attempts: User.maximum_attempts.pred)
+      subject.response = ActionDispatch::Response.new
+    end
 
-  shared_context 'sign_up' do
-    let(:user) { double(email: 'new@example.com') }
+    context 'when using a form based provider' do
+      let(:provider) { :ldap }
 
-    before do
-      stub_omniauth_setting(block_auto_created_users: false)
+      it 'locks the user when sign in fails' do
+        allow(subject).to receive(:params).and_return(ActionController::Parameters.new(username: user.username))
+        request.env['omniauth.error.strategy'] = OmniAuth::Strategies::LDAP.new(nil)
+
+        subject.send(:failure)
+
+        expect(user.reload).to be_access_locked
+      end
     end
-  end
 
-  context 'sign up' do
-    include_context 'sign_up'
+    context 'when using a button based provider' do
+      let(:provider) { :github }
 
-    it 'is allowed' do
-      post provider
+      it 'does not lock the user when sign in fails' do
+        request.env['omniauth.error.strategy'] = OmniAuth::Strategies::GitHub.new(nil)
 
-      expect(request.env['warden']).to be_authenticated
+        subject.send(:failure)
+
+        expect(user.reload).not_to be_access_locked
+      end
     end
   end
 
-  context 'when OAuth is disabled' do
-    before do
-      stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
-      settings = Gitlab::CurrentSettings.current_application_settings
-      settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
-    end
+  context 'strategies' do
+    context 'github' do
+      let(:extern_uid) { 'my-uid' }
+      let(:provider) { :github }
 
-    it 'prevents login via POST' do
-      post provider
+      it 'allows sign in' do
+        post provider
 
-      expect(request.env['warden']).not_to be_authenticated
-    end
+        expect(request.env['warden']).to be_authenticated
+      end
 
-    it 'shows warning when attempting login' do
-      post provider
+      shared_context 'sign_up' do
+        let(:user) { double(email: 'new@example.com') }
 
-      expect(response).to redirect_to new_user_session_path
-      expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
-    end
+        before do
+          stub_omniauth_setting(block_auto_created_users: false)
+        end
+      end
+
+      context 'sign up' do
+        include_context 'sign_up'
+
+        it 'is allowed' do
+          post provider
+
+          expect(request.env['warden']).to be_authenticated
+        end
+      end
+
+      context 'when OAuth is disabled' do
+        before do
+          stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+          settings = Gitlab::CurrentSettings.current_application_settings
+          settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
+        end
+
+        it 'prevents login via POST' do
+          post provider
+
+          expect(request.env['warden']).not_to be_authenticated
+        end
 
-    it 'allows linking the disabled provider' do
-      user.identities.destroy_all
-      sign_in(user)
+        it 'shows warning when attempting login' do
+          post provider
 
-      expect { post provider }.to change { user.reload.identities.count }.by(1)
+          expect(response).to redirect_to new_user_session_path
+          expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
+        end
+
+        it 'allows linking the disabled provider' do
+          user.identities.destroy_all
+          sign_in(user)
+
+          expect { post provider }.to change { user.reload.identities.count }.by(1)
+        end
+
+        context 'sign up' do
+          include_context 'sign_up'
+
+          it 'is prevented' do
+            post provider
+
+            expect(request.env['warden']).not_to be_authenticated
+          end
+        end
+      end
     end
 
-    context 'sign up' do
-      include_context 'sign_up'
+    context 'auth0' do
+      let(:extern_uid) { '' }
+      let(:provider) { :auth0 }
 
-      it 'is prevented' do
-        post provider
+      it 'does not allow sign in without extern_uid' do
+        post 'auth0'
 
         expect(request.env['warden']).not_to be_authenticated
+        expect(response.status).to eq(302)
+        expect(controller).to set_flash[:alert].to('Wrong extern UID provided. Make sure Auth0 is configured correctly.')
       end
     end
   end
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index 03cbbb21e6240731d2050263cfcec0340f8de6cd..de6ef919221870f26bf445688ec4f4a5a829e40d 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -84,6 +84,35 @@ describe ProfilesController, :request_store do
       expect(user.username).to eq(new_username)
     end
 
+    it 'updates a username using JSON request' do
+      sign_in(user)
+
+      put :update_username,
+          user: { username: new_username },
+          format: :json
+
+      expect(response.status).to eq(200)
+      expect(json_response['message']).to eq('Username successfully changed')
+    end
+
+    it 'renders an error message when the username was not updated' do
+      sign_in(user)
+
+      put :update_username,
+          user: { username: 'invalid username.git' },
+          format: :json
+
+      expect(response.status).to eq(422)
+      expect(json_response['message']).to match(/Username change failed/)
+    end
+
+    it 'raises a correct error when the username is missing' do
+      sign_in(user)
+
+      expect { put :update_username, user: { gandalf: 'you shall not pass' } }
+        .to raise_error(ActionController::ParameterMissing)
+    end
+
     context 'with legacy storage' do
       it 'moves dependent projects to new namespace' do
         project = create(:project_empty_repo, :legacy_storage, namespace: namespace)
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index 25a2e13fe1a636cf09d2d6490f48a720f257173d..4ea6f869aa33482f8c672c8cae47f4ccb41d0804 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -145,9 +145,23 @@ describe Projects::ArtifactsController do
       context 'when using local file storage' do
         it_behaves_like 'a valid file' do
           let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+          let(:store) { ObjectStorage::Store::LOCAL }
           let(:archive_path) { JobArtifactUploader.root }
         end
       end
+
+      context 'when using remote file storage' do
+        before do
+          stub_artifacts_object_storage
+        end
+
+        it_behaves_like 'a valid file' do
+          let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
+          let!(:job) { create(:ci_build, :success, pipeline: pipeline) }
+          let(:store) { ObjectStorage::Store::REMOTE }
+          let(:archive_path) { 'https://' }
+        end
+      end
     end
   end
 
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 734396ddf7bcbaf9bb38d81d44d1540f82edd5c3..16fb377b002223a422137d1834882febdd2fd4df 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -398,6 +398,22 @@ describe Projects::BranchesController do
       end
     end
 
+    # We need :request_store because Gitaly only counts the queries whenever
+    # `RequestStore.active?` in GitalyClient.enforce_gitaly_request_limits
+    # And the main goal of this test is making sure TooManyInvocationsError
+    # was not raised whenever the cache is enabled yet cold.
+    context 'when cache is enabled yet cold', :request_store do
+      it 'return with a status 200' do
+        get :index,
+            namespace_id: project.namespace,
+            project_id: project,
+            state: 'all',
+            format: :html
+
+        expect(response).to have_gitlab_http_status(200)
+      end
+    end
+
     context 'when branch contains an invalid UTF-8 sequence' do
       before do
         project.repository.create_branch("wrong-\xE5-utf8-sequence")
@@ -407,10 +423,43 @@ describe Projects::BranchesController do
         get :index,
             namespace_id: project.namespace,
             project_id: project,
+            state: 'all',
             format: :html
 
         expect(response).to have_gitlab_http_status(200)
       end
     end
+
+    context 'when deprecated sort/search/page parameters are specified' do
+      it 'returns with a status 301 when sort specified' do
+        get :index,
+            namespace_id: project.namespace,
+            project_id: project,
+            sort: 'updated_asc',
+            format: :html
+
+        expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
+      end
+
+      it 'returns with a status 301 when search specified' do
+        get :index,
+            namespace_id: project.namespace,
+            project_id: project,
+            search: 'feature',
+            format: :html
+
+        expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
+      end
+
+      it 'returns with a status 301 when page specified' do
+        get :index,
+            namespace_id: project.namespace,
+            project_id: project,
+            page: 2,
+            format: :html
+
+        expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
+      end
+    end
   end
 end
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1249a5528a9b7833c3052b5cb824f831f54bc02d
--- /dev/null
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+
+describe Projects::Ci::LintsController do
+  let(:project) { create(:project, :repository) }
+  let(:user) { create(:user) }
+
+  before do
+    sign_in(user)
+  end
+
+  describe 'GET #show' do
+    context 'with enough privileges' do
+      before do
+        project.add_developer(user)
+
+        get :show, namespace_id: project.namespace, project_id: project
+      end
+
+      it 'should be success' do
+        expect(response).to be_success
+      end
+
+      it 'should render show page' do
+        expect(response).to render_template :show
+      end
+
+      it 'should retrieve project' do
+        expect(assigns(:project)).to eq(project)
+      end
+    end
+
+    context 'without enough privileges' do
+      before do
+        project.add_guest(user)
+
+        get :show, namespace_id: project.namespace, project_id: project
+      end
+
+      it 'should respond with 404' do
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+  end
+
+  describe 'POST #create' do
+    let(:remote_file_path) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+
+    let(:remote_file_content) do
+      <<~HEREDOC
+      before_script:
+        - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+        - ruby -v
+        - which ruby
+        - gem install bundler --no-ri --no-rdoc
+        - bundle install --jobs $(nproc)  "${FLAGS[@]}"
+      HEREDOC
+    end
+
+    let(:content) do
+      <<~HEREDOC
+      include:
+        - #{remote_file_path}
+
+      rubocop:
+        script:
+          - bundle exec rubocop
+      HEREDOC
+    end
+
+    context 'with a valid gitlab-ci.yml' do
+      before do
+        WebMock.stub_request(:get, remote_file_path).to_return(body: remote_file_content)
+        project.add_developer(user)
+
+        post :create, namespace_id: project.namespace, project_id: project, content: content
+      end
+
+      it 'should be success' do
+        expect(response).to be_success
+      end
+
+      it 'render show page' do
+        expect(response).to render_template :show
+      end
+
+      it 'should retrieve project' do
+        expect(assigns(:project)).to eq(project)
+      end
+    end
+
+    context 'with an invalid gitlab-ci.yml' do
+      let(:content) do
+        <<~HEREDOC
+        rubocop:
+          scriptt:
+            - bundle exec rubocop
+        HEREDOC
+      end
+
+      before do
+        project.add_developer(user)
+
+        post :create, namespace_id: project.namespace, project_id: project, content: content
+      end
+
+      it 'should assign errors' do
+        expect(assigns[:error]).to eq('jobs:rubocop config contains unknown keys: scriptt')
+      end
+    end
+
+    context 'without enough privileges' do
+      before do
+        project.add_guest(user)
+
+        post :create, namespace_id: project.namespace, project_id: project, content: content
+      end
+
+      it 'should respond with 404' do
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 15ce418d0d6e4a93c5abfd173af407adbf342cad..82b20e1285093c591c8ac190fd9af63ef4e40527 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::ClustersController do
       context 'when project has one or more clusters' do
         let(:project) { create(:project) }
         let!(:enabled_cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
-        let!(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, projects: [project]) }
+        let!(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, :production_environment, projects: [project]) }
         it 'lists available clusters' do
           go
 
@@ -32,7 +32,7 @@ describe Projects::ClustersController do
 
           before do
             allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
-            create_list(:cluster, 2, :provided_by_gcp, projects: [project])
+            create_list(:cluster, 2, :provided_by_gcp, :production_environment, projects: [project])
             get :index, namespace_id: project.namespace, project_id: project, page: last_page
           end
 
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 73e7921fab78c54d043c8448a317ae862cdee16c..6c67dfde63ac50544e0ef3d8b606bb8d48e152bb 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -129,10 +129,10 @@ describe Projects::DeploymentsController do
     end
 
     context 'when metrics are enabled' do
-      let(:prometheus_service) { double('prometheus_service') }
+      let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
 
       before do
-        allow(deployment.project).to receive(:prometheus_service).and_return(prometheus_service)
+        allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
       end
 
       context 'when environment has no metrics' do
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index fcb0c2f28c8d823ab8b3164416c91bdb6f0b0aa4..53647749a6096ef072fe326b5305dd5c9113e0c9 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -16,6 +16,53 @@ describe Projects::DiscussionsController do
     }
   end
 
+  describe 'GET show' do
+    before do
+      sign_in user
+    end
+
+    context 'when user is not authorized to read the MR' do
+      it 'returns 404' do
+        get :show, request_params, format: :json
+
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+
+    context 'when user is authorized to read the MR' do
+      before do
+        project.add_reporter(user)
+      end
+
+      it 'returns status 200' do
+        get :show, request_params, format: :json
+
+        expect(response).to have_gitlab_http_status(200)
+      end
+
+      it 'returns status 404 if MR does not exists' do
+        merge_request.destroy!
+
+        get :show, request_params, format: :json
+
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+
+    context 'when user is authorized but note is LegacyDiffNote' do
+      before do
+        project.add_developer(user)
+        note.update!(type: 'LegacyDiffNote')
+      end
+
+      it 'returns status 200' do
+        get :show, request_params, format: :json
+
+        expect(response).to have_gitlab_http_status(200)
+      end
+    end
+  end
+
   describe 'POST resolve' do
     before do
       sign_in user
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 9918d52e4028a036eef3ee6343e5c41d396e75eb..ca86b0bc7373886e72cb853951fabc76a71a8743 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -938,7 +938,7 @@ describe Projects::IssuesController do
   end
 
   describe 'POST create_merge_request' do
-    let(:project) { create(:project, :repository) }
+    let(:project) { create(:project, :repository, :public) }
 
     before do
       project.add_developer(user)
@@ -955,6 +955,22 @@ describe Projects::IssuesController do
       expect(response).to match_response_schema('merge_request')
     end
 
+    it 'is not available when the project is archived' do
+      project.update!(archived: true)
+
+      create_merge_request
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it 'is not available for users who cannot create merge requests' do
+      sign_in(create(:user))
+
+      create_merge_request
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
     def create_merge_request
       post :create_merge_request, namespace_id: project.namespace.to_param,
                                   project_id: project.to_param,
@@ -974,7 +990,7 @@ describe Projects::IssuesController do
       it 'returns discussion json' do
         get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
 
-        expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes diff_discussion individual_note resolvable resolve_with_issue_path resolved])
+        expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes diff_discussion individual_note resolvable resolved])
       end
 
       context 'with cross-reference system note', :request_store do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index f3e303bb0fe32b8c775f0255aef63b22bcce94b1..f677cec3408a50706173539bff01b864d6f6567a 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -1,7 +1,9 @@
+# coding: utf-8
 require 'spec_helper'
 
 describe Projects::JobsController do
   include ApiHelpers
+  include HttpIOHelpers
 
   let(:project) { create(:project, :public) }
   let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -188,7 +190,10 @@ describe Projects::JobsController do
         expect(response).to have_gitlab_http_status(:ok)
         expect(json_response['id']).to eq job.id
         expect(json_response['status']).to eq job.status
-        expect(json_response['html']).to be_nil
+      end
+
+      it 'returns no job log message' do
+        expect(json_response['html']).to eq('No job log')
       end
     end
 
@@ -203,6 +208,41 @@ describe Projects::JobsController do
       end
     end
 
+    context 'when trace artifact is in ObjectStorage' do
+      let!(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
+
+      before do
+        allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
+        allow_any_instance_of(JobArtifactUploader).to receive(:url) { remote_trace_url }
+        allow_any_instance_of(JobArtifactUploader).to receive(:size) { remote_trace_size }
+      end
+
+      context 'when there are no network issues' do
+        before do
+          stub_remote_trace_206
+
+          get_trace
+        end
+
+        it 'returns a trace' do
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['id']).to eq job.id
+          expect(json_response['status']).to eq job.status
+          expect(json_response['html']).to eq(job.trace.html)
+        end
+      end
+
+      context 'when there is a network issue' do
+        before do
+          stub_remote_trace_500
+        end
+
+        it 'returns a trace' do
+          expect { get_trace }.to raise_error(Gitlab::Ci::Trace::HttpIO::FailedToGetChunkError)
+        end
+      end
+    end
+
     def get_trace
       get :trace, namespace_id: project.namespace,
                   project_id: project,
@@ -446,14 +486,18 @@ describe Projects::JobsController do
   end
 
   describe 'GET raw' do
-    before do
-      get_raw
+    subject do
+      post :raw, namespace_id: project.namespace,
+                 project_id: project,
+                 id: job.id
     end
 
     context 'when job has a trace artifact' do
       let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
 
       it 'returns a trace' do
+        response = subject
+
         expect(response).to have_gitlab_http_status(:ok)
         expect(response.content_type).to eq 'text/plain; charset=utf-8'
         expect(response.body).to eq job.job_artifacts_trace.open.read
@@ -464,24 +508,51 @@ describe Projects::JobsController do
       let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
 
       it 'send a trace file' do
+        response = subject
+
         expect(response).to have_gitlab_http_status(:ok)
         expect(response.content_type).to eq 'text/plain; charset=utf-8'
         expect(response.body).to eq 'BUILD TRACE'
       end
     end
 
+    context 'when job has a trace in database' do
+      let(:job) { create(:ci_build, pipeline: pipeline) }
+
+      before do
+        job.update_column(:trace, 'Sample trace')
+      end
+
+      it 'send a trace file' do
+        response = subject
+
+        expect(response).to have_gitlab_http_status(:ok)
+        expect(response.content_type).to eq 'text/plain; charset=utf-8'
+        expect(response.body).to eq 'Sample trace'
+      end
+    end
+
     context 'when job does not have a trace file' do
       let(:job) { create(:ci_build, pipeline: pipeline) }
 
       it 'returns not_found' do
-        expect(response).to have_gitlab_http_status(:not_found)
+        response = subject
+
+        expect(response).to have_gitlab_http_status(:ok)
+        expect(response.body).to eq ''
       end
     end
 
-    def get_raw
-      post :raw, namespace_id: project.namespace,
-                 project_id: project,
-                 id: job.id
+    context 'when the trace artifact is in ObjectStorage' do
+      let!(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+      before do
+        allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
+      end
+
+      it 'redirect to the trace file url' do
+        expect(subject).to redirect_to(job.job_artifacts_trace.file.url)
+      end
     end
   end
 end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 00cf464ec5bcf25890efe0036e74bd375e93921a..548c5ef36e7977f1d3663ea87606d304c8d0d696 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -20,14 +20,23 @@ describe Projects::MilestonesController do
   describe "#show" do
     render_views
 
-    def view_milestone
-      get :show, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
+    def view_milestone(options = {})
+      params = { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
+      get :show, params.merge(options)
     end
 
     it 'shows milestone page' do
       view_milestone
 
       expect(response).to have_gitlab_http_status(200)
+      expect(response.content_type).to eq 'text/html'
+    end
+
+    it 'returns milestone json' do
+      view_milestone format: :json
+
+      expect(response).to have_http_status(404)
+      expect(response.content_type).to eq 'application/json'
     end
   end
 
@@ -98,10 +107,8 @@ describe Projects::MilestonesController do
       it 'shows group milestone' do
         post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
 
-        group_milestone = assigns(:milestone)
-
-        expect(response).to redirect_to(group_milestone_path(project.group, group_milestone.iid))
-        expect(flash[:notice]).to eq('Milestone has been promoted to group milestone.')
+        expect(flash[:notice]).to eq("#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\">group milestone</a>.")
+        expect(response).to redirect_to(project_milestones_path(project))
       end
     end
 
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index 4705c50de7e74a99841273cbfdf2c962a4e774cc..11f54eef5318861d8409228235a06dbe0df3eba3 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -65,4 +65,41 @@ describe Projects::PagesController do
       end
     end
   end
+
+  describe 'PATCH update' do
+    let(:request_params) do
+      {
+        namespace_id: project.namespace,
+        project_id: project,
+        project: { pages_https_only: false }
+      }
+    end
+
+    let(:update_service) { double(execute: { status: :success }) }
+
+    before do
+      allow(Projects::UpdateService).to receive(:new) { update_service }
+    end
+
+    it 'returns 302 status' do
+      patch :update, request_params
+
+      expect(response).to have_gitlab_http_status(:found)
+    end
+
+    it 'redirects back to the pages settings' do
+      patch :update, request_params
+
+      expect(response).to redirect_to(project_pages_path(project))
+    end
+
+    it 'calls the update service' do
+      expect(Projects::UpdateService)
+        .to receive(:new)
+        .with(project, user, request_params[:project])
+        .and_return(update_service)
+
+      patch :update, request_params
+    end
+  end
 end
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index 83a3799e88351b544211fb354ec9374805193738..d4058a5c515a85b0846175d2c70593ee887f1f5d 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::PagesDomainsController do
   end
 
   let(:pages_domain_params) do
-    build(:pages_domain, :with_certificate, :with_key, domain: 'my.otherdomain.com').slice(:key, :certificate, :domain)
+    build(:pages_domain, domain: 'my.otherdomain.com').slice(:key, :certificate, :domain)
   end
 
   before do
@@ -68,7 +68,7 @@ describe Projects::PagesDomainsController do
     end
 
     let(:pages_domain_params) do
-      attributes_for(:pages_domain, :with_certificate, :with_key).slice(:key, :certificate)
+      attributes_for(:pages_domain).slice(:key, :certificate)
     end
 
     let(:params) do
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index 966ffdf6996c47c584b8458f198fbc18eea4dc17..3506305f7553fe1b7b95f88c360a4f3a4b796696 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -80,7 +80,7 @@ describe Projects::PipelineSchedulesController do
       context 'when variables_attributes has one variable' do
         let(:schedule) do
           basic_param.merge({
-            variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+            variables_attributes: [{ key: 'AAA', secret_value: 'AAA123' }]
           })
         end
 
@@ -101,7 +101,8 @@ describe Projects::PipelineSchedulesController do
       context 'when variables_attributes has two variables and duplicated' do
         let(:schedule) do
           basic_param.merge({
-            variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }]
+            variables_attributes: [{ key: 'AAA', secret_value: 'AAA123' },
+                                   { key: 'AAA', secret_value: 'BBB123' }]
           })
         end
 
@@ -152,7 +153,7 @@ describe Projects::PipelineSchedulesController do
         context 'when params include one variable' do
           let(:schedule) do
             basic_param.merge({
-              variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+              variables_attributes: [{ key: 'AAA', secret_value: 'AAA123' }]
             })
           end
 
@@ -169,7 +170,8 @@ describe Projects::PipelineSchedulesController do
         context 'when params include two duplicated variables' do
           let(:schedule) do
             basic_param.merge({
-              variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }]
+              variables_attributes: [{ key: 'AAA', secret_value: 'AAA123' },
+                                     { key: 'AAA', secret_value: 'BBB123' }]
             })
           end
 
@@ -194,7 +196,7 @@ describe Projects::PipelineSchedulesController do
         context 'when adds a new variable' do
           let(:schedule) do
             basic_param.merge({
-              variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+              variables_attributes: [{ key: 'AAA', secret_value: 'AAA123' }]
             })
           end
 
@@ -209,7 +211,7 @@ describe Projects::PipelineSchedulesController do
         context 'when adds a new duplicated variable' do
           let(:schedule) do
             basic_param.merge({
-              variables_attributes: [{ key: 'CCC', value: 'AAA123' }]
+              variables_attributes: [{ key: 'CCC', secret_value: 'AAA123' }]
             })
           end
 
@@ -224,7 +226,7 @@ describe Projects::PipelineSchedulesController do
         context 'when updates a variable' do
           let(:schedule) do
             basic_param.merge({
-              variables_attributes: [{ id: pipeline_schedule_variable.id, value: 'new_value' }]
+              variables_attributes: [{ id: pipeline_schedule_variable.id, secret_value: 'new_value' }]
             })
           end
 
@@ -252,7 +254,7 @@ describe Projects::PipelineSchedulesController do
           let(:schedule) do
             basic_param.merge({
               variables_attributes: [{ id: pipeline_schedule_variable.id, _destroy: true },
-                                     { key: 'CCC', value: 'CCC123' }]
+                                     { key: 'CCC', secret_value: 'CCC123' }]
             })
           end
 
diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb
index 1cc488bef324d9c0c3586f0b4023eadc51da10df..694896b6bcf684e83a70bcb3b3ade82117d84733 100644
--- a/spec/controllers/projects/pipelines_settings_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb
@@ -11,60 +11,11 @@ describe Projects::PipelinesSettingsController do
     sign_in(user)
   end
 
-  describe 'PATCH update' do
-    subject do
-      patch :update,
-        namespace_id: project.namespace.to_param,
-        project_id: project,
-        project: {
-          auto_devops_attributes: params
-        }
-    end
-
-    context 'when updating the auto_devops settings' do
-      let(:params) { { enabled: '', domain: 'mepmep.md' } }
-
-      it 'redirects to the settings page' do
-        subject
-
-        expect(response).to have_gitlab_http_status(302)
-        expect(flash[:notice]).to eq("Pipelines settings for '#{project.name}' were successfully updated.")
-      end
-
-      context 'following the instance default' do
-        let(:params) { { enabled: '' } }
-
-        it 'allows enabled to be set to nil' do
-          subject
-          project_auto_devops.reload
-
-          expect(project_auto_devops.enabled).to be_nil
-        end
-      end
-
-      context 'when run_auto_devops_pipeline is true' do
-        before do
-          expect_any_instance_of(Projects::UpdateService).to receive(:run_auto_devops_pipeline?).and_return(true)
-        end
-
-        it 'queues a CreatePipelineWorker' do
-          expect(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args)
-
-          subject
-        end
-      end
-
-      context 'when run_auto_devops_pipeline is not true' do
-        before do
-          expect_any_instance_of(Projects::UpdateService).to receive(:run_auto_devops_pipeline?).and_return(false)
-        end
-
-        it 'does not queue a CreatePipelineWorker' do
-          expect(CreatePipelineWorker).not_to receive(:perform_async).with(project.id, user.id, :web, any_args)
+  describe 'GET show' do
+    it 'redirects with 302 status code' do
+      get :show, namespace_id: project.namespace, project_id: project
 
-          subject
-        end
-      end
+      expect(response).to have_gitlab_http_status(302)
     end
   end
 end
diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
index f17f819feeef029da98324861b2811d20b19f7dc..b2b245dba90eb80a3abb7afd7b9ea8611e858afb 100644
--- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb
+++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
@@ -4,21 +4,22 @@ describe Projects::Prometheus::MetricsController do
   let(:user) { create(:user) }
   let(:project) { create(:project) }
 
-  let(:prometheus_service) { double('prometheus_service') }
+  let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
 
   before do
-    allow(controller).to receive(:project).and_return(project)
-    allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return(prometheus_service)
-
     project.add_master(user)
     sign_in(user)
   end
 
   describe 'GET #active_common' do
+    before do
+      allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+    end
+
     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({})
+          allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({})
         end
 
         it 'returns no content response' do
@@ -32,7 +33,7 @@ describe Projects::Prometheus::MetricsController do
         let(:sample_response) { { some_data: 1 } }
 
         before do
-          allow(prometheus_service).to receive(:matched_metrics).and_return(sample_response)
+          allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response)
         end
 
         it 'returns no content response' do
@@ -53,6 +54,18 @@ describe Projects::Prometheus::MetricsController do
     end
   end
 
+  describe '#prometheus_adapter' do
+    before do
+      allow(controller).to receive(:project).and_return(project)
+    end
+
+    it 'calls prometheus adapter service' do
+      expect_any_instance_of(::Prometheus::AdapterService).to receive(:prometheus_adapter)
+
+      subject.__send__(:prometheus_adapter)
+    end
+  end
+
   def project_params(opts = {})
     opts.reverse_merge(namespace_id: project.namespace, project_id: project)
   end
diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb
index 80be135b5d8b930b52252933d6ad704cb7529929..096e29bc39f8187efebb0502fc8328814ba3e544 100644
--- a/spec/controllers/projects/protected_branches_controller_spec.rb
+++ b/spec/controllers/projects/protected_branches_controller_spec.rb
@@ -1,6 +1,16 @@
 require('spec_helper')
 
 describe Projects::ProtectedBranchesController do
+  let(:project) { create(:project, :repository) }
+  let(:protected_branch) { create(:protected_branch, project: project) }
+  let(:project_params) { { namespace_id: project.namespace.to_param, project_id: project } }
+  let(:base_params) { project_params.merge(id: protected_branch.id) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_master(user)
+  end
+
   describe "GET #index" do
     let(:project) { create(:project_empty_repo, :public) }
 
@@ -8,4 +18,91 @@ describe Projects::ProtectedBranchesController do
       get(:index, namespace_id: project.namespace.to_param, project_id: project)
     end
   end
+
+  describe "POST #create" do
+    let(:master_access_level) { [{ access_level: Gitlab::Access::MASTER }] }
+    let(:access_level_params) do
+      { merge_access_levels_attributes: master_access_level,
+        push_access_levels_attributes: master_access_level }
+    end
+    let(:create_params) { attributes_for(:protected_branch).merge(access_level_params) }
+
+    before do
+      sign_in(user)
+    end
+
+    it 'creates the protected branch rule' do
+      expect do
+        post(:create, project_params.merge(protected_branch: create_params))
+      end.to change(ProtectedBranch, :count).by(1)
+    end
+
+    context 'when a policy restricts rule deletion' do
+      before do
+        policy = instance_double(ProtectedBranchPolicy, can?: false)
+        allow(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+      end
+
+      it "prevents creation of the protected branch rule" do
+        post(:create, project_params.merge(protected_branch: create_params))
+
+        expect(ProtectedBranch.count).to eq 0
+      end
+    end
+  end
+
+  describe "PUT #update" do
+    let(:update_params) { { name: 'new_name' } }
+
+    before do
+      sign_in(user)
+    end
+
+    it 'updates the protected branch rule' do
+      put(:update, base_params.merge(protected_branch: update_params))
+
+      expect(protected_branch.reload.name).to eq('new_name')
+      expect(json_response["name"]).to eq('new_name')
+    end
+
+    context 'when a policy restricts rule deletion' do
+      before do
+        policy = instance_double(ProtectedBranchPolicy, can?: false)
+        allow(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+      end
+
+      it "prevents update of the protected branch rule" do
+        old_name = protected_branch.name
+
+        put(:update, base_params.merge(protected_branch: update_params))
+
+        expect(protected_branch.reload.name).to eq(old_name)
+      end
+    end
+  end
+
+  describe "DELETE #destroy" do
+    before do
+      sign_in(user)
+    end
+
+    it "deletes the protected branch rule" do
+      delete(:destroy, base_params)
+
+      expect { ProtectedBranch.find(protected_branch.id) }.to raise_error(ActiveRecord::RecordNotFound)
+    end
+
+    context 'when a policy restricts rule deletion' do
+      before do
+        policy = instance_double(ProtectedBranchPolicy, can?: false)
+        allow(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+      end
+
+      it "prevents deletion of the protected branch rule" do
+        delete(:destroy, base_params)
+
+        expect(response.status).to eq(403)
+      end
+    end
+  end
 end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index b7df42168e00ece9aae98b1c29102eeda5a698f9..08e2ccf893a432821e1f570751f81c2ab7a6e7f1 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -8,10 +8,7 @@ describe Projects::RawController do
       let(:id) { 'master/README.md' }
 
       it 'delivers ASCII file' do
-        get(:show,
-            namespace_id: public_project.namespace.to_param,
-            project_id: public_project,
-            id: id)
+        get_show(public_project, id)
 
         expect(response).to have_gitlab_http_status(200)
         expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
@@ -25,10 +22,7 @@ describe Projects::RawController do
       let(:id) { 'master/files/images/6049019_460s.jpg' }
 
       it 'sets image content type header' do
-        get(:show,
-            namespace_id: public_project.namespace.to_param,
-            project_id: public_project,
-            id: id)
+        get_show(public_project, id)
 
         expect(response).to have_gitlab_http_status(200)
         expect(response.header['Content-Type']).to eq('image/jpeg')
@@ -54,21 +48,40 @@ describe Projects::RawController do
 
           it 'serves the file' do
             expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: 'lfs_object.iso', disposition: 'attachment')
-            get(:show,
-                namespace_id: public_project.namespace.to_param,
-                project_id: public_project,
-                id: id)
+            get_show(public_project, id)
 
             expect(response).to have_gitlab_http_status(200)
           end
+
+          context 'and lfs uses object storage' do
+            before do
+              lfs_object.file = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png")
+              lfs_object.save!
+              stub_lfs_object_storage
+              lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+            end
+
+            it 'responds with redirect to file' do
+              get_show(public_project, id)
+
+              expect(response).to have_gitlab_http_status(302)
+              expect(response.location).to include(lfs_object.reload.file.path)
+            end
+
+            it 'sets content disposition' do
+              get_show(public_project, id)
+
+              file_uri = URI.parse(response.location)
+              params = CGI.parse(file_uri.query)
+
+              expect(params["response-content-disposition"].first).to eq 'attachment;filename="lfs_object.iso"'
+            end
+          end
         end
 
         context 'when project does not have access' do
           it 'does not serve the file' do
-            get(:show,
-                namespace_id: public_project.namespace.to_param,
-                project_id: public_project,
-                id: id)
+            get_show(public_project, id)
 
             expect(response).to have_gitlab_http_status(404)
           end
@@ -81,10 +94,7 @@ describe Projects::RawController do
         end
 
         it 'delivers ASCII file' do
-          get(:show,
-              namespace_id: public_project.namespace.to_param,
-              project_id: public_project,
-              id: id)
+          get_show(public_project, id)
 
           expect(response).to have_gitlab_http_status(200)
           expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
@@ -95,4 +105,10 @@ describe Projects::RawController do
       end
     end
   end
+
+  def get_show(project, id)
+    get(:show, namespace_id: project.namespace.to_param,
+               project_id: project,
+               id: id)
+  end
 end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 04d16e9891380c18e1e5f484a540f64b9458e0af..a102a3a3c8c61cb35d1d016c53b304888b0a0b86 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::RepositoriesController do
   describe "GET archive" do
     context 'as a guest' do
       it 'responds with redirect in correct format' do
-        get :archive, namespace_id: project.namespace, project_id: project, format: "zip", ref: 'master'
+        get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
 
         expect(response.header["Content-Type"]).to start_with('text/html')
         expect(response).to be_redirect
@@ -22,18 +22,55 @@ describe Projects::RepositoriesController do
       end
 
       it "uses Gitlab::Workhorse" do
-        get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
+        get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
 
         expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
       end
 
+      it 'responds with redirect to the short name archive if fully qualified' do
+        get :archive, namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master", format: "zip"
+
+        expect(assigns(:ref)).to eq("master")
+        expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
+      end
+
+      it 'handles legacy queries with no ref' do
+        get :archive, namespace_id: project.namespace, project_id: project, format: "zip"
+
+        expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
+      end
+
+      it 'handles legacy queries with the ref specified as ref in params' do
+        get :archive, namespace_id: project.namespace, project_id: project, ref: 'feature', format: 'zip'
+
+        expect(response).to have_gitlab_http_status(200)
+        expect(assigns(:ref)).to eq('feature')
+        expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
+      end
+
+      it 'handles legacy queries with the ref specified as id in params' do
+        get :archive, namespace_id: project.namespace, project_id: project, id: 'feature', format: 'zip'
+
+        expect(response).to have_gitlab_http_status(200)
+        expect(assigns(:ref)).to eq('feature')
+        expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
+      end
+
+      it 'prioritizes the id param over the ref param when both are specified' do
+        get :archive, namespace_id: project.namespace, project_id: project, id: 'feature', ref: 'feature_conflict', format: 'zip'
+
+        expect(response).to have_gitlab_http_status(200)
+        expect(assigns(:ref)).to eq('feature')
+        expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
+      end
+
       context "when the service raises an error" do
         before do
           allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
         end
 
         it "renders Not Found" do
-          get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
+          get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
 
           expect(response).to have_gitlab_http_status(404)
         end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 847ac6f2be055d996ddefaf52d8776b25eda55a9..e4dc61b3a680b776ffeafa85096230efe8610b1a 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -23,6 +23,18 @@ describe Projects::ServicesController do
       end
     end
 
+    context 'when validations fail' do
+      let(:service_params) { { active: 'true', token: '' } }
+
+      it 'returns error messages in JSON response' do
+        put :test, namespace_id: project.namespace, project_id: project, id: :hipchat, service: service_params
+
+        expect(json_response['message']).to eq "Validations failed."
+        expect(json_response['service_response']).to eq "Token can't be blank"
+        expect(response).to have_gitlab_http_status(200)
+      end
+    end
+
     context 'success' do
       context 'with empty project' do
         let(:project) { create(:project) }
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 0202149f3359e2640badbd61a0a111acb3d21b10..7dae9b85d78435f566fb6d525850fb750ef290b0 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -1,8 +1,9 @@
 require('spec_helper')
 
 describe Projects::Settings::CiCdController do
-  let(:project) { create(:project, :public, :access_requestable) }
-  let(:user) { create(:user) }
+  set(:user) { create(:user) }
+  set(:project_auto_devops) { create(:project_auto_devops) }
+  let(:project) { project_auto_devops.project }
 
   before do
     project.add_master(user)
@@ -27,7 +28,7 @@ describe Projects::Settings::CiCdController do
       allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true)
     end
 
-    subject { post :reset_cache, namespace_id: project.namespace, project_id: project }
+    subject { post :reset_cache, namespace_id: project.namespace, project_id: project, format: :json }
 
     it 'calls reset project cache service' do
       expect(ResetProjectCacheService).to receive_message_chain(:new, :execute)
@@ -35,19 +36,11 @@ describe Projects::Settings::CiCdController do
       subject
     end
 
-    it 'redirects to project pipelines path' do
-      subject
-
-      expect(response).to have_gitlab_http_status(:redirect)
-      expect(response).to redirect_to(project_pipelines_path(project))
-    end
-
     context 'when service returns successfully' do
-      it 'sets the flash notice variable' do
+      it 'returns a success header' do
         subject
 
-        expect(controller).to set_flash[:notice]
-        expect(controller).not_to set_flash[:error]
+        expect(response).to have_gitlab_http_status(:ok)
       end
     end
 
@@ -56,11 +49,113 @@ describe Projects::Settings::CiCdController do
         allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false)
       end
 
-      it 'sets the flash error variable' do
+      it 'returns an error header' do
         subject
 
-        expect(controller).not_to set_flash[:notice]
-        expect(controller).to set_flash[:error]
+        expect(response).to have_gitlab_http_status(:bad_request)
+      end
+    end
+  end
+
+  describe 'PATCH update' do
+    let(:params) { { ci_config_path: '' } }
+
+    subject do
+      patch :update,
+            namespace_id: project.namespace.to_param,
+            project_id: project,
+            project: params
+    end
+
+    it 'redirects to the settings page' do
+      subject
+
+      expect(response).to have_gitlab_http_status(302)
+      expect(flash[:notice]).to eq("Pipelines settings for '#{project.name}' were successfully updated.")
+    end
+
+    context 'when updating the auto_devops settings' do
+      let(:params) { { auto_devops_attributes: { enabled: '', domain: 'mepmep.md' } } }
+
+      context 'following the instance default' do
+        let(:params) { { auto_devops_attributes: { enabled: '' } } }
+
+        it 'allows enabled to be set to nil' do
+          subject
+          project_auto_devops.reload
+
+          expect(project_auto_devops.enabled).to be_nil
+        end
+      end
+
+      context 'when run_auto_devops_pipeline is true' do
+        before do
+          expect_any_instance_of(Projects::UpdateService).to receive(:run_auto_devops_pipeline?).and_return(true)
+        end
+
+        context 'when the project repository is empty' do
+          it 'sets a warning flash' do
+            expect(subject).to set_flash[:warning]
+          end
+
+          it 'does not queue a CreatePipelineWorker' do
+            expect(CreatePipelineWorker).not_to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args)
+
+            subject
+          end
+        end
+
+        context 'when the project repository is not empty' do
+          let(:project) { create(:project, :repository) }
+
+          it 'sets a success flash' do
+            allow(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args)
+
+            expect(subject).to set_flash[:success]
+          end
+
+          it 'queues a CreatePipelineWorker' do
+            expect(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args)
+
+            subject
+          end
+        end
+      end
+
+      context 'when run_auto_devops_pipeline is not true' do
+        before do
+          expect_any_instance_of(Projects::UpdateService).to receive(:run_auto_devops_pipeline?).and_return(false)
+        end
+
+        it 'does not queue a CreatePipelineWorker' do
+          expect(CreatePipelineWorker).not_to receive(:perform_async).with(project.id, user.id, :web, any_args)
+
+          subject
+        end
+      end
+    end
+
+    context 'when updating general settings' do
+      context 'when build_timeout_human_readable is not specified' do
+        let(:params) { { build_timeout_human_readable: '' } }
+
+        it 'set default timeout' do
+          subject
+
+          project.reload
+          expect(project.build_timeout).to eq(3600)
+        end
+      end
+
+      context 'when build_timeout_human_readable is specified' do
+        let(:params) { { build_timeout_human_readable: '1h 30m' } }
+
+        it 'set specified timeout' do
+          subject
+
+          project.reload
+          expect(project.build_timeout).to eq(5400)
+        end
       end
     end
   end
diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
index b32eb39b1fb278efe1df8f5280e9e540efec57c3..7688538a468ff0fd6a6c07cbd46ff68b7829b3e1 100644
--- a/spec/controllers/root_controller_spec.rb
+++ b/spec/controllers/root_controller_spec.rb
@@ -90,6 +90,30 @@ describe RootController do
         end
       end
 
+      context 'who has customized their dashboard setting for assigned issues' do
+        before do
+          user.dashboard = 'issues'
+        end
+
+        it 'redirects to their assigned issues' do
+          get :index
+
+          expect(response).to redirect_to issues_dashboard_path(assignee_id: user.id)
+        end
+      end
+
+      context 'who has customized their dashboard setting for assigned merge requests' do
+        before do
+          user.dashboard = 'merge_requests'
+        end
+
+        it 'redirects to their assigned merge requests' do
+          get :index
+
+          expect(response).to redirect_to merge_requests_dashboard_path(assignee_id: user.id)
+        end
+      end
+
       context 'who uses the default dashboard setting' do
         it 'renders the default dashboard' do
           get :index
diff --git a/spec/db/production/settings_spec.rb b/spec/db/production/settings_spec.rb
index 79e673308548448d2802dd4fd96e548bdb7f451a..c8d016070f53c5c72ed71e7f02e920fcd0184e90 100644
--- a/spec/db/production/settings_spec.rb
+++ b/spec/db/production/settings_spec.rb
@@ -2,10 +2,15 @@ require 'spec_helper'
 require 'rainbow/ext/string'
 
 describe 'seed production settings' do
-  include StubENV
   let(:settings_file) { Rails.root.join('db/fixtures/production/010_settings.rb') }
   let(:settings) { Gitlab::CurrentSettings.current_application_settings }
 
+  before do
+    # It's important to set this variable so that we don't save a memoized
+    # (supposed to be) in-memory record in `Gitlab::CurrentSettings.in_memory_application_settings`
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+  end
+
   context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do
     before do
       stub_env('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN', '013456789')
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
index 5f9c57c0c8d28d679d29a2cce3375e754436496c..18c7453bd1b4723d7361e67eb5e639cdf93e0a23 100644
--- a/spec/factories/appearances.rb
+++ b/spec/factories/appearances.rb
@@ -2,8 +2,21 @@
 
 FactoryBot.define do
   factory :appearance do
-    title       "MepMep"
-    description "This is my Community Edition instance"
+    title "GitLab Community Edition"
+    description "Open source software to collaborate on code"
     new_project_guidelines "Custom project guidelines"
   end
+
+  trait :with_logo do
+    logo { fixture_file_upload('spec/fixtures/dk.png') }
+  end
+
+  trait :with_header_logo do
+    header_logo { fixture_file_upload('spec/fixtures/dk.png') }
+  end
+
+  trait :with_logos do
+    with_logo
+    with_header_logo
+  end
 end
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
index a0abbbce686168da937bfc65f0a791a256caf9a3..d37e2bf511e26ecc9c733ff32fd7bbbdfee1bc55 100644
--- a/spec/factories/award_emoji.rb
+++ b/spec/factories/award_emoji.rb
@@ -4,6 +4,10 @@ FactoryBot.define do
     user
     awardable factory: :issue
 
+    after(:create) do |award, evaluator|
+      award.awardable.project.add_guest(evaluator.user)
+    end
+
     trait :upvote
     trait :downvote do
       name "thumbsdown"
diff --git a/spec/factories/badge.rb b/spec/factories/badge.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b87ece946cb049a209feb5e0f9cb879fc45fc0bd
--- /dev/null
+++ b/spec/factories/badge.rb
@@ -0,0 +1,14 @@
+FactoryBot.define do
+  trait :base_badge do
+    link_url { generate(:url) }
+    image_url { generate(:url) }
+  end
+
+  factory :project_badge, traits: [:base_badge], class: ProjectBadge do
+    project
+  end
+
+  factory :group_badge, aliases: [:badge], traits: [:base_badge], class: GroupBadge do
+    group
+  end
+end
diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb
index 1e125237ae890e3c53a361ccb55813274563417c..6524bb4830cbaaeb51ec87e5707588c687d83f86 100644
--- a/spec/factories/boards.rb
+++ b/spec/factories/boards.rb
@@ -1,6 +1,29 @@
 FactoryBot.define do
   factory :board do
-    project
+    transient do
+      project nil
+      group nil
+      project_id nil
+      group_id nil
+      parent nil
+    end
+
+    after(:build, :stub) do |board, evaluator|
+      if evaluator.group
+        board.group = evaluator.group
+      elsif evaluator.group_id
+        board.group_id = evaluator.group_id
+      elsif evaluator.project
+        board.project = evaluator.project
+      elsif evaluator.project_id
+        board.project_id = evaluator.project_id
+      elsif evaluator.parent
+        id = evaluator.parent.id
+        evaluator.parent.is_a?(Group) ? board.group_id = id : evaluator.project_id = id
+      else
+        board.project = create(:project, :empty_repo)
+      end
+    end
 
     after(:create) do |board|
       board.lists.create(list_type: :closed)
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index f6ba3a581ca041fed54490ac9d35fefe8c8ab8e3..4acc008ed38388941fda73d43858943d32f88935 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -62,6 +62,7 @@ FactoryBot.define do
     end
 
     trait :pending do
+      queued_at 'Di 29. Okt 09:50:59 CET 2013'
       status 'pending'
     end
 
@@ -206,7 +207,7 @@ FactoryBot.define do
       options do
         {
             image: { name: 'ruby:2.1', entrypoint: '/bin/sh' },
-            services: ['postgres', { name: 'docker:dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
+            services: ['postgres', { name: 'docker:stable-dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
             after_script: %w(ls date),
             artifacts: {
                 name: 'artifacts_file',
@@ -237,5 +238,15 @@ FactoryBot.define do
     trait :protected do
       protected true
     end
+
+    trait :script_failure do
+      failed
+      failure_reason 1
+    end
+
+    trait :api_failure do
+      failed
+      failure_reason 2
+    end
   end
 end
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 7ee379ca2ec3a983c178bee21136f90d7c8362db..3d3287d8168d9aa90318fd6dc16bb082f6436d63 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -5,6 +5,10 @@ FactoryBot.define do
     job factory: :ci_build
     file_type :archive
 
+    trait :remote_store do
+      file_store JobArtifactUploader::Store::REMOTE
+    end
+
     after :build do |artifact|
       artifact.project ||= artifact.job.project
     end
@@ -35,5 +39,11 @@ FactoryBot.define do
           Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain')
       end
     end
+
+    trait :correct_checksum do
+      after(:build) do |artifact, evaluator|
+        artifact.file_sha256 = Digest::SHA256.file(artifact.file.path).hexdigest
+      end
+    end
   end
 end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 20d5580f0c20797893d9111d19274b0df036422f..98566f907f962f3f507ab91227f88ebd6167838c 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -32,5 +32,9 @@ FactoryBot.define do
     trait :disabled do
       enabled false
     end
+
+    trait :production_environment do
+      sequence(:environment_scope) { |n| "production#{n}/*" }
+    end
   end
 end
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5fea4a9d5a68d2802fbe022330e44cc1db31fb7d
--- /dev/null
+++ b/spec/factories/deploy_tokens.rb
@@ -0,0 +1,14 @@
+FactoryBot.define do
+  factory :deploy_token do
+    token { SecureRandom.hex(50) }
+    sequence(:name) { |n| "PDT #{n}" }
+    read_repository true
+    read_registry true
+    revoked false
+    expires_at { 5.days.from_now }
+
+    trait :revoked do
+      revoked true
+    end
+  end
+end
diff --git a/spec/factories/internal_ids.rb b/spec/factories/internal_ids.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fbde07a391a790ed7f1f82400cd5c7de16b5e2ef
--- /dev/null
+++ b/spec/factories/internal_ids.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+  factory :internal_id do
+    project
+    usage :issues
+    last_value { project.issues.maximum(:iid) || 0 }
+  end
+end
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 8eb709022ce97d23ea7c0e9fc24612b41b20eb5a..eaf3a4ed497ccab47a289cf6dd7032a65cd388c5 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -9,4 +9,14 @@ FactoryBot.define do
   trait :with_file do
     file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
   end
+
+  # The uniqueness constraint means we can't use the correct OID for all LFS
+  # objects, so the test needs to decide which (if any) object gets it
+  trait :correct_oid do
+    oid 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75'
+  end
+
+  trait :object_storage do
+    file_store { LfsObjectUploader::Store::REMOTE }
+  end
 end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 3f4e408b3a6347fe199a54be3a52ff5184fcd135..857333f222de5460649481bedfe87846e9643266 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -16,6 +16,8 @@ FactoryBot.define do
     factory :note_on_personal_snippet,   traits: [:on_personal_snippet]
     factory :system_note,                traits: [:system]
 
+    factory :discussion_note, class: DiscussionNote
+
     factory :discussion_note_on_merge_request, traits: [:on_merge_request], class: DiscussionNote do
       association :project, :repository
 
@@ -31,6 +33,8 @@ FactoryBot.define do
 
     factory :discussion_note_on_personal_snippet, traits: [:on_personal_snippet], class: DiscussionNote
 
+    factory :discussion_note_on_snippet, traits: [:on_snippet], class: DiscussionNote
+
     factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote
 
     factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote do
@@ -96,6 +100,10 @@ FactoryBot.define do
       noteable { create(:issue, project: project) }
     end
 
+    trait :on_snippet do
+      noteable { create(:snippet, project: project) }
+    end
+
     trait :on_merge_request do
       noteable { create(:merge_request, source_project: project) }
     end
diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb
index 35b44e1c52edd4b23881b4c99ecda02b38418821..20671da016ea9c82ff8429b25ba7918064a4c656 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -4,25 +4,7 @@ FactoryBot.define do
     verified_at { Time.now }
     enabled_until { 1.week.from_now }
 
-    trait :disabled do
-      verified_at nil
-      enabled_until nil
-    end
-
-    trait :unverified do
-      verified_at nil
-    end
-
-    trait :reverify do
-      enabled_until { 1.hour.from_now }
-    end
-
-    trait :expired do
-      enabled_until { 1.hour.ago }
-    end
-
-    trait :with_certificate do
-      certificate '-----BEGIN CERTIFICATE-----
+    certificate '-----BEGIN CERTIFICATE-----
 MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
 LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
 MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
@@ -36,10 +18,8 @@ joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
 5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
 YHi2yesCrOvVXt+lgPTd
 -----END CERTIFICATE-----'
-    end
 
-    trait :with_key do
-      key '-----BEGIN PRIVATE KEY-----
+    key '-----BEGIN PRIVATE KEY-----
 MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
 SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
 PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
@@ -55,6 +35,30 @@ EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
 63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
 nNp/xedE1YxutQ==
 -----END PRIVATE KEY-----'
+
+    trait :disabled do
+      verified_at nil
+      enabled_until nil
+    end
+
+    trait :unverified do
+      verified_at nil
+    end
+
+    trait :reverify do
+      enabled_until { 1.hour.from_now }
+    end
+
+    trait :expired do
+      enabled_until { 1.hour.ago }
+    end
+
+    trait :without_certificate do
+      certificate nil
+    end
+
+    trait :without_key do
+      key nil
     end
 
     trait :with_missing_chain do
diff --git a/spec/factories/project_deploy_tokens.rb b/spec/factories/project_deploy_tokens.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4866cb58d88f79eea59716b4bca9fa96cb2d9d4e
--- /dev/null
+++ b/spec/factories/project_deploy_tokens.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+  factory :project_deploy_token do
+    project
+    deploy_token
+  end
+end
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 493b7bc021ca3080f0e3f8515932dd30e1f2449d..a448d565e4b01fa1a5b56167b4d45e8b07065ac3 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -15,6 +15,7 @@ FactoryBot.define do
       issues_events true
       confidential_issues_events true
       note_events true
+      confidential_note_events true
       job_events true
       pipeline_events true
       wiki_page_events true
diff --git a/spec/factories/redirect_routes.rb b/spec/factories/redirect_routes.rb
index c29c81c5df9f81e17a43b2402ab8e2ead440cfaa..774232d0b34a95b107a1a84d37b75624ef81e542 100644
--- a/spec/factories/redirect_routes.rb
+++ b/spec/factories/redirect_routes.rb
@@ -2,14 +2,5 @@ FactoryBot.define do
   factory :redirect_route do
     sequence(:path) { |n| "redirect#{n}" }
     source factory: :group
-    permanent false
-
-    trait :permanent do
-      permanent true
-    end
-
-    trait :temporary do
-      permanent false
-    end
   end
 end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index ff3a2a76accac2268f84b919e32d149eefb0189a..b45f6f30e4063f6fc02c497570549f1c42127fcb 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -5,6 +5,7 @@ FactoryBot.define do
     uploader "AvatarUploader"
     mount_point :avatar
     secret nil
+    store ObjectStorage::Store::LOCAL
 
     # we should build a mount agnostic upload by default
     transient do
@@ -27,6 +28,10 @@ FactoryBot.define do
       secret SecureRandom.hex
     end
 
+    trait :object_storage do
+      store ObjectStorage::Store::REMOTE
+    end
+
     trait :namespace_upload do
       model { build(:group) }
       path { File.join(secret, filename) }
diff --git a/spec/factories/users_star_projects.rb b/spec/factories/users_star_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6afd08a2084fd433d5b91bcfbff4141752758a3f
--- /dev/null
+++ b/spec/factories/users_star_projects.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+  factory :users_star_project do
+    project
+    user
+  end
+end
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index 9cb351282a04144944733639f2adf9b089ade301..430a8d22b0f1b6d9944fbeee01c083a9210cb626 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -45,7 +45,7 @@ feature 'Admin Broadcast Messages' do
 
     page.within('.broadcast-message-preview') do
       expect(page).to have_selector('strong', text: 'Markdown')
-      expect(page).to have_selector('gl-emoji[data-name="tada"]')
+      expect(page).to have_emoji('tada')
     end
   end
 end
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index 9ea3cfa72c6acb7781ac1b1ec229140f14e595c9..9946cc77d1d5ae5da433d6cdedf82137970c1d29 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -55,14 +55,19 @@ feature 'Admin disables Git access protocol' do
   end
 
   def disable_http_protocol
-    visit admin_application_settings_path
-    find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[2]').select_option
-    click_on 'Save'
+    switch_git_protocol(2)
   end
 
   def disable_ssh_protocol
+    switch_git_protocol(3)
+  end
+
+  def switch_git_protocol(value)
     visit admin_application_settings_path
-    find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[3]').select_option
-    click_on 'Save'
+
+    page.within('.as-visibility-access') do
+      find('#application_setting_enabled_git_access_protocol').find(:xpath, "option[#{value}]").select_option
+      click_on 'Save'
+    end
   end
 end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index a5f22848031a3444c6635cd261c85051e94018ef..d5e603baeae77427cb128c3daf799006ec890107 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -173,7 +173,7 @@ feature 'Admin Groups' do
 
       visit admin_group_path(group)
 
-      expect(page).to have_content(empty_project.name_with_namespace)
+      expect(page).to have_content(empty_project.full_name)
       expect(page).to have_content('Projects shared with')
     end
   end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index f266f2ecc54b51ff924551a2b3072980715c4fed..25ed3bdc88e24d7d70d48ca73d52b4d398c5d71e 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -24,6 +24,16 @@ describe 'Admin::Hooks' do
       visit admin_hooks_path
       expect(page).to have_content(system_hook.url)
     end
+
+    it 'renders plugins list as well' do
+      allow(Gitlab::Plugin).to receive(:files).and_return(['foo.rb', 'bar.clj'])
+
+      visit admin_hooks_path
+
+      expect(page).to have_content('Plugins')
+      expect(page).to have_content('foo.rb')
+      expect(page).to have_content('bar.clj')
+    end
   end
 
   describe 'New Hook' do
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index d02ac6c2e2a6870eba94496e4bc625cd0b9f2f56..6d8350e99f1048aa9bae6c7eac229cbce38c3057 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -58,7 +58,7 @@ describe "Admin::Projects"  do
       expect(current_path).to eq admin_project_path(project)
       expect(page).to have_content(project.path)
       expect(page).to have_content(project.name)
-      expect(page).to have_content(project.name_with_namespace)
+      expect(page).to have_content(project.full_name)
       expect(page).to have_content(project.creator.name)
     end
   end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 7eeed7da99890e301c17058eae68addc02ea6f7d..8de2e3d199b98720f49f023c0f6adcd463c84ada 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -76,8 +76,8 @@ describe "Admin Runners" do
 
     describe 'projects' do
       it 'contains project names' do
-        expect(page).to have_content(@project1.name_with_namespace)
-        expect(page).to have_content(@project2.name_with_namespace)
+        expect(page).to have_content(@project1.full_name)
+        expect(page).to have_content(@project2.full_name)
       end
     end
 
@@ -89,8 +89,8 @@ describe "Admin Runners" do
       end
 
       it 'contains name of correct project' do
-        expect(page).to have_content(@project1.name_with_namespace)
-        expect(page).not_to have_content(@project2.name_with_namespace)
+        expect(page).to have_content(@project1.full_name)
+        expect(page).not_to have_content(@project2.full_name)
       end
     end
 
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 39b213988f0444b70c03a86cdf91945c1d00b8c7..7853d2952ea51cb56940d8d62d83046b77c3436b 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -10,18 +10,21 @@ feature 'Admin updates settings' do
   end
 
   scenario 'Change visibility settings' do
-    choose "application_setting_default_project_visibility_20"
-    click_button 'Save'
+    page.within('.as-visibility-access') do
+      choose "application_setting_default_project_visibility_20"
+      click_button 'Save changes'
+    end
 
     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'
+    page.within('.as-visibility-access') 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 changes'
+    end
 
     expect(page).to have_content "Application settings saved successfully"
     expect(find('#application_setting_visibility_level_0')).not_to be_checked
@@ -29,34 +32,204 @@ feature 'Admin updates settings' do
     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/'
-    fill_in 'Help page text', with: 'Example text'
-    check 'Hide marketing-related entries from help'
-    fill_in 'Support page URL', with: 'http://example.com/help'
-    uncheck 'Project export enabled'
-    click_button 'Save'
+  scenario 'Modify import sources' do
+    expect(Gitlab::CurrentSettings.import_sources).not_to be_empty
+
+    page.within('.as-visibility-access') do
+      Gitlab::ImportSources.options.map do |name, _|
+        uncheck name
+      end
+
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(Gitlab::CurrentSettings.import_sources).to be_empty
+
+    page.within('.as-visibility-access') do
+      check "Repo by URL"
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(Gitlab::CurrentSettings.import_sources).to eq(['git'])
+  end
+
+  scenario 'Change Visibility and Access Controls' do
+    page.within('.as-visibility-access') do
+      uncheck 'Project export enabled'
+      click_button 'Save changes'
+    end
+
+    expect(Gitlab::CurrentSettings.project_export_enabled).to be_falsey
+    expect(page).to have_content "Application settings saved successfully"
+  end
+
+  scenario 'Change Account and Limit Settings' do
+    page.within('.as-account-limit') do
+      uncheck 'Gravatar enabled'
+      click_button 'Save changes'
+    end
 
     expect(Gitlab::CurrentSettings.gravatar_enabled).to be_falsey
+    expect(page).to have_content "Application settings saved successfully"
+  end
+
+  scenario 'Change Sign-in restrictions' do
+    page.within('.as-signin') do
+      fill_in 'Home page URL', with: 'https://about.gitlab.com/'
+      click_button 'Save changes'
+    end
+
     expect(Gitlab::CurrentSettings.home_page_url).to eq "https://about.gitlab.com/"
+    expect(page).to have_content "Application settings saved successfully"
+  end
+
+  scenario 'Modify oauth providers' do
+    expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to be_empty
+
+    page.within('.as-signin') do
+      uncheck 'Google'
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to include('google_oauth2')
+
+    page.within('.as-signin') do
+      check "Google"
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).not_to include('google_oauth2')
+  end
+
+  scenario 'Change Help page' do
+    page.within('.as-help-page') do
+      fill_in 'Help page text', with: 'Example text'
+      check 'Hide marketing-related entries from help'
+      fill_in 'Support page URL', with: 'http://example.com/help'
+      click_button 'Save changes'
+    end
+
     expect(Gitlab::CurrentSettings.help_page_text).to eq "Example text"
     expect(Gitlab::CurrentSettings.help_page_hide_commercial_content).to be_truthy
     expect(Gitlab::CurrentSettings.help_page_support_url).to eq "http://example.com/help"
-    expect(Gitlab::CurrentSettings.project_export_enabled).to be_falsey
     expect(page).to have_content "Application settings saved successfully"
   end
 
-  scenario 'Change AutoDevOps settings' do
-    check 'Enabled Auto DevOps (Beta) for projects by default'
-    fill_in 'Auto devops domain', with: 'domain.com'
-    click_button 'Save'
+  scenario 'Change Pages settings' do
+    page.within('.as-pages') do
+      fill_in 'Maximum size of pages (MB)', with: 15
+      check 'Require users to prove ownership of custom domains'
+      click_button 'Save changes'
+    end
+
+    expect(Gitlab::CurrentSettings.max_pages_size).to eq 15
+    expect(Gitlab::CurrentSettings.pages_domain_verification_enabled?).to be_truthy
+    expect(page).to have_content "Application settings saved successfully"
+  end
+
+  scenario 'Change CI/CD settings' do
+    page.within('.as-ci-cd') do
+      check 'Enabled Auto DevOps (Beta) for projects by default'
+      fill_in 'Auto devops domain', with: 'domain.com'
+      click_button 'Save changes'
+    end
 
     expect(Gitlab::CurrentSettings.auto_devops_enabled?).to be true
     expect(Gitlab::CurrentSettings.auto_devops_domain).to eq('domain.com')
     expect(page).to have_content "Application settings saved successfully"
   end
 
+  scenario 'Change Influx settings' do
+    page.within('.as-influx') do
+      check 'Enable InfluxDB Metrics'
+      click_button 'Save changes'
+    end
+
+    expect(Gitlab::CurrentSettings.metrics_enabled?).to be true
+    expect(page).to have_content "Application settings saved successfully"
+  end
+
+  scenario 'Change Prometheus settings' do
+    page.within('.as-prometheus') do
+      check 'Enable Prometheus Metrics'
+      click_button 'Save changes'
+    end
+
+    expect(Gitlab::CurrentSettings.prometheus_metrics_enabled?).to be true
+    expect(page).to have_content "Application settings saved successfully"
+  end
+
+  scenario 'Change Performance bar settings' do
+    group = create(:group)
+
+    page.within('.as-performance-bar') do
+      check 'Enable the Performance Bar'
+      fill_in 'Allowed group', with: group.path
+      click_on 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(find_field('Enable the Performance Bar')).to be_checked
+    expect(find_field('Allowed group').value).to eq group.path
+
+    page.within('.as-performance-bar') do
+      uncheck 'Enable the Performance Bar'
+      click_on 'Save changes'
+    end
+
+    expect(page).to have_content 'Application settings saved successfully'
+    expect(find_field('Enable the Performance Bar')).not_to be_checked
+    expect(find_field('Allowed group').value).to be_nil
+  end
+
+  scenario 'Change Background jobs settings' do
+    page.within('.as-background') do
+      fill_in 'Throttling Factor', with: 1
+      click_button 'Save changes'
+    end
+
+    expect(Gitlab::CurrentSettings.sidekiq_throttling_factor).to eq(1)
+    expect(page).to have_content "Application settings saved successfully"
+  end
+
+  scenario 'Change Spam settings' do
+    page.within('.as-spam') do
+      check 'Enable reCAPTCHA'
+      fill_in 'reCAPTCHA Site Key', with: 'key'
+      fill_in 'reCAPTCHA Private Key', with: 'key'
+      fill_in 'IPs per user', with: 15
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(Gitlab::CurrentSettings.recaptcha_enabled).to be true
+    expect(Gitlab::CurrentSettings.unique_ips_limit_per_user).to eq(15)
+  end
+
+  scenario 'Configure web terminal' do
+    page.within('.as-terminal') do
+      fill_in 'Max session time', with: 15
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(Gitlab::CurrentSettings.terminal_max_session_time).to eq(15)
+  end
+
+  scenario 'Enable outbound requests' do
+    page.within('.as-outbound') do
+      check 'Allow requests to the local network from hooks and services'
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Application settings saved successfully"
+    expect(Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services).to be true
+  end
+
   scenario 'Change Slack Notifications Service template settings' do
     first(:link, 'Service Templates').click
     click_link 'Slack notifications'
@@ -81,20 +254,14 @@ feature 'Admin updates settings' do
     expect(find('#service_push_channel').value).to eq '#test_channel'
   end
 
-  context 'sign-in restrictions', :js do
-    it 'de-activates oauth sign-in source' do
-      find('input#application_setting_enabled_oauth_sign_in_sources_[value=gitlab]').send_keys(:return)
-
-      expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active')
-    end
-  end
-
   scenario 'Change Keys settings' do
-    select 'Are forbidden', from: 'RSA SSH keys'
-    select 'Are allowed', from: 'DSA SSH keys'
-    select 'Must be at least 384 bits', from: 'ECDSA SSH keys'
-    select 'Are forbidden', from: 'ED25519 SSH keys'
-    click_on 'Save'
+    page.within('.as-visibility-access') do
+      select 'Are forbidden', from: 'RSA SSH keys'
+      select 'Are allowed', from: 'DSA SSH keys'
+      select 'Must be at least 384 bits', from: 'ECDSA SSH keys'
+      select 'Are forbidden', from: 'ED25519 SSH keys'
+      click_on 'Save changes'
+    end
 
     forbidden = ApplicationSetting::FORBIDDEN_KEY_VALUE.to_s
 
@@ -105,29 +272,6 @@ feature 'Admin updates settings' do
     expect(find_field('ED25519 SSH keys').value).to eq(forbidden)
   end
 
-  scenario 'Change Performance Bar settings' do
-    group = create(:group)
-
-    check 'Enable the Performance Bar'
-    fill_in 'Allowed group', with: group.path
-
-    click_on 'Save'
-
-    expect(page).to have_content 'Application settings saved successfully'
-
-    expect(find_field('Enable the Performance Bar')).to be_checked
-    expect(find_field('Allowed group').value).to eq group.path
-
-    uncheck 'Enable the Performance Bar'
-
-    click_on 'Save'
-
-    expect(page).to have_content 'Application settings saved successfully'
-
-    expect(find_field('Enable the Performance Bar')).not_to be_checked
-    expect(find_field('Allowed group').value).to be_nil
-  end
-
   def check_all_events
     page.check('Active')
     page.check('Push')
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index d673bac499510926a27554fa6cf1f12f0a405ac1..fb6c71ce9976e562222ba03b9dc876ae52a35203 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -13,17 +13,26 @@ describe "Dashboard Issues Feed"  do
     end
 
     describe "atom feed" do
-      it "renders atom feed via personal access token" do
+      it "returns 400 if no filter is used" do
         personal_access_token = create(:personal_access_token, user: user)
 
         visit issues_dashboard_path(:atom, private_token: personal_access_token.token)
 
+        expect(response_headers['Content-Type']).to have_content('application/atom+xml')
+        expect(page.status_code).to eq(400)
+      end
+
+      it "renders atom feed via personal access token" do
+        personal_access_token = create(:personal_access_token, user: user)
+
+        visit issues_dashboard_path(:atom, private_token: personal_access_token.token, assignee_id: user.id)
+
         expect(response_headers['Content-Type']).to have_content('application/atom+xml')
         expect(body).to have_selector('title', text: "#{user.name} issues")
       end
 
       it "renders atom feed via RSS token" do
-        visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+        visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id)
 
         expect(response_headers['Content-Type']).to have_content('application/atom+xml')
         expect(body).to have_selector('title', text: "#{user.name} issues")
@@ -44,7 +53,7 @@ describe "Dashboard Issues Feed"  do
         let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
 
         it "renders issue fields" do
-          visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+          visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
 
           entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
 
@@ -67,7 +76,7 @@ describe "Dashboard Issues Feed"  do
         end
 
         it "renders issue label and milestone info" do
-          visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+          visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
 
           entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
 
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 3d13f806b1160c28db9c4f69cdfaad2cde481ef0..52ff47d57f9f456f5b9fd4284469debf43f65de6 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -343,7 +343,7 @@ describe 'Issue Boards', :js do
 
           wait_for_requests
 
-          click_link 'Create new label'
+          click_link 'Create project label'
 
           fill_in('new_label_name', with: 'Testing New Label')
 
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index b2dbfcd0031eb93caa988272c05192389ea946f7..4d31123a699bc2398e622bf2ac17e701f817601f 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -237,6 +237,22 @@ describe 'Issue Boards', :js do
   end
 
   context 'labels' do
+    it 'shows current labels when editing' do
+      click_card(card)
+
+      page.within('.labels') do
+        click_link 'Edit'
+
+        wait_for_requests
+
+        page.within('.value') do
+          expect(page).to have_selector('.label', count: 2)
+          expect(page).to have_content(development.title)
+          expect(page).to have_content(stretch.title)
+        end
+      end
+    end
+
     it 'adds a single label' do
       click_card(card)
 
@@ -296,7 +312,9 @@ describe 'Issue Boards', :js do
 
         wait_for_requests
 
-        click_link stretch.title
+        within('.dropdown-menu-labels') do
+          click_link stretch.title
+        end
 
         wait_for_requests
 
@@ -312,12 +330,12 @@ describe 'Issue Boards', :js do
       expect(card).not_to have_content(stretch.title)
     end
 
-    it 'creates new label' do
+    it 'creates project label' do
       click_card(card)
 
       page.within('.labels') do
         click_link 'Edit'
-        click_link 'Create new label'
+        click_link 'Create project label'
         fill_in 'new_label_name', with: 'test label'
         first('.suggest-colors-dropdown a').click
         click_button 'Create'
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index 11a54079f4f34c22298db60599a06cd8a023b42e..5fdb8044db2fb37ee219f34de5024d45c214b57d 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -24,7 +24,7 @@ describe 'Sub-group project issue boards', :js do
 
     page.within '.labels' do
       click_link 'Edit'
-      click_link 'Create new label'
+      click_link 'Create project label'
     end
 
     page.within '.dropdown-new-label' do
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
deleted file mode 100644
index b1dceec9da8d2a152fd2485f5442edcaf2bb9a1b..0000000000000000000000000000000000000000
--- a/spec/features/ci_lint_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'spec_helper'
-
-describe 'CI Lint', :js do
-  before do
-    sign_in(create(:user))
-
-    visit ci_lint_path
-    find('#ci-editor')
-    execute_script("ace.edit('ci-editor').setValue(#{yaml_content.to_json});")
-
-    # Ace editor updates a hidden textarea and it happens asynchronously
-    wait_for('YAML content') do
-      find('.ace_content').text.present?
-    end
-  end
-
-  describe 'YAML parsing' do
-    before do
-      click_on 'Validate'
-    end
-
-    context 'YAML is correct' do
-      let(:yaml_content) do
-        File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
-      end
-
-      it 'parses Yaml' do
-        within "table" do
-          expect(page).to have_content('Job - rspec')
-          expect(page).to have_content('Job - spinach')
-          expect(page).to have_content('Deploy Job - staging')
-          expect(page).to have_content('Deploy Job - production')
-        end
-      end
-    end
-
-    context 'YAML is incorrect' do
-      let(:yaml_content) { 'value: cannot have :' }
-
-      it 'displays information about an error' do
-        expect(page).to have_content('Status: syntax is incorrect')
-      end
-    end
-
-    describe 'YAML revalidate' do
-      let(:yaml_content) { 'my yaml content' }
-
-      it 'loads previous YAML content after validation' do
-        expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea')
-      end
-    end
-  end
-
-  describe 'YAML clearing' do
-    before do
-      click_on 'Clear'
-    end
-
-    context 'YAML is present' do
-      let(:yaml_content) do
-        File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
-      end
-
-      it 'YAML content is cleared' do
-        expect(page).to have_field('content', with: '', visible: false, type: 'textarea')
-      end
-    end
-  end
-end
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index 8759950e013e976dbe94df41d594936a8da652fa..bab34ac9346be84f286fc056dde9ead3015cfe39 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 feature 'Dashboard Issues filtering', :js do
-  include SortingHelper
+  include Spec::Support::Helpers::Features::SortingHelpers
 
   let(:user)      { create(:user) }
   let(:project)   { create(:project) }
@@ -17,6 +17,12 @@ feature 'Dashboard Issues filtering', :js do
     visit_issues
   end
 
+  context 'without any filter' do
+    it 'shows error message' do
+      expect(page).to have_content 'Please select at least one filter to see results'
+    end
+  end
+
   context 'filtering by milestone' do
     it 'shows all issues with no milestone' do
       show_milestone_dropdown
@@ -27,15 +33,6 @@ feature 'Dashboard Issues filtering', :js do
       expect(page).to have_selector('.issue', count: 1)
     end
 
-    it 'shows all issues with any milestone' do
-      show_milestone_dropdown
-
-      click_link 'Any Milestone'
-
-      expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
-      expect(page).to have_selector('.issue', count: 2)
-    end
-
     it 'shows all issues with the selected milestone' do
       show_milestone_dropdown
 
@@ -68,13 +65,6 @@ feature 'Dashboard Issues filtering', :js do
     let(:label) { create(:label, project: project) }
     let!(:label_link) { create(:label_link, label: label, target: issue) }
 
-    it 'shows all issues without filter' do
-      page.within 'ul.content-list' do
-        expect(page).to have_content issue.title
-        expect(page).to have_content issue2.title
-      end
-    end
-
     it 'shows all issues with the selected label' do
       page.within '.labels-filter' do
         find('.dropdown').click
@@ -89,15 +79,19 @@ feature 'Dashboard Issues filtering', :js do
   end
 
   context 'sorting' do
-    it 'shows sorted issues' do
-      sorting_by('Created date')
-      visit_issues
+    before do
+      visit_issues(assignee_id: user.id)
+    end
+
+    it 'remembers last sorting value' do
+      sort_by('Created date')
+      visit_issues(assignee_id: user.id)
 
       expect(find('.issues-filters')).to have_content('Created date')
     end
 
     it 'keeps sorting issues after visiting Projects Issues page' do
-      sorting_by('Created date')
+      sort_by('Created date')
       visit project_issues_path(project)
 
       expect(find('.issues-filters')).to have_content('Created date')
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 54652e2d84954f4ec9c1bb98210dfae5d04737f3..e41a2e4ce091c5052d0c494946d542b1e9eaf56a 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -51,15 +51,6 @@ RSpec.describe 'Dashboard Issues' do
       expect(page).not_to have_content(other_issue.title)
     end
 
-    it 'shows all issues' do
-      click_link('Reset filters')
-
-      expect(page).to have_content(authored_issue.title)
-      expect(page).to have_content(authored_issue_on_public_project.title)
-      expect(page).to have_content(assigned_issue.title)
-      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, state: 'closed'), url: true)
@@ -74,8 +65,8 @@ RSpec.describe 'Dashboard Issues' do
       find('.new-project-item-select-button').click
 
       page.within('.select2-results') do
-        expect(page).to have_content(project.name_with_namespace)
-        expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace)
+        expect(page).to have_content(project.full_name)
+        expect(page).not_to have_content(project_with_issues_disabled.full_name)
       end
     end
 
@@ -84,8 +75,8 @@ RSpec.describe 'Dashboard Issues' do
 
       wait_for_requests
 
-      project_path = "/#{project.path_with_namespace}"
-      project_json = { name: project.name_with_namespace, url: project_path }.to_json
+      project_path = "/#{project.full_path}"
+      project_json = { name: project.full_name, url: project_path }.to_json
 
       # simulate selection, and prevent overlap by dropdown menu
       first('.project-item-select', visible: false)
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 744041ac425cf5ec230a3892e8dde70447c92b99..0965b745c0395aad5cafced41933132cfe60c947 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
 feature 'Dashboard Merge Requests' do
+  include Spec::Support::Helpers::Features::SortingHelpers
   include FilterItemSelectHelper
-  include SortingHelper
   include ProjectForksHelper
 
   let(:current_user) { create :user }
@@ -28,8 +28,8 @@ feature 'Dashboard Merge Requests' do
       find('.new-project-item-select-button').click
 
       page.within('.select2-results') do
-        expect(page).to have_content(project.name_with_namespace)
-        expect(page).not_to have_content(project_with_disabled_merge_requests.name_with_namespace)
+        expect(page).to have_content(project.full_name)
+        expect(page).not_to have_content(project_with_disabled_merge_requests.full_name)
       end
     end
   end
@@ -103,19 +103,15 @@ feature 'Dashboard Merge Requests' do
       expect(page).not_to have_content(other_merge_request.title)
     end
 
-    it 'shows all merge requests', :js do
+    it 'shows error message without filter', :js 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)
+      expect(page).to have_content('Please select at least one filter to see results')
     end
 
     it 'shows sorted merge requests' do
-      sorting_by('Created date')
+      sort_by('Created date')
 
       visit merge_requests_dashboard_path(assignee_id: current_user.id)
 
@@ -123,7 +119,7 @@ feature 'Dashboard Merge Requests' do
     end
 
     it 'keeps sorting merge requests after visiting Projects MR page' do
-      sorting_by('Created date')
+      sort_by('Created date')
 
       visit project_merge_requests_path(project)
 
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 586c7b48d0b18470a123ede982636e6346ec1ea6..257a38225037af2b549b54ac423cb5dc5a04032c 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -37,6 +37,14 @@ feature 'Dashboard Projects' do
 
       expect(page).to have_xpath("//time[@datetime='#{project.last_repository_updated_at.getutc.iso8601}']")
     end
+
+    it 'shows the last_activity_at attribute as the update date' do
+      project.update_attributes!(last_repository_updated_at: 1.hour.ago, last_activity_at: Time.now)
+
+      visit dashboard_projects_path
+
+      expect(page).to have_xpath("//time[@datetime='#{project.last_activity_at.getutc.iso8601}']")
+    end
   end
 
   context 'when last_repository_updated_at and last_activity_at are missing' do
@@ -81,7 +89,7 @@ feature 'Dashboard Projects' do
   end
 
   describe 'with a pipeline', :clean_gitlab_redis_shared_state do
-    let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
+    let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha, ref: project.default_branch) }
 
     before do
       # Since the cache isn't updated when a new pipeline is created
@@ -94,7 +102,7 @@ feature 'Dashboard Projects' do
       visit dashboard_projects_path
 
       page.within('.controls') do
-        expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']")
+        expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
         expect(page).to have_css('.ci-status-link')
         expect(page).to have_css('.ci-status-icon-success')
         expect(page).to have_link('Commit: passed')
diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index 2fc34301d5186eddfc409a99367071bab9f47c05..7b359b0c651ec1f28033938a594a719910b2997b 100644
--- a/spec/features/dashboard/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -24,14 +24,14 @@ feature 'Dashboard > User filters todos', :js do
   it 'filters by project' do
     click_button 'Project'
     within '.dropdown-menu-project' do
-      fill_in 'Search projects', with: project_1.name_with_namespace
-      click_link project_1.name_with_namespace
+      fill_in 'Search projects', with: project_1.full_name
+      click_link project_1.full_name
     end
 
     wait_for_requests
 
-    expect(page).to     have_content project_1.name_with_namespace
-    expect(page).not_to have_content project_2.name_with_namespace
+    expect(page).to     have_content project_1.full_name
+    expect(page).not_to have_content project_2.full_name
   end
 
   context 'Author filter' do
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index d3b25ec3d6c4b99c345377f5f4cae60e81911c9a..7bc809b310409511c6eda6c8c9eecba5f74eb409 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -8,11 +8,30 @@ feature 'Group activity page' do
   context 'when signed in' do
     before do
       sign_in(user)
-      visit path
     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"
+    describe 'RSS' do
+      before do
+        visit path
+      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
+
+    context 'when project is in the group', :js do
+      let(:project) { create(:project, :public, namespace: group) }
+
+      before do
+        project.add_master(user)
+
+        visit path
+      end
+
+      it 'renders user joined to project event' do
+        expect(page).to have_content 'joined project'
+      end
+    end
   end
 
   context 'when signed out' do
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index b83bad3befb52811e0fe5dd5d70c49f9f035009a..1ce30015e81592b3d6de15ca6ffbfe754431802f 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -76,6 +76,27 @@ feature 'Edit group settings' do
       end
     end
   end
+
+  describe 'edit group avatar' do
+    before do
+      visit edit_group_path(group)
+
+      attach_file(:group_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
+
+      expect { click_button 'Save group' }.to change { group.reload.avatar? }.to(true)
+    end
+
+    it 'uploads new group avatar' do
+      expect(group.avatar).to be_instance_of AvatarUploader
+      expect(group.avatar.url).to eq "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif"
+      expect(page).to have_link('Remove avatar')
+    end
+
+    it 'removes group avatar' do
+      expect { click_link 'Remove avatar' }.to change { group.reload.avatar? }.to(false)
+      expect(page).not_to have_link('Remove avatar')
+    end
+  end
 end
 
 def update_path(new_group_path)
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 450bc0ff8cf95d23418c8a9e0c253931004aa83d..90bf7ba49f6eda39c733726d58421b81d33f5524 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -3,8 +3,11 @@ require 'spec_helper'
 feature 'Group issues page' do
   include FilteredSearchHelpers
 
+  let(:group) { create(:group) }
+  let(:project) { create(:project, :public, group: group)}
+  let(:path) { issues_group_path(group) }
+
   context 'with shared examples' do
-    let(:path) { issues_group_path(group) }
     let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
 
     include_examples 'project features apply to issuables', Issue
@@ -31,7 +34,6 @@ feature 'Group issues page' do
       let(:access_level) { ProjectFeature::ENABLED }
       let(:user) { user_in_group }
       let(:user2) { user_outside_group }
-      let(:path) { issues_group_path(group) }
 
       it 'filters by only group users' do
         filtered_search.set('assignee:')
@@ -43,9 +45,7 @@ feature 'Group issues page' do
   end
 
   context 'issues list', :nested_groups do
-    let(:group) { create(:group)}
     let(:subgroup) { create(:group, parent: group) }
-    let(:project) { create(:project, :public, group: group)}
     let(:subgroup_project) { create(:project, :public, group: subgroup)}
     let!(:issue) { create(:issue, project: project, title: 'root group issue') }
     let!(:subgroup_issue) { create(:issue, project: subgroup_project, title: 'subgroup issue') }
@@ -59,5 +59,17 @@ feature 'Group issues page' do
         expect(page).to have_content('subgroup issue')
       end
     end
+
+    context 'when project is archived' do
+      before do
+        project.archive!
+      end
+
+      it 'does not render issue' do
+        visit path
+
+        expect(page).not_to have_content issue.title[0..80]
+      end
+    end
   end
 end
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index 7ce6a61d50ca405f56055ff548165bcf3a71989b..672ae785c2dbf23df324c3094acf4473e58a323c 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -5,14 +5,14 @@ feature 'Group merge requests page' do
 
   let(:path) { merge_requests_group_path(group) }
   let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: 'this is my created issuable') }
+  let(:access_level) { ProjectFeature::ENABLED }
+  let(:user) { user_in_group }
 
   include_examples 'project features apply to issuables', MergeRequest
 
   context 'archived issuable' do
     let(:project_archived) { create(:project, :archived, :merge_requests_enabled, :repository, group: group) }
     let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') }
-    let(:access_level) { ProjectFeature::ENABLED }
-    let(:user) { user_in_group }
 
     before do
       issuable_archived
@@ -36,9 +36,17 @@ feature 'Group merge requests page' do
     end
   end
 
+  context 'when merge request assignee to user' do
+    before do
+      issuable.update!(assignee: user)
+
+      visit path
+    end
+
+    it { expect(page).to have_content issuable.title[0..80] }
+  end
+
   context 'group filtered search', :js do
-    let(:access_level) { ProjectFeature::ENABLED }
-    let(:user) { user_in_group }
     let(:user2) { user_outside_group }
 
     it 'filters by assignee only group users' do
diff --git a/spec/features/groups/settings/group_badges_spec.rb b/spec/features/groups/settings/group_badges_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..92217294446b30148d90fc704a05311c54f026b2
--- /dev/null
+++ b/spec/features/groups/settings/group_badges_spec.rb
@@ -0,0 +1,124 @@
+require 'spec_helper'
+
+feature 'Group Badges' do
+  include WaitForRequests
+
+  let(:user) { create(:user) }
+  let(:group) { create(:group) }
+  let(:badge_link_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'}
+  let(:badge_image_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'}
+  let!(:badge_1) { create(:group_badge, group: group) }
+  let!(:badge_2) { create(:group_badge, group: group) }
+
+  before do
+    group.add_owner(user)
+    sign_in(user)
+
+    visit(group_settings_badges_path(group))
+  end
+
+  it 'shows a list of badges', :js do
+    page.within '.badge-settings' do
+      wait_for_requests
+
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 2
+      expect(rows[0]).to have_content badge_1.link_url
+      expect(rows[1]).to have_content badge_2.link_url
+    end
+  end
+
+  context 'adding a badge', :js do
+    it 'user can preview a badge' do
+      page.within '.badge-settings form' do
+        fill_in 'badge-link-url', with: badge_link_url
+        fill_in 'badge-image-url', with: badge_image_url
+        within '#badge-preview' do
+          expect(find('a')[:href]).to eq badge_link_url
+          expect(find('a img')[:src]).to eq badge_image_url
+        end
+      end
+    end
+
+    it do
+      page.within '.badge-settings' do
+        fill_in 'badge-link-url', with: badge_link_url
+        fill_in 'badge-image-url', with: badge_image_url
+
+        click_button 'Add badge'
+        wait_for_requests
+
+        within '.panel-body' do
+          expect(find('a')[:href]).to eq badge_link_url
+          expect(find('a img')[:src]).to eq badge_image_url
+        end
+      end
+    end
+  end
+
+  context 'editing a badge', :js do
+    it 'form is shown when clicking edit button in list' do
+      page.within '.badge-settings' do
+        wait_for_requests
+        rows = all('.panel-body > div')
+        expect(rows.length).to eq 2
+        rows[1].find('[aria-label="Edit"]').click
+
+        within 'form' do
+          expect(find('#badge-link-url').value).to eq badge_2.link_url
+          expect(find('#badge-image-url').value).to eq badge_2.image_url
+        end
+      end
+    end
+
+    it 'updates a badge when submitting the edit form' do
+      page.within '.badge-settings' do
+        wait_for_requests
+        rows = all('.panel-body > div')
+        expect(rows.length).to eq 2
+        rows[1].find('[aria-label="Edit"]').click
+        within 'form' do
+          fill_in 'badge-link-url', with: badge_link_url
+          fill_in 'badge-image-url', with: badge_image_url
+
+          click_button 'Save changes'
+          wait_for_requests
+        end
+
+        rows = all('.panel-body > div')
+        expect(rows.length).to eq 2
+        expect(rows[1]).to have_content badge_link_url
+      end
+    end
+  end
+
+  context 'deleting a badge', :js do
+    def click_delete_button(badge_row)
+      badge_row.find('[aria-label="Delete"]').click
+    end
+
+    it 'shows a modal when deleting a badge' do
+      wait_for_requests
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 2
+
+      click_delete_button(rows[1])
+
+      expect(find('.modal .modal-title')).to have_content 'Delete badge?'
+    end
+
+    it 'deletes a badge when confirming the modal' do
+      wait_for_requests
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 2
+      click_delete_button(rows[1])
+
+      find('.modal .btn-danger').click
+      wait_for_requests
+
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 1
+      expect(rows[0]).to have_content badge_1.link_url
+    end
+  end
+end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index ceccc4714057b0217cd285816fba37b8a7db296e..3a0424d60f8a55f58b93dfd6fc30ded40507f606 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -15,14 +15,44 @@ feature 'Group show page' do
     end
 
     it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+
+    context 'when group does not exist' do
+      let(:path) { group_path('not-exist') }
+
+      it { expect(status_code).to eq(404) }
+    end
   end
 
   context 'when signed out' do
-    before do
-      visit path
+    describe 'RSS' do
+      before do
+        visit path
+      end
+
+      it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+    end
+
+    context 'when group has a public project', :js do
+      let!(:project) { create(:project, :public, namespace: group) }
+
+      it 'renders public project' do
+        visit path
+
+        expect(page).to have_link group.name
+        expect(page).to have_link project.name
+      end
     end
 
-    it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+    context 'when group has a private project', :js do
+      let!(:project) { create(:project, :private, namespace: group) }
+
+      it 'does not render private project' do
+        visit path
+
+        expect(page).to have_link group.name
+        expect(page).not_to have_link project.name
+      end
+    end
   end
 
   context 'subgroup support' do
@@ -68,7 +98,7 @@ feature 'Group show page' do
 
     it 'shows the project info' do
       expect(page).to have_content(project.title)
-      expect(page).to have_selector('gl-emoji[data-name="smile"]')
+      expect(page).to have_emoji('smile')
     end
   end
 end
diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e81c3180e78102db0a09f0953abda41817956fe7
--- /dev/null
+++ b/spec/features/groups/user_browse_projects_group_page_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+describe 'User browse group projects page' do
+  let(:user) { create :user }
+  let(:group) { create :group }
+
+  context 'when user is owner' do
+    before do
+      group.add_owner(user)
+    end
+
+    context 'when user signed in' do
+      before do
+        sign_in(user)
+      end
+
+      context 'when group has archived project', :js do
+        let!(:project) { create :project, :archived, namespace: group }
+
+        it 'renders projects list' do
+          visit projects_group_path(group)
+
+          expect(page).to have_link project.name
+          expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
+        end
+      end
+    end
+  end
+end
diff --git a/spec/features/ide_spec.rb b/spec/features/ide_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b3f24c2966dc1cd6ab341c5725f9356e6bd556a0
--- /dev/null
+++ b/spec/features/ide_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe 'IDE', :js do
+  describe 'sub-groups' do
+    let(:user) { create(:user) }
+    let(:group) { create(:group) }
+    let(:subgroup) { create(:group, parent: group) }
+    let(:subgroup_project) { create(:project, :repository, namespace: subgroup) }
+
+    before do
+      subgroup_project.add_master(user)
+      sign_in(user)
+
+      visit project_path(subgroup_project)
+
+      click_link('Web IDE')
+
+      wait_for_requests
+    end
+
+    it 'loads project in web IDE' do
+      expect(page).to have_selector('.context-header', text: subgroup_project.name)
+    end
+  end
+end
diff --git a/spec/features/issuables/discussion_lock_spec.rb b/spec/features/issuables/discussion_lock_spec.rb
index ecbe51a7bc2540e7beb923b9ef21ad72412ad6e1..7ea29ff252b448e1c883944752b23d836107f52a 100644
--- a/spec/features/issuables/discussion_lock_spec.rb
+++ b/spec/features/issuables/discussion_lock_spec.rb
@@ -14,7 +14,7 @@ describe 'Discussion Lock', :js do
       project.add_developer(user)
     end
 
-    context 'when the discussion is   unlocked' do
+    context 'when the discussion is unlocked' do
       it 'the user can lock the issue' do
         visit project_issue_path(project, issue)
 
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index b3c509648109020e3ce3192bbb892fabebd4e365..08ba91a2682786840a5653c9d04002e5b29929ea 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -22,15 +22,6 @@ describe 'Filter issues', :js do
     end
   end
 
-  def expect_issues_list_count(open_count, closed_count = 0)
-    all_count = open_count + closed_count
-
-    expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
-    page.within '.issues-list' do
-      expect(page).to have_selector('.issue', count: open_count)
-    end
-  end
-
   before do
     project.add_master(user)
 
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index ef6b8edd0ad90426f3cd5d35030e1323ea7e834f..4625a50b8d90af1a7e900c97484e13f506a75908 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -226,6 +226,23 @@ describe 'New/edit issue', :js do
 
       expect(page).to have_selector('.atwho-view')
     end
+
+    describe 'milestone' do
+      let!(:milestone) { create(:milestone, title: '">&lt;img src=x onerror=alert(document.domain)&gt;', project: project) }
+
+      it 'escapes milestone' do
+        click_button 'Milestone'
+
+        page.within '.issue-milestone' do
+          click_link milestone.title
+        end
+
+        page.within '.js-milestone-select' do
+          expect(page).to have_content milestone.title
+          expect(page).not_to have_selector 'img'
+        end
+      end
+    end
   end
 
   context 'edit issue' do
@@ -306,10 +323,10 @@ describe 'New/edit issue', :js do
       visit new_project_issue_path(sub_group_project)
     end
 
-    it 'creates new label from dropdown' do
+    it 'creates project label from dropdown' do
       click_button 'Labels'
 
-      click_link 'Create new label'
+      click_link 'Create project label'
 
       page.within '.dropdown-new-label' do
         fill_in 'new_label_name', with: 'test label'
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 64b4f9e7e67de0cf66aa2c11874f7afed9f256d6..830c794376d2cbe5a156ca7b40ed51907be39520 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -5,9 +5,9 @@ feature 'Issue Sidebar' do
 
   let(:group) { create(:group, :nested) }
   let(:project) { create(:project, :public, namespace: group) }
-  let(:issue) { create(:issue, project: project) }
   let!(:user) { create(:user)}
   let!(:label) { create(:label, project: project, title: 'bug') }
+  let(:issue) { create(:labeled_issue, project: project, labels: [label]) }
   let!(:xss_label) { create(:label, project: project, title: '&lt;script&gt;alert("xss");&lt;&#x2F;script&gt;') }
 
   before do
@@ -112,27 +112,34 @@ feature 'Issue Sidebar' do
 
     context 'editing issue labels', :js do
       before do
+        issue.update_attributes(labels: [label])
         page.within('.block.labels') do
           find('.edit-link').click
         end
       end
 
-      it 'shows option to create a new label' do
+      it 'shows the current set of labels' do
+        page.within('.issuable-show-labels') do
+          expect(page).to have_content label.title
+        end
+      end
+
+      it 'shows option to create a project label' do
         page.within('.block.labels') do
-          expect(page).to have_content 'Create new'
+          expect(page).to have_content 'Create project'
         end
       end
 
-      context 'creating a new label', :js do
+      context 'creating a project label', :js do
         before do
           page.within('.block.labels') do
-            click_link 'Create new'
+            click_link 'Create project'
           end
         end
 
         it 'shows dropdown switches to "create label" section' do
           page.within('.block.labels') do
-            expect(page).to have_content 'Create new label'
+            expect(page).to have_content 'Create project label'
           end
         end
 
@@ -161,6 +168,50 @@ feature 'Issue Sidebar' do
         end
       end
     end
+
+    context 'interacting with collapsed sidebar', :js do
+      collapsed_sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
+      expanded_sidebar_selector = 'aside.right-sidebar.right-sidebar-expanded'
+      confidentiality_sidebar_block = '.block.confidentiality'
+      lock_sidebar_block = '.block.lock'
+      collapsed_sidebar_block_icon = '.sidebar-collapsed-icon'
+
+      before do
+        resize_screen_sm
+      end
+
+      it 'confidentiality block expands then collapses sidebar' do
+        expect(page).to have_css(collapsed_sidebar_selector)
+
+        page.within(confidentiality_sidebar_block) do
+          find(collapsed_sidebar_block_icon).click
+        end
+
+        expect(page).to have_css(expanded_sidebar_selector)
+
+        page.within(confidentiality_sidebar_block) do
+          page.find('button', text: 'Cancel').click
+        end
+
+        expect(page).to have_css(collapsed_sidebar_selector)
+      end
+
+      it 'lock block expands then collapses sidebar' do
+        expect(page).to have_css(collapsed_sidebar_selector)
+
+        page.within(lock_sidebar_block) do
+          find(collapsed_sidebar_block_icon).click
+        end
+
+        expect(page).to have_css(expanded_sidebar_selector)
+
+        page.within(lock_sidebar_block) do
+          page.find('button', text: 'Cancel').click
+        end
+
+        expect(page).to have_css(collapsed_sidebar_selector)
+      end
+    end
   end
 
   context 'as a guest' do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 076a02150a403b823505aeff895fa94393b6b25b..3c01ff345fc4b254a4650c73cd5ec1b7d81a8e5c 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -73,7 +73,7 @@ feature 'issue move to another project' do
         wait_for_requests
 
         page.within '.js-sidebar-move-issue-block' do
-          expect(page).to have_content new_project.name_with_namespace
+          expect(page).to have_content new_project.full_name
         end
       end
     end
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index a75ca1d42b3fb54761617280aea9029bd66a8394..73022afbda21cc7148b1423940e1f75db5ab093f 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -34,9 +34,6 @@ describe 'New issue', :js do
 
       click_button 'Submit issue'
 
-      # reCAPTCHA alerts when it can't contact the server, so just accept it and move on
-      page.driver.browser.switch_to.alert.accept
-
       # it is impossible to test recaptcha automatically and there is no possibility to fill in recaptcha
       # recaptcha verification is skipped in test environment and it always returns true
       expect(page).not_to have_content('issue title')
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index 8e6493bbd930bedb375e0ca4857111e2af59821a..4a44ec302fc37ff8c850490f8c61879f6c11ae0e 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -14,7 +14,7 @@ feature 'Manually create a todo item from issue', :js do
   it 'creates todo when clicking button' do
     page.within '.issuable-sidebar' do
       click_button 'Add todo'
-      expect(page).to have_content 'Mark done'
+      expect(page).to have_content 'Mark todo as done'
     end
 
     page.within '.header-content .todos-count' do
@@ -31,7 +31,7 @@ feature 'Manually create a todo item from issue', :js do
   it 'marks a todo as done' do
     page.within '.issuable-sidebar' do
       click_button 'Add todo'
-      click_button 'Mark done'
+      click_button 'Mark todo as done'
     end
 
     expect(page).to have_selector('.todos-count', visible: false)
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index e711a191db222167c7c2bbade79702460a0e44fa..ff2a0e1571934ec677bbeed55347033edf3f375f 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 feature 'Issues > User uses quick actions', :js do
-  include QuickActionsHelpers
+  include Spec::Support::Helpers::Features::NotesHelpers
 
   it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do
     let(:issuable) { create(:issue, project: project) }
@@ -36,7 +36,7 @@ feature 'Issues > User uses quick actions', :js do
 
       context 'when the current user can update the due date' do
         it 'does not create a note, and sets the due date accordingly' do
-          write_note("/due 2016-08-28")
+          add_note("/due 2016-08-28")
 
           expect(page).not_to have_content '/due 2016-08-28'
           expect(page).to have_content 'Commands applied'
@@ -57,9 +57,8 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'does not create a note, and sets the due date accordingly' do
-          write_note("/due 2016-08-28")
+          add_note("/due 2016-08-28")
 
-          expect(page).to have_content '/due 2016-08-28'
           expect(page).not_to have_content 'Commands applied'
 
           issue.reload
@@ -76,7 +75,7 @@ feature 'Issues > User uses quick actions', :js do
         it 'does not create a note, and removes the due date accordingly' do
           expect(issue.due_date).to eq Date.new(2016, 8, 28)
 
-          write_note("/remove_due_date")
+          add_note("/remove_due_date")
 
           expect(page).not_to have_content '/remove_due_date'
           expect(page).to have_content 'Commands applied'
@@ -97,9 +96,8 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'does not create a note, and sets the due date accordingly' do
-          write_note("/remove_due_date")
+          add_note("/remove_due_date")
 
-          expect(page).to have_content '/remove_due_date'
           expect(page).not_to have_content 'Commands applied'
 
           issue.reload
@@ -113,7 +111,7 @@ feature 'Issues > User uses quick actions', :js do
       let(:issue) { create(:issue, project: project) }
 
       it 'does not recognize the command nor create a note' do
-        write_note("/wip")
+        add_note("/wip")
 
         expect(page).not_to have_content '/wip'
       end
@@ -125,7 +123,7 @@ feature 'Issues > User uses quick actions', :js do
 
       context 'when the current user can update issues' do
         it 'does not create a note, and marks the issue as a duplicate' do
-          write_note("/duplicate ##{original_issue.to_reference}")
+          add_note("/duplicate ##{original_issue.to_reference}")
 
           expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
           expect(page).to have_content 'Commands applied'
@@ -145,9 +143,8 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'does not create a note, and does not mark the issue as a duplicate' do
-          write_note("/duplicate ##{original_issue.to_reference}")
+          add_note("/duplicate ##{original_issue.to_reference}")
 
-          expect(page).to have_content "/duplicate ##{original_issue.to_reference}"
           expect(page).not_to have_content 'Commands applied'
           expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
 
@@ -169,7 +166,7 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'moves the issue' do
-          write_note("/move #{target_project.full_path}")
+          add_note("/move #{target_project.full_path}")
 
           expect(page).to have_content 'Commands applied'
           expect(issue.reload).to be_closed
@@ -189,7 +186,7 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'does not move the issue' do
-          write_note("/move #{project_unauthorized.full_path}")
+          add_note("/move #{project_unauthorized.full_path}")
 
           expect(page).not_to have_content 'Commands applied'
           expect(issue.reload).to be_open
@@ -203,7 +200,7 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'does not move the issue' do
-          write_note("/move not/valid")
+          add_note("/move not/valid")
 
           expect(page).not_to have_content 'Commands applied'
           expect(issue.reload).to be_open
@@ -226,7 +223,7 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'applies the commands to both issues and moves the issue' do
-          write_note("/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"\n\n/move #{target_project.full_path}")
+          add_note("/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"\n\n/move #{target_project.full_path}")
 
           expect(page).to have_content 'Commands applied'
           expect(issue.reload).to be_closed
@@ -245,7 +242,7 @@ feature 'Issues > User uses quick actions', :js do
         end
 
         it 'moves the issue and applies the commands to both issues' do
-          write_note("/move #{target_project.full_path}\n\n/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"")
+          add_note("/move #{target_project.full_path}\n\n/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"")
 
           expect(page).to have_content 'Commands applied'
           expect(issue.reload).to be_closed
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e05e7b7f38264a436b44e1bbad90612884d0ff9
--- /dev/null
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -0,0 +1,305 @@
+require 'spec_helper'
+
+feature 'Labels Hierarchy', :js, :nested_groups do
+  include FilteredSearchHelpers
+
+  let!(:user) { create(:user) }
+  let!(:grandparent) { create(:group) }
+  let!(:parent) { create(:group, parent: grandparent) }
+  let!(:child) { create(:group, parent: parent) }
+  let!(:project_1) { create(:project, namespace: parent) }
+
+  let!(:grandparent_group_label) { create(:group_label, group: grandparent, title: 'Label_1') }
+  let!(:parent_group_label) { create(:group_label, group: parent, title: 'Label_2') }
+  let!(:child_group_label) { create(:group_label, group: child, title: 'Label_3') }
+  let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') }
+
+  before do
+    grandparent.add_owner(user)
+
+    sign_in(user)
+  end
+
+  shared_examples 'assigning labels from sidebar' do
+    it 'can assign all ancestors labels' do
+      [grandparent_group_label, parent_group_label, project_label_1].each do |label|
+        page.within('.block.labels') do
+          find('.edit-link').click
+        end
+
+        wait_for_requests
+
+        find('a.label-item', text: label.title).click
+        find('.dropdown-menu-close-icon').click
+
+        wait_for_requests
+
+        expect(page).to have_selector('span.label', text: label.title)
+      end
+    end
+
+    it 'does not find child group labels on dropdown' do
+      page.within('.block.labels') do
+        find('.edit-link').click
+      end
+
+      wait_for_requests
+
+      expect(page).not_to have_selector('span.label', text: child_group_label.title)
+    end
+  end
+
+  shared_examples 'filtering by ancestor labels for projects' do |board = false|
+    it 'filters by ancestor labels' do
+      [grandparent_group_label, parent_group_label, project_label_1].each do |label|
+        select_label_on_dropdown(label.title)
+
+        wait_for_requests
+
+        if board
+          expect(page).to have_selector('.card-title') do |card|
+            expect(card).to have_selector('a', text: labeled_issue.title)
+          end
+        else
+          expect_issues_list_count(1)
+          expect(page).to have_selector('span.issue-title-text', text: labeled_issue.title)
+        end
+      end
+    end
+
+    it 'does not filter by descendant group labels' do
+      filtered_search.set("label:")
+
+      wait_for_requests
+
+      expect(page).not_to have_selector('.btn-link', text: child_group_label.title)
+    end
+  end
+
+  shared_examples 'filtering by ancestor labels for groups' do |board = false|
+    let(:project_2) { create(:project, namespace: parent) }
+    let!(:project_label_2) { create(:label, project: project_2, title: 'Label_4') }
+
+    let(:project_3) { create(:project, namespace: child) }
+    let!(:group_label_3) { create(:group_label, group: child, title: 'Label_5') }
+    let!(:project_label_3) { create(:label, project: project_3, title: 'Label_6') }
+
+    let!(:labeled_issue_2) { create(:labeled_issue, project: project_2, labels: [grandparent_group_label, parent_group_label, project_label_2]) }
+    let!(:labeled_issue_3) { create(:labeled_issue, project: project_3, labels: [grandparent_group_label, parent_group_label, group_label_3]) }
+
+    let!(:issue_2) { create(:issue, project: project_2) }
+
+    it 'filters by ancestors and current group labels' do
+      [grandparent_group_label, parent_group_label].each do |label|
+        select_label_on_dropdown(label.title)
+
+        wait_for_requests
+
+        if board
+          expect(page).to have_selector('.card-title') do |card|
+            expect(card).to have_selector('a', text: labeled_issue.title)
+          end
+
+          expect(page).to have_selector('.card-title') do |card|
+            expect(card).to have_selector('a', text: labeled_issue_2.title)
+          end
+        else
+          expect_issues_list_count(3)
+          expect(page).to have_selector('span.issue-title-text', text: labeled_issue.title)
+          expect(page).to have_selector('span.issue-title-text', text: labeled_issue_2.title)
+          expect(page).to have_selector('span.issue-title-text', text: labeled_issue_3.title)
+        end
+      end
+    end
+
+    it 'filters by descendant group labels' do
+      wait_for_requests
+
+      select_label_on_dropdown(group_label_3.title)
+
+      if board
+        expect(page).to have_selector('.card-title') do |card|
+          expect(card).not_to have_selector('a', text: labeled_issue_2.title)
+        end
+
+        expect(page).to have_selector('.card-title') do |card|
+          expect(card).to have_selector('a', text: labeled_issue_3.title)
+        end
+      else
+        expect_issues_list_count(1)
+        expect(page).to have_selector('span.issue-title-text', text: labeled_issue_3.title)
+      end
+    end
+
+    it 'does not filter by descendant group project labels' do
+      filtered_search.set("label:")
+
+      wait_for_requests
+
+      expect(page).not_to have_selector('.btn-link', text: project_label_3.title)
+    end
+  end
+
+  context 'when creating new issuable' do
+    before do
+      visit new_project_issue_path(project_1)
+    end
+
+    it 'should be able to assign ancestor group labels' do
+      fill_in 'issue_title', with: 'new created issue'
+      fill_in 'issue_description', with: 'new issue description'
+
+      find(".js-label-select").click
+      wait_for_requests
+
+      find('a.label-item', text: grandparent_group_label.title).click
+      find('a.label-item', text: parent_group_label.title).click
+      find('a.label-item', text: project_label_1.title).click
+
+      find('.btn-create').click
+
+      expect(page.find('.issue-details h2.title')).to have_content('new created issue')
+      expect(page).to have_selector('span.label', text: grandparent_group_label.title)
+      expect(page).to have_selector('span.label', text: parent_group_label.title)
+      expect(page).to have_selector('span.label', text: project_label_1.title)
+    end
+  end
+
+  context 'issuable sidebar' do
+    let!(:issue) { create(:issue, project: project_1) }
+
+    context 'on issue sidebar' do
+      before do
+        visit project_issue_path(project_1, issue)
+      end
+
+      it_behaves_like 'assigning labels from sidebar'
+    end
+
+    context 'on project board issue sidebar' do
+      let(:board)   { create(:board, project: project_1) }
+
+      before do
+        visit project_board_path(project_1, board)
+
+        wait_for_requests
+
+        find('.card').click
+      end
+
+      it_behaves_like 'assigning labels from sidebar'
+    end
+
+    context 'on group board issue sidebar' do
+      let(:board)   { create(:board, group: parent) }
+
+      before do
+        visit group_board_path(parent, board)
+
+        wait_for_requests
+
+        find('.card').click
+      end
+
+      it_behaves_like 'assigning labels from sidebar'
+    end
+  end
+
+  context 'issuable filtering' do
+    let!(:labeled_issue) { create(:labeled_issue, project: project_1, labels: [grandparent_group_label, parent_group_label, project_label_1]) }
+    let!(:issue) { create(:issue, project: project_1) }
+
+    context 'on project issuable list' do
+      before do
+        visit project_issues_path(project_1)
+      end
+
+      it_behaves_like 'filtering by ancestor labels for projects'
+
+      it 'does not filter by descendant group labels' do
+        filtered_search.set("label:")
+
+        wait_for_requests
+
+        expect(page).not_to have_selector('.btn-link', text: child_group_label.title)
+      end
+    end
+
+    context 'on group issuable list' do
+      before do
+        visit issues_group_path(parent)
+      end
+
+      it_behaves_like 'filtering by ancestor labels for groups'
+    end
+
+    context 'on project boards filter' do
+      let(:board) { create(:board, project: project_1) }
+
+      before do
+        visit project_board_path(project_1, board)
+      end
+
+      it_behaves_like 'filtering by ancestor labels for projects', true
+    end
+
+    context 'on group boards filter' do
+      let(:board) { create(:board, group: parent) }
+
+      before do
+        visit group_board_path(parent, board)
+      end
+
+      it_behaves_like 'filtering by ancestor labels for groups', true
+    end
+  end
+
+  context 'creating boards lists' do
+    context 'on project boards' do
+      let(:board) { create(:board, project: project_1) }
+
+      before do
+        visit project_board_path(project_1, board)
+        find('.js-new-board-list').click
+        wait_for_requests
+      end
+
+      it 'creates lists from all ancestor labels' do
+        [grandparent_group_label, parent_group_label, project_label_1].each do |label|
+          find('a', text: label.title).click
+        end
+
+        wait_for_requests
+
+        expect(page).to have_selector('.board-title-text', text: grandparent_group_label.title)
+        expect(page).to have_selector('.board-title-text', text: parent_group_label.title)
+        expect(page).to have_selector('.board-title-text', text: project_label_1.title)
+      end
+    end
+
+    context 'on group boards' do
+      let(:board) { create(:board, group: parent) }
+
+      before do
+        visit group_board_path(parent, board)
+        find('.js-new-board-list').click
+        wait_for_requests
+      end
+
+      it 'creates lists from all ancestor group labels' do
+        [grandparent_group_label, parent_group_label].each do |label|
+          find('a', text: label.title).click
+        end
+
+        wait_for_requests
+
+        expect(page).to have_selector('.board-title-text', text: grandparent_group_label.title)
+        expect(page).to have_selector('.board-title-text', text: parent_group_label.title)
+      end
+
+      it 'does not create lists from descendant groups' do
+        expect(page).not_to have_selector('a', text: child_group_label.title)
+      end
+    end
+  end
+end
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index f82ed6300cc7f307ad8c369f319d064db6f5d0fc..4d897f09b5706fd58acb7cf5c0ef0c56587faaff 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -20,7 +20,7 @@ describe 'Copy as GFM', :js do
     end
 
     # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
-    # The handlers defined in app/assets/javascripts/copy_as_gfm.js consequently convert that same HTML to GFM.
+    # The handlers defined in app/assets/javascripts/behaviors/markdown/copy_as_gfm.js consequently convert that same HTML to GFM.
     # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle
     # by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper.
 
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a3323da1b1f210aafeb57cbd46de8f8b34721895
--- /dev/null
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe 'a maintainer edits files on a source-branch of an MR from a fork', :js do
+  include ProjectForksHelper
+  let(:user) { create(:user, username: 'the-maintainer') }
+  let(:target_project) { create(:project, :public, :repository) }
+  let(:author) { create(:user, username: 'mr-authoring-machine') }
+  let(:source_project) { fork_project(target_project, author, repository: true) }
+
+  let(:merge_request) do
+    create(:merge_request,
+           source_project: source_project,
+           target_project: target_project,
+           source_branch: 'fix',
+           target_branch: 'master',
+           author: author,
+           allow_maintainer_to_push: true)
+  end
+
+  before do
+    target_project.add_master(user)
+    sign_in(user)
+
+    visit project_merge_request_path(target_project, merge_request)
+    click_link 'Changes'
+    wait_for_requests
+    first('.js-file-title').click_link 'Edit'
+    wait_for_requests
+  end
+
+  it 'mentions commits will go to the source branch' do
+    expect(page).to have_content('Your changes can be committed to fix because a merge request is open.')
+  end
+
+  it 'allows committing to the source branch' do
+    find('.ace_text-input', visible: false).send_keys('Updated the readme')
+
+    click_button 'Commit changes'
+    wait_for_requests
+
+    expect(page).to have_content('Your changes have been successfully committed')
+    expect(page).to have_content('Updated the readme')
+  end
+end
diff --git a/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb b/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb41d7de8edcaa63882ef207f5d0107ea70cc182
--- /dev/null
+++ b/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe 'create a merge request that allows maintainers to push', :js do
+  include ProjectForksHelper
+  let(:user) { create(:user) }
+  let(:target_project) { create(:project, :public, :repository) }
+  let(:source_project) { fork_project(target_project, user, repository: true, namespace: user.namespace) }
+
+  def visit_new_merge_request
+    visit project_new_merge_request_path(
+      source_project,
+      merge_request: {
+        source_project_id: source_project.id,
+        target_project_id: target_project.id,
+        source_branch: 'fix',
+        target_branch: 'master'
+      })
+  end
+
+  before do
+    sign_in(user)
+  end
+
+  it 'allows setting maintainer push possible' do
+    visit_new_merge_request
+
+    check 'Allow edits from maintainers'
+
+    click_button 'Submit merge request'
+
+    wait_for_requests
+
+    expect(page).to have_content('Allows edits from maintainers')
+  end
+
+  it 'shows a message when one of the projects is private' do
+    source_project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+
+    visit_new_merge_request
+
+    expect(page).to have_content('Not available for private projects')
+  end
+
+  it 'shows a message when the source branch is protected' do
+    create(:protected_branch, project: source_project, name: 'fix')
+
+    visit_new_merge_request
+
+    expect(page).to have_content('Not available for protected branches')
+  end
+
+  context 'when the merge request is being created within the same project' do
+    let(:source_project) { target_project }
+
+    it 'hides the checkbox if the merge request is being created within the same project' do
+      target_project.add_developer(user)
+
+      visit_new_merge_request
+
+      expect(page).not_to have_content('Allows edits from maintainers')
+    end
+  end
+
+  context 'when a maintainer tries to edit the option' do
+    let(:maintainer) { create(:user) }
+    let(:merge_request) do
+      create(:merge_request,
+             source_project: source_project,
+             target_project: target_project,
+             source_branch: 'fixes')
+    end
+
+    before do
+      target_project.add_master(maintainer)
+
+      sign_in(maintainer)
+    end
+
+    it 'it hides the option from maintainers' do
+      visit edit_project_merge_request_path(target_project, merge_request)
+
+      expect(page).not_to have_content('Allows edits from maintainers')
+    end
+  end
+end
diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index 2f24cfbd9e3bc86138c6aa16f347127fbe33103e..859a4c65562433f9b282fc0374c5d697c02a1984 100644
--- a/spec/features/merge_request/user_awards_emoji_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -35,6 +35,14 @@ describe 'Merge request > User awards emoji', :js do
 
       expect(page).to have_selector('.emoji-menu', count: 1)
     end
+
+    describe 'the project is archived' do
+      let(:project) { create(:project, :public, :repository, :archived) }
+
+      it 'does not see award menu button' do
+        expect(page).not_to have_selector('.js-award-holder')
+      end
+    end
   end
 
   describe 'logged out' do
diff --git a/spec/features/merge_request/user_cherry_picks_spec.rb b/spec/features/merge_request/user_cherry_picks_spec.rb
index 494096b21c041263f94e82889c286bb2346882b7..61d1bdaa95aba69ac6f6ceaf30a0b5a09bc86e01 100644
--- a/spec/features/merge_request/user_cherry_picks_spec.rb
+++ b/spec/features/merge_request/user_cherry_picks_spec.rb
@@ -40,6 +40,14 @@ describe 'Merge request > User cherry-picks', :js do
 
         expect(page).to have_link 'Cherry-pick'
       end
+
+      it 'hides the cherry pick button for an archived project' do
+        project.update!(archived: true)
+
+        visit project_merge_request_path(project, merge_request)
+
+        expect(page).not_to have_link 'Cherry-pick'
+      end
     end
   end
 end
diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index 890774922aae5cfd46f69a6397eb65eda72a2912..db92a3504f31efa9fde21e34ce521601a01cba60 100644
--- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -125,6 +125,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
       expect(page).to have_content "canceled the automatic merge"
     end
 
+    it 'allows to remove source branch' do
+      click_link "Remove source branch"
+
+      expect(page).to have_content "The source branch will be removed"
+    end
+
     context 'when pipeline succeeds' do
       before do
         build.success
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 3e83a54968297fd8f57f568dd31c401706e0087a..b4ad4b64d8ec06bfc5182a0daa3810847fa4b150 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -108,6 +108,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
           it 'shows resolved discussion when toggled' do
             find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click
 
+            expect(page.find(".line-holder-placeholder")).to be_visible
             expect(page.find(".timeline-content #note_#{note.id}")).to be_visible
           end
         end
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
index 8a834adbf177465228e27cb4add8030dfe8709ab..3b6fffb7abdd658c4b945d0cd425e7618e8dddf6 100644
--- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -5,15 +5,18 @@ describe 'Merge request > User scrolls to note on load', :js do
   let(:user) { project.creator }
   let(:merge_request) { create(:merge_request, source_project: project, author: user) }
   let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+  let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
   let(:fragment_id) { "#note_#{note.id}" }
+  let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" }
 
   before do
     sign_in(user)
     page.current_window.resize_to(1000, 300)
-    visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
   end
 
-  it 'scrolls down to fragment' do
+  it 'scrolls note into view' do
+    visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
+
     page_height = page.current_window.size[1]
     page_scroll_y = page.evaluate_script("window.scrollY")
     fragment_position_top = page.evaluate_script("Math.round($('#{fragment_id}').offset().top)")
@@ -23,4 +26,30 @@ describe 'Merge request > User scrolls to note on load', :js do
     expect(fragment_position_top).to be >= page_scroll_y
     expect(fragment_position_top).to be < (page_scroll_y + page_height)
   end
+
+  it 'renders un-collapsed notes with diff' do
+    page.current_window.resize_to(1000, 1000)
+
+    visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
+
+    page.execute_script "window.scrollTo(0,0)"
+
+    note_element = find(fragment_id)
+    note_container = note_element.ancestor('.js-toggle-container')
+
+    expect(note_element.visible?).to eq true
+
+    page.within note_container do
+      expect(page).not_to have_selector('.js-error-lazy-load-diff')
+    end
+  end
+
+  it 'expands collapsed notes' do
+    visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
+    note_element = find(collapsed_fragment_id)
+    note_container = note_element.ancestor('.js-toggle-container')
+
+    expect(note_element.visible?).to eq true
+    expect(note_container.find('.line_content.noteable_line.old', match: :first).visible?).to eq true
+  end
 end
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 3abe363d523a871d0a18481de406cbc7e60675f8..f744d7941f531f9e0e37e9a50983e691913a67b9 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -22,7 +22,7 @@ describe 'Merge request > User sees deployment widget', :js do
       wait_for_requests
 
       expect(page).to have_content("Deployed to #{environment.name}")
-      expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
+      expect(find('.js-deploy-time')['data-original-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
     end
 
     context 'with stop action' do
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 56224e505d95f0715514bde194440c78d5a97ec8..51a65407aec5ba947cb1e3b33ded50232faa3dd5 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -1,6 +1,8 @@
 require 'rails_helper'
 
 describe 'Merge request > User sees merge widget', :js do
+  include ProjectForksHelper
+
   let(:project) { create(:project, :repository) }
   let(:project_only_mwps) { create(:project, :repository, only_allow_merge_if_pipeline_succeeds: true) }
   let(:user) { project.creator }
@@ -285,7 +287,29 @@ describe 'Merge request > User sees merge widget', :js do
     end
 
     it 'user cannot remove source branch' do
-      expect(page).to have_field('remove-source-branch-input', disabled: true)
+      expect(page).not_to have_field('remove-source-branch-input')
+    end
+  end
+
+  context 'user cannot merge project and cannot push to fork', :js do
+    let(:forked_project) { fork_project(project, nil, repository: true) }
+    let(:user2) { create(:user) }
+
+    before do
+      project.add_developer(user2)
+      sign_out(:user)
+      sign_in(user2)
+      merge_request.update(
+        source_project: forked_project,
+        target_project: project,
+        merge_params: { 'force_remove_source_branch' => '1' }
+      )
+      visit project_merge_request_path(project, merge_request)
+    end
+
+    it 'user cannot remove source branch' do
+      expect(page).not_to have_field('remove-source-branch-input')
+      expect(page).to have_content('Removes source branch')
     end
   end
 
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index a43ba05c64c6971380b96b46a1f7b3b124442053..fd1629746ef4a2cfd38418b54c42b5e7048dd35c 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -9,6 +9,7 @@ describe 'Merge request < User sees mini pipeline graph', :js do
 
   before do
     build.run
+    build.trace.set('hello')
     sign_in(user)
     visit_merge_request
   end
@@ -26,15 +27,15 @@ describe 'Merge request < User sees mini pipeline graph', :js do
     let(:artifacts_file2) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
 
     before do
-      create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
-      create(:ci_build, pipeline: pipeline, when: 'manual')
+      create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
+      create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
     end
 
     it 'avoids repeated database queries' do
       before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
 
-      create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
-      create(:ci_build, pipeline: pipeline, when: 'manual')
+      create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
+      create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
 
       after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
 
diff --git a/spec/features/merge_request/user_uses_slash_commands_spec.rb b/spec/features/merge_request/user_uses_slash_commands_spec.rb
index bd739e69d6c0a6675f4844a4f2d85a2ecd6ffd85..7f261b580f73e87c359bd4e466499ab584054bb1 100644
--- a/spec/features/merge_request/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_request/user_uses_slash_commands_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 describe 'Merge request > User uses quick actions', :js do
-  include QuickActionsHelpers
+  include Spec::Support::Helpers::Features::NotesHelpers
 
   let(:project) { create(:project, :public, :repository) }
   let(:user) { project.creator }
@@ -33,7 +33,7 @@ describe 'Merge request > User uses quick actions', :js do
     describe 'toggling the WIP prefix in the title from note' do
       context 'when the current user can toggle the WIP prefix' do
         it 'adds the WIP: prefix to the title' do
-          write_note("/wip")
+          add_note("/wip")
 
           expect(page).not_to have_content '/wip'
           expect(page).to have_content 'Commands applied'
@@ -44,7 +44,7 @@ describe 'Merge request > User uses quick actions', :js do
         it 'removes the WIP: prefix from the title' do
           merge_request.title = merge_request.wip_title
           merge_request.save
-          write_note("/wip")
+          add_note("/wip")
 
           expect(page).not_to have_content '/wip'
           expect(page).to have_content 'Commands applied'
@@ -62,7 +62,7 @@ describe 'Merge request > User uses quick actions', :js do
         end
 
         it 'does not change the WIP prefix' do
-          write_note("/wip")
+          add_note("/wip")
 
           expect(page).not_to have_content '/wip'
           expect(page).not_to have_content 'Commands applied'
@@ -75,7 +75,7 @@ describe 'Merge request > User uses quick actions', :js do
     describe 'merging the MR from the note' do
       context 'when the current user can merge the MR' do
         it 'merges the MR' do
-          write_note("/merge")
+          add_note("/merge")
 
           expect(page).to have_content 'Commands applied'
 
@@ -90,7 +90,7 @@ describe 'Merge request > User uses quick actions', :js do
         end
 
         it 'does not merge the MR' do
-          write_note("/merge")
+          add_note("/merge")
 
           expect(page).not_to have_content 'Your commands have been executed!'
 
@@ -107,7 +107,7 @@ describe 'Merge request > User uses quick actions', :js do
         end
 
         it 'does not merge the MR' do
-          write_note("/merge")
+          add_note("/merge")
 
           expect(page).not_to have_content 'Your commands have been executed!'
 
@@ -118,7 +118,7 @@ describe 'Merge request > User uses quick actions', :js do
 
     describe 'adding a due date from note' do
       it 'does not recognize the command nor create a note' do
-        write_note('/due 2016-08-28')
+        add_note('/due 2016-08-28')
 
         expect(page).not_to have_content '/due 2016-08-28'
       end
@@ -162,7 +162,7 @@ describe 'Merge request > User uses quick actions', :js do
     describe '/target_branch command from note' do
       context 'when the current user can change target branch' do
         it 'changes target branch from a note' do
-          write_note("message start \n/target_branch merge-test\n message end.")
+          add_note("message start \n/target_branch merge-test\n message end.")
 
           wait_for_requests
           expect(page).not_to have_content('/target_branch')
@@ -173,7 +173,7 @@ describe 'Merge request > User uses quick actions', :js do
         end
 
         it 'does not fail when target branch does not exists' do
-          write_note('/target_branch totally_not_existing_branch')
+          add_note('/target_branch totally_not_existing_branch')
 
           expect(page).not_to have_content('/target_branch')
 
@@ -190,7 +190,7 @@ describe 'Merge request > User uses quick actions', :js do
         end
 
         it 'does not change target branch' do
-          write_note('/target_branch merge-test')
+          add_note('/target_branch merge-test')
 
           expect(page).not_to have_content '/target_branch merge-test'
 
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index cc12a1005baeb39a1531e0ce72f0616a81840ff5..6c51e4bbe268a3fcf98c281b9d6120e21699891b 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -97,4 +97,29 @@ feature 'Milestone' do
       end
     end
   end
+
+  feature 'Deleting a milestone' do
+    scenario "The delete milestone button does not show for unauthorized users" do
+      create(:milestone, project: project, title: 8.7)
+      sign_out(user)
+
+      visit group_milestones_path(group)
+
+      expect(page).to have_selector('.js-delete-milestone-button', count: 0)
+    end
+  end
+
+  feature 'deprecation popover', :js do
+    it 'opens deprecation popover' do
+      milestone = create(:milestone, project: project)
+
+      visit group_milestone_path(group, milestone, title: milestone.title)
+
+      expect(page).to have_selector('.milestone-deprecation-message')
+
+      find('.milestone-deprecation-message .js-popover-link').click
+
+      expect(page).to have_selector('.milestone-deprecation-message .popover')
+    end
+  end
 end
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
deleted file mode 100644
index 50c5e0bb65facd6858205792041cea7aa17d3dc0..0000000000000000000000000000000000000000
--- a/spec/features/milestones/show_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'rails_helper'
-
-describe 'Milestone show' do
-  let(:user) { create(:user) }
-  let(:project) { create(:project) }
-  let(:milestone) { create(:milestone, project: project) }
-  let(:labels) { create_list(:label, 2, project: project) }
-  let(:issue_params) { { project: project, assignees: [user], author: user, milestone: milestone, labels: labels } }
-
-  before do
-    project.add_user(user, :developer)
-    sign_in(user)
-  end
-
-  def visit_milestone
-    visit project_milestone_path(project, milestone)
-  end
-
-  it 'avoids N+1 database queries' do
-    create(:labeled_issue, issue_params)
-    control = ActiveRecord::QueryRecorder.new { visit_milestone }
-    create_list(:labeled_issue, 10, issue_params)
-
-    expect { visit_milestone }.not_to exceed_query_limit(control)
-  end
-end
diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8fd057d587cba2a3e3454a562b1f273fa2e1568b
--- /dev/null
+++ b/spec/features/milestones/user_creates_milestone_spec.rb
@@ -0,0 +1,29 @@
+require "rails_helper"
+
+describe "User creates milestone", :js do
+  set(:user) { create(:user) }
+  set(:project) { create(:project) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(new_project_milestone_path(project))
+  end
+
+  it "creates milestone" do
+    TITLE = "v2.3".freeze
+
+    fill_in("Title", with: TITLE)
+    fill_in("Description", with: "# Description header")
+    click_button("Create milestone")
+
+    expect(page).to have_content(TITLE)
+      .and have_content("Issues")
+      .and have_header_with_correct_id_and_link(1, "Description header", "description-header")
+
+    visit(activity_project_path(project))
+
+    expect(page).to have_content("#{user.name} opened milestone")
+  end
+end
diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..414702daba40701f749ad4385d2327d006babf58
--- /dev/null
+++ b/spec/features/milestones/user_deletes_milestone_spec.rb
@@ -0,0 +1,25 @@
+require "rails_helper"
+
+describe "User deletes milestone", :js do
+  set(:user) { create(:user) }
+  set(:project) { create(:project) }
+  set(:milestone) { create(:milestone, project: project) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(project_milestones_path(project))
+  end
+
+  it "deletes milestone" do
+    click_button("Delete")
+    click_button("Delete milestone")
+
+    expect(page).to have_content("No milestones to show")
+
+    visit(activity_project_path(project))
+
+    expect(page).to have_content("#{user.name} destroyed milestone")
+  end
+end
diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..83d8e2ff9e96cb565a3a9a9fd39ace77c3d920d3
--- /dev/null
+++ b/spec/features/milestones/user_views_milestone_spec.rb
@@ -0,0 +1,31 @@
+require "rails_helper"
+
+describe "User views milestone" do
+  set(:user) { create(:user) }
+  set(:project) { create(:project) }
+  set(:milestone) { create(:milestone, project: project) }
+  set(:labels) { create_list(:label, 2, project: project) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+  end
+
+  it "avoids N+1 database queries" do
+    ISSUE_PARAMS = { project: project, assignees: [user], author: user, milestone: milestone, labels: labels }.freeze
+
+    create(:labeled_issue, ISSUE_PARAMS)
+
+    control = ActiveRecord::QueryRecorder.new { visit_milestone }
+
+    create(:labeled_issue, ISSUE_PARAMS)
+
+    expect { visit_milestone }.not_to exceed_query_limit(control)
+  end
+
+  private
+
+  def visit_milestone
+    visit(project_milestone_path(project, milestone))
+  end
+end
diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bebe40f73fd085e6560296d69b5e2f4582074f47
--- /dev/null
+++ b/spec/features/milestones/user_views_milestones_spec.rb
@@ -0,0 +1,35 @@
+require "rails_helper"
+
+describe "User views milestones" do
+  set(:user) { create(:user) }
+  set(:project) { create(:project) }
+  set(:milestone) { create(:milestone, project: project) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(project_milestones_path(project))
+  end
+
+  it "shows milestone" do
+    expect(page).to have_content(milestone.title)
+      .and have_content(milestone.expires_at)
+      .and have_content("Issues")
+  end
+
+  context "with issues" do
+    set(:issue) { create(:issue, project: project, milestone: milestone) }
+    set(:closed_issue) { create(:closed_issue, project: project, milestone: milestone) }
+
+    it "opens milestone" do
+      click_link(milestone.title)
+
+      expect(current_path).to eq(project_milestone_path(project, milestone))
+      expect(page).to have_content(milestone.title)
+        .and have_selector("#tab-issues li.issuable-row", count: 2)
+        .and have_content(issue.title)
+        .and have_content(closed_issue.title)
+    end
+  end
+end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 0848857ed1ee65566f2806565fc51c00db0f6ec8..15dcb30cbddc8816b81e38af16382e42ccca9784 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -97,9 +97,13 @@ describe 'Profile account page', :js do
     end
 
     it 'changes my username' do
-      fill_in 'user_username', with: 'new-username'
+      fill_in 'username-change-input', with: 'new-username'
 
-      click_button('Update username')
+      page.find('[data-target="#username-change-confirmation-modal"]').click
+
+      page.within('.modal') do
+        find('.js-modal-primary-action').click
+      end
 
       expect(page).to have_content('new-username')
     end
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 171e061e60e84afe506ff8bb19d9cf882e11eac1..215b658eb7b9a6a81e5233901736daffc68b671c 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-feature 'Profile > Account' do
+feature 'Profile > Account', :js do
   given(:user) { create(:user, username: 'foo') }
 
   before do
@@ -43,14 +43,14 @@ feature 'Profile > Account' do
         update_username(new_username)
         visit new_project_path
         expect(current_path).to eq(new_project_path)
-        expect(find('.breadcrumbs-sub-title')).to have_content(project.path)
+        expect(find('.breadcrumbs-sub-title')).to have_content('Details')
       end
 
       scenario 'the old project path redirects to the new path' do
         update_username(new_username)
         visit old_project_path
         expect(current_path).to eq(new_project_path)
-        expect(find('.breadcrumbs-sub-title')).to have_content(project.path)
+        expect(find('.breadcrumbs-sub-title')).to have_content('Details')
       end
     end
   end
@@ -59,6 +59,12 @@ end
 def update_username(new_username)
   allow(user.namespace).to receive(:move_dir)
   visit profile_account_path
-  fill_in 'user_username', with: new_username
-  click_button 'Update username'
+
+  fill_in 'username-change-input', with: new_username
+
+  page.find('[data-target="#username-change-confirmation-modal"]').click
+
+  page.within('.modal') do
+    find('.js-modal-primary-action').click
+  end
 end
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
index 1952fdae798d09bfd9f978046abec756096b3dd4..95953fbcfac7f4e7534d83ce4f5f8579ae6dc64f 100644
--- a/spec/features/profiles/user_visits_notifications_tab_spec.rb
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -16,6 +16,6 @@ feature 'User visits the notifications tab', :js do
     first('#notifications-button').click
     click_link('On mention')
 
-    expect(page).to have_content('On mention')
+    expect(page).to have_selector('#notifications-button', text: 'On mention')
   end
 end
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index 2693e5392681ed4544393eb1712eb4e515b1e30c..cd1cfe0799864efe274c0e9a100eb9bbe448af28 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
 feature 'Project Activity RSS' do
-  let(:user) { create(:user) }
-  let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+  let(:project) { create(:project, :public) }
+  let(:user) { project.owner }
   let(:path) { activity_project_path(project) }
 
   before do
@@ -11,8 +11,7 @@ feature 'Project Activity RSS' do
 
   context 'when signed in' do
     before do
-      project.add_developer(user)
-      sign_in(user)
+      sign_in(project.owner)
       visit path
     end
 
diff --git a/spec/features/projects/activity/user_sees_activity_spec.rb b/spec/features/projects/activity/user_sees_activity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..644a837dc1455d2bcacb1b8671d2e9687c71ddd4
--- /dev/null
+++ b/spec/features/projects/activity/user_sees_activity_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Projects > Activity > User sees activity' do
+  let(:project) { create(:project, :repository, :public) }
+  let(:user) { project.creator }
+
+  before do
+    event = create(:push_event, project: project, author: user)
+    create(:push_event_payload,
+           event: event,
+           action: :created,
+           commit_to: '6d394385cf567f80a8fd85055db1ab4c5295806f',
+           ref: 'fix',
+           commit_count: 1)
+    visit activity_project_path(project)
+  end
+
+  it 'shows the last push in the activity page', :js do
+    expect(page).to have_content "#{user.name} pushed new branch fix"
+  end
+end
diff --git a/spec/features/projects/actve_tabs_spec.rb b/spec/features/projects/actve_tabs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0bda68b83e7ec6886e79999e55e6417b10f7ca98
--- /dev/null
+++ b/spec/features/projects/actve_tabs_spec.rb
@@ -0,0 +1,137 @@
+require 'spec_helper'
+
+describe 'Project active tab' do
+  let(:user) { create :user }
+  let(:project) { create(:project, :repository) }
+
+  before do
+    project.add_master(user)
+    sign_in(user)
+  end
+
+  def click_tab(title)
+    page.within '.sidebar-top-level-items > .active' do
+      click_link(title)
+    end
+  end
+
+  shared_examples 'page has active tab' do |title|
+    it "activates #{title} tab" do
+      expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
+      expect(find('.sidebar-top-level-items > li.active')).to have_content(title)
+    end
+  end
+
+  shared_examples 'page has active sub tab' do |title|
+    it "activates #{title} sub tab" do
+      expect(page).to have_selector('.sidebar-sub-level-items  > li.active:not(.fly-out-top-item)', count: 1)
+      expect(find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)'))
+        .to have_content(title)
+    end
+  end
+
+  context 'on project Home' do
+    before do
+      visit project_path(project)
+    end
+
+    it_behaves_like 'page has active tab', 'Overview'
+    it_behaves_like 'page has active sub tab', 'Details'
+
+    context 'on project Home/Activity' do
+      before do
+        click_tab('Activity')
+      end
+
+      it_behaves_like 'page has active tab', 'Overview'
+      it_behaves_like 'page has active sub tab', 'Activity'
+    end
+  end
+
+  context 'on project Repository' do
+    before do
+      root_ref = project.repository.root_ref
+      visit project_tree_path(project, root_ref)
+    end
+
+    it_behaves_like 'page has active tab', 'Repository'
+
+    %w(Files Commits Graph Compare Charts Branches Tags).each do |sub_menu|
+      context "on project Repository/#{sub_menu}" do
+        before do
+          click_tab(sub_menu)
+        end
+
+        it_behaves_like 'page has active tab', 'Repository'
+        it_behaves_like 'page has active sub tab', sub_menu
+      end
+    end
+  end
+
+  context 'on project Issues' do
+    before do
+      visit project_issues_path(project)
+    end
+
+    it_behaves_like 'page has active tab', 'Issues'
+
+    %w(Milestones Labels).each do |sub_menu|
+      context "on project Issues/#{sub_menu}" do
+        before do
+          click_tab(sub_menu)
+        end
+
+        it_behaves_like 'page has active tab', 'Issues'
+        it_behaves_like 'page has active sub tab', sub_menu
+      end
+    end
+  end
+
+  context 'on project Merge Requests' do
+    before do
+      visit project_merge_requests_path(project)
+    end
+
+    it_behaves_like 'page has active tab', 'Merge Requests'
+  end
+
+  context 'on project Wiki' do
+    before do
+      visit project_wiki_path(project, :home)
+    end
+
+    it_behaves_like 'page has active tab', 'Wiki'
+  end
+
+  context 'on project Members' do
+    before do
+      visit project_project_members_path(project)
+    end
+
+    it_behaves_like 'page has active tab', 'Members'
+  end
+
+  context 'on project Settings' do
+    before do
+      visit edit_project_path(project)
+    end
+
+    context 'on project Settings/Integrations' do
+      before do
+        click_tab('Integrations')
+      end
+
+      it_behaves_like 'page has active tab', 'Settings'
+      it_behaves_like 'page has active sub tab', 'Integrations'
+    end
+
+    context 'on project Settings/Repository' do
+      before do
+        click_tab('Repository')
+      end
+
+      it_behaves_like 'page has active tab', 'Settings'
+      it_behaves_like 'page has active sub tab', 'Repository'
+    end
+  end
+end
diff --git a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
index adff0a10f0e71f332b4ea88182ac040124cdbfeb..12e07647ecd3e43aac46882aafecbf361d1def56 100644
--- a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
+++ b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
@@ -99,6 +99,74 @@ describe 'User interacts with awards in an issue', :js do
       click_button('Comment')
     end
 
-    expect(page).to have_selector('gl-emoji[data-name="smile"]')
+    expect(page).to have_emoji('smile')
+  end
+
+  context 'when a project is archived' do
+    let(:project) { create(:project, :archived) }
+
+    it 'hides the add award button' do
+      page.within('.awards') do
+        expect(page).not_to have_css('.js-add-award')
+      end
+    end
+  end
+
+  context 'awards on a note' do
+    let!(:note) { create(:note, noteable: issue, project: issue.project) }
+    let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') }
+
+    it 'shows the award on the note' do
+      page.within('.note-awards') do
+        expect(page).to have_emoji('100')
+      end
+    end
+
+    it 'allows adding a vote to an award' do
+      page.within('.note-awards') do
+        find('gl-emoji[data-name="100"]').click
+      end
+      wait_for_requests
+
+      expect(note.reload.award_emoji.size).to eq(2)
+    end
+
+    it 'allows adding a new emoji' do
+      page.within('.note-actions') do
+        find('a.js-add-award').click
+      end
+      page.within('.emoji-menu-content') do
+        find('gl-emoji[data-name="8ball"]').click
+      end
+      wait_for_requests
+
+      page.within('.note-awards') do
+        expect(page).to have_emoji('8ball')
+      end
+      expect(note.reload.award_emoji.size).to eq(2)
+    end
+
+    context 'when the project is archived' do
+      let(:project) { create(:project, :archived) }
+
+      it 'hides the buttons for adding new emoji' do
+        page.within('.note-awards') do
+          expect(page).not_to have_css('.award-menu-holder')
+        end
+
+        page.within('.note-actions') do
+          expect(page).not_to have_css('a.js-add-award')
+        end
+      end
+
+      it 'does not allow toggling existing emoji' do
+        page.within('.note-awards') do
+          find('gl-emoji[data-name="100"]').click
+        end
+        wait_for_requests
+
+        expect(note.reload.award_emoji.size).to eq(1)
+      end
+    end
   end
 end
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index c705e4796900f3879c2e4cb7b3f8117664654400..0abef4bc447cc4a8b345ac1e894f97f31c1b8230 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -6,7 +6,7 @@ feature 'list of badges' do
     project = create(:project, :repository)
     project.add_master(user)
     sign_in(user)
-    visit project_pipelines_settings_path(project)
+    visit project_settings_ci_cd_path(project)
   end
 
   scenario 'user wants to see build status badge' do
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 88813d9b5ff2d707ea43f94ce30765627dbcdcf7..ac82f869f0f963c4e03a47cfe445f5c975fb5890 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -509,4 +509,29 @@ feature 'File blob', :js do
       end
     end
   end
+
+  context 'realtime pipelines' do
+    before do
+      Files::CreateService.new(
+        project,
+        project.creator,
+        start_branch: 'feature',
+        branch_name: 'feature',
+        commit_message: "Add ruby file",
+        file_path: 'files/ruby/test.rb',
+        file_content: "# Awesome content"
+      ).execute
+
+      create(:ci_pipeline, status: 'running', project: project, ref: 'feature', sha: project.commit('feature').sha)
+      visit_blob('files/ruby/test.rb', ref: 'feature')
+    end
+
+    it 'should show the realtime pipeline status' do
+      page.within('.commit-actions') do
+        expect(page).to have_css('.ci-status-icon')
+        expect(page).to have_css('.ci-status-icon-running')
+        expect(page).to have_css('.js-ci-status-icon-running')
+      end
+    end
+  end
 end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 39bcea013e786ade893cfd039cde67b70fb4270a..605298ba8abb29139108195038f21849e283f5d4 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do
   describe 'when checking branches' do
     context 'with artifacts' do
       before do
-        visit project_branches_path(project, search: 'binary-encoding')
+        visit project_branches_filtered_path(project, state: 'all', search: 'binary-encoding')
       end
 
       scenario 'shows download artifacts button' do
diff --git a/spec/features/projects/branches/user_creates_branch_spec.rb b/spec/features/projects/branches/user_creates_branch_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b706ad6495462f408f22b6f426a8d53de2875af0
--- /dev/null
+++ b/spec/features/projects/branches/user_creates_branch_spec.rb
@@ -0,0 +1,46 @@
+require "spec_helper"
+
+describe "User creates branch", :js do
+  include Spec::Support::Helpers::Features::BranchesHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :repository) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(new_project_branch_path(project))
+  end
+
+  it "creates new branch" do
+    BRANCH_NAME = "deploy_keys".freeze
+
+    create_branch(BRANCH_NAME)
+
+    expect(page).to have_content(BRANCH_NAME)
+  end
+
+  context "when branch name is invalid" do
+    it "does not create new branch" do
+      INVALID_BRANCH_NAME = "1.0 stable".freeze
+
+      fill_in("branch_name", with: INVALID_BRANCH_NAME)
+      page.find("body").click # defocus the branch_name input
+
+      select_branch("master")
+      click_button("Create branch")
+
+      expect(page).to have_content("Branch name is invalid")
+      expect(page).to have_content("can't contain spaces")
+    end
+  end
+
+  context "when branch name already exists" do
+    it "does not create new branch" do
+      create_branch("master")
+
+      expect(page).to have_content("Branch already exists")
+    end
+  end
+end
diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96f215e1606d1748e31f11ed125dc0d5b23e1290
--- /dev/null
+++ b/spec/features/projects/branches/user_deletes_branch_spec.rb
@@ -0,0 +1,23 @@
+require "spec_helper"
+
+describe "User deletes branch", :js do
+  set(:user) { create(:user) }
+  set(:project) { create(:project, :repository) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(project_branches_path(project))
+  end
+
+  it "deletes branch" do
+    fill_in("branch-search", with: "improve/awesome").native.send_keys(:enter)
+
+    page.within(".js-branch-improve\\/awesome") do
+      accept_alert { find(".btn-remove").click }
+    end
+
+    expect(page).to have_css(".js-branch-improve\\/awesome", visible: :hidden)
+  end
+end
diff --git a/spec/features/projects/branches/user_views_branches_spec.rb b/spec/features/projects/branches/user_views_branches_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62ae793151c6b28ab0d76c30a5d16884a0d8583b
--- /dev/null
+++ b/spec/features/projects/branches/user_views_branches_spec.rb
@@ -0,0 +1,34 @@
+require "spec_helper"
+
+describe "User views branches" do
+  set(:project) { create(:project, :repository) }
+  set(:user) { project.owner }
+
+  before do
+    sign_in(user)
+  end
+
+  context "all branches" do
+    before do
+      visit(project_branches_path(project))
+    end
+
+    it "shows branches" do
+      expect(page).to have_content("Branches").and have_content("master")
+    end
+  end
+
+  context "protected branches" do
+    set(:protected_branch) { create(:protected_branch, project: project) }
+
+    before do
+      visit(project_protected_branches_path(project))
+    end
+
+    it "shows branches" do
+      page.within(".protected-branches-list") do
+        expect(page).to have_content(protected_branch.name).and have_no_content("master")
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 2fddd274078c617231515d25b851698b5c001d4a..b7ce1b9993a99612dae343bba4ae3be68706286e 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -11,15 +11,109 @@ describe 'Branches' do
       project.add_developer(user)
     end
 
-    describe 'Initial branches page' do
-      it 'shows all the branches sorted by last updated by default' do
+    context 'on the projects with 6 active branches and 4 stale branches' do
+      let(:project) { create(:project, :public, :empty_repo) }
+      let(:repository) { project.repository }
+      let(:threshold) { Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD }
+
+      before do
+        # Add 4 stale branches
+        (1..4).reverse_each do |i|
+          Timecop.freeze((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") }
+        end
+        # Add 6 active branches
+        (1..6).each do |i|
+          Timecop.freeze((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") }
+        end
+      end
+
+      describe 'Overview page of the branches' do
+        it 'shows the first 5 active branches and the first 4 stale branches sorted by last updated' do
+          visit project_branches_path(project)
+
+          expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active'))
+          expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale'))
+
+          expect(page).to have_link('Show more active branches', href: project_branches_filtered_path(project, state: 'active'))
+          expect(page).not_to have_content('Show more stale branches')
+        end
+      end
+
+      describe 'Active branches page' do
+        it 'shows 6 active branches sorted by last updated' do
+          visit project_branches_filtered_path(project, state: 'active')
+
+          expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_desc, state: 'active'))
+        end
+      end
+
+      describe 'Stale branches page' do
+        it 'shows 4 active branches sorted by last updated' do
+          visit project_branches_filtered_path(project, state: 'stale')
+
+          expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale'))
+        end
+      end
+
+      describe 'All branches page' do
+        it 'shows 10 branches sorted by last updated' do
+          visit project_branches_filtered_path(project, state: 'all')
+
+          expect(page).to have_content(sorted_branches(repository, count: 10, sort_by: :updated_desc))
+        end
+      end
+
+      context 'with branches over more than one page' do
+        before do
+          allow(Kaminari.config).to receive(:default_per_page).and_return(5)
+        end
+
+        it 'shows only default_per_page active branches sorted by last updated' do
+          visit project_branches_filtered_path(project, state: 'active')
+
+          expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc, state: 'active'))
+        end
+
+        it 'shows only default_per_page branches sorted by last updated on All branches' do
+          visit project_branches_filtered_path(project, state: 'all')
+
+          expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc))
+        end
+      end
+    end
+
+    describe 'Find branches' do
+      it 'shows filtered branches', :js do
         visit project_branches_path(project)
 
+        fill_in 'branch-search', with: 'fix'
+        find('#branch-search').native.send_keys(:enter)
+
+        expect(page).to have_content('fix')
+        expect(find('.all-branches')).to have_selector('li', count: 1)
+      end
+    end
+
+    describe 'Delete unprotected branch on Overview' do
+      it 'removes branch after confirmation', :js do
+        visit project_branches_filtered_path(project, state: 'all')
+
+        expect(all('.all-branches').last).to have_selector('li', count: 20)
+        accept_confirm { find('.js-branch-add-pdf-text-binary .btn-remove').click }
+
+        expect(all('.all-branches').last).to have_selector('li', count: 19)
+      end
+    end
+
+    describe 'All branches page' do
+      it 'shows all the branches sorted by last updated by default' do
+        visit project_branches_filtered_path(project, state: 'all')
+
         expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc))
       end
 
       it 'sorts the branches by name' do
-        visit project_branches_path(project)
+        visit project_branches_filtered_path(project, state: 'all')
 
         click_button "Last updated" # Open sorting dropdown
         click_link "Name"
@@ -28,7 +122,7 @@ describe 'Branches' do
       end
 
       it 'sorts the branches by oldest updated' do
-        visit project_branches_path(project)
+        visit project_branches_filtered_path(project, state: 'all')
 
         click_button "Last updated" # Open sorting dropdown
         click_link "Oldest updated"
@@ -41,13 +135,13 @@ describe 'Branches' do
 
         %w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') }
 
-        expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count)
+        expect { visit project_branches_filtered_path(project, state: 'all') }.not_to exceed_query_limit(control_count)
       end
     end
 
-    describe 'Find branches' do
+    describe 'Find branches on All branches' do
       it 'shows filtered branches', :js do
-        visit project_branches_path(project)
+        visit project_branches_filtered_path(project, state: 'all')
 
         fill_in 'branch-search', with: 'fix'
         find('#branch-search').native.send_keys(:enter)
@@ -57,9 +151,9 @@ describe 'Branches' do
       end
     end
 
-    describe 'Delete unprotected branch' do
+    describe 'Delete unprotected branch on All branches' do
       it 'removes branch after confirmation', :js do
-        visit project_branches_path(project)
+        visit project_branches_filtered_path(project, state: 'all')
 
         fill_in 'branch-search', with: 'fix'
 
@@ -73,6 +167,19 @@ describe 'Branches' do
         expect(find('.all-branches')).to have_selector('li', count: 0)
       end
     end
+
+    context 'on project with 0 branch' do
+      let(:project) { create(:project, :public, :empty_repo) }
+      let(:repository) { project.repository }
+
+      describe '0 branches on Overview' do
+        it 'shows warning' do
+          visit project_branches_path(project)
+
+          expect(page).not_to have_selector('.all-branches')
+        end
+      end
+    end
   end
 
   context 'logged in as master' do
@@ -83,11 +190,31 @@ describe 'Branches' do
 
     describe 'Initial branches page' do
       it 'shows description for admin' do
-        visit project_branches_path(project)
+        visit project_branches_filtered_path(project, state: 'all')
 
         expect(page).to have_content("Protected branches can be managed in project settings")
       end
     end
+
+    it 'shows the merge request button' do
+      visit project_branches_path(project)
+
+      page.within first('.all-branches li') do
+        expect(page).to have_content 'Merge request'
+      end
+    end
+
+    context 'when the project is archived' do
+      let(:project) { create(:project, :public, :repository, :archived) }
+
+      it 'does not show the merge request button when the project is archived' do
+        visit project_branches_path(project)
+
+        page.within first('.all-branches li') do
+          expect(page).not_to have_content 'Merge request'
+        end
+      end
+    end
   end
 
   context 'logged out' do
@@ -97,17 +224,23 @@ describe 'Branches' do
 
     it 'does not show merge request button' do
       page.within first('.all-branches li') do
-        expect(page).not_to have_content 'Merge Request'
+        expect(page).not_to have_content 'Merge request'
       end
     end
   end
 
-  def sorted_branches(repository, count:, sort_by:)
+  def sorted_branches(repository, count:, sort_by:, state: nil)
+    branches = repository.branches_sorted_by(sort_by)
+    branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state
     sorted_branches =
-      repository.branches_sorted_by(sort_by).first(count).map do |branch|
+      branches.first(count).map do |branch|
         Regexp.escape(branch.name)
       end
 
     Regexp.new(sorted_branches.join('.*'))
   end
+
+  def create_file(message: 'message', branch_name:)
+    repository.create_file(user, generate(:branch), 'content', message: message, branch_name: branch_name)
+  end
 end
diff --git a/spec/features/projects/ci/lint_spec.rb b/spec/features/projects/ci/lint_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..313950072e77e3cd4d75a777bae71cabfa39033b
--- /dev/null
+++ b/spec/features/projects/ci/lint_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe 'CI Lint', :js do
+  let(:project) { create(:project, :repository) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit project_ci_lint_path(project)
+    find('#ci-editor')
+    execute_script("ace.edit('ci-editor').setValue(#{yaml_content.to_json});")
+
+    # Ace editor updates a hidden textarea and it happens asynchronously
+    wait_for('YAML content') do
+      find('.ace_content').text.present?
+    end
+  end
+
+  describe 'YAML parsing' do
+    before do
+      click_on 'Validate'
+    end
+
+    context 'YAML is correct' do
+      let(:yaml_content) do
+        File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+      end
+
+      it 'parses Yaml' do
+        within "table" do
+          expect(page).to have_content('Job - rspec')
+          expect(page).to have_content('Job - spinach')
+          expect(page).to have_content('Deploy Job - staging')
+          expect(page).to have_content('Deploy Job - production')
+        end
+      end
+    end
+
+    context 'YAML is incorrect' do
+      let(:yaml_content) { 'value: cannot have :' }
+
+      it 'displays information about an error' do
+        expect(page).to have_content('Status: syntax is incorrect')
+        expect(page).to have_selector('.ace_content', text: yaml_content)
+      end
+    end
+
+    describe 'YAML revalidate' do
+      let(:yaml_content) { 'my yaml content' }
+
+      it 'loads previous YAML content after validation' do
+        expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea')
+      end
+    end
+  end
+
+  describe 'YAML clearing' do
+    before do
+      click_on 'Clear'
+    end
+
+    context 'YAML is present' do
+      let(:yaml_content) do
+        File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+      end
+
+      it 'YAML content is cleared' do
+        expect(page).to have_field('content', with: '', visible: false, type: 'textarea')
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 4d47cdb500c8f0c6673b320f056ba84a884051fb..dfe8e02dce08069287c46cf6d908cdb3776c716d 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -33,7 +33,7 @@ feature 'Gcp Cluster', :js do
           visit project_clusters_path(project)
 
           click_link 'Add Kubernetes cluster'
-          click_link 'Create on GKE'
+          click_link 'Create on Google Kubernetes Engine'
         end
 
         context 'when user filled form with valid parameters' do
@@ -139,7 +139,7 @@ feature 'Gcp Cluster', :js do
         visit project_clusters_path(project)
 
         click_link 'Add Kubernetes cluster'
-        click_link 'Create on GKE'
+        click_link 'Create on Google Kubernetes Engine'
 
         fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
         fill_in 'cluster_name', with: 'dev-cluster'
@@ -159,7 +159,7 @@ feature 'Gcp Cluster', :js do
         visit project_clusters_path(project)
 
         click_link 'Add Kubernetes cluster'
-        click_link 'Create on GKE'
+        click_link 'Create on Google Kubernetes Engine'
 
         fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
         fill_in 'cluster_name', with: 'dev-cluster'
@@ -177,7 +177,7 @@ feature 'Gcp Cluster', :js do
       visit project_clusters_path(project)
 
       click_link 'Add Kubernetes cluster'
-      click_link 'Create on GKE'
+      click_link 'Create on Google Kubernetes Engine'
     end
 
     it 'user sees a login page' do
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index bd9f7745cf810f5c2289f8fa4a50a65d34f3952d..a251a2f4e520e4d40a0780e34ed8aa0d4be8dc4b 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -83,7 +83,7 @@ feature 'Clusters', :js do
       visit project_clusters_path(project)
 
       click_link 'Add Kubernetes cluster'
-      click_link 'Create on GKE'
+      click_link 'Create on Google Kubernetes Engine'
     end
 
     it 'user sees a login page' do
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index c4c399e30583cc54c56927f4763f3691a120c6a3..1df45865d6fb102f7768da3089fe53eb680a7168 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -89,4 +89,15 @@ describe 'Cherry-pick Commits' do
       expect(page).to have_content('The commit has been successfully cherry-picked.')
     end
   end
+
+  context 'when the project is archived' do
+    let(:project) { create(:project, :repository, :archived, namespace: group) }
+
+    it 'does not show the cherry-pick link' do
+      find('.header-action-buttons a.dropdown-toggle').click
+
+      expect(page).not_to have_text("Cherry-pick")
+      expect(page).not_to have_css("a[href='#modal-cherry-pick-commit']")
+    end
+  end
 end
diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5174f793367cdf92b23fa4ae1518f42723ed7a89
--- /dev/null
+++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb
@@ -0,0 +1,110 @@
+require "spec_helper"
+
+describe "User comments on commit", :js do
+  include Spec::Support::Helpers::Features::NotesHelpers
+  include RepoHelpers
+
+  let(:project) { create(:project, :repository) }
+  let(:user) { create(:user) }
+
+  COMMENT_TEXT = "XML attached".freeze
+
+  before do
+    sign_in(user)
+    project.add_developer(user)
+
+    visit(project_commit_path(project, sample_commit.id))
+  end
+
+  context "when adding new comment" do
+    it "adds comment" do
+      EMOJI = ":+1:".freeze
+
+      page.within(".js-main-target-form") do
+        expect(page).not_to have_link("Cancel")
+
+        fill_in("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}")
+
+        # Check on `Preview` tab
+        click_link("Preview")
+
+        expect(find(".js-md-preview")).to have_content(COMMENT_TEXT).and have_css("gl-emoji")
+        expect(page).not_to have_css(".js-note-text")
+
+        # Check on `Write` tab
+        click_link("Write")
+
+        expect(page).to have_field("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}")
+
+        # Submit comment from the `Preview` tab to get rid of a separate `it` block
+        # which would specially tests if everything gets cleared from the note form.
+        click_link("Preview")
+        click_button("Comment")
+      end
+
+      wait_for_requests
+
+      page.within(".note") do
+        expect(page).to have_content(COMMENT_TEXT).and have_css("gl-emoji")
+      end
+
+      page.within(".js-main-target-form") do
+        expect(page).to have_field("note[note]", with: "").and have_no_css(".js-md-preview")
+      end
+    end
+  end
+
+  context "when editing comment" do
+    before do
+      add_note(COMMENT_TEXT)
+    end
+
+    it "edits comment" do
+      NEW_COMMENT_TEXT = "+1 Awesome!".freeze
+
+      page.within(".main-notes-list") do
+        note = find(".note")
+        note.hover
+
+        note.find(".js-note-edit").click
+      end
+
+      page.find(".current-note-edit-form textarea")
+
+      page.within(".current-note-edit-form") do
+        fill_in("note[note]", with: NEW_COMMENT_TEXT)
+        click_button("Save comment")
+      end
+
+      wait_for_requests
+
+      page.within(".note") do
+        expect(page).to have_content(NEW_COMMENT_TEXT)
+      end
+    end
+  end
+
+  context "when deleting comment" do
+    before do
+      add_note(COMMENT_TEXT)
+    end
+
+    it "deletes comment" do
+      page.within(".note") do
+        expect(page).to have_content(COMMENT_TEXT)
+      end
+
+      page.within(".main-notes-list") do
+        note = find(".note")
+        note.hover
+
+        find(".more-actions").click
+        find(".more-actions .dropdown-menu li", match: :first)
+
+        accept_confirm { find(".js-note-delete").click }
+      end
+
+      expect(page).not_to have_css(".note")
+    end
+  end
+end
diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb
index 221f1d7757e5fd70c18acb870f2977d644d63344..42844a03ea62dcc721f6de15bb1aad1b654ddeb7 100644
--- a/spec/features/projects/commit/user_reverts_commit_spec.rb
+++ b/spec/features/projects/commit/user_reverts_commit_spec.rb
@@ -10,13 +10,16 @@ describe 'User reverts a commit', :js do
     sign_in(user)
 
     visit(project_commit_path(project, sample_commit.id))
+  end
 
+  def click_revert
     find('.header-action-buttons .dropdown').click
     find('a[href="#modal-revert-commit"]').click
   end
 
   context 'without creating a new merge request' do
     before do
+      click_revert
       page.within('#modal-revert-commit') do
         uncheck('create_merge_request')
         click_button('Revert')
@@ -44,6 +47,10 @@ describe 'User reverts a commit', :js do
   end
 
   context 'with creating a new merge request' do
+    before do
+      click_revert
+    end
+
     it 'reverts a commit' do
       page.within('#modal-revert-commit') do
         click_button('Revert')
@@ -53,4 +60,14 @@ describe 'User reverts a commit', :js do
       expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master")
     end
   end
+
+  context 'when the project is archived' do
+    let(:project) { create(:project, :repository, :archived, namespace: user.namespace) }
+
+    it 'does not show the revert link' do
+      find('.header-action-buttons .dropdown').click
+
+      expect(page).not_to have_link('Revert')
+    end
+  end
 end
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
deleted file mode 100644
index bf55917bf4c09b7ff73d33c943ed21b3445c611e..0000000000000000000000000000000000000000
--- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'rails_helper'
-
-feature 'Developer views empty project instructions' do
-  let(:project) { create(:project, :empty_repo) }
-  let(:developer) { create(:user) }
-
-  background do
-    project.add_developer(developer)
-
-    sign_in(developer)
-  end
-
-  context 'without an SSH key' do
-    scenario 'defaults to HTTP' do
-      visit_project
-
-      expect_instructions_for('http')
-    end
-
-    scenario 'switches to SSH', :js do
-      visit_project
-
-      select_protocol('SSH')
-
-      expect_instructions_for('ssh')
-    end
-  end
-
-  context 'with an SSH key' do
-    background do
-      create(:personal_key, user: developer)
-    end
-
-    scenario 'defaults to SSH' do
-      visit_project
-
-      expect_instructions_for('ssh')
-    end
-
-    scenario 'switches to HTTP', :js do
-      visit_project
-
-      select_protocol('HTTP')
-
-      expect_instructions_for('http')
-    end
-  end
-
-  def visit_project
-    visit project_path(project)
-  end
-
-  def select_protocol(protocol)
-    find('#clone-dropdown').click
-    find(".#{protocol.downcase}-selector").click
-  end
-
-  def expect_instructions_for(protocol)
-    msg = :"#{protocol.downcase}_url_to_repo"
-
-    expect(page).to have_content("git clone #{project.send(msg)}")
-  end
-end
diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb
deleted file mode 100644
index 1d4b4d0fdca6206efb7a6258b6b7979fc8ec990e..0000000000000000000000000000000000000000
--- a/spec/features/projects/edit_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'rails_helper'
-
-feature 'Project edit', :js do
-  let(:admin)   { create(:admin) }
-  let(:user)    { create(:user) }
-  let(:project) { create(:project) }
-
-  context 'feature visibility' do
-    before do
-      project.add_master(user)
-      sign_in(user)
-
-      visit edit_project_path(project)
-    end
-
-    context 'merge requests select' do
-      it 'hides merge requests section' do
-        find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
-
-        expect(page).to have_selector('.merge-requests-feature', visible: false)
-      end
-
-      context 'given project with merge_requests_disabled access level' do
-        let(:project) { create(:project, :merge_requests_disabled) }
-
-        it 'hides merge requests section' do
-          expect(page).to have_selector('.merge-requests-feature', visible: false)
-        end
-      end
-    end
-
-    context 'builds select' do
-      it 'hides builds select section' do
-        find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
-
-        expect(page).to have_selector('.builds-feature', visible: false)
-      end
-
-      context 'given project with builds_disabled access level' do
-        let(:project) { create(:project, :builds_disabled) }
-
-        it 'hides builds select section' do
-          expect(page).to have_selector('.builds-feature', visible: false)
-        end
-      end
-    end
-  end
-
-  context 'LFS enabled setting' do
-    before do
-      sign_in(admin)
-    end
-
-    it 'displays the correct elements' do
-      allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
-      visit edit_project_path(project)
-
-      expect(page).to have_content('Git Large File Storage')
-      expect(page).to have_selector('input[name="project[lfs_enabled]"] + button', visible: true)
-    end
-  end
-end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 64e600144e0c43de5323f718ae4af22a8366f3da..b233af83eec9e78060bd7b2a6e43386c6327e4e4 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -234,7 +234,7 @@ feature 'Environment' do
     end
 
     scenario 'user deletes the branch with running environment' do
-      visit project_branches_path(project, search: 'feature')
+      visit project_branches_filtered_path(project, state: 'all', search: 'feature')
 
       remove_branch_with_hooks(project, user, 'feature') do
         page.within('.js-branch-feature') { find('a.btn-remove').click }
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
deleted file mode 100644
index 2c38c380d9d53b264c885f7416f034e04cc87b1e..0000000000000000000000000000000000000000
--- a/spec/features/projects/files/browse_files_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-
-feature 'user browses project', :js do
-  let(:project) { create(:project, :repository) }
-  let(:user) { create(:user) }
-
-  before do
-    project.add_master(user)
-    sign_in(user)
-    visit project_tree_path(project, project.default_branch)
-  end
-
-  scenario "can see blame of '.gitignore'" do
-    click_link ".gitignore"
-    click_link 'Blame'
-
-    expect(page).to have_content "*.rb"
-    expect(page).to have_content "Dmitriy Zaporozhets"
-    expect(page).to have_content "Initial commit"
-  end
-
-  scenario 'can see raw content of LFS pointer with LFS disabled' do
-    allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
-    click_link 'files'
-    click_link 'lfs'
-    click_link 'lfs_object.iso'
-    wait_for_requests
-
-    expect(page).not_to have_content 'Download (1.5 MB)'
-    expect(page).to have_content 'version https://git-lfs.github.com/spec/v1'
-    expect(page).to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
-    expect(page).to have_content 'size 1575078'
-  end
-
-  scenario 'can see last commit for current directory' do
-    last_commit = project.repository.last_commit_for_path(project.default_branch, 'files')
-
-    click_link 'files'
-    wait_for_requests
-
-    page.within('.blob-commit-info') do
-      expect(page).to have_content last_commit.short_id
-      expect(page).to have_content last_commit.author_name
-    end
-  end
-end
diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb
deleted file mode 100644
index 8d982636525427d21710d83074b42db7808dba94..0000000000000000000000000000000000000000
--- a/spec/features/projects/files/creating_a_file_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-feature 'User wants to create a file' do
-  let(:project) { create(:project, :repository) }
-  let(:user) { create(:user) }
-
-  background do
-    project.add_master(user)
-    sign_in user
-    visit project_new_blob_path(project, project.default_branch)
-  end
-
-  def submit_new_file(options)
-    file_name = find('#file_name')
-    file_name.set options[:file_name] || 'README.md'
-
-    file_content = find('#file-content', visible: false)
-    file_content.set options[:file_content] || 'Some content'
-
-    click_button 'Commit changes'
-  end
-
-  scenario 'file name contains Chinese characters' do
-    submit_new_file(file_name: '娴嬭瘯.md')
-    expect(page).to have_content 'The file has been successfully created.'
-  end
-
-  scenario 'directory name contains Chinese characters' do
-    submit_new_file(file_name: '涓枃/娴嬭瘯.md')
-    expect(page).to have_content 'The file has been successfully created'
-  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'
-  end
-end
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index f4a39e331fd97423ecf9f8069641c535f09875ce..004585f7c9e0ea81f47860b536d145365a57989f 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -1,22 +1,15 @@
 require 'spec_helper'
-require 'fileutils'
 
-feature 'User wants to add a Dockerfile file' do
+describe 'Projects > Files > User wants to add a Dockerfile file' do
   before do
-    user = create(:user)
     project = create(:project, :repository)
-    project.add_master(user)
-
-    sign_in user
-
+    sign_in project.owner
     visit project_new_blob_path(project, 'master', file_name: 'Dockerfile')
   end
 
-  scenario 'user can see Dockerfile dropdown' do
+  it 'user can pick a Dockerfile file from the dropdown', :js do
     expect(page).to have_css('.dockerfile-selector')
-  end
 
-  scenario 'user can pick a Dockerfile file from the dropdown', :js do
     find('.js-dockerfile-selector').click
 
     wait_for_requests
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 2101627f324b7e0d21fb561e1c4e11e58057f3af..03cb3530e2b671c719711e9ec2631b3e1213a34e 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -1,42 +1,36 @@
 require 'spec_helper'
 
-feature 'Download buttons in files tree' do
-  given(:user) { create(:user) }
-  given(:role) { :developer }
-  given(:status) { 'success' }
-  given(:project) { create(:project, :repository) }
+describe 'Projects > Files > Download buttons in files tree' do
+  let(:project) { create(:project, :repository) }
+  let(:user) { project.creator }
 
-  given(:pipeline) do
+  let(:pipeline) do
     create(:ci_pipeline,
            project: project,
            sha: project.commit.sha,
            ref: project.default_branch,
-           status: status)
+           status: 'success')
   end
 
-  given!(:build) do
+  let!(:build) do
     create(:ci_build, :success, :artifacts,
            pipeline: pipeline,
            status: pipeline.status,
            name: 'build')
   end
 
-  background do
+  before do
     sign_in(user)
-    project.add_role(user, role)
-  end
+    project.add_developer(user)
 
-  describe 'when files tree' do
-    context 'with artifacts' do
-      before do
-        visit project_tree_path(project, project.default_branch)
-      end
+    visit project_tree_path(project, project.default_branch)
+  end
 
-      scenario 'shows download artifacts button' do
-        href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
+  context 'with artifacts' do
+    it 'shows download artifacts button' do
+      href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
 
-        expect(page).to have_link "Download '#{build.name}'", href: href
-      end
+      expect(page).to have_link "Download '#{build.name}'", href: href
     end
   end
 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 8d32ada57953ad49588db48f3a912865777aefd4..41af70d8ebc98bd4f03f2bc535279f84a31ae522 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -1,10 +1,9 @@
 require 'spec_helper'
 
-feature 'User uses soft wrap whilst editing file', :js do
+describe 'Projects > Files > User uses soft wrap whilst editing file', :js do
   before do
-    user = create(:user)
     project = create(:project, :repository)
-    project.add_master(user)
+    user = project.owner
     sign_in user
     visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
     page.within('.file-editor.code') do
@@ -23,7 +22,7 @@ feature 'User uses soft wrap whilst editing file', :js do
 
   let(:toggle_button) { find('.soft-wrap-toggle') }
 
-  scenario 'user clicks the "Soft wrap" button and then "No wrap" button' do
+  it 'user clicks the "Soft wrap" button and then "No wrap" button' do
     wrapped_content_width = get_content_width
     toggle_button.click
     expect(toggle_button).to have_content 'No wrap'
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index d874cdbff8dc892d9d792724bf3e855d371021d6..4074e67e2d24a5c394aeacbc539fb3097a65dfc6 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
-feature 'User wants to edit a file' do
+describe 'Projects > Files > User wants to edit a file' do
   let(:project) { create(:project, :repository) }
-  let(:user) { create(:user) }
+  let(:user) { project.owner }
   let(:commit_params) do
     {
       start_branch: project.default_branch,
@@ -15,14 +15,13 @@ feature 'User wants to edit a file' do
     }
   end
 
-  background do
-    project.add_master(user)
+  before do
     sign_in user
     visit project_edit_blob_path(project,
                                            File.join(project.default_branch, '.gitignore'))
   end
 
-  scenario 'file has been updated since the user opened the edit page' do
+  it 'file has been updated since the user opened the edit page' do
     Files::UpdateService.new(project, user, commit_params).execute
 
     click_button 'Commit changes'
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 ead9f7e91685da6ba6b492a81b6780ad9374c1df..b6dbf76bc9b19abc87a04a7db9588869750d6187 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
@@ -1,16 +1,15 @@
 require 'spec_helper'
 
-feature 'User views files page' do
-  let(:user) { create(:user) }
+describe 'Projects > Files > User views files page' do
   let(:project) { create(:forked_project_with_submodules) }
+  let(:user) { project.owner }
 
   before do
-    project.add_master(user)
     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
+  it 'user sees folders and submodules sorted together, followed by files' do
     rows = all('td.tree-item-file-name').map(&:text)
     tree = project.repository.tree
 
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index e9ff06c72d8b88b95a5529b5b5caf77ceaa3b758..cd0235f2b9e412f023283bbf636d93fe8e4d5313 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -1,11 +1,10 @@
 require 'spec_helper'
 
-feature 'Find file keyboard shortcuts', :js do
-  let(:user) { create(:user) }
+describe 'Projects > Files > Find file keyboard shortcuts', :js do
   let(:project) { create(:project, :repository) }
+  let(:user) { project.owner }
 
   before do
-    project.add_master(user)
     sign_in user
 
     visit project_find_file_path(project, project.repository.root_ref)
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 79f3fd09b481fdfd2eb0fff71c90f97335b8f7e5..9fa4c053a4010b3d2f1ae3f3c125e989d32039fc 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -1,25 +1,24 @@
 require 'spec_helper'
 
-feature 'User wants to add a .gitignore file' do
+describe 'Projects > Files > User wants to add a .gitignore file' do
   before do
-    user = create(:user)
     project = create(:project, :repository)
-    project.add_master(user)
-    sign_in user
+    sign_in project.owner
     visit project_new_blob_path(project, 'master', file_name: '.gitignore')
   end
 
-  scenario 'user can see .gitignore dropdown' do
+  it 'user can pick a .gitignore file from the dropdown', :js do
     expect(page).to have_css('.gitignore-selector')
-  end
 
-  scenario 'user can pick a .gitignore file from the dropdown', :js do
     find('.js-gitignore-selector').click
+
     wait_for_requests
+
     within '.gitignore-selector' do
       find('.dropdown-input-field').set('rails')
       find('.dropdown-content li', text: 'Rails').click
     end
+
     wait_for_requests
 
     expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Rails')
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 db6c67b802e6122599ff9290f61789d23687d6a3..53aff183562a5607ed9e0834463de86c98c1c0a2 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -1,25 +1,24 @@
 require 'spec_helper'
 
-feature 'User wants to add a .gitlab-ci.yml file' do
+describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do
   before do
-    user = create(:user)
     project = create(:project, :repository)
-    project.add_master(user)
-    sign_in user
+    sign_in project.owner
     visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml')
   end
 
-  scenario 'user can see .gitlab-ci.yml dropdown' do
+  it 'user can pick a template from the dropdown', :js do
     expect(page).to have_css('.gitlab-ci-yml-selector')
-  end
 
-  scenario 'user can pick a template from the dropdown', :js do
     find('.js-gitlab-ci-yml-selector').click
+
     wait_for_requests
+
     within '.gitlab-ci-yml-selector' do
       find('.dropdown-input-field').set('Jekyll')
       find('.dropdown-content li', text: 'Jekyll').click
     end
+
     wait_for_requests
 
     expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Jekyll')
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 07599600876a32c5f3280a9bca6995d7b509129b..b410199fd1fc3338fe4d4387f7150a17ce4dc57c 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
@@ -1,17 +1,17 @@
 require 'spec_helper'
 
-feature 'project owner creates a license file', :js do
-  let(:project_master) { create(:user) }
+describe 'Projects > Files > Project owner creates a license file', :js do
   let(:project) { create(:project, :repository) }
-  background do
+  let(:project_master) { project.owner }
+
+  before do
     project.repository.delete_file(project_master, 'LICENSE',
       message: 'Remove LICENSE', branch_name: 'master')
-    project.add_master(project_master)
     sign_in(project_master)
     visit project_path(project)
   end
 
-  scenario 'project master creates a license file manually from a template' do
+  it 'project master creates a license file manually from a template' do
     visit project_tree_path(project, project.repository.root_ref)
     find('.add-to-tree').click
     click_link 'New file'
@@ -35,7 +35,7 @@ feature 'project owner creates a license file', :js do
     expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
   end
 
-  scenario 'project master creates a license file from the "Add license" link' do
+  it 'project master creates a license file from the "Add license" link' do
     click_link 'Add License'
 
     expect(page).to have_content('New file')
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 7f1d19341037ccebca0a4652c0611a5dc0644dc5..53d8ace7c946a6598ea8331a9a3655e0db5562b9 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
@@ -1,15 +1,14 @@
 require 'spec_helper'
 
-feature 'project owner sees a link to create a license file in empty project', :js do
-  let(:project_master) { create(:user) }
+describe 'Projects > Files > Project owner sees a link to create a license file in empty project', :js do
   let(:project) { create(:project_empty_repo) }
+  let(:project_master) { project.owner }
 
-  background do
-    project.add_master(project_master)
+  before do
     sign_in(project_master)
   end
 
-  scenario 'project master creates a license file from a template' do
+  it 'project master creates a license file from a template' do
     visit project_path(project)
     click_on 'Add License'
     expect(page).to have_content('New file')
diff --git a/spec/features/projects/files/template_selector_menu_spec.rb b/spec/features/projects/files/template_selector_menu_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b549a69ddf31f52490ba450349682d075e901891
--- /dev/null
+++ b/spec/features/projects/files/template_selector_menu_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+feature 'Template selector menu', :js do
+  let(:project) { create(:project, :repository) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_master(user)
+    sign_in user
+  end
+
+  context 'editing a non-matching file' do
+    before do
+      create_and_edit_file('README.md')
+    end
+
+    scenario 'is not displayed' do
+      check_template_selector_menu_display(false)
+    end
+
+    context 'user toggles preview' do
+      before do
+        click_link 'Preview'
+      end
+
+      scenario 'template selector menu is not displayed' do
+        check_template_selector_menu_display(false)
+        click_link 'Write'
+        check_template_selector_menu_display(false)
+      end
+    end
+  end
+
+  context 'editing a matching file' do
+    before do
+      visit project_edit_blob_path(project, File.join(project.default_branch, 'LICENSE'))
+    end
+
+    scenario 'is displayed' do
+      check_template_selector_menu_display(true)
+    end
+
+    context 'user toggles preview' do
+      before do
+        click_link 'Preview'
+      end
+
+      scenario 'template selector menu is hidden and shown correctly' do
+        check_template_selector_menu_display(false)
+        click_link 'Write'
+        check_template_selector_menu_display(true)
+      end
+    end
+  end
+end
+
+def check_template_selector_menu_display(is_visible)
+  count = is_visible ? 1 : 0
+  expect(page).to have_css('.template-selectors-menu', count: count)
+end
+
+def create_and_edit_file(file_name)
+  visit project_new_blob_path(project, 'master', file_name: file_name)
+  click_button "Commit changes"
+  visit project_edit_blob_path(project, File.join(project.default_branch, file_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 97408a9c41ec48aa04dc307f4b3203c07ad3c106..342a93b328f8f7ccb777e41201aca19cd31c8634 100644
--- a/spec/features/projects/files/template_type_dropdown_spec.rb
+++ b/spec/features/projects/files/template_type_dropdown_spec.rb
@@ -1,11 +1,10 @@
 require 'spec_helper'
 
-feature 'Template type dropdown selector', :js do
+describe 'Projects > Files > Template type dropdown selector', :js do
   let(:project) { create(:project, :repository) }
-  let(:user) { create(:user) }
+  let(:user) { project.owner }
 
   before do
-    project.add_master(user)
     sign_in user
   end
 
@@ -14,16 +13,16 @@ feature 'Template type dropdown selector', :js do
       create_and_edit_file('.random-file.js')
     end
 
-    scenario 'not displayed' do
+    it 'not displayed' do
       check_type_selector_display(false)
     end
 
-    scenario 'selects every template type correctly' do
+    it 'selects every template type correctly' do
       fill_in 'file_path', with: '.gitignore'
       try_selecting_all_types
     end
 
-    scenario 'updates toggle value when input matches' do
+    it 'updates toggle value when input matches' do
       fill_in 'file_path', with: '.gitignore'
       check_type_selector_toggle_text('.gitignore')
     end
@@ -34,15 +33,15 @@ feature 'Template type dropdown selector', :js do
       visit project_edit_blob_path(project, File.join(project.default_branch, 'LICENSE'))
     end
 
-    scenario 'displayed' do
+    it 'displayed' do
       check_type_selector_display(true)
     end
 
-    scenario 'is displayed when input matches' do
+    it 'is displayed when input matches' do
       check_type_selector_display(true)
     end
 
-    scenario 'selects every template type correctly' do
+    it 'selects every template type correctly' do
       try_selecting_all_types
     end
 
@@ -51,7 +50,7 @@ feature 'Template type dropdown selector', :js do
         click_link 'Preview changes'
       end
 
-      scenario 'type selector is hidden and shown correctly' do
+      it 'type selector is hidden and shown correctly' do
         check_type_selector_display(false)
         click_link 'Write'
         check_type_selector_display(true)
@@ -64,15 +63,15 @@ feature 'Template type dropdown selector', :js do
       visit project_new_blob_path(project, 'master', file_name: '.gitignore')
     end
 
-    scenario 'is displayed' do
+    it 'is displayed' do
       check_type_selector_display(true)
     end
 
-    scenario 'toggle is set to the correct value' do
+    it 'toggle is set to the correct value' do
       check_type_selector_toggle_text('.gitignore')
     end
 
-    scenario 'selects every template type correctly' do
+    it 'selects every template type correctly' do
       try_selecting_all_types
     end
   end
@@ -82,15 +81,15 @@ feature 'Template type dropdown selector', :js do
       visit project_new_blob_path(project, project.default_branch)
     end
 
-    scenario 'type selector is shown' do
+    it 'type selector is shown' do
       check_type_selector_display(true)
     end
 
-    scenario 'toggle is set to the proper value' do
+    it 'toggle is set to the proper value' do
       check_type_selector_toggle_text('Choose type')
     end
 
-    scenario 'selects every template type correctly' do
+    it 'selects every template type correctly' do
       try_selecting_all_types
     end
   end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index fbf35fb4e1c3a20f2a99ca098457b5f6e13e5062..5de0bc009fbde6a0ea7b51e7cd2a9121a91b46ab 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -1,11 +1,10 @@
 require 'spec_helper'
 
-feature 'Template Undo Button', :js do
+describe 'Projects > Files > Template Undo Button', :js do
   let(:project) { create(:project, :repository) }
-  let(:user) { create(:user) }
+  let(:user) { project.owner }
 
   before do
-    project.add_master(user)
     sign_in user
   end
 
@@ -15,7 +14,7 @@ feature 'Template Undo Button', :js do
       select_file_template('.js-license-selector', 'Apache License 2.0')
     end
 
-    scenario 'reverts template application' do
+    it 'reverts template application' do
       try_template_undo('http://www.apache.org/licenses/', 'Apply a license template')
     end
   end
@@ -27,7 +26,7 @@ feature 'Template Undo Button', :js do
       select_file_template('.js-license-selector', 'Apache License 2.0')
     end
 
-    scenario 'reverts template application' do
+    it 'reverts template application' do
       try_template_undo('http://www.apache.org/licenses/', 'Apply a license template')
     end
   end
diff --git a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2d67837763c80acae9a2b1266bc4609f03340384
--- /dev/null
+++ b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
+describe 'Projects > Files > User browses a tree with a folder containing only a folder' do
+  let(:project) { create(:project, :empty_repo) }
+  let(:user) { project.owner }
+
+  before do
+    # We need to disable the tree.flat_path provided by Gitaly to reproduce the issue
+    allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
+
+    project.repository.create_dir(user, 'foo/bar', branch_name: 'master', message: 'Add the foo/bar folder')
+    sign_in(user)
+    visit(project_tree_path(project, project.repository.root_ref))
+  end
+
+  it 'shows the nested folder on a single row' do
+    expect(page).to have_content('foo/bar')
+  end
+end
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9c1f11f4c12af7c7d0a38fe0a8801ff74eaa7176
--- /dev/null
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -0,0 +1,153 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User browses files' do
+  let(:fork_message) do
+    "You're not allowed to make changes to this project directly. "\
+    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+  let(:project) { create(:project, :repository, name: 'Shop') }
+  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+  let(:tree_path_ref_6d39438) { project_tree_path(project, '6d39438') }
+  let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+  let(:user) { project.owner }
+
+  before do
+    sign_in(user)
+  end
+
+  it 'shows last commit for current directory' do
+    visit(tree_path_root_ref)
+
+    click_link 'files'
+
+    last_commit = project.repository.last_commit_for_path(project.default_branch, 'files')
+    page.within('.blob-commit-info') do
+      expect(page).to have_content last_commit.short_id
+      expect(page).to have_content last_commit.author_name
+    end
+  end
+
+  context 'when browsing the master branch' do
+    before do
+      visit(tree_path_root_ref)
+    end
+
+    it 'shows files from a repository' do
+      expect(page).to have_content('VERSION')
+      expect(page).to have_content('.gitignore')
+      expect(page).to have_content('LICENSE')
+    end
+
+    it 'shows the "Browse Directory" link' do
+      click_link('files')
+      click_link('History')
+
+      expect(page).to have_link('Browse Directory')
+      expect(page).not_to have_link('Browse Code')
+    end
+
+    it 'shows the "Browse File" link' do
+      page.within('.tree-table') do
+        click_link('README.md')
+      end
+      click_link('History')
+
+      expect(page).to have_link('Browse File')
+      expect(page).not_to have_link('Browse Files')
+    end
+
+    it 'shows the "Browse Files" link' do
+      click_link('History')
+
+      expect(page).to have_link('Browse Files')
+      expect(page).not_to have_link('Browse Directory')
+    end
+
+    it 'redirects to the permalink URL' do
+      click_link('.gitignore')
+      click_link('Permalink')
+
+      permalink_path = project_blob_path(project, "#{project.repository.commit.sha}/.gitignore")
+
+      expect(current_path).to eq(permalink_path)
+    end
+  end
+
+  context 'when browsing a specific ref' do
+    before do
+      visit(tree_path_ref_6d39438)
+    end
+
+    it 'shows files from a repository for "6d39438"' do
+      expect(current_path).to eq(tree_path_ref_6d39438)
+      expect(page).to have_content('.gitignore')
+      expect(page).to have_content('LICENSE')
+    end
+
+    it 'shows files from a repository with apostroph in its name', :js do
+      first('.js-project-refs-dropdown').click
+
+      page.within('.project-refs-form') do
+        click_link("'test'")
+      end
+
+      expect(page).to have_selector('.dropdown-toggle-text', text: "'test'")
+
+      visit(project_tree_path(project, "'test'"))
+
+      expect(page).to have_css('.tree-commit-link', visible: true)
+      expect(page).not_to have_content('Loading commit data...')
+    end
+
+    it 'shows the code with a leading dot in the directory', :js do
+      first('.js-project-refs-dropdown').click
+
+      page.within('.project-refs-form') do
+        click_link('fix')
+      end
+
+      visit(project_tree_path(project, 'fix/.testdir'))
+
+      expect(page).to have_css('.tree-commit-link', visible: true)
+      expect(page).not_to have_content('Loading commit data...')
+    end
+
+    it 'does not show the permalink link' do
+      click_link('.gitignore')
+
+      expect(page).not_to have_link('permalink')
+    end
+  end
+
+  context 'when browsing a file content' do
+    before do
+      visit(tree_path_root_ref)
+      click_link('.gitignore')
+    end
+
+    it 'shows a file content', :js do
+      wait_for_requests
+      expect(page).to have_content('*.rbc')
+    end
+
+    it 'is possible to blame' do
+      click_link 'Blame'
+
+      expect(page).to have_content "*.rb"
+      expect(page).to have_content "Dmitriy Zaporozhets"
+      expect(page).to have_content "Initial commit"
+    end
+  end
+
+  context 'when browsing a raw file' do
+    before do
+      visit(project_blob_path(project, File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path)))
+    end
+
+    it 'shows a raw file content' do
+      click_link('Open raw')
+      expect(source).to eq('') # Body is filled in by gitlab-workhorse
+    end
+  end
+end
diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c559a301ca1c6883a1c1bba96a816d71f309f6e0
--- /dev/null
+++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User browses LFS files' do
+  let(:project) { create(:project, :repository) }
+  let(:user) { project.owner }
+
+  before do
+    sign_in(user)
+  end
+
+  context 'when LFS is disabled', :js do
+    before do
+      allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
+      visit project_tree_path(project, 'lfs')
+    end
+
+    it 'is possible to see raw content of LFS pointer' do
+      click_link 'files'
+      click_link 'lfs'
+      click_link 'lfs_object.iso'
+
+      expect(page).to have_content 'version https://git-lfs.github.com/spec/v1'
+      expect(page).to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
+      expect(page).to have_content 'size 1575078'
+      expect(page).not_to have_content 'Download (1.5 MB)'
+    end
+  end
+
+  context 'when LFS is enabled' do
+    before do
+      allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+      visit project_tree_path(project, 'lfs')
+    end
+
+    it 'shows an LFS object' do
+      click_link('files')
+      click_link('lfs')
+      click_link('lfs_object.iso')
+
+      expect(page).to have_content('Download (1.5 MB)')
+      expect(page).not_to have_content('version https://git-lfs.github.com/spec/v1')
+      expect(page).not_to have_content('oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897')
+      expect(page).not_to have_content('size 1575078')
+
+      page.within('.content') do
+        expect(page).to have_content('Delete')
+        expect(page).to have_content('History')
+        expect(page).to have_content('Permalink')
+        expect(page).to have_content('Replace')
+        expect(page).not_to have_content('Annotate')
+        expect(page).not_to have_content('Blame')
+        expect(page).not_to have_content('Edit')
+        expect(page).to have_link('Download')
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..847b5f0860f7fb65b500a3b8420a3c454a760c8e
--- /dev/null
+++ b/spec/features/projects/files/user_creates_directory_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User creates a directory', :js do
+  let(:fork_message) do
+    "You're not allowed to make changes to this project directly. "\
+    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+  let(:project) { create(:project, :repository) }
+  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+    visit project_tree_path(project, 'master')
+  end
+
+  context 'with default target branch' do
+    before do
+      first('.add-to-tree').click
+      click_link('New directory')
+    end
+
+    it 'creates the directory in the default branch' do
+      fill_in(:dir_name, with: 'new_directory')
+      click_button('Create directory')
+
+      expect(page).to have_content('master')
+      expect(page).to have_content('The directory has been successfully created')
+      expect(page).to have_content('new_directory')
+    end
+
+    it 'does not create a directory with a name of already existed directory' do
+      fill_in(:dir_name, with: 'files')
+      fill_in(:commit_message, with: 'New commit message', visible: true)
+      click_button('Create directory')
+
+      expect(page).to have_content('A directory with this name already exists')
+      expect(current_path).to eq(project_tree_path(project, 'master'))
+    end
+  end
+
+  context 'with a new target branch' do
+    before do
+      first('.add-to-tree').click
+      click_link('New directory')
+      fill_in(:dir_name, with: 'new_directory')
+      fill_in(:branch_name, with: 'new-feature')
+      click_button('Create directory')
+    end
+
+    it 'creates the directory in the new branch and redirect to the merge request' do
+      expect(page).to have_content('new-feature')
+      expect(page).to have_content('The directory has been successfully created')
+      expect(page).to have_content('New Merge Request')
+      expect(page).to have_content('From new-feature into master')
+      expect(page).to have_content('Add new directory')
+
+      expect(current_path).to eq(project_new_merge_request_path(project))
+    end
+  end
+
+  context 'when an user does not have write access' do
+    before do
+      project2.add_reporter(user)
+      visit(project2_tree_path_root_ref)
+    end
+
+    it 'creates a directory in a forked project' do
+      find('.add-to-tree').click
+      click_link('New directory')
+
+      expect(page).to have_content(fork_message)
+
+      find('.add-to-tree').click
+      click_link('New directory')
+      fill_in(:dir_name, with: 'new_directory')
+      fill_in(:commit_message, with: 'New commit message', visible: true)
+      click_button('Create directory')
+
+      fork = user.fork_of(project2.reload)
+
+      expect(current_path).to eq(project_new_merge_request_path(fork))
+    end
+  end
+end
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..208cc8d81f76eefa89c10732e1063b92554f4b1a
--- /dev/null
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -0,0 +1,190 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User creates files' do
+  let(:fork_message) do
+    "You're not allowed to make changes to this project directly. "\
+    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+  let(:project) { create(:project, :repository, name: 'Shop') }
+  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_master(user)
+    sign_in(user)
+  end
+
+  context 'without commiting a new file' do
+    context 'when an user has write access' do
+      before do
+        visit(project_tree_path_root_ref)
+      end
+
+      it 'opens new file page' do
+        find('.add-to-tree').click
+        click_link('New file')
+
+        expect(page).to have_content('New file')
+        expect(page).to have_content('Commit message')
+      end
+    end
+
+    context 'when an user does not have write access' do
+      before do
+        project2.add_reporter(user)
+        visit(project2_tree_path_root_ref)
+      end
+
+      it 'opens new file page on a forked project' do
+        find('.add-to-tree').click
+        click_link('New file')
+
+        expect(page).to have_selector('.file-editor')
+        expect(page).to have_content(fork_message)
+        expect(page).to have_content('New file')
+        expect(page).to have_content('Commit message')
+      end
+    end
+  end
+
+  context 'with commiting a new file' do
+    context 'when an user has write access' do
+      before do
+        visit(project_tree_path_root_ref)
+
+        find('.add-to-tree').click
+        click_link('New file')
+        expect(page).to have_selector('.file-editor')
+      end
+
+      def submit_new_file(options)
+        file_name = find('#file_name')
+        file_name.set options[:file_name] || 'README.md'
+
+        file_content = find('#file-content', visible: false)
+        file_content.set options[:file_content] || 'Some content'
+
+        click_button 'Commit changes'
+      end
+
+      it 'allows Chinese characters in file name' do
+        submit_new_file(file_name: '娴嬭瘯.md')
+        expect(page).to have_content 'The file has been successfully created.'
+      end
+
+      it 'allows Chinese characters in directory name' do
+        submit_new_file(file_name: '涓枃/娴嬭瘯.md')
+        expect(page).to have_content 'The file has been successfully created'
+      end
+
+      it 'does not allow directory traversal in file name' do
+        submit_new_file(file_name: '../README.md')
+        expect(page).to have_content 'Path cannot include directory traversal'
+      end
+
+      it 'creates and commit a new file', :js do
+        find('#editor')
+        execute_script("ace.edit('editor').setValue('*.rbca')")
+        fill_in(:file_name, with: 'not_a_file.md')
+        fill_in(:commit_message, with: 'New commit message', visible: true)
+        click_button('Commit changes')
+
+        new_file_path = project_blob_path(project, 'master/not_a_file.md')
+
+        expect(current_path).to eq(new_file_path)
+
+        wait_for_requests
+
+        expect(page).to have_content('*.rbca')
+      end
+
+      it 'creates and commit a new file with new lines at the end of file', :js do
+        find('#editor')
+        execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
+        fill_in(:file_name, with: 'not_a_file.md')
+        fill_in(:commit_message, with: 'New commit message', visible: true)
+        click_button('Commit changes')
+
+        new_file_path = project_blob_path(project, 'master/not_a_file.md')
+
+        expect(current_path).to eq(new_file_path)
+
+        find('.js-edit-blob').click
+
+        find('#editor')
+        expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
+      end
+
+      it 'creates and commit a new file with a directory name', :js do
+        fill_in(:file_name, with: 'foo/bar/baz.txt')
+
+        expect(page).to have_selector('.file-editor')
+
+        find('#editor')
+        execute_script("ace.edit('editor').setValue('*.rbca')")
+        fill_in(:commit_message, with: 'New commit message', visible: true)
+        click_button('Commit changes')
+
+        expect(current_path).to eq(project_blob_path(project, 'master/foo/bar/baz.txt'))
+
+        wait_for_requests
+
+        expect(page).to have_content('*.rbca')
+      end
+
+      it 'creates and commit a new file specifying a new branch', :js do
+        expect(page).to have_selector('.file-editor')
+
+        find('#editor')
+        execute_script("ace.edit('editor').setValue('*.rbca')")
+        fill_in(:file_name, with: 'not_a_file.md')
+        fill_in(:commit_message, with: 'New commit message', visible: true)
+        fill_in(:branch_name, with: 'new_branch_name', visible: true)
+        click_button('Commit changes')
+
+        expect(current_path).to eq(project_new_merge_request_path(project))
+
+        click_link('Changes')
+
+        wait_for_requests
+
+        expect(page).to have_content('*.rbca')
+      end
+    end
+
+    context 'when an user does not have write access' do
+      before do
+        project2.add_reporter(user)
+        visit(project2_tree_path_root_ref)
+
+        find('.add-to-tree').click
+        click_link('New file')
+      end
+
+      it 'shows a message saying the file will be committed in a fork' do
+        message = "A new branch will be created in your fork and a new merge request will be started."
+
+        expect(page).to have_content(message)
+      end
+
+      it 'creates and commit new file in forked project', :js do
+        expect(page).to have_selector('.file-editor')
+        expect(page).to have_content
+
+        find('#editor')
+        execute_script("ace.edit('editor').setValue('*.rbca')")
+
+        fill_in(:file_name, with: 'not_a_file.md')
+        fill_in(:commit_message, with: 'New commit message', visible: true)
+        click_button('Commit changes')
+
+        fork = user.fork_of(project2.reload)
+
+        expect(current_path).to eq(project_new_merge_request_path(fork))
+        expect(page).to have_content('New commit message')
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..36d3e001a64e60a4d2f99bf1e68e4c0b45d6b763
--- /dev/null
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User deletes files' do
+  let(:fork_message) do
+    "You're not allowed to make changes to this project directly. "\
+    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+  let(:project) { create(:project, :repository, name: 'Shop') }
+  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+  let(:user) { create(:user) }
+
+  before do
+    sign_in(user)
+  end
+
+  context 'when an user has write access' do
+    before do
+      project.add_master(user)
+      visit(project_tree_path_root_ref)
+    end
+
+    it 'deletes the file', :js do
+      click_link('.gitignore')
+
+      expect(page).to have_content('.gitignore')
+
+      click_on('Delete')
+      fill_in(:commit_message, with: 'New commit message', visible: true)
+      click_button('Delete file')
+
+      expect(current_path).to eq(project_tree_path(project, 'master'))
+      expect(page).not_to have_content('.gitignore')
+    end
+  end
+
+  context 'when an user does not have write access' do
+    before do
+      project2.add_reporter(user)
+      visit(project2_tree_path_root_ref)
+    end
+
+    it 'deletes the file in a forked project', :js do
+      click_link('.gitignore')
+
+      expect(page).to have_content('.gitignore')
+
+      click_on('Delete')
+
+      expect(page).to have_link('Fork')
+      expect(page).to have_button('Cancel')
+
+      click_link('Fork')
+
+      expect(page).to have_content(fork_message)
+
+      click_on('Delete')
+      fill_in(:commit_message, with: 'New commit message', visible: true)
+      click_button('Delete file')
+
+      fork = user.fork_of(project2.reload)
+
+      expect(current_path).to eq(project_new_merge_request_path(fork))
+      expect(page).to have_content('New commit message')
+    end
+  end
+end
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dc6e4fd27cbdd3690aae70ad9b6c9a10f2841358
--- /dev/null
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -0,0 +1,196 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User edits files' do
+  include ProjectForksHelper
+  let(:project) { create(:project, :repository, name: 'Shop') }
+  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+  let(:user) { create(:user) }
+
+  before do
+    sign_in(user)
+  end
+
+  shared_examples 'unavailable for an archived project' do
+    it 'does not show the edit link for an archived project', :js do
+      project.update!(archived: true)
+      visit project_tree_path(project, project.repository.root_ref)
+
+      click_link('.gitignore')
+
+      aggregate_failures 'available edit buttons' do
+        expect(page).not_to have_text('Edit')
+        expect(page).not_to have_text('Web IDE')
+
+        expect(page).not_to have_text('Replace')
+        expect(page).not_to have_text('Delete')
+      end
+    end
+  end
+
+  context 'when an user has write access' do
+    before do
+      project.add_master(user)
+      visit(project_tree_path_root_ref)
+    end
+
+    it 'inserts a content of a file', :js do
+      click_link('.gitignore')
+      find('.js-edit-blob').click
+      find('.file-editor', match: :first)
+
+      find('#editor')
+      execute_script("ace.edit('editor').setValue('*.rbca')")
+
+      expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+    end
+
+    it 'does not show the edit link if a file is binary' do
+      binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png')
+      visit(project_blob_path(project, binary_file))
+
+      page.within '.content' do
+        expect(page).not_to have_link('edit')
+      end
+    end
+
+    it 'commits an edited file', :js do
+      click_link('.gitignore')
+      find('.js-edit-blob').click
+      find('.file-editor', match: :first)
+
+      find('#editor')
+      execute_script("ace.edit('editor').setValue('*.rbca')")
+      fill_in(:commit_message, with: 'New commit message', visible: true)
+      click_button('Commit changes')
+
+      expect(current_path).to eq(project_blob_path(project, 'master/.gitignore'))
+
+      wait_for_requests
+
+      expect(page).to have_content('*.rbca')
+    end
+
+    it 'commits an edited file to a new branch', :js do
+      click_link('.gitignore')
+      find('.js-edit-blob').click
+
+      find('.file-editor', match: :first)
+
+      find('#editor')
+      execute_script("ace.edit('editor').setValue('*.rbca')")
+      fill_in(:commit_message, with: 'New commit message', visible: true)
+      fill_in(:branch_name, with: 'new_branch_name', visible: true)
+      click_button('Commit changes')
+
+      expect(current_path).to eq(project_new_merge_request_path(project))
+
+      click_link('Changes')
+
+      expect(page).to have_content('*.rbca')
+    end
+
+    it 'shows the diff of an edited file', :js do
+      click_link('.gitignore')
+      find('.js-edit-blob').click
+      find('.file-editor', match: :first)
+
+      find('#editor')
+      execute_script("ace.edit('editor').setValue('*.rbca')")
+      click_link('Preview changes')
+
+      expect(page).to have_css('.line_holder.new')
+    end
+
+    it_behaves_like 'unavailable for an archived project'
+  end
+
+  context 'when an user does not have write access' do
+    before do
+      project2.add_reporter(user)
+      visit(project2_tree_path_root_ref)
+    end
+
+    it 'inserts a content of a file in a forked project', :js do
+      click_link('.gitignore')
+      find('.js-edit-blob').click
+
+      expect(page).to have_link('Fork')
+      expect(page).to have_button('Cancel')
+
+      click_link('Fork')
+
+      expect(page).to have_content(
+        "You're not allowed to make changes to this project directly. "\
+        "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+      )
+
+      find('.file-editor', match: :first)
+
+      find('#editor')
+      execute_script("ace.edit('editor').setValue('*.rbca')")
+
+      expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+    end
+
+    it 'commits an edited file in a forked project', :js do
+      click_link('.gitignore')
+      find('.js-edit-blob').click
+
+      expect(page).to have_link('Fork')
+      expect(page).to have_button('Cancel')
+
+      click_link('Fork')
+
+      find('.file-editor', match: :first)
+
+      find('#editor')
+      execute_script("ace.edit('editor').setValue('*.rbca')")
+      fill_in(:commit_message, with: 'New commit message', visible: true)
+      click_button('Commit changes')
+
+      fork = user.fork_of(project2.reload)
+
+      expect(current_path).to eq(project_new_merge_request_path(fork))
+
+      wait_for_requests
+
+      expect(page).to have_content('New commit message')
+    end
+
+    context 'when the user already had a fork of the project', :js do
+      let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) }
+      before do
+        visit(project2_tree_path_root_ref)
+      end
+
+      it 'links to the forked project for editing' do
+        click_link('.gitignore')
+        find('.js-edit-blob').click
+
+        expect(page).not_to have_link('Fork')
+        expect(page).not_to have_button('Cancel')
+
+        find('#editor')
+        execute_script("ace.edit('editor').setValue('*.rbca')")
+        fill_in(:commit_message, with: 'Another commit', visible: true)
+        click_button('Commit changes')
+
+        fork = user.fork_of(project2)
+
+        expect(current_path).to eq(project_new_merge_request_path(fork))
+
+        wait_for_requests
+
+        expect(page).to have_content('Another commit')
+        expect(page).to have_content("From #{forked_project.full_path}")
+        expect(page).to have_content("into #{project2.full_path}")
+      end
+
+      it_behaves_like 'unavailable for an archived project' do
+        let(:project) { project2 }
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2fb9da2f0a2f5fea5403941fe1afab4b5a453bee
--- /dev/null
+++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe 'user reads pipeline status', :js do
+  let(:project) { create(:project, :repository) }
+  let(:user) { create(:user) }
+  let(:v110_pipeline) { create_pipeline('v1.1.0', 'success') }
+  let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') }
+
+  before do
+    project.add_master(user)
+
+    project.repository.add_tag(user, 'x1.1.0', 'v1.1.0')
+    v110_pipeline
+    x110_pipeline
+
+    sign_in(user)
+  end
+
+  shared_examples 'visiting project tree' do
+    scenario 'sees the correct pipeline status' do
+      visit project_tree_path(project, expected_pipeline.ref)
+      wait_for_requests
+
+      page.within('.blob-commit-info') do
+        expect(page).to have_link('', href: project_pipeline_path(project, expected_pipeline))
+        expect(page).to have_selector(".ci-status-icon-#{expected_pipeline.status}")
+      end
+    end
+  end
+
+  it_behaves_like 'visiting project tree' do
+    let(:expected_pipeline) { v110_pipeline }
+  end
+
+  it_behaves_like 'visiting project tree' do
+    let(:expected_pipeline) { x110_pipeline }
+  end
+
+  def create_pipeline(ref, status)
+    create(:ci_pipeline,
+      project: project,
+      ref: ref,
+      sha: project.commit(ref).sha,
+      status: status)
+  end
+end
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9ac3417b671d7be6eba2b9380bc715d6c54b0c2c
--- /dev/null
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User replaces files' do
+  include DropzoneHelper
+
+  let(:fork_message) do
+    "You're not allowed to make changes to this project directly. "\
+    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+  let(:project) { create(:project, :repository, name: 'Shop') }
+  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+  let(:user) { create(:user) }
+
+  before do
+    sign_in(user)
+  end
+
+  context 'when an user has write access' do
+    before do
+      project.add_master(user)
+      visit(project_tree_path_root_ref)
+    end
+
+    it 'replaces an existed file with a new one', :js do
+      click_link('.gitignore')
+
+      expect(page).to have_content('.gitignore')
+
+      click_on('Replace')
+      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+      page.within('#modal-upload-blob') do
+        fill_in(:commit_message, with: 'Replacement file commit message')
+      end
+
+      click_button('Replace file')
+
+      expect(page).to have_content('Lorem ipsum dolor sit amet')
+      expect(page).to have_content('Sed ut perspiciatis unde omnis')
+      expect(page).to have_content('Replacement file commit message')
+    end
+  end
+
+  context 'when an user does not have write access' do
+    before do
+      project2.add_reporter(user)
+      visit(project2_tree_path_root_ref)
+    end
+
+    it 'replaces an existed file with a new one in a forked project', :js do
+      click_link('.gitignore')
+
+      expect(page).to have_content('.gitignore')
+
+      click_on('Replace')
+
+      expect(page).to have_link('Fork')
+      expect(page).to have_button('Cancel')
+
+      click_link('Fork')
+
+      expect(page).to have_content(fork_message)
+
+      click_on('Replace')
+      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+      page.within('#modal-upload-blob') do
+        fill_in(:commit_message, with: 'Replacement file commit message')
+      end
+
+      click_button('Replace file')
+
+      expect(page).to have_content('Replacement file commit message')
+
+      fork = user.fork_of(project2.reload)
+
+      expect(current_path).to eq(project_new_merge_request_path(fork))
+
+      click_link('Changes')
+
+      expect(page).to have_content('Lorem ipsum dolor sit amet')
+      expect(page).to have_content('Sed ut perspiciatis unde omnis')
+    end
+  end
+end
diff --git a/spec/features/projects/files/user_searches_for_files_spec.rb b/spec/features/projects/files/user_searches_for_files_spec.rb
index a105685bca7003a1701df5610e90c46b45cb2b69..a90e4918fb19e26665121b15d9d24ea1e6871342 100644
--- a/spec/features/projects/files/user_searches_for_files_spec.rb
+++ b/spec/features/projects/files/user_searches_for_files_spec.rb
@@ -1,8 +1,7 @@
 require 'spec_helper'
 
-describe 'User searches for files' do
-  let(:user) { create(:user) }
-  let(:project) { create(:project, :repository) }
+describe 'Projects > Files > User searches for files' do
+  let(:user) { project.owner }
 
   before do
     sign_in(user)
@@ -10,11 +9,10 @@ describe 'User searches for files' do
 
   describe 'project main screen' do
     context 'when project is empty' do
-      let(:empty_project) { create(:project) }
+      let(:project) { create(:project) }
 
       before do
-        empty_project.add_developer(user)
-        visit project_path(empty_project)
+        visit project_path(project)
       end
 
       it 'does not show any result' do
@@ -26,6 +24,8 @@ describe 'User searches for files' do
     end
 
     context 'when project is not empty' do
+      let(:project) { create(:project, :repository) }
+
       before do
         project.add_developer(user)
         visit project_path(project)
@@ -38,16 +38,16 @@ describe 'User searches for files' do
   end
 
   describe 'project tree screen' do
+    let(:project) { create(:project, :repository) }
+
     before do
       project.add_developer(user)
       visit project_tree_path(project, project.default_branch)
     end
 
-    it 'shows "Find file" button' do
+    it 'shows found files' do
       expect(page).to have_selector('.tree-controls .shortcuts-find-file')
-    end
 
-    it 'shows found files' do
       fill_in('search', with: 'coffee')
       click_button('Go')
 
diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8b212faa29d6b344991de56de784de3f27a053d7
--- /dev/null
+++ b/spec/features/projects/files/user_uploads_files_spec.rb
@@ -0,0 +1,105 @@
+require 'spec_helper'
+
+describe 'Projects > Files > User uploads files' do
+  include DropzoneHelper
+
+  let(:fork_message) do
+    "You're not allowed to make changes to this project directly. "\
+    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :repository, name: 'Shop', creator: user) }
+  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+
+  before do
+    project.add_master(user)
+    sign_in(user)
+  end
+
+  context 'when an user has write access' do
+    before do
+      visit(project_tree_path_root_ref)
+    end
+
+    it 'uploads and commit a new text file', :js do
+      find('.add-to-tree').click
+      click_link('Upload file')
+      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+      page.within('#modal-upload-blob') do
+        fill_in(:commit_message, with: 'New commit message')
+      end
+
+      fill_in(:branch_name, with: 'new_branch_name', visible: true)
+      click_button('Upload file')
+
+      expect(page).to have_content('New commit message')
+      expect(current_path).to eq(project_new_merge_request_path(project))
+
+      click_link('Changes')
+      find("a[data-action='diffs']", text: 'Changes').click
+
+      wait_for_requests
+
+      expect(page).to have_content('Lorem ipsum dolor sit amet')
+      expect(page).to have_content('Sed ut perspiciatis unde omnis')
+    end
+
+    it 'uploads and commit a new image file', :js do
+      find('.add-to-tree').click
+      click_link('Upload file')
+      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
+
+      page.within('#modal-upload-blob') do
+        fill_in(:commit_message, with: 'New commit message')
+        fill_in(:branch_name, with: 'new_branch_name', visible: true)
+        click_button('Upload file')
+      end
+
+      wait_for_all_requests
+
+      visit(project_blob_path(project, 'new_branch_name/logo_sample.svg'))
+
+      expect(page).to have_css('.file-content img')
+    end
+  end
+
+  context 'when an user does not have write access' do
+    before do
+      project2.add_reporter(user)
+      visit(project2_tree_path_root_ref)
+    end
+
+    it 'uploads and commit a new file to a forked project', :js do
+      find('.add-to-tree').click
+      click_link('Upload file')
+
+      expect(page).to have_content(fork_message)
+
+      find('.add-to-tree').click
+      click_link('Upload file')
+      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+      page.within('#modal-upload-blob') do
+        fill_in(:commit_message, with: 'New commit message')
+      end
+
+      click_button('Upload file')
+
+      expect(page).to have_content('New commit message')
+
+      fork = user.fork_of(project2.reload)
+
+      expect(current_path).to eq(project_new_merge_request_path(fork))
+
+      find("a[data-action='diffs']", text: 'Changes').click
+
+      wait_for_requests
+
+      expect(page).to have_content('Lorem ipsum dolor sit amet')
+      expect(page).to have_content('Sed ut perspiciatis unde omnis')
+    end
+  end
+end
diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb
index 842840cc04ca8d99bf8d8ffd4e4959a5a7b9d985..1743b1e083f355c3400aa037ab4a85dc49371230 100644
--- a/spec/features/projects/fork_spec.rb
+++ b/spec/features/projects/fork_spec.rb
@@ -25,6 +25,110 @@ describe 'Project fork' do
     expect(page).to have_css('a.disabled', text: 'Fork')
   end
 
+  it 'forks the project' do
+    visit project_path(project)
+
+    click_link 'Fork'
+
+    page.within '.fork-thumbnail-container' do
+      click_link user.name
+    end
+
+    expect(page).to have_content 'Forked from'
+
+    visit project_path(project)
+
+    expect(page).to have_content(/new merge request/i)
+
+    page.within '.nav-sidebar' do
+      first(:link, 'Merge Requests').click
+    end
+
+    expect(page).to have_content(/new merge request/i)
+
+    page.within '#content-body' do
+      click_link('New merge request')
+    end
+
+    expect(current_path).to have_content(/#{user.namespace.name}/i)
+  end
+
+  it 'shows the forked project on the list' do
+    visit project_path(project)
+
+    click_link 'Fork'
+
+    page.within '.fork-thumbnail-container' do
+      click_link user.name
+    end
+
+    visit project_forks_path(project)
+
+    forked_project = user.fork_of(project.reload)
+
+    page.within('.js-projects-list-holder') do
+      expect(page).to have_content("#{forked_project.namespace.human_name} / #{forked_project.name}")
+    end
+
+    forked_project.update!(path: 'test-crappy-path')
+
+    visit project_forks_path(project)
+
+    page.within('.js-projects-list-holder') do
+      expect(page).to have_content("#{forked_project.namespace.human_name} / #{forked_project.name}")
+    end
+  end
+
+  context 'when the project is private' do
+    let(:project) { create(:project, :repository) }
+    let(:another_user) { create(:user, name: 'Mike') }
+
+    before do
+      project.add_reporter(user)
+      project.add_reporter(another_user)
+    end
+
+    it 'renders private forks of the project' do
+      visit project_path(project)
+
+      another_project_fork = Projects::ForkService.new(project, another_user).execute
+
+      click_link 'Fork'
+
+      page.within '.fork-thumbnail-container' do
+        click_link user.name
+      end
+
+      visit project_forks_path(project)
+
+      page.within('.js-projects-list-holder') do
+        user_project_fork = user.fork_of(project.reload)
+        expect(page).to have_content("#{user_project_fork.namespace.human_name} / #{user_project_fork.name}")
+      end
+
+      expect(page).not_to have_content("#{another_project_fork.namespace.human_name} / #{another_project_fork.name}")
+      expect(page).to have_content("1 private fork")
+    end
+  end
+
+  context 'when the user already forked the project' do
+    before do
+      create(:project, :repository, name: project.name, namespace: user.namespace)
+    end
+
+    it 'renders error' do
+      visit project_path(project)
+
+      click_link 'Fork'
+
+      page.within '.fork-thumbnail-container' do
+        click_link user.name
+      end
+
+      expect(page).to have_content "Name has already been taken"
+    end
+  end
+
   context 'master in group' do
     let(:group) { create(:group) }
 
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..57172610aede47b973a65ce0f30d010be6c1d415
--- /dev/null
+++ b/spec/features/projects/graph_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe 'Project Graph', :js do
+  let(:user) { create :user }
+  let(:project) { create(:project, :repository, namespace: user.namespace) }
+
+  before do
+    project.add_master(user)
+
+    sign_in(user)
+  end
+
+  shared_examples 'page should have commits graphs' do
+    it 'renders commits' do
+      expect(page).to have_content('Commit statistics for master')
+      expect(page).to have_content('Commits per day of month')
+    end
+  end
+
+  shared_examples 'page should have languages graphs' do
+    it 'renders languages' do
+      expect(page).to have_content(/Ruby 66.* %/)
+      expect(page).to have_content(/JavaScript 22.* %/)
+    end
+  end
+
+  it 'renders graphs' do
+    visit project_graph_path(project, 'master')
+
+    expect(page).to have_selector('.stat-graph', visible: false)
+  end
+
+  context 'commits graph' do
+    before do
+      visit commits_project_graph_path(project, 'master')
+    end
+
+    it_behaves_like 'page should have commits graphs'
+    it_behaves_like 'page should have languages graphs'
+  end
+
+  context 'languages graph' do
+    before do
+      visit languages_project_graph_path(project, 'master')
+    end
+
+    it_behaves_like 'page should have commits graphs'
+    it_behaves_like 'page should have languages graphs'
+  end
+
+  context 'charts graph' do
+    before do
+      visit charts_project_graph_path(project, 'master')
+    end
+
+    it_behaves_like 'page should have commits graphs'
+    it_behaves_like 'page should have languages graphs'
+  end
+
+  context 'when CI enabled' do
+    before do
+      project.enable_ci
+
+      visit ci_project_graph_path(project, 'master')
+    end
+
+    it 'renders CI graphs' do
+      expect(page).to have_content 'Overall'
+      expect(page).to have_content 'Pipelines for last week'
+      expect(page).to have_content 'Pipelines for last month'
+      expect(page).to have_content 'Pipelines for last year'
+      expect(page).to have_content 'Commit duration in minutes for last 30 commits'
+    end
+  end
+end
diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb
deleted file mode 100644
index 199682b943c256ee8ddcd8efaeabcaf4de0f730e..0000000000000000000000000000000000000000
--- a/spec/features/projects/guest_navigation_menu_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-require 'spec_helper'
-
-describe 'Guest navigation menu' do
-  let(:project) { create(:project, :private, public_builds: false) }
-  let(:guest) { create(:user) }
-
-  before do
-    project.add_guest(guest)
-
-    sign_in(guest)
-  end
-
-  it 'shows allowed tabs only' do
-    visit project_path(project)
-
-    within('.nav-sidebar') do
-      expect(page).to have_content 'Overview'
-      expect(page).to have_content 'Issues'
-      expect(page).to have_content 'Wiki'
-
-      expect(page).not_to have_content 'Repository'
-      expect(page).not_to have_content 'Pipelines'
-      expect(page).not_to have_content 'Merge Requests'
-    end
-  end
-
-  it 'does not show fork button' do
-    visit project_path(project)
-
-    within('.count-buttons') do
-      expect(page).not_to have_link 'Fork'
-    end
-  end
-
-  it 'does not show clone path' do
-    visit project_path(project)
-
-    within('.project-repo-buttons') do
-      expect(page).not_to have_selector '.project-clone-holder'
-    end
-  end
-
-  describe 'project landing page' do
-    before do
-      project.project_feature.update!(
-        issues_access_level: ProjectFeature::DISABLED,
-        wiki_access_level: ProjectFeature::DISABLED
-      )
-    end
-
-    it 'does not show the project file list landing page' do
-      visit project_path(project)
-
-      expect(page).not_to have_selector '.project-stats'
-      expect(page).not_to have_selector '.project-last-commit'
-      expect(page).not_to have_selector '.project-show-files'
-      expect(page).to have_selector '.project-show-customize_workflow'
-    end
-
-    it 'shows the customize workflow when issues and wiki are disabled' do
-      visit project_path(project)
-
-      expect(page).to have_selector '.project-show-customize_workflow'
-    end
-
-    it 'shows the wiki when enabled' do
-      project.project_feature.update!(wiki_access_level: ProjectFeature::PRIVATE)
-
-      visit project_path(project)
-
-      expect(page).to have_selector '.project-show-wiki'
-    end
-
-    it 'shows the issues when enabled' do
-      project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
-
-      visit project_path(project)
-
-      expect(page).to have_selector '.issues-list'
-    end
-  end
-end
diff --git a/spec/features/projects/hook_logs/user_reads_log_spec.rb b/spec/features/projects/hook_logs/user_reads_log_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..18e975fa6532fb0ed3ea571de6735f7ce1c85e6d
--- /dev/null
+++ b/spec/features/projects/hook_logs/user_reads_log_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Hook logs' do
+  given(:web_hook_log) { create(:web_hook_log, response_body: '<script>') }
+  given(:project) { web_hook_log.web_hook.project }
+  given(:user) { create(:user) }
+
+  before do
+    project.add_master(user)
+
+    sign_in(user)
+  end
+
+  scenario 'user reads log without getting XSS' do
+    visit(
+      project_hook_hook_log_path(
+        project, web_hook_log.web_hook, web_hook_log))
+
+    expect(page).to have_content('<script>')
+  end
+end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index e8bb9c6a86c8abab7b2f1bd346585aafcfac7095..b25f5161748473bac0b267563bd7856ef99d726e 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -41,6 +41,7 @@ feature 'Import/Export - project import integration test', :js do
 
         project = Project.last
         expect(project).not_to be_nil
+        expect(project.description).to eq("Foo Bar")
         expect(project.issues).not_to be_empty
         expect(project.merge_requests).not_to be_empty
         expect(project_hook_exists?(project)).to be true
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 0cc68aff4947757cbddeaee30bc82076cafec2b4..ecb7651acad10a83a29eb0c2a833c9f2368903e3 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/issues/user_comments_on_issue_spec.rb b/spec/features/projects/issues/user_comments_on_issue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c45fdc7642fe9779bce0e79b7e15830f5377557b
--- /dev/null
+++ b/spec/features/projects/issues/user_comments_on_issue_spec.rb
@@ -0,0 +1,73 @@
+require "spec_helper"
+
+describe "User comments on issue", :js do
+  include Spec::Support::Helpers::Features::NotesHelpers
+
+  let(:project) { create(:project_empty_repo, :public) }
+  let(:issue) { create(:issue, project: project) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_guest(user)
+    sign_in(user)
+
+    visit(project_issue_path(project, issue))
+  end
+
+  context "when adding comments" do
+    it "adds comment" do
+      content = "XML attached"
+      target_form = ".js-main-target-form"
+
+      add_note(content)
+
+      page.within(".note") do
+        expect(page).to have_content(content)
+      end
+
+      page.within(target_form) do
+        find(".error-alert", visible: false)
+      end
+    end
+
+    it "adds comment with code block" do
+      comment = "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```"
+
+      add_note(comment)
+
+      expect(page).to have_content(comment)
+    end
+  end
+
+  context "when editing comments" do
+    it "edits comment" do
+      add_note("# Comment with a header")
+
+      page.within(".note-body > .note-text") do
+        expect(page).to have_content("Comment with a header").and have_no_css("#comment-with-a-header")
+      end
+
+      page.within(".main-notes-list") do
+        note = find(".note")
+
+        note.hover
+        note.find(".js-note-edit").click
+      end
+
+      expect(page).to have_css(".current-note-edit-form textarea")
+
+      comment = "+1 Awesome!"
+
+      page.within(".current-note-edit-form") do
+        fill_in("note[note]", with: comment)
+        click_button("Save comment")
+      end
+
+      wait_for_requests
+
+      page.within(".note") do
+        expect(page).to have_content(comment)
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/issues/user_creates_issue_spec.rb b/spec/features/projects/issues/user_creates_issue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e76f7c5589d84c7d35df90cb806201c17ffb746a
--- /dev/null
+++ b/spec/features/projects/issues/user_creates_issue_spec.rb
@@ -0,0 +1,87 @@
+require "spec_helper"
+
+describe "User creates issue" do
+  let(:project) { create(:project_empty_repo, :public) }
+  let(:user) { create(:user) }
+
+  context "when signed in as guest" do
+    before do
+      project.add_guest(user)
+      sign_in(user)
+
+      visit(new_project_issue_path(project))
+    end
+
+    it "creates issue" do
+      page.within(".issue-form") do
+        expect(page).to have_no_content("Assign to")
+        .and have_no_content("Labels")
+        .and have_no_content("Milestone")
+      end
+
+      issue_title = "500 error on profile"
+
+      fill_in("Title", with: issue_title)
+      click_button("Submit issue")
+
+      expect(page).to have_content(issue_title)
+        .and have_content(user.name)
+        .and have_content(project.name)
+    end
+  end
+
+  context "when signed in as developer", :js do
+    before do
+      project.add_developer(user)
+      sign_in(user)
+
+      visit(new_project_issue_path(project))
+    end
+
+    context "when previewing" do
+      it "previews content" do
+        form = first(".gfm-form")
+        textarea = first(".gfm-form textarea")
+
+        page.within(form) do
+          click_link("Preview")
+
+          preview = find(".js-md-preview") # this element is findable only when the "Preview" link is clicked.
+
+          expect(preview).to have_content("Nothing to preview.")
+
+          click_link("Write")
+          fill_in("Description", with: "Bug fixed :smile:")
+          click_link("Preview")
+
+          expect(preview).to have_css("gl-emoji")
+          expect(textarea).not_to be_visible
+        end
+      end
+    end
+
+    context "with labels" do
+      LABEL_TITLES = %w(bug feature enhancement).freeze
+
+      before do
+        LABEL_TITLES.each do |title|
+          create(:label, project: project, title: title)
+        end
+      end
+
+      it "creates issue" do
+        issue_title = "500 error on profile"
+
+        fill_in("Title", with: issue_title)
+        click_button("Label")
+        click_link(LABEL_TITLES.first)
+        click_button("Submit issue")
+
+        expect(page).to have_content(issue_title)
+          .and have_content(user.name)
+          .and have_content(project.name)
+          .and have_content(LABEL_TITLES.first)
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/issues/user_edits_issue_spec.rb b/spec/features/projects/issues/user_edits_issue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1d9c3abc20feb92bb5e710cc74083643e0677d0b
--- /dev/null
+++ b/spec/features/projects/issues/user_edits_issue_spec.rb
@@ -0,0 +1,25 @@
+require "spec_helper"
+
+describe "User edits issue", :js do
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:user) { create(:user) }
+  set(:issue) { create(:issue, project: project, author: user) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(edit_project_issue_path(project, issue))
+  end
+
+  it "previews content" do
+    form = first(".gfm-form")
+
+    page.within(form) do
+      fill_in("Description", with: "Bug fixed :smile:")
+      click_link("Preview")
+    end
+
+    expect(form).to have_link("Write")
+  end
+end
diff --git a/spec/features/projects/issues/user_sorts_issues_spec.rb b/spec/features/projects/issues/user_sorts_issues_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c3d63000dac3edc39ed96aba08c4cbf532d2aaa0
--- /dev/null
+++ b/spec/features/projects/issues/user_sorts_issues_spec.rb
@@ -0,0 +1,39 @@
+require "spec_helper"
+
+describe "User sorts issues" do
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:issue1) { create(:issue, project: project) }
+  set(:issue2) { create(:issue, project: project) }
+  set(:issue3) { create(:issue, project: project) }
+
+  before do
+    create_list(:award_emoji, 2, :upvote, awardable: issue1)
+    create_list(:award_emoji, 2, :downvote, awardable: issue2)
+    create(:award_emoji, :downvote, awardable: issue1)
+    create(:award_emoji, :upvote, awardable: issue2)
+
+    visit(project_issues_path(project))
+  end
+
+  it "sorts by popularity" do
+    find("button.dropdown-toggle").click
+
+    page.within(".content ul.dropdown-menu.dropdown-menu-align-right li") do
+      click_link("Popularity")
+    end
+
+    page.within(".issues-list") do
+      page.within("li.issue:nth-child(1)") do
+        expect(page).to have_content(issue1.title)
+      end
+
+      page.within("li.issue:nth-child(2)") do
+        expect(page).to have_content(issue2.title)
+      end
+
+      page.within("li.issue:nth-child(3)") do
+        expect(page).to have_content(issue3.title)
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/issues/user_toggles_subscription_spec.rb b/spec/features/projects/issues/user_toggles_subscription_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..117a614b98063481df6731d9fe870cf837b85f14
--- /dev/null
+++ b/spec/features/projects/issues/user_toggles_subscription_spec.rb
@@ -0,0 +1,28 @@
+require "spec_helper"
+
+describe "User toggles subscription", :js do
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:user) { create(:user) }
+  set(:issue) { create(:issue, project: project, author: user) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(project_issue_path(project, issue))
+  end
+
+  it "unsibscribes from issue" do
+    subscription_button = find(".js-issuable-subscribe-button")
+
+    # Check we're subscribed.
+    expect(subscription_button).to have_css("button.is-checked")
+
+    # Toggle subscription.
+    find(".js-issuable-subscribe-button button").click
+    wait_for_requests
+
+    # Check we're unsubscribed.
+    expect(subscription_button).to have_css("button:not(.is-checked)")
+  end
+end
diff --git a/spec/features/projects/issues/user_views_issue_spec.rb b/spec/features/projects/issues/user_views_issue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4093876c2897a158851271c6698504ebd5447a3d
--- /dev/null
+++ b/spec/features/projects/issues/user_views_issue_spec.rb
@@ -0,0 +1,32 @@
+require "spec_helper"
+
+describe "User views issue" do
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:user) { create(:user) }
+  set(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+
+    visit(project_issue_path(project, issue))
+  end
+
+  it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") }
+
+  it 'shows the merge request and issue actions', :aggregate_failures do
+    expect(page).to have_link('New issue')
+    expect(page).to have_button('Create merge request')
+    expect(page).to have_link('Close issue')
+  end
+
+  context 'when the project is archived' do
+    let(:project) { create(:project, :public, :archived) }
+
+    it 'hides the merge request and issue actions', :aggregate_failures do
+      expect(page).not_to have_link('New issue')
+      expect(page).not_to have_button('Create merge request')
+      expect(page).not_to have_link('Close issue')
+    end
+  end
+end
diff --git a/spec/features/projects/issues/user_views_issues_spec.rb b/spec/features/projects/issues/user_views_issues_spec.rb
index d35009b89749762cc5e6bdce86872dacd712c04b..58afb4efb861f5057b4df655305a27c9a0bf6953 100644
--- a/spec/features/projects/issues/user_views_issues_spec.rb
+++ b/spec/features/projects/issues/user_views_issues_spec.rb
@@ -1,56 +1,116 @@
-require 'spec_helper'
+require "spec_helper"
 
-describe 'User views issues' do
+describe "User views issues" do
+  let!(:closed_issue) { create(:closed_issue, project: project) }
+  let!(:open_issue1) { create(:issue, project: project) }
+  let!(:open_issue2) { create(:issue, project: project) }
   set(:user) { create(:user) }
 
-  shared_examples_for 'shows issues' do
-    it 'shows issues' do
-      expect(page).to have_content(project.name)
-        .and have_content(issue1.title)
-        .and have_content(issue2.title)
-        .and have_no_selector('.js-new-board-list')
+  shared_examples "opens issue from list" do
+    it "opens issue" do
+      click_link(issue.title)
+
+      expect(page).to have_content(issue.title)
     end
   end
 
-  context 'when project is public' do
-    set(:project) { create(:project_empty_repo, :public) }
-    set(:issue1) { create(:issue, project: project) }
-    set(:issue2) { create(:issue, project: project) }
+  shared_examples "open issues" do
+    context "open issues" do
+      let(:label) { create(:label, project: project, title: "bug") }
 
-    context 'when signed in' do
       before do
-        project.add_developer(user)
-        sign_in(user)
+        open_issue1.labels << label
+
+        visit(project_issues_path(project, state: :opened))
+      end
 
-        visit(project_issues_path(project))
+      it "shows open issues" do
+        expect(page).to have_content(project.name)
+          .and have_content(open_issue1.title)
+          .and have_content(open_issue2.title)
+          .and have_no_content(closed_issue.title)
+          .and have_no_selector(".js-new-board-list")
       end
 
-      include_examples 'shows issues'
+      it "opens issues by label" do
+        page.within(".issues-list") do
+          click_link(label.title)
+        end
+
+        expect(page).to have_content(open_issue1.title)
+          .and have_no_content(open_issue2.title)
+          .and have_no_content(closed_issue.title)
+      end
+
+      include_examples "opens issue from list" do
+        let(:issue) { open_issue1 }
+      end
     end
+  end
 
-    context 'when not signed in' do
+  shared_examples "closed issues" do
+    context "closed issues" do
       before do
-        visit(project_issues_path(project))
+        visit(project_issues_path(project, state: :closed))
+      end
+
+      it "shows closed issues" do
+        expect(page).to have_content(project.name)
+          .and have_content(closed_issue.title)
+          .and have_no_content(open_issue1.title)
+          .and have_no_content(open_issue2.title)
+          .and have_no_selector(".js-new-board-list")
       end
 
-      include_examples 'shows issues'
+      include_examples "opens issue from list" do
+        let(:issue) { closed_issue }
+      end
     end
   end
 
-  context 'when project is internal' do
-    set(:project) { create(:project_empty_repo, :internal) }
-    set(:issue1) { create(:issue, project: project) }
-    set(:issue2) { create(:issue, project: project) }
-
-    context 'when signed in' do
+  shared_examples "all issues" do
+    context "all issues" do
       before do
-        project.add_developer(user)
-        sign_in(user)
+        visit(project_issues_path(project, state: :all))
+      end
 
-        visit(project_issues_path(project))
+      it "shows all issues" do
+        expect(page).to have_content(project.name)
+          .and have_content(closed_issue.title)
+          .and have_content(open_issue1.title)
+          .and have_content(open_issue2.title)
+          .and have_no_selector(".js-new-board-list")
       end
 
-      include_examples 'shows issues'
+      include_examples "opens issue from list" do
+        let(:issue) { closed_issue }
+      end
+    end
+  end
+
+  %w[internal public].each do |visibility|
+    shared_examples "#{visibility} project" do
+      context "when project is #{visibility}" do
+        let(:project) { create(:project_empty_repo, :"#{visibility}") }
+
+        include_examples "open issues"
+        include_examples "closed issues"
+        include_examples "all issues"
+      end
     end
   end
+
+  context "when signed in as developer" do
+    before do
+      project.add_developer(user)
+      sign_in(user)
+    end
+
+    include_examples "public project"
+    include_examples "internal project"
+  end
+
+  context "when not signed in" do
+    include_examples "public project"
+  end
 end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31abadf9bd62e770134b32c6a7b5d9bd118ec1d9
--- /dev/null
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -0,0 +1,130 @@
+require 'spec_helper'
+
+describe 'Project Jobs Permissions' do
+  let(:user) { create(:user) }
+  let(:group) { create(:group, name: 'some group') }
+  let(:project) { create(:project, :repository, namespace: group) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+  let!(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
+
+  before do
+    sign_in(user)
+
+    project.enable_ci
+  end
+
+  describe 'jobs pages' do
+    shared_examples 'recent job page details responds with status' do |status|
+      before do
+        visit project_job_path(project, job)
+      end
+
+      it { expect(status_code).to eq(status) }
+    end
+
+    shared_examples 'project jobs page responds with status' do |status|
+      before do
+        visit project_jobs_path(project)
+      end
+
+      it { expect(status_code).to eq(status) }
+    end
+
+    context 'when public access for jobs is disabled' do
+      before do
+        project.update(public_builds: false)
+      end
+
+      context 'when user is a guest' do
+        before do
+          project.add_guest(user)
+        end
+
+        it_behaves_like 'recent job page details responds with status', 404
+        it_behaves_like 'project jobs page responds with status', 404
+      end
+
+      context 'when project is internal' do
+        before do
+          project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+        end
+
+        it_behaves_like 'recent job page details responds with status', 404
+        it_behaves_like 'project jobs page responds with status', 404
+      end
+    end
+
+    context 'when public access for jobs is enabled' do
+      before do
+        project.update(public_builds: true)
+      end
+
+      context 'when project is internal' do
+        before do
+          project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+        end
+
+        it_behaves_like 'recent job page details responds with status', 200 do
+          it 'renders job details', :js do
+            expect(page).to have_content "Job ##{job.id}"
+            expect(page).to have_css '#build-trace'
+          end
+        end
+
+        it_behaves_like 'project jobs page responds with status', 200 do
+          it 'renders job' do
+            page.within('.build') do
+              expect(page).to have_content("##{job.id}")
+                .and have_content(job.sha[0..7])
+                .and have_content(job.ref)
+                .and have_content(job.name)
+            end
+          end
+        end
+      end
+    end
+  end
+
+  describe 'artifacts page' do
+    context 'when recent job has artifacts available' do
+      before do
+        artifacts = Rails.root.join('spec/fixtures/ci_build_artifacts.zip')
+        archive = fixture_file_upload(artifacts, 'application/zip')
+
+        job.update_attributes(legacy_artifacts_file: archive)
+      end
+
+      context 'when public access for jobs is disabled' do
+        before do
+          project.update(public_builds: false)
+        end
+
+        context 'when user with guest role' do
+          before do
+            project.add_guest(user)
+          end
+
+          it 'responds with 404 status' do
+            visit download_project_job_artifacts_path(project, job)
+
+            expect(status_code).to eq(404)
+          end
+        end
+
+        context 'when user with reporter role' do
+          before do
+            project.add_reporter(user)
+          end
+
+          it 'starts download artifact' do
+            visit download_project_job_artifacts_path(project, job)
+
+            expect(status_code).to eq(200)
+            expect(page.response_headers['Content-Type']).to eq 'application/zip'
+            expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 4c49cff30d4cecac808fa80298e9aeeb8a82a755..bff5bbe99afbecab2738f1b2fa809fd7bf5c9d4f 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -1,16 +1,15 @@
 require 'spec_helper'
 
 describe 'User browses a job', :js do
-  let!(:build) { create(:ci_build, :running, :coverage, pipeline: pipeline) }
-  let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
-  let(:project) { create(:project, :repository, namespace: user.namespace) }
   let(:user) { create(:user) }
+  let(:user_access_level) { :developer }
+  let(:project) { create(:project, :repository, namespace: user.namespace) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+  let!(:build) { create(:ci_build, :success, :trace_artifact, :coverage, pipeline: pipeline) }
 
   before do
     project.add_master(user)
     project.enable_ci
-    build.success
-    build.trace.set('job trace')
 
     sign_in(user)
 
@@ -21,7 +20,9 @@ describe 'User browses a job', :js do
     expect(page).to have_content("Job ##{build.id}")
     expect(page).to have_css('#build-trace')
 
-    accept_confirm { click_link('Erase') }
+    # scroll to the top of the page first
+    execute_script "window.scrollTo(0,0)"
+    accept_confirm { find('.js-erase-link').click }
 
     expect(page).to have_no_css('.artifacts')
     expect(build).not_to have_trace
@@ -34,4 +35,26 @@ describe 'User browses a job', :js do
 
     expect(build.project.running_or_pending_build_count).to eq(build.project.builds.running_or_pending.count(:all))
   end
+
+  context 'with a failed job' do
+    let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) }
+
+    it 'displays the failure reason' do
+      within('.builds-container') do
+        build_link = first('.build-job > a')
+        expect(build_link['data-title']).to eq('test - failed <br> (unknown failure)')
+      end
+    end
+  end
+
+  context 'when a failed job has been retried' do
+    let!(:build) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) }
+
+    it 'displays the failure reason and retried label' do
+      within('.builds-container') do
+        build_link = first('.build-job > a')
+        expect(build_link['data-title']).to eq('test - failed <br> (unknown failure) (retried)')
+      end
+    end
+  end
 end
diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb
index 767777f3bf9044b9c559a31e110ec0624dccb1d0..36ebbeadd4a0e162fc657cdfb8ea382fa6902484 100644
--- a/spec/features/projects/jobs/user_browses_jobs_spec.rb
+++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb
@@ -29,4 +29,15 @@ describe 'User browses jobs' do
       expect(ci_lint_tool_link[:href]).to end_with(ci_lint_path)
     end
   end
+
+  context 'with a failed job' do
+    let!(:build) { create(:ci_build, :coverage, :failed, pipeline: pipeline) }
+
+    it 'displays a tooltip with the failure reason' do
+      page.within('.ci-table') do
+        failed_job_link = page.find('.ci-failed')
+        expect(failed_job_link[:title]).to eq('Failed <br> (unknown failure)')
+      end
+    end
+  end
 end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 5d311f2dde33deb4c4c60d3abd35c1547e620368..a00db6dd161b57175dbea39982b892ccbacfddc4 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -113,7 +113,7 @@ feature 'Jobs' do
 
   describe "GET /:project/jobs/:id" do
     context "Job from project" do
-      let(:job) { create(:ci_build, :success, pipeline: pipeline) }
+      let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline) }
 
       before do
         visit project_job_path(project, job)
@@ -136,7 +136,7 @@ feature 'Jobs' do
     end
 
     context 'when job is not running', :js do
-      let(:job) { create(:ci_build, :success, pipeline: pipeline) }
+      let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
 
       before do
         visit project_job_path(project, job)
@@ -153,7 +153,7 @@ feature 'Jobs' do
       end
 
       context 'if job failed' do
-        let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
+        let(:job) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) }
 
         before do
           visit project_job_path(project, job)
@@ -339,7 +339,7 @@ feature 'Jobs' do
 
       context 'job is successfull and has deployment' do
         let(:deployment) { create(:deployment) }
-        let(:job) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
+        let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
 
         it 'shows a link for the job' do
           visit project_job_path(project, job)
@@ -349,7 +349,7 @@ feature 'Jobs' do
       end
 
       context 'job is complete and not successful' do
-        let(:job) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
+        let(:job) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) }
 
         it 'shows a link for the job' do
           visit project_job_path(project, job)
@@ -360,7 +360,7 @@ feature 'Jobs' do
 
       context 'job creates a new deployment' do
         let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
-        let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
+        let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, pipeline: pipeline) }
 
         it 'shows a link to latest deployment' do
           visit project_job_path(project, job)
@@ -379,6 +379,7 @@ feature 'Jobs' do
       end
 
       it 'shows manual action empty state' do
+        expect(page).to have_content(job.detailed_status(user).illustration[:title])
         expect(page).to have_content('This job requires a manual action')
         expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
         expect(page).to have_link('Trigger this manual action')
@@ -402,6 +403,7 @@ feature 'Jobs' do
       end
 
       it 'shows empty state' do
+        expect(page).to have_content(job.detailed_status(user).illustration[:title])
         expect(page).to have_content('This job has not been triggered yet')
         expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
       end
@@ -415,10 +417,64 @@ feature 'Jobs' do
       end
 
       it 'shows pending empty state' do
+        expect(page).to have_content(job.detailed_status(user).illustration[:title])
         expect(page).to have_content('This job has not started yet')
         expect(page).to have_content('This job is in pending state and is waiting to be picked by a runner')
       end
     end
+
+    context 'Canceled job' do
+      context 'with log' do
+        let(:job) { create(:ci_build, :canceled, :trace_artifact, pipeline: pipeline) }
+
+        before do
+          visit project_job_path(project, job)
+        end
+
+        it 'renders job log' do
+          expect(page).to have_selector('.js-build-output')
+        end
+      end
+
+      context 'without log' do
+        let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
+
+        before do
+          visit project_job_path(project, job)
+        end
+
+        it 'renders empty state' do
+          expect(page).to have_content(job.detailed_status(user).illustration[:title])
+          expect(page).not_to have_selector('.js-build-output')
+          expect(page).to have_content('This job has been canceled')
+        end
+      end
+    end
+
+    context 'Skipped job' do
+      let(:job) { create(:ci_build, :skipped, pipeline: pipeline) }
+
+      before do
+        visit project_job_path(project, job)
+      end
+
+      it 'renders empty state' do
+        expect(page).to have_content(job.detailed_status(user).illustration[:title])
+        expect(page).not_to have_selector('.js-build-output')
+        expect(page).to have_content('This job has been skipped')
+      end
+    end
+
+    context 'when job is failed but has no trace' do
+      let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
+
+      it 'renders empty state' do
+        visit project_job_path(project, job)
+
+        expect(job).not_to have_trace
+        expect(page).to have_content('This job does not have a trace.')
+      end
+    end
   end
 
   describe "POST /:project/jobs/:id/cancel", :js do
@@ -435,16 +491,18 @@ feature 'Jobs' do
     end
   end
 
-  describe "POST /:project/jobs/:id/retry" do
+  describe "POST /:project/jobs/:id/retry", :js do
     context "Job from project", :js do
       before do
         job.run!
+        job.cancel!
         visit project_job_path(project, job)
-        find('.js-cancel-job').click()
+        wait_for_requests
+
         find('.js-retry-button').click
       end
 
-      it 'shows the right status and buttons', :js do
+      it 'shows the right status and buttons' do
         page.within('aside.right-sidebar') do
           expect(page).to have_content 'Cancel'
         end
diff --git a/spec/features/projects/labels/user_creates_labels_spec.rb b/spec/features/projects/labels/user_creates_labels_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9fd7f3ee77582337954254e56e22eed64efa8cb0
--- /dev/null
+++ b/spec/features/projects/labels/user_creates_labels_spec.rb
@@ -0,0 +1,88 @@
+require "spec_helper"
+
+describe "User creates labels" do
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:user) { create(:user) }
+
+  shared_examples_for "label creation" do
+    it "creates new label" do
+      title = "bug"
+
+      create_label(title)
+
+      page.within(".other-labels .manage-labels-list") do
+        expect(page).to have_content(title)
+      end
+    end
+  end
+
+  context "in project" do
+    before do
+      project.add_master(user)
+      sign_in(user)
+
+      visit(new_project_label_path(project))
+    end
+
+    context "when data is valid" do
+      include_examples "label creation"
+    end
+
+    context "when data is invalid" do
+      context "when title is invalid" do
+        it "shows error message" do
+          create_label("")
+
+          page.within(".label-form") do
+            expect(page).to have_content("Title can't be blank")
+          end
+        end
+      end
+
+      context "when color is invalid" do
+        it "shows error message" do
+          create_label("feature", "#12")
+
+          page.within(".label-form") do
+            expect(page).to have_content("Color must be a valid color code")
+          end
+        end
+      end
+    end
+
+    context "when label already exists" do
+      let!(:label) { create(:label, project: project) }
+
+      it "shows error message" do
+        create_label(label.title)
+
+        page.within(".label-form") do
+          expect(page).to have_content("Title has already been taken")
+        end
+      end
+    end
+  end
+
+  context "in another project" do
+    set(:another_project) { create(:project_empty_repo, :public) }
+
+    before do
+      create(:label, project: project, title: "bug") # Create label for `project` (not `another_project`) project.
+
+      another_project.add_master(user)
+      sign_in(user)
+
+      visit(new_project_label_path(another_project))
+    end
+
+    include_examples "label creation"
+  end
+
+  private
+
+  def create_label(title, color = "#F95610")
+    fill_in("Title", with: title)
+    fill_in("Background color", with: color)
+    click_button("Create label")
+  end
+end
diff --git a/spec/features/projects/labels/user_edits_labels_spec.rb b/spec/features/projects/labels/user_edits_labels_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d1041ff5c1e05fd91d504f38b6e09cda7c7fdcde
--- /dev/null
+++ b/spec/features/projects/labels/user_edits_labels_spec.rb
@@ -0,0 +1,25 @@
+require "spec_helper"
+
+describe "User edits labels" do
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:label) { create(:label, project: project) }
+  set(:user) { create(:user) }
+
+  before do
+    project.add_master(user)
+    sign_in(user)
+
+    visit(edit_project_label_path(project, label))
+  end
+
+  it "updates label's title" do
+    new_title = "fix"
+
+    fill_in("Title", with: new_title)
+    click_button("Save changes")
+
+    page.within(".other-labels .manage-labels-list") do
+      expect(page).to have_content(new_title).and have_no_content(label.title)
+    end
+  end
+end
diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f4fda6de465bd5ac598ae72b9f042620a1fc54da
--- /dev/null
+++ b/spec/features/projects/labels/user_removes_labels_spec.rb
@@ -0,0 +1,52 @@
+require "spec_helper"
+
+describe "User removes labels" do
+  let(:project) { create(:project_empty_repo, :public) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_master(user)
+    sign_in(user)
+  end
+
+  context "when one label" do
+    let!(:label) { create(:label, project: project) }
+
+    before do
+      visit(project_labels_path(project))
+    end
+
+    it "removes label" do
+      page.within(".labels") do
+        page.first(".label-list-item") do
+          first(".remove-row").click
+          first(:link, "Delete label").click
+        end
+      end
+
+      expect(page).to have_content("Label was removed").and have_no_content(label.title)
+    end
+  end
+
+  context "when many labels", :js do
+    before do
+      create_list(:label, 3, project: project)
+
+      visit(project_labels_path(project))
+    end
+
+    it "removes all labels" do
+      page.within(".labels") do
+        loop do
+          li = page.first(".label-list-item")
+          break unless li
+
+          li.click_link("Delete")
+          click_link("Delete label")
+        end
+
+        expect(page).to have_content("Generate a default set of labels").and have_content("New label")
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/labels/user_views_labels_spec.rb b/spec/features/projects/labels/user_views_labels_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0cbeca4e3929023229e6670ae8bc476ead5deb21
--- /dev/null
+++ b/spec/features/projects/labels/user_views_labels_spec.rb
@@ -0,0 +1,23 @@
+require "spec_helper"
+
+describe "User views labels" do
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:user) { create(:user) }
+
+  LABEL_TITLES = %w[bug enhancement feature].freeze
+
+  before do
+    LABEL_TITLES.each { |title| create(:label, project: project, title: title) }
+
+    project.add_guest(user)
+    sign_in(user)
+
+    visit(project_labels_path(project))
+  end
+
+  it "shows all labels" do
+    page.within('.other-labels .manage-labels-list') do
+      LABEL_TITLES.each { |title| expect(page).to have_content(title) }
+    end
+  end
+end
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
deleted file mode 100644
index 81f08e44cf3f8b3d076067201be5d61adeaab43b..0000000000000000000000000000000000000000
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'spec_helper'
-
-feature 'Download buttons in project main page' do
-  given(:user) { create(:user) }
-  given(:role) { :developer }
-  given(:status) { 'success' }
-  given(:project) { create(:project, :repository) }
-
-  given(:pipeline) do
-    create(:ci_pipeline,
-           project: project,
-           sha: project.commit.sha,
-           ref: project.default_branch,
-           status: status)
-  end
-
-  given!(:build) do
-    create(:ci_build, :success, :artifacts,
-           pipeline: pipeline,
-           status: pipeline.status,
-           name: 'build')
-  end
-
-  background do
-    sign_in(user)
-    project.add_role(user, role)
-  end
-
-  describe 'when checking project main page' do
-    context 'with artifacts' do
-      before do
-        visit project_path(project)
-      end
-
-      scenario 'shows download artifacts button' do
-        href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
-
-        expect(page).to have_link "Download '#{build.name}'", href: href
-      end
-
-      scenario 'download links have download attribute' do
-        expect(page).to have_selector('a', text: 'Download')
-        page.all('a', text: 'Download').each do |link|
-          expect(link[:download]).to eq ''
-        end
-      end
-    end
-  end
-end
diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb
deleted file mode 100644
index 3c98c11b4903cee06e36333219ac562def8238ef..0000000000000000000000000000000000000000
--- a/spec/features/projects/main/rss_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-
-feature 'Project RSS' do
-  let(:user) { create(:user) }
-  let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
-  let(:path) { project_path(project) }
-
-  context 'when signed in' do
-    before do
-      project.add_developer(user)
-      sign_in(user)
-      visit path
-    end
-
-    it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
-  end
-
-  context 'when signed out' do
-    before do
-      visit path
-    end
-
-    it_behaves_like "an autodiscoverable RSS feed without an RSS token"
-  end
-end
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 d575596937d287e4f76fa2867ff27b34ba6ed887..1f4eec0a3172c3f25f8ade58d0c63942ec7a3f40 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -25,7 +25,7 @@ feature 'Projects > Members > Master manages access requests' do
     perform_enqueued_jobs { click_on 'Grant access' }
 
     expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email]
-    expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was granted"
+    expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was granted"
   end
 
   scenario 'master can deny access' do
@@ -36,7 +36,7 @@ feature 'Projects > Members > Master manages access requests' do
     perform_enqueued_jobs { click_on 'Deny access' }
 
     expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email]
-    expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was denied"
+    expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was denied"
   end
 
   def expect_visible_access_request(project, user)
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 4eb3615681258971565be2a4c8ed7ede982a3ee8..672d5daa3d8f0b9de6c57fdbefcceb7492ac4527 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -21,7 +21,7 @@ feature 'Projects > Members > User requests access', :js do
     perform_enqueued_jobs { click_link 'Request Access' }
 
     expect(ActionMailer::Base.deliveries.last.to).to eq [master.notification_email]
-    expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.name_with_namespace} project"
+    expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.full_name} project"
 
     expect(project.requesters.exists?(user_id: user)).to be_truthy
     expect(page).to have_content 'Your request for access has been queued for review.'
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 85d518c0cc3b9922759457880214bf04d9711e8c..b571d5a0e269a46d5b9fe83229d5650d8f0879f3 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -45,6 +45,18 @@ feature 'Merge Request button' do
           end
         end
       end
+
+      context 'when the project is archived' do
+        it 'hides the link' do
+          project.update!(archived: true)
+
+          visit url
+
+          within("#content-body") do
+            expect(page).not_to have_link(label)
+          end
+        end
+      end
     end
 
     context 'logged in as non-member' do
@@ -81,8 +93,8 @@ feature 'Merge Request button' do
   context 'on branches page' do
     it_behaves_like 'Merge request button only shown when allowed' do
       let(:label) { 'Merge request' }
-      let(:url) { project_branches_path(project, search: 'feature') }
-      let(:fork_url) { project_branches_path(forked_project, search: 'feature') }
+      let(:url) { project_branches_filtered_path(project, state: 'all', search: 'feature') }
+      let(:fork_url) { project_branches_filtered_path(forked_project, state: 'all', search: 'feature') }
     end
   end
 
diff --git a/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb b/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb
index a41d683dbbb74f368fac0844c31b7fcdab153502..f3e97bc9eb2ca142845ee35d6b761614f0d31eeb 100644
--- a/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb
+++ b/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb
@@ -56,4 +56,12 @@ describe 'User reverts a merge request', :js do
 
     expect(page).to have_content('The merge request has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
   end
+
+  it 'cannot revert a merge requests for an archived project' do
+    project.update!(archived: true)
+
+    visit(merge_request_path(merge_request))
+
+    expect(page).not_to have_link('Revert')
+  end
 end
diff --git a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb
index bf95dbb7d09ac69b6bd9b351e97dcb028c897b82..115e548b691960a75f52f37f7a71e48331833326 100644
--- a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb
+++ b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb
@@ -94,6 +94,18 @@ describe 'User views open merge requests' do
       end
 
       include_examples 'shows merge requests'
+
+      it 'shows the new merge request button' do
+        expect(page).to have_link('New merge request')
+      end
+
+      context 'when the project is archived' do
+        let(:project) { create(:project, :public, :repository, :archived) }
+
+        it 'hides the new merge request button' do
+          expect(page).not_to have_link('New merge request')
+        end
+      end
     end
   end
 
diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb
index c531b81e04db38221a6edb3aed693903e27c994d..b64786d4eecc4c8f6b22efacf8fecc0503eb5abc 100644
--- a/spec/features/projects/milestones/milestones_sorting_spec.rb
+++ b/spec/features/projects/milestones/milestones_sorting_spec.rb
@@ -1,7 +1,6 @@
 require 'spec_helper'
 
 feature 'Milestones sorting', :js do
-  include SortingHelper
   let(:user)    { create(:user) }
   let(:project) { create(:project, name: 'test', namespace: user.namespace) }
 
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b5104747d00588524bbe16f2b97391f866fa0be1..a5954fec54be72c47192656ddcf07c7df74da840 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -142,7 +142,7 @@ feature 'New project' do
 
     context 'from git repository url, "Repo by URL"' do
       before do
-        first('.import_git').click
+        first('.js-import-git-toggle-button').click
       end
 
       it 'does not autocomplete sensitive git repo URL' do
@@ -173,11 +173,11 @@ feature 'New project' do
 
     context 'from GitHub' do
       before do
-        first('.import_github').click
+        first('.js-import-github').click
       end
 
       it 'shows import instructions' do
-        expect(page).to have_content('Import Projects from GitHub')
+        expect(page).to have_content('Import repositories from GitHub')
         expect(current_path).to eq new_import_github_path
       end
     end
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index 2a0d235ef04c4c09e2e17801f5b27fc5a8c5b88c..bdd49f731c7259b487631426516d1510edefa148 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -40,11 +40,6 @@ feature 'Pages' do
       end
 
       context 'when support for external domains is disabled' do
-        before do
-          allow(Gitlab.config.pages).to receive(:external_http).and_return(nil)
-          allow(Gitlab.config.pages).to receive(:external_https).and_return(nil)
-        end
-
         it 'renders message that support is disabled' do
           visit project_pages_path(project)
 
@@ -52,7 +47,9 @@ feature 'Pages' do
         end
       end
 
-      context 'when pages are exposed on external HTTP address' do
+      context 'when pages are exposed on external HTTP address', :http_pages_enabled do
+        given(:project) { create(:project, pages_https_only: false) }
+
         shared_examples 'adds new domain' do
           it 'adds new domain' do
             visit new_project_pages_domain_path(project)
@@ -64,11 +61,6 @@ feature 'Pages' do
           end
         end
 
-        before do
-          allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80'])
-          allow(Gitlab.config.pages).to receive(:external_https).and_return(nil)
-        end
-
         it 'allows to add new domain' do
           visit project_pages_path(project)
 
@@ -80,13 +72,13 @@ feature 'Pages' do
         context 'when project in group namespace' do
           it_behaves_like 'adds new domain' do
             let(:group) { create :group }
-            let(:project) { create :project, namespace: group }
+            let(:project) { create(:project, namespace: group, pages_https_only: false) }
           end
         end
 
         context 'when pages domain is added' do
           before do
-            project.pages_domains.create!(domain: 'my.test.domain.com')
+            create(:pages_domain, project: project, domain: 'my.test.domain.com')
 
             visit new_project_pages_domain_path(project)
           end
@@ -104,7 +96,7 @@ feature 'Pages' do
         end
       end
 
-      context 'when pages are exposed on external HTTPS address' do
+      context 'when pages are exposed on external HTTPS address', :https_pages_enabled do
         let(:certificate_pem) do
           <<~PEM
           -----BEGIN CERTIFICATE-----
@@ -145,11 +137,6 @@ feature 'Pages' do
           KEY
         end
 
-        before do
-          allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80'])
-          allow(Gitlab.config.pages).to receive(:external_https).and_return(['1.1.1.1:443'])
-        end
-
         it 'adds new domain with certificate' do
           visit new_project_pages_domain_path(project)
 
@@ -163,7 +150,7 @@ feature 'Pages' do
 
         describe 'updating the certificate for an existing domain' do
           let!(:domain) do
-            create(:pages_domain, :with_key, :with_certificate, project: project)
+            create(:pages_domain, project: project)
           end
 
           it 'allows the certificate to be updated' do
@@ -237,6 +224,70 @@ feature 'Pages' do
     it_behaves_like 'no pages deployed'
   end
 
+  describe 'HTTPS settings', :js, :https_pages_enabled do
+    background do
+      project.namespace.update(owner: user)
+
+      allow_any_instance_of(Project).to receive(:pages_deployed?) { true }
+    end
+
+    scenario 'tries to change the setting' do
+      visit project_pages_path(project)
+      expect(page).to have_content("Force domains with SSL certificates to use HTTPS")
+
+      uncheck :project_pages_https_only
+
+      click_button 'Save'
+
+      expect(page).to have_text('Your changes have been saved')
+      expect(page).not_to have_checked_field('project_pages_https_only')
+    end
+
+    context 'setting could not be updated' do
+      let(:service) { instance_double('Projects::UpdateService') }
+
+      before do
+        allow(Projects::UpdateService).to receive(:new).and_return(service)
+        allow(service).to receive(:execute).and_return(status: :error)
+      end
+
+      scenario 'tries to change the setting' do
+        visit project_pages_path(project)
+
+        uncheck :project_pages_https_only
+
+        click_button 'Save'
+
+        expect(page).to have_text('Something went wrong on our end')
+      end
+    end
+
+    context 'non-HTTPS domain exists' do
+      given(:project) { create(:project, pages_https_only: false) }
+
+      before do
+        create(:pages_domain, :without_key, :without_certificate, project: project)
+      end
+
+      scenario 'the setting is disabled' do
+        visit project_pages_path(project)
+
+        expect(page).to have_field(:project_pages_https_only, disabled: true)
+        expect(page).not_to have_button('Save')
+      end
+    end
+
+    context 'HTTPS pages are disabled', :https_pages_disabled do
+      scenario 'the setting is unavailable' do
+        visit project_pages_path(project)
+
+        expect(page).not_to have_field(:project_pages_https_only)
+        expect(page).not_to have_content('Force domains with SSL certificates to use HTTPS')
+        expect(page).not_to have_button('Save')
+      end
+    end
+  end
+
   describe 'Remove page' do
     context 'when user is the owner' do
       let(:project) { create :project, :repository }
@@ -258,7 +309,7 @@ feature 'Pages' do
         end
 
         let(:ci_build) do
-          build(
+          create(
             :ci_build,
             project: project,
             pipeline: pipeline,
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 65e24862d43f8b8d98a881cb4aaf1bbb95a25b17..065d00d51d4668f7501516a157995aed69ff1bf7 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -160,9 +160,9 @@ feature 'Pipeline Schedules', :js do
         click_link 'New schedule'
         fill_in_schedule_form
         all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA')
-        all('[name="schedule[variables_attributes][][value]"]')[0].set('AAA123')
+        all('[name="schedule[variables_attributes][][secret_value]"]')[0].set('AAA123')
         all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB')
-        all('[name="schedule[variables_attributes][][value]"]')[1].set('BBB123')
+        all('[name="schedule[variables_attributes][][secret_value]"]')[1].set('BBB123')
         save_pipeline_schedule
       end
 
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 266ef693d0b48d591700190d87271f3151f9d8dc..990e5c4d9df2985c2fc4573cda801ef8d9bec367 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -115,6 +115,13 @@ describe 'Pipeline', :js do
 
           expect(page).not_to have_content('Retry job')
         end
+
+        it 'should include the failure reason' do
+          page.within('#ci-badge-test') do
+            build_link = page.find('.js-pipeline-graph-job-link')
+            expect(build_link['data-original-title']).to eq('test - failed <br> (unknown failure)')
+          end
+        end
       end
 
       context 'when pipeline has manual jobs' do
@@ -289,6 +296,15 @@ describe 'Pipeline', :js do
 
       it { expect(build_manual.reload).to be_pending }
     end
+
+    context 'failed jobs' do
+      it 'displays a tooltip with the failure reason' do
+        page.within('.ci-table') do
+          failed_job_link = page.find('.ci-failed')
+          expect(failed_job_link[:title]).to eq('Failed <br> (unknown failure)')
+        end
+      end
+    end
   end
 
   describe 'GET /:project/pipelines/:id/failures' do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 3a8e7c05cc43fbbb6c5dc8e9af054a9956e0d019..705ba78a0b78e10f4f8941361785d3799fe45dfe 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -86,7 +86,22 @@ describe 'Pipelines', :js do
         it 'updates content when tab is clicked' do
           page.find('.js-pipelines-tab-pending').click
           wait_for_requests
-          expect(page).to have_content('No pipelines to show.')
+          expect(page).to have_content('There are currently no pending pipelines.')
+        end
+      end
+
+      context 'navigation links' do
+        before do
+          visit project_pipelines_path(project)
+          wait_for_requests
+        end
+
+        it 'renders run pipeline link' do
+          expect(page).to have_link('Run Pipeline')
+        end
+
+        it 'renders ci lint link' do
+          expect(page).to have_link('CI Lint')
         end
       end
 
@@ -334,6 +349,18 @@ describe 'Pipelines', :js do
 
           it { expect(page).not_to have_selector('.build-artifacts') }
         end
+
+        context 'with trace artifact' do
+          before do
+            create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+
+            visit_project_pipelines
+          end
+
+          it 'does not show trace artifact as artifacts' do
+            expect(page).not_to have_selector('.build-artifacts')
+          end
+        end
       end
 
       context 'mini pipeline graph' do
@@ -367,6 +394,23 @@ describe 'Pipelines', :js do
             expect(build.reload).to be_canceled
           end
         end
+
+        context 'for a failed pipeline' do
+          let!(:build) do
+            create(:ci_build, :failed, pipeline: pipeline,
+                                       stage: 'build',
+                                       name: 'build')
+          end
+
+          it 'should display the failure reason' do
+            find('.js-builds-dropdown-button').click
+
+            within('.js-builds-dropdown-list') do
+              build_element = page.find('.mini-pipeline-graph-dropdown-item')
+              expect(build_element['data-title']).to eq('build - failed <br> (unknown failure)')
+            end
+          end
+        end
       end
 
       context 'with pagination' do
@@ -473,7 +517,7 @@ describe 'Pipelines', :js do
           end
 
           it 'creates a new pipeline' do
-            expect { click_on 'Create pipeline' }
+            expect { click_on 'Run pipeline' }
               .to change { Ci::Pipeline.count }.by(1)
 
             expect(Ci::Pipeline.last).to be_web
@@ -482,7 +526,7 @@ describe 'Pipelines', :js do
 
         context 'without gitlab-ci.yml' do
           before do
-            click_on 'Create pipeline'
+            click_on 'Run pipeline'
           end
 
           it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
@@ -495,7 +539,7 @@ describe 'Pipelines', :js do
               click_link 'master'
             end
 
-            expect { click_on 'Create pipeline' }
+            expect { click_on 'Run pipeline' }
               .to change { Ci::Pipeline.count }.by(1)
           end
         end
@@ -513,7 +557,7 @@ describe 'Pipelines', :js do
         it 'has field to add a new pipeline' do
           expect(page).to have_selector('.js-branch-select')
           expect(find('.js-branch-select')).to have_content project.default_branch
-          expect(page).to have_content('Create for')
+          expect(page).to have_content('Run on')
         end
       end
 
@@ -542,7 +586,7 @@ describe 'Pipelines', :js do
       end
 
       it 'has a clear caches button' do
-        expect(page).to have_link 'Clear runner caches'
+        expect(page).to have_button 'Clear Runner Caches'
       end
 
       describe 'user clicks the button' do
@@ -552,19 +596,33 @@ describe 'Pipelines', :js do
           end
 
           it 'increments jobs_cache_index' do
-            click_link 'Clear runner caches'
+            click_button 'Clear Runner Caches'
+            wait_for_requests
             expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
           end
         end
 
         context 'when project does not have jobs_cache_index' do
           it 'sets jobs_cache_index to 1' do
-            click_link 'Clear runner caches'
+            click_button 'Clear Runner Caches'
+            wait_for_requests
             expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
           end
         end
       end
     end
+
+    describe 'Empty State' do
+      let(:project) { create(:project, :repository) }
+
+      before do
+        visit project_pipelines_path(project)
+      end
+
+      it 'renders empty state' do
+        expect(page).to have_content 'Build with confidence'
+      end
+    end
   end
 
   context 'when user is not logged in' do
@@ -575,7 +633,9 @@ describe 'Pipelines', :js do
     context 'when project is public' do
       let(:project) { create(:project, :public, :repository) }
 
-      it { expect(page).to have_content 'Build with confidence' }
+      context 'without pipelines' do
+        it { expect(page).to have_content 'This project is not currently set up to run pipelines.' }
+      end
     end
 
     context 'when project is private' do
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
deleted file mode 100644
index a3ea778d401318422e1c4710518ec6c0e63497e6..0000000000000000000000000000000000000000
--- a/spec/features/projects/project_settings_spec.rb
+++ /dev/null
@@ -1,205 +0,0 @@
-require 'spec_helper'
-
-describe 'Edit Project Settings' do
-  include Select2Helper
-
-  let(:user) { create(:user) }
-  let(:project) { create(:project, namespace: user.namespace, path: 'gitlab', name: 'sample') }
-
-  before do
-    sign_in(user)
-  end
-
-  describe 'Project settings section', :js do
-    it 'shows errors for invalid project name' do
-      visit edit_project_path(project)
-      fill_in 'project_name_edit', with: 'foo&bar'
-      page.within('.general-settings') do
-        click_button 'Save changes'
-      end
-      expect(page).to have_field 'project_name_edit', with: 'foo&bar'
-      expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
-      expect(page).to have_button 'Save changes'
-    end
-
-    it 'shows a successful notice when the project is updated' do
-      visit edit_project_path(project)
-      fill_in 'project_name_edit', with: 'hello world'
-      page.within('.general-settings') do
-        click_button 'Save changes'
-      end
-      expect(page).to have_content "Project 'hello world' was successfully updated."
-    end
-  end
-
-  describe 'Merge request settings section' do
-    it 'shows "Merge commit" strategy' do
-      visit edit_project_path(project)
-
-      page.within '.merge-requests-feature' do
-        expect(page).to have_content 'Merge commit'
-      end
-    end
-
-    it 'shows "Merge commit with semi-linear history " strategy' do
-      visit edit_project_path(project)
-
-      page.within '.merge-requests-feature' do
-        expect(page).to have_content 'Merge commit with semi-linear history'
-      end
-    end
-
-    it 'shows "Fast-forward merge" strategy' do
-      visit edit_project_path(project)
-
-      page.within '.merge-requests-feature' do
-        expect(page).to have_content 'Fast-forward merge'
-      end
-    end
-  end
-
-  describe 'Rename repository section' do
-    context 'with invalid characters' do
-      it 'shows errors for invalid project path/name' do
-        rename_project(project, name: 'foo&bar', path: 'foo&bar')
-        expect(page).to have_field 'Project name', with: 'foo&bar'
-        expect(page).to have_field 'Path', with: 'foo&bar'
-        expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
-        expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
-      end
-    end
-
-    context 'when changing project name' do
-      it 'renames the repository' do
-        rename_project(project, name: 'bar')
-        expect(find('.breadcrumbs')).to have_content(project.name)
-      end
-
-      context 'with emojis' do
-        it 'shows error for invalid project name' do
-          rename_project(project, name: '馃殌 foo bar 鈽侊笍')
-          expect(page).to have_field 'Project name', with: '馃殌 foo bar 鈽侊笍'
-          expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'."
-        end
-      end
-    end
-
-    context 'when changing project path' do
-      let(:project) { create(:project, :repository, namespace: user.namespace, name: 'gitlabhq') }
-
-      before(:context) do
-        TestEnv.clean_test_path
-      end
-
-      after do
-        TestEnv.clean_test_path
-      end
-
-      specify 'the project is accessible via the new path' do
-        rename_project(project, path: 'bar')
-        new_path = namespace_project_path(project.namespace, 'bar')
-        visit new_path
-        expect(current_path).to eq(new_path)
-        expect(find('.breadcrumbs')).to have_content(project.name)
-      end
-
-      specify 'the project is accessible via a redirect from the old path' do
-        old_path = project_path(project)
-        rename_project(project, path: 'bar')
-        new_path = namespace_project_path(project.namespace, 'bar')
-        visit old_path
-        expect(current_path).to eq(new_path)
-        expect(find('.breadcrumbs')).to have_content(project.name)
-      end
-
-      context 'and a new project is added with the same path' do
-        it 'overrides the redirect' do
-          old_path = project_path(project)
-          rename_project(project, path: 'bar')
-          new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
-          visit old_path
-          expect(current_path).to eq(old_path)
-          expect(find('.breadcrumbs')).to have_content(new_project.name)
-        end
-      end
-    end
-  end
-
-  describe 'Transfer project section', :js do
-    let!(:project) { create(:project, :repository, namespace: user.namespace, name: 'gitlabhq') }
-    let!(:group) { create(:group) }
-
-    before(:context) do
-      TestEnv.clean_test_path
-    end
-
-    before do
-      group.add_owner(user)
-    end
-
-    after do
-      TestEnv.clean_test_path
-    end
-
-    specify 'the project is accessible via the new path' do
-      transfer_project(project, group)
-      new_path = namespace_project_path(group, project)
-
-      visit new_path
-      wait_for_requests
-
-      expect(current_path).to eq(new_path)
-      expect(find('.breadcrumbs')).to have_content(project.name)
-    end
-
-    specify 'the project is accessible via a redirect from the old path' do
-      old_path = project_path(project)
-      transfer_project(project, group)
-      new_path = namespace_project_path(group, project)
-
-      visit old_path
-      wait_for_requests
-
-      expect(current_path).to eq(new_path)
-      expect(find('.breadcrumbs')).to have_content(project.name)
-    end
-
-    context 'and a new project is added with the same path' do
-      it 'overrides the redirect' do
-        old_path = project_path(project)
-        transfer_project(project, group)
-        new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
-        visit old_path
-        expect(current_path).to eq(old_path)
-        expect(find('.breadcrumbs')).to have_content(new_project.name)
-      end
-    end
-  end
-end
-
-def rename_project(project, name: nil, path: nil)
-  visit edit_project_path(project)
-  fill_in('project_name', with: name) if name
-  fill_in('Path', with: path) if path
-  click_button('Rename project')
-  wait_for_edit_project_page_reload
-  project.reload
-end
-
-def transfer_project(project, namespace)
-  visit edit_project_path(project)
-  select2(namespace.id, from: '#new_namespace_id')
-  click_button('Transfer project')
-  confirm_transfer_modal
-  wait_for_edit_project_page_reload
-  project.reload
-end
-
-def confirm_transfer_modal
-  fill_in('confirm_name_input', with: project.path)
-  click_button 'Confirm'
-end
-
-def wait_for_edit_project_page_reload
-  expect(find('.project-edit-container')).to have_content('Rename repository')
-end
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1a13fe03a67524c9e7c46fd48cd8e6df4852c69b
--- /dev/null
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'Disable individual triggers' do
+  let(:project) { create(:project) }
+  let(:user) { project.owner }
+  let(:checkbox_selector) { 'input[type=checkbox][id$=_events]' }
+
+  before do
+    sign_in(user)
+
+    visit(project_settings_integrations_path(project))
+
+    click_link(service_name)
+  end
+
+  context 'service has multiple supported events' do
+    let(:service_name) { 'HipChat' }
+
+    it 'shows trigger checkboxes' do
+      event_count = HipchatService.supported_events.count
+
+      expect(page).to have_content "Trigger"
+      expect(page).to have_css(checkbox_selector, count: event_count)
+    end
+  end
+
+  context 'services only has one supported event' do
+    let(:service_name) { 'Asana' }
+
+    it "doesn't show unnecessary Trigger checkboxes" do
+      expect(page).not_to have_content "Trigger"
+      expect(page).not_to have_css(checkbox_selector)
+    end
+  end
+end
diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb
index 28954a4fb4022193d0b01cedd29aaf7f02a11bb9..a4d1b78b83b2eaba38026408cb447358c870fe91 100644
--- a/spec/features/projects/settings/forked_project_settings_spec.rb
+++ b/spec/features/projects/settings/forked_project_settings_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-feature 'Settings for a forked project', :js do
+describe 'Projects > Settings > For a forked project', :js do
   include ProjectForksHelper
   let(:user) { create(:user) }
   let(:original_project) { create(:project) }
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index f6a1a46df11e6f94ce36d5f7270c2dabfa0a44f5..5178d63050eb4b28427873f6a6939fd9a2987e67 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -1,20 +1,20 @@
 require 'spec_helper'
 
-feature 'Integration settings' do
+describe 'Projects > Settings > Integration settings' do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:role) { :developer }
   let(:integrations_path) { project_settings_integrations_path(project) }
 
-  background do
+  before do
     sign_in(user)
     project.add_role(user, role)
   end
 
   context 'for developer' do
-    given(:role) { :developer }
+    let(:role) { :developer }
 
-    scenario 'to be disallowed to view' do
+    it 'to be disallowed to view' do
       visit integrations_path
 
       expect(page.status_code).to eq(404)
@@ -22,13 +22,13 @@ feature 'Integration settings' do
   end
 
   context 'for master' do
-    given(:role) { :master }
+    let(:role) { :master }
 
     context 'Webhooks' do
       let(:hook) { create(:project_hook, :all_events_enabled, enable_ssl_verification: true, project: project) }
       let(:url) { generate(:url) }
 
-      scenario 'show list of webhooks' do
+      it 'show list of webhooks' do
         hook
 
         visit integrations_path
@@ -46,7 +46,7 @@ feature 'Integration settings' do
         expect(page).to have_content('Wiki page events')
       end
 
-      scenario 'create webhook' do
+      it 'create webhook' do
         visit integrations_path
 
         fill_in 'hook_url', with: url
@@ -63,7 +63,7 @@ feature 'Integration settings' do
         expect(page).to have_content('Job events')
       end
 
-      scenario 'edit existing webhook' do
+      it 'edit existing webhook' do
         hook
         visit integrations_path
 
@@ -76,7 +76,7 @@ feature 'Integration settings' do
         expect(page).to have_content(url)
       end
 
-      scenario 'test existing webhook', :js do
+      it 'test existing webhook', :js do
         WebMock.stub_request(:post, hook.url)
         visit integrations_path
 
@@ -87,14 +87,14 @@ feature 'Integration settings' do
       end
 
       context 'remove existing webhook' do
-        scenario 'from webhooks list page' do
+        it 'from webhooks list page' do
           hook
           visit integrations_path
 
           expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
         end
 
-        scenario 'from webhook edit page' do
+        it 'from webhook edit page' do
           hook
           visit integrations_path
           click_link 'Edit'
@@ -108,7 +108,7 @@ feature 'Integration settings' do
       let(:hook) { create(:project_hook, project: project) }
       let(:hook_log) { create(:web_hook_log, web_hook: hook, internal_error_message: 'some error') }
 
-      scenario 'show list of hook logs' do
+      it 'show list of hook logs' do
         hook_log
         visit edit_project_hook_path(project, hook)
 
@@ -116,7 +116,7 @@ feature 'Integration settings' do
         expect(page).to have_content(hook_log.url)
       end
 
-      scenario 'show hook log details' do
+      it 'show hook log details' do
         hook_log
         visit edit_project_hook_path(project, hook)
         click_link 'View details'
@@ -126,7 +126,7 @@ feature 'Integration settings' do
         expect(page).to have_content('Resend Request')
       end
 
-      scenario 'retry hook log' do
+      it 'retry hook log' do
         WebMock.stub_request(:post, hook.url)
 
         hook_log
diff --git a/spec/features/projects/settings/lfs_settings_spec.rb b/spec/features/projects/settings/lfs_settings_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0fd28a5681c9b5ef9f97855a246fb2d49ca6bc5e
--- /dev/null
+++ b/spec/features/projects/settings/lfs_settings_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+describe 'Projects > Settings > LFS settings' do
+  let(:admin) { create(:admin) }
+  let(:project) { create(:project) }
+
+  context 'LFS enabled setting' do
+    before do
+      allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+
+      sign_in(admin)
+    end
+
+    it 'displays the correct elements', :js do
+      visit edit_project_path(project)
+
+      expect(page).to have_content('Git Large File Storage')
+      expect(page).to have_selector('input[name="project[lfs_enabled]"] + button', visible: true)
+    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
deleted file mode 100644
index 015db603d33834cad8bd01bf09763c288bb9c8a7..0000000000000000000000000000000000000000
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'spec_helper'
-
-feature 'Project settings > Merge Requests', :js do
-  let(:project) { create(:project, :public) }
-  let(:user) { create(:user) }
-
-  background do
-    project.add_master(user)
-    sign_in(user)
-  end
-
-  context 'when Merge Request and Pipelines are initially enabled' do
-    context 'when Pipelines are initially enabled' do
-      before do
-        visit edit_project_path(project)
-      end
-
-      scenario 'shows the Merge Requests settings' do
-        expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
-        expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
-
-        within('.sharing-permissions-form') do
-          find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
-          find('input[value="Save changes"]').send_keys(:return)
-        end
-
-        expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
-        expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
-      end
-    end
-
-    context 'when Pipelines are initially disabled' do
-      before do
-        project.project_feature.update_attribute('builds_access_level', ProjectFeature::DISABLED)
-        visit edit_project_path(project)
-      end
-
-      scenario 'shows the Merge Requests settings that do not depend on Builds feature' do
-        expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
-        expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
-
-        within('.sharing-permissions-form') do
-          find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
-          find('input[value="Save changes"]').send_keys(:return)
-        end
-
-        expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
-        expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
-      end
-    end
-  end
-
-  context 'when Merge Request are initially disabled' do
-    before do
-      project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::DISABLED)
-      visit edit_project_path(project)
-    end
-
-    scenario 'does not show the Merge Requests settings' do
-      expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
-      expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
-
-      within('.sharing-permissions-form') do
-        find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
-        find('input[value="Save changes"]').send_keys(:return)
-      end
-
-      expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
-      expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
-    end
-  end
-
-  describe 'Checkbox to enable merge request link' do
-    before do
-      visit edit_project_path(project)
-    end
-
-    scenario 'is initially checked' do
-      checkbox = find_field('project_printing_merge_request_link_enabled')
-      expect(checkbox).to be_checked
-    end
-
-    scenario 'when unchecked sets :printing_merge_request_link_enabled to false' do
-      uncheck('project_printing_merge_request_link_enabled')
-      within('.merge-request-settings-form') do
-        click_on('Save changes')
-      end
-
-      # Wait for save to complete and page to reload
-      checkbox = find_field('project_printing_merge_request_link_enabled')
-      expect(checkbox).not_to be_checked
-
-      project.reload
-      expect(project.printing_merge_request_link_enabled).to be(false)
-    end
-  end
-end
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index d07208555647b2708e8bd446f970be75d90dea2a..d9020333f289d23918eb85cf225886c59d7a84f1 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -1,19 +1,19 @@
 require 'spec_helper'
 
-feature "Pipelines settings" do
+describe "Projects > Settings > Pipelines settings" do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:role) { :developer }
 
-  background do
+  before do
     sign_in(user)
     project.add_role(user, role)
   end
 
   context 'for developer' do
-    given(:role) { :developer }
+    let(:role) { :developer }
 
-    scenario 'to be disallowed to view' do
+    it 'to be disallowed to view' do
       visit project_settings_ci_cd_path(project)
 
       expect(page.status_code).to eq(404)
@@ -21,9 +21,9 @@ feature "Pipelines settings" do
   end
 
   context 'for master' do
-    given(:role) { :master }
+    let(:role) { :master }
 
-    scenario 'be allowed to change' do
+    it 'be allowed to change' do
       visit project_settings_ci_cd_path(project)
 
       fill_in('Test coverage parsing', with: 'coverage_regex')
@@ -34,7 +34,7 @@ feature "Pipelines settings" do
       expect(page).to have_field('Test coverage parsing', with: 'coverage_regex')
     end
 
-    scenario 'updates auto_cancel_pending_pipelines' do
+    it 'updates auto_cancel_pending_pipelines' do
       visit project_settings_ci_cd_path(project)
 
       page.check('Auto-cancel redundant, pending pipelines')
diff --git a/spec/features/projects/settings/project_badges_spec.rb b/spec/features/projects/settings/project_badges_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cc3551a4c2154b07496b93db66d539941ab15552
--- /dev/null
+++ b/spec/features/projects/settings/project_badges_spec.rb
@@ -0,0 +1,125 @@
+require 'spec_helper'
+
+feature 'Project Badges' do
+  include WaitForRequests
+
+  let(:user) { create(:user) }
+  let(:group) { create(:group) }
+  let(:project) { create(:project, namespace: group) }
+  let(:badge_link_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'}
+  let(:badge_image_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'}
+  let!(:project_badge) { create(:project_badge, project: project) }
+  let!(:group_badge) { create(:group_badge, group: group) }
+
+  before do
+    group.add_master(user)
+    sign_in(user)
+
+    visit(project_settings_badges_path(project))
+  end
+
+  it 'shows a list of badges', :js do
+    page.within '.badge-settings' do
+      wait_for_requests
+
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 2
+      expect(rows[0]).to have_content group_badge.link_url
+      expect(rows[1]).to have_content project_badge.link_url
+    end
+  end
+
+  context 'adding a badge', :js do
+    it 'user can preview a badge' do
+      page.within '.badge-settings form' do
+        fill_in 'badge-link-url', with: badge_link_url
+        fill_in 'badge-image-url', with: badge_image_url
+        within '#badge-preview' do
+          expect(find('a')[:href]).to eq badge_link_url
+          expect(find('a img')[:src]).to eq badge_image_url
+        end
+      end
+    end
+
+    it do
+      page.within '.badge-settings' do
+        fill_in 'badge-link-url', with: badge_link_url
+        fill_in 'badge-image-url', with: badge_image_url
+
+        click_button 'Add badge'
+        wait_for_requests
+
+        within '.panel-body' do
+          expect(find('a')[:href]).to eq badge_link_url
+          expect(find('a img')[:src]).to eq badge_image_url
+        end
+      end
+    end
+  end
+
+  context 'editing a badge', :js do
+    it 'form is shown when clicking edit button in list' do
+      page.within '.badge-settings' do
+        wait_for_requests
+        rows = all('.panel-body > div')
+        expect(rows.length).to eq 2
+        rows[1].find('[aria-label="Edit"]').click
+
+        within 'form' do
+          expect(find('#badge-link-url').value).to eq project_badge.link_url
+          expect(find('#badge-image-url').value).to eq project_badge.image_url
+        end
+      end
+    end
+
+    it 'updates a badge when submitting the edit form' do
+      page.within '.badge-settings' do
+        wait_for_requests
+        rows = all('.panel-body > div')
+        expect(rows.length).to eq 2
+        rows[1].find('[aria-label="Edit"]').click
+        within 'form' do
+          fill_in 'badge-link-url', with: badge_link_url
+          fill_in 'badge-image-url', with: badge_image_url
+
+          click_button 'Save changes'
+          wait_for_requests
+        end
+
+        rows = all('.panel-body > div')
+        expect(rows.length).to eq 2
+        expect(rows[1]).to have_content badge_link_url
+      end
+    end
+  end
+
+  context 'deleting a badge', :js do
+    def click_delete_button(badge_row)
+      badge_row.find('[aria-label="Delete"]').click
+    end
+
+    it 'shows a modal when deleting a badge' do
+      wait_for_requests
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 2
+
+      click_delete_button(rows[1])
+
+      expect(find('.modal .modal-title')).to have_content 'Delete badge?'
+    end
+
+    it 'deletes a badge when confirming the modal' do
+      wait_for_requests
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 2
+      click_delete_button(rows[1])
+
+      find('.modal .btn-danger').click
+      wait_for_requests
+
+      rows = all('.panel-body > div')
+      expect(rows.length).to eq 1
+      expect(rows[0]).to have_content group_badge.link_url
+    end
+  end
+end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 14670e910067212cc03e6bb5a5592af0604b4751..e1dfe617691f067958bbb0fd770589a988366bbe 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -1,19 +1,19 @@
 require 'spec_helper'
 
-feature 'Repository settings' do
+describe 'Projects > Settings > Repository settings' do
   let(:project) { create(:project_empty_repo) }
   let(:user) { create(:user) }
   let(:role) { :developer }
 
-  background do
+  before do
     project.add_role(user, role)
     sign_in(user)
   end
 
   context 'for developer' do
-    given(:role) { :developer }
+    let(:role) { :developer }
 
-    scenario 'is not allowed to view' do
+    it 'is not allowed to view' do
       visit project_settings_repository_path(project)
 
       expect(page.status_code).to eq(404)
@@ -21,14 +21,14 @@ feature 'Repository settings' do
   end
 
   context 'for master' do
-    given(:role) { :master }
+    let(:role) { :master }
 
     context 'Deploy Keys', :js do
       let(:private_deploy_key) { create(:deploy_key, title: 'private_deploy_key', public: false) }
       let(:public_deploy_key) { create(:another_deploy_key, title: 'public_deploy_key', public: true) }
       let(:new_ssh_key) { attributes_for(:key)[:key] }
 
-      scenario 'get list of keys' do
+      it 'get list of keys' do
         project.deploy_keys << private_deploy_key
         project.deploy_keys << public_deploy_key
 
@@ -38,7 +38,7 @@ feature 'Repository settings' do
         expect(page).to have_content('public_deploy_key')
       end
 
-      scenario 'add a new deploy key' do
+      it 'add a new deploy key' do
         visit project_settings_repository_path(project)
 
         fill_in 'deploy_key_title', with: 'new_deploy_key'
@@ -50,7 +50,7 @@ feature 'Repository settings' do
         expect(page).to have_content('Write access allowed')
       end
 
-      scenario 'edit an existing deploy key' do
+      it 'edit an existing deploy key' do
         project.deploy_keys << private_deploy_key
         visit project_settings_repository_path(project)
 
@@ -64,7 +64,7 @@ feature 'Repository settings' do
         expect(page).to have_content('Write access allowed')
       end
 
-      scenario 'edit a deploy key from projects user has access to' do
+      it 'edit a deploy key from projects user has access to' do
         project2 = create(:project_empty_repo)
         project2.add_role(user, role)
         project2.deploy_keys << private_deploy_key
@@ -79,7 +79,7 @@ feature 'Repository settings' do
         expect(page).to have_content('updated_deploy_key')
       end
 
-      scenario 'remove an existing deploy key' do
+      it 'remove an existing deploy key' do
         project.deploy_keys << private_deploy_key
         visit project_settings_repository_path(project)
 
@@ -88,5 +88,32 @@ feature 'Repository settings' do
         expect(page).not_to have_content(private_deploy_key.title)
       end
     end
+
+    context 'Deploy tokens' do
+      let!(:deploy_token) { create(:deploy_token, projects: [project]) }
+
+      before do
+        stub_container_registry_config(enabled: true)
+        visit project_settings_repository_path(project)
+      end
+
+      scenario 'view deploy tokens' do
+        within('.deploy-tokens') do
+          expect(page).to have_content(deploy_token.name)
+          expect(page).to have_content('read_repository')
+          expect(page).to have_content('read_registry')
+        end
+      end
+
+      scenario 'add a new deploy token' do
+        fill_in 'deploy_token_name', with: 'new_deploy_key'
+        fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s
+        check 'deploy_token_read_repository'
+        check 'deploy_token_read_registry'
+        click_button 'Create deploy token'
+
+        expect(page).to have_content('Your new project deploy token has been created')
+      end
+    end
   end
 end
diff --git a/spec/features/projects/settings/user_archives_project_spec.rb b/spec/features/projects/settings/user_archives_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..38c8a8c24682f1988e705cdd499e12cc79966b8b
--- /dev/null
+++ b/spec/features/projects/settings/user_archives_project_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe 'Projects > Settings > User archives a project' do
+  let(:user) { create(:user) }
+
+  before do
+    project.add_master(user)
+
+    sign_in(user)
+
+    visit edit_project_path(project)
+  end
+
+  context 'when a project is archived' do
+    let(:project) { create(:project, :archived, namespace: user.namespace) }
+
+    it 'unarchives a project' do
+      expect(page).to have_content('Unarchive project')
+
+      click_link('Unarchive')
+
+      expect(page).not_to have_content('Archived project')
+    end
+  end
+
+  context 'when a project is unarchived' do
+    let(:project) { create(:project, :repository, namespace: user.namespace) }
+
+    it 'archives a project' do
+      expect(page).to have_content('Archive project')
+
+      click_link('Archive')
+
+      expect(page).to have_content('Archived')
+    end
+  end
+end
diff --git a/spec/features/projects/settings/user_changes_avatar_spec.rb b/spec/features/projects/settings/user_changes_avatar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2dcc79d8a124b4e7436a1bc3e0ea3378375a45b0
--- /dev/null
+++ b/spec/features/projects/settings/user_changes_avatar_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe 'Projects > Settings > User changes avatar' do
+  let(:project) { create(:project, :repository) }
+  let(:user) { project.creator }
+
+  before do
+    project.add_master(user)
+    sign_in(user)
+  end
+
+  it 'saves the new avatar' do
+    expect(project.reload.avatar.url).to be_nil
+
+    save_avatar(project)
+
+    expect(project.reload.avatar.url).to eq "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif"
+  end
+
+  context 'with an avatar already set' do
+    before do
+      save_avatar(project)
+    end
+
+    it 'is possible to remove the avatar' do
+      click_link 'Remove avatar'
+
+      expect(page).not_to have_link('Remove avatar')
+
+      expect(project.reload.avatar.url).to be_nil
+    end
+  end
+
+  def save_avatar(project)
+    visit edit_project_path(project)
+    attach_file(
+      :project_avatar,
+      File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
+    )
+    page.within '.general-settings' do
+      click_button 'Save changes'
+    end
+  end
+end
diff --git a/spec/features/projects/settings/user_changes_default_branch_spec.rb b/spec/features/projects/settings/user_changes_default_branch_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e925539351dbe6746d99f299c4b9f8634046eb26
--- /dev/null
+++ b/spec/features/projects/settings/user_changes_default_branch_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'Projects > Settings > User changes default branch' do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :repository, namespace: user.namespace) }
+
+  before do
+    sign_in(user)
+    visit edit_project_path(project)
+  end
+
+  it 'allows to change the default branch' do
+    select 'fix', from: 'project_default_branch'
+    page.within '.general-settings' do
+      click_button 'Save changes'
+    end
+
+    expect(find(:css, 'select#project_default_branch').value).to eq 'fix'
+  end
+end
diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb
index 91e8059865c873454e8d1ee7d3dc72fd74325357..fdf427970917effcf6400f3691dd4dd7f5320550 100644
--- a/spec/features/projects/settings/user_manages_group_links_spec.rb
+++ b/spec/features/projects/settings/user_manages_group_links_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'User manages group links' do
+describe 'Projects > Settings > User manages group links' do
   include Select2Helper
 
   let(:user) { create(:user) }
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b6e65fcbda1aed301a7d97b58530534b9d251a22
--- /dev/null
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe 'Projects > Settings > User manages merge request settings' do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :public, namespace: user.namespace, path: 'gitlab', name: 'sample') }
+
+  before do
+    sign_in(user)
+    visit edit_project_path(project)
+  end
+
+  it 'shows "Merge commit" strategy' do
+    page.within '.merge-requests-feature' do
+      expect(page).to have_content 'Merge commit'
+    end
+  end
+
+  it 'shows "Merge commit with semi-linear history " strategy' do
+    page.within '.merge-requests-feature' do
+      expect(page).to have_content 'Merge commit with semi-linear history'
+    end
+  end
+
+  it 'shows "Fast-forward merge" strategy' do
+    page.within '.merge-requests-feature' do
+      expect(page).to have_content 'Fast-forward merge'
+    end
+  end
+
+  context 'when Merge Request and Pipelines are initially enabled', :js do
+    context 'when Pipelines are initially enabled' do
+      it 'shows the Merge Requests settings' do
+        expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
+        expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
+
+        within('.sharing-permissions-form') do
+          find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
+          find('input[value="Save changes"]').send_keys(:return)
+        end
+
+        expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
+        expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
+      end
+    end
+
+    context 'when Pipelines are initially disabled', :js do
+      before do
+        project.project_feature.update_attribute('builds_access_level', ProjectFeature::DISABLED)
+        visit edit_project_path(project)
+      end
+
+      it 'shows the Merge Requests settings that do not depend on Builds feature' do
+        expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
+        expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
+
+        within('.sharing-permissions-form') do
+          find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
+          find('input[value="Save changes"]').send_keys(:return)
+        end
+
+        expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
+        expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
+      end
+    end
+  end
+
+  context 'when Merge Request are initially disabled', :js do
+    before do
+      project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::DISABLED)
+      visit edit_project_path(project)
+    end
+
+    it 'does not show the Merge Requests settings' do
+      expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
+      expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
+
+      within('.sharing-permissions-form') do
+        find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
+        find('input[value="Save changes"]').send_keys(:return)
+      end
+
+      expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
+      expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
+    end
+  end
+
+  describe 'Checkbox to enable merge request link', :js do
+    it 'is initially checked' do
+      checkbox = find_field('project_printing_merge_request_link_enabled')
+      expect(checkbox).to be_checked
+    end
+
+    it 'when unchecked sets :printing_merge_request_link_enabled to false' do
+      uncheck('project_printing_merge_request_link_enabled')
+      within('.merge-request-settings-form') do
+        click_on('Save changes')
+      end
+
+      # Wait for save to complete and page to reload
+      checkbox = find_field('project_printing_merge_request_link_enabled')
+      expect(checkbox).not_to be_checked
+
+      project.reload
+      expect(project.printing_merge_request_link_enabled).to be(false)
+    end
+  end
+end
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index 2709047b8de3bd4531ef25467388ee736abe4d2b..8af9552216521d1962781cb728b59cda3a4e9a97 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'User manages project members' do
+describe 'Projects > Settings > User manages project members' do
   let(:group) { create(:group, name: 'OpenSource') }
   let(:project) { create(:project) }
   let(:project2) { create(:project) }
@@ -39,7 +39,7 @@ describe 'User manages project members' do
       click_link('Import')
     end
 
-    select(project2.name_with_namespace, from: 'source_project_id')
+    select(project2.full_name, from: 'source_project_id')
     click_button('Import')
 
     project_member = project.project_members.find_by(user_id: user_mike.id)
diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..64c9af4b70625894a4309749b8475c06d646d0e3
--- /dev/null
+++ b/spec/features/projects/settings/user_renames_a_project_spec.rb
@@ -0,0 +1,100 @@
+require 'spec_helper'
+
+describe 'Projects > Settings > User renames a project' do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, namespace: user.namespace, path: 'gitlab', name: 'sample') }
+
+  before do
+    sign_in(user)
+    visit edit_project_path(project)
+  end
+
+  def rename_project(project, name: nil, path: nil)
+    fill_in('project_name', with: name) if name
+    fill_in('Path', with: path) if path
+    click_button('Rename project')
+    wait_for_edit_project_page_reload
+    project.reload
+  end
+
+  def wait_for_edit_project_page_reload
+    expect(find('.project-edit-container')).to have_content('Rename repository')
+  end
+
+  context 'with invalid characters' do
+    it 'shows errors for invalid project path/name' do
+      rename_project(project, name: 'foo&bar', path: 'foo&bar')
+      expect(page).to have_field 'Project name', with: 'foo&bar'
+      expect(page).to have_field 'Path', with: 'foo&bar'
+      expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
+      expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
+    end
+  end
+
+  it 'shows a successful notice when the project is updated' do
+    fill_in 'project_name_edit', with: 'hello world'
+    page.within('.general-settings') do
+      click_button 'Save changes'
+    end
+
+    expect(page).to have_content "Project 'hello world' was successfully updated."
+  end
+
+  context 'when changing project name' do
+    it 'renames the repository' do
+      rename_project(project, name: 'bar')
+      expect(find('.breadcrumbs')).to have_content(project.name)
+    end
+
+    context 'with emojis' do
+      it 'shows error for invalid project name' do
+        rename_project(project, name: '馃殌 foo bar 鈽侊笍')
+        expect(page).to have_field 'Project name', with: '馃殌 foo bar 鈽侊笍'
+        expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'."
+      end
+    end
+  end
+
+  context 'when changing project path' do
+    let(:project) { create(:project, :repository, namespace: user.namespace, name: 'gitlabhq') }
+
+    before(:context) do
+      TestEnv.clean_test_path
+    end
+
+    after do
+      TestEnv.clean_test_path
+    end
+
+    it 'the project is accessible via the new path' do
+      rename_project(project, path: 'bar')
+      new_path = namespace_project_path(project.namespace, 'bar')
+      visit new_path
+
+      expect(current_path).to eq(new_path)
+      expect(find('.breadcrumbs')).to have_content(project.name)
+    end
+
+    it 'the project is accessible via a redirect from the old path' do
+      old_path = project_path(project)
+      rename_project(project, path: 'bar')
+      new_path = namespace_project_path(project.namespace, 'bar')
+      visit old_path
+
+      expect(current_path).to eq(new_path)
+      expect(find('.breadcrumbs')).to have_content(project.name)
+    end
+
+    context 'and a new project is added with the same path' do
+      it 'overrides the redirect' do
+        old_path = project_path(project)
+        rename_project(project, path: 'bar')
+        new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
+        visit old_path
+
+        expect(current_path).to eq(old_path)
+        expect(find('.breadcrumbs')).to have_content(new_project.name)
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/settings/user_tags_project_spec.rb b/spec/features/projects/settings/user_tags_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..57b4b1287fa94348ca4161fa6be59b02a97cee79
--- /dev/null
+++ b/spec/features/projects/settings/user_tags_project_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe 'Projects > Settings > User tags a project' do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, namespace: user.namespace) }
+
+  before do
+    sign_in(user)
+    visit edit_project_path(project)
+  end
+
+  context 'when a project is archived' do
+    it 'unarchives a project' do
+      fill_in 'Tags', with: 'tag1, tag2'
+
+      page.within '.general-settings' do
+        click_button 'Save changes'
+      end
+
+      expect(find_field('Tags').value).to eq 'tag1, tag2'
+    end
+  end
+end
diff --git a/spec/features/projects/settings/user_transfers_a_project_spec.rb b/spec/features/projects/settings/user_transfers_a_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96b7cf1f93b62996569353e7eabdf9e6db1b23be
--- /dev/null
+++ b/spec/features/projects/settings/user_transfers_a_project_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe 'Projects > Settings > User transfers a project', :js do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :repository, namespace: user.namespace) }
+  let(:group) { create(:group) }
+
+  before do
+    group.add_owner(user)
+    sign_in(user)
+  end
+
+  def transfer_project(project, group)
+    visit edit_project_path(project)
+
+    page.within('.js-project-transfer-form') do
+      page.find('.select2-container').click
+    end
+
+    page.find("div[role='option']", text: group.full_name).click
+
+    click_button('Transfer project')
+
+    fill_in 'confirm_name_input', with: project.name
+
+    click_button 'Confirm'
+
+    wait_for_requests
+  end
+
+  it 'allows transferring a project to a group' do
+    old_path = project_path(project)
+    transfer_project(project, group)
+    new_path = namespace_project_path(group, project)
+
+    expect(project.reload.namespace).to eq(group)
+
+    visit new_path
+    wait_for_requests
+
+    expect(current_path).to eq(new_path)
+    expect(find('.breadcrumbs')).to have_content(project.name)
+
+    visit old_path
+    wait_for_requests
+
+    expect(current_path).to eq(new_path)
+    expect(find('.breadcrumbs')).to have_content(project.name)
+  end
+
+  context 'and a new project is added with the same path' do
+    it 'overrides the redirect' do
+      old_path = project_path(project)
+      project_path = project.path
+      transfer_project(project, group)
+      new_project = create(:project, namespace: user.namespace, path: project_path)
+      visit old_path
+
+      expect(current_path).to eq(old_path)
+      expect(find('.breadcrumbs')).to have_content(new_project.name)
+    end
+  end
+
+  context 'when nested groups are available', :nested_groups do
+    it 'allows transferring a project to a subgroup' do
+      subgroup = create(:group, parent: group)
+
+      transfer_project(project, subgroup)
+
+      expect(project.reload.namespace).to eq(subgroup)
+    end
+  end
+end
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index 06f6702670bb77c4645ee058f867b2e464edc889..2ec6990313ff7576bd1ae0815c2e23ea015b91e6 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-feature 'Visibility settings', :js do
+describe 'Projects > Settings > Visibility settings', :js do
   let(:user) { create(:user) }
   let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) }
 
@@ -10,14 +10,14 @@ feature 'Visibility settings', :js do
       visit edit_project_path(project)
     end
 
-    scenario 'project visibility select is available' do
+    it 'project visibility select is available' do
       visibility_select_container = find('.project-visibility-setting')
 
       expect(visibility_select_container.find('select').value).to eq project.visibility_level.to_s
       expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of authentication.'
     end
 
-    scenario 'project visibility description updates on change' do
+    it 'project visibility description updates on change' do
       visibility_select_container = find('.project-visibility-setting')
       visibility_select = visibility_select_container.find('select')
       visibility_select.select('Private')
@@ -25,6 +25,38 @@ feature 'Visibility settings', :js do
       expect(visibility_select.value).to eq '0'
       expect(visibility_select_container).to have_content 'Access must be granted explicitly to each user.'
     end
+
+    context 'merge requests select' do
+      it 'hides merge requests section' do
+        find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
+
+        expect(page).to have_selector('.merge-requests-feature', visible: false)
+      end
+
+      context 'given project with merge_requests_disabled access level' do
+        let(:project) { create(:project, :merge_requests_disabled, namespace: user.namespace) }
+
+        it 'hides merge requests section' do
+          expect(page).to have_selector('.merge-requests-feature', visible: false)
+        end
+      end
+    end
+
+    context 'builds select' do
+      it 'hides builds select section' do
+        find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
+
+        expect(page).to have_selector('.builds-feature', visible: false)
+      end
+
+      context 'given project with builds_disabled access level' do
+        let(:project) { create(:project, :builds_disabled, namespace: user.namespace) }
+
+        it 'hides builds select section' do
+          expect(page).to have_selector('.builds-feature', visible: false)
+        end
+      end
+    end
   end
 
   context 'as master' do
@@ -36,7 +68,7 @@ feature 'Visibility settings', :js do
       visit edit_project_path(project)
     end
 
-    scenario 'project visibility is locked' do
+    it 'project visibility is locked' do
       visibility_select_container = find('.project-visibility-setting')
 
       expect(visibility_select_container).to have_selector 'select[name="project[visibility_level]"]:disabled'
diff --git a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8803b5222be8a63ea5dac01a497d48450fb74344
--- /dev/null
+++ b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
@@ -0,0 +1,63 @@
+require 'rails_helper'
+
+feature 'Projects > Show > Developer views empty project instructions' do
+  let(:project) { create(:project, :empty_repo) }
+  let(:developer) { create(:user) }
+
+  background do
+    project.add_developer(developer)
+
+    sign_in(developer)
+  end
+
+  context 'without an SSH key' do
+    scenario 'defaults to HTTP' do
+      visit_project
+
+      expect_instructions_for('http')
+    end
+
+    scenario 'switches to SSH', :js do
+      visit_project
+
+      select_protocol('SSH')
+
+      expect_instructions_for('ssh')
+    end
+  end
+
+  context 'with an SSH key' do
+    background do
+      create(:personal_key, user: developer)
+    end
+
+    scenario 'defaults to SSH' do
+      visit_project
+
+      expect_instructions_for('ssh')
+    end
+
+    scenario 'switches to HTTP', :js do
+      visit_project
+
+      select_protocol('HTTP')
+
+      expect_instructions_for('http')
+    end
+  end
+
+  def visit_project
+    visit project_path(project)
+  end
+
+  def select_protocol(protocol)
+    find('#clone-dropdown').click
+    find(".#{protocol.downcase}-selector").click
+  end
+
+  def expect_instructions_for(protocol)
+    msg = :"#{protocol.downcase}_url_to_repo"
+
+    expect(page).to have_content("git clone #{project.send(msg)}")
+  end
+end
diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..254affd4a9422cbe738266dc1f9908513a51cf8d
--- /dev/null
+++ b/spec/features/projects/show/download_buttons_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+feature 'Projects > Show > Download buttons' do
+  given(:user) { create(:user) }
+  given(:role) { :developer }
+  given(:status) { 'success' }
+  given(:project) { create(:project, :repository) }
+
+  given(:pipeline) do
+    create(:ci_pipeline,
+           project: project,
+           sha: project.commit.sha,
+           ref: project.default_branch,
+           status: status)
+  end
+
+  given!(:build) do
+    create(:ci_build, :success, :artifacts,
+           pipeline: pipeline,
+           status: pipeline.status,
+           name: 'build')
+  end
+
+  background do
+    sign_in(user)
+    project.add_role(user, role)
+  end
+
+  describe 'when checking project main page' do
+    context 'with artifacts' do
+      before do
+        visit project_path(project)
+      end
+
+      scenario 'shows download artifacts button' do
+        href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
+      end
+
+      scenario 'download links have download attribute' do
+        expect(page).to have_selector('a', text: 'Download')
+        page.all('a', text: 'Download').each do |link|
+          expect(link[:download]).to eq ''
+        end
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/show/no_password_spec.rb
similarity index 100%
rename from spec/features/projects/no_password_spec.rb
rename to spec/features/projects/show/no_password_spec.rb
diff --git a/spec/features/projects/show/redirects_spec.rb b/spec/features/projects/show/redirects_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8d41c547d77149d47ac04d8ca5a42225d8a2d61a
--- /dev/null
+++ b/spec/features/projects/show/redirects_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe 'Projects > Show > Redirects' do
+  let(:user) { create :user }
+  let(:public_project) { create :project, :public }
+  let(:private_project) { create :project, :private }
+
+  before do
+    allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com')
+  end
+
+  it 'shows public project page' do
+    visit project_path(public_project)
+
+    page.within '.breadcrumbs .breadcrumb-item-text' do
+      expect(page).to have_content(public_project.name)
+    end
+  end
+
+  it 'redirects to sign in page when project is private' do
+    visit project_path(private_project)
+
+    expect(current_path).to eq(new_user_session_path)
+  end
+
+  it 'redirects to sign in page when project does not exist' do
+    visit project_path(build(:project, :public))
+
+    expect(current_path).to eq(new_user_session_path)
+  end
+
+  it 'redirects to public project page after signing in' do
+    visit project_path(public_project)
+
+    first(:link, 'Sign in').click
+
+    fill_in 'user_login',    with: user.email
+    fill_in 'user_password', with: user.password
+    click_button 'Sign in'
+
+    expect(status_code).to eq(200)
+    expect(current_path).to eq("/#{public_project.full_path}")
+  end
+
+  it 'redirects to private project page after sign in' do
+    visit project_path(private_project)
+
+    owner = private_project.owner
+    fill_in 'user_login',    with: owner.email
+    fill_in 'user_password', with: owner.password
+    click_button 'Sign in'
+
+    expect(status_code).to eq(200)
+    expect(current_path).to eq("/#{private_project.full_path}")
+  end
+
+  context 'when signed in' do
+    before do
+      sign_in(user)
+    end
+
+    it 'returns 404 status when project does not exist' do
+      visit project_path(build(:project, :public))
+
+      expect(status_code).to eq(404)
+    end
+
+    it 'returns 404 when project is private' do
+      visit project_path(private_project)
+
+      expect(status_code).to eq(404)
+    end
+  end
+end
diff --git a/spec/features/projects/show/rss_spec.rb b/spec/features/projects/show/rss_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d02eaf3453357896b88c3e43b235abc9862f867f
--- /dev/null
+++ b/spec/features/projects/show/rss_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+feature 'Projects > Show > RSS' do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+  let(:path) { project_path(project) }
+
+  context 'when signed in' do
+    before do
+      project.add_developer(user)
+      sign_in(user)
+      visit path
+    end
+
+    it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+  end
+
+  context 'when signed out' do
+    before do
+      visit path
+    end
+
+    it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+  end
+end
diff --git a/spec/features/projects/show/user_interacts_with_stars_spec.rb b/spec/features/projects/show/user_interacts_with_stars_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ba28c0e1b8ad78cb7ca81e7a4357f18f7575200e
--- /dev/null
+++ b/spec/features/projects/show/user_interacts_with_stars_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe 'Projects > Show > User interacts with project stars' do
+  let(:project) { create(:project, :public, :repository) }
+
+  context 'when user is signed in', :js do
+    let(:user) { create(:user) }
+
+    before do
+      sign_in(user)
+      visit(project_path(project))
+    end
+
+    it 'toggles the star' do
+      find('.star-btn').click
+
+      expect(page).to have_css('.star-count', text: 1)
+
+      find('.star-btn').click
+
+      expect(page).to have_css('.star-count', text: 0)
+    end
+  end
+
+  context 'when user is not signed in' do
+    before do
+      visit(project_path(project))
+    end
+
+    it 'does not allow to star a project' do
+      expect(page).not_to have_content('.toggle-star')
+
+      find('.star-btn').click
+
+      expect(current_path).to eq(new_user_session_path)
+    end
+  end
+end
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31b105229bef69206be6314b62d7c5634ba379ea
--- /dev/null
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe 'Projects > Show > User manages notifications', :js do
+  let(:project) { create(:project, :public, :repository) }
+
+  before do
+    sign_in(project.owner)
+    visit project_path(project)
+  end
+
+  it 'changes the notification setting' do
+    first('.notifications-btn').click
+    click_link 'On mention'
+
+    page.within '#notifications-button' do
+      expect(page).to have_content 'On mention'
+    end
+  end
+end
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7b3711531c6f74c5ce806591780efbb56654c38d
--- /dev/null
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe 'Projects > Show > Collaboration links' do
+  let(:project) { create(:project, :repository) }
+  let(:user) { create(:user) }
+
+  before do
+    project.add_developer(user)
+    sign_in(user)
+  end
+
+  it 'shows all the expected links' do
+    visit project_path(project)
+
+    # The navigation bar
+    page.within('.header-new') do
+      aggregate_failures 'dropdown links in the navigation bar' do
+        expect(page).to have_link('New issue')
+        expect(page).to have_link('New merge request')
+        expect(page).to have_link('New snippet', href: new_project_snippet_path(project))
+      end
+    end
+
+    # The project header
+    page.within('.project-home-panel') do
+      aggregate_failures 'dropdown links in the project home panel' do
+        expect(page).to have_link('New issue')
+        expect(page).to have_link('New merge request')
+        expect(page).to have_link('New snippet')
+        expect(page).to have_link('New file')
+        expect(page).to have_link('New branch')
+        expect(page).to have_link('New tag')
+      end
+    end
+
+    # The dropdown above the tree
+    page.within('.repo-breadcrumb') do
+      aggregate_failures 'dropdown links above the repo tree' do
+        expect(page).to have_link('New file')
+        expect(page).to have_link('Upload file')
+        expect(page).to have_link('New directory')
+        expect(page).to have_link('New branch')
+        expect(page).to have_link('New tag')
+      end
+    end
+
+    # The Web IDE
+    expect(page).to have_link('Web IDE')
+  end
+
+  it 'hides the links when the project is archived' do
+    project.update!(archived: true)
+
+    visit project_path(project)
+
+    page.within('.header-new') do
+      aggregate_failures 'dropdown links' do
+        expect(page).not_to have_link('New issue')
+        expect(page).not_to have_link('New merge request')
+        expect(page).not_to have_link('New snippet', href: new_project_snippet_path(project))
+      end
+    end
+
+    page.within('.project-home-panel') do
+      aggregate_failures 'dropdown links' do
+        expect(page).not_to have_link('New issue')
+        expect(page).not_to have_link('New merge request')
+        expect(page).not_to have_link('New snippet')
+        expect(page).not_to have_link('New file')
+        expect(page).not_to have_link('New branch')
+        expect(page).not_to have_link('New tag')
+      end
+    end
+
+    page.within('.repo-breadcrumb') do
+      aggregate_failures 'dropdown links' do
+        expect(page).not_to have_link('New file')
+        expect(page).not_to have_link('Upload file')
+        expect(page).not_to have_link('New directory')
+        expect(page).not_to have_link('New branch')
+        expect(page).not_to have_link('New tag')
+      end
+    end
+
+    expect(page).not_to have_link('Web IDE')
+  end
+end
diff --git a/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb b/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aa23bef6fd88500561c3f089a582a63f8cdff136
--- /dev/null
+++ b/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe 'Projects > Show > User sees a deletion failure message' do
+  let(:project) { create(:project, :empty_repo, pending_delete: true) }
+
+  before do
+    sign_in(project.owner)
+  end
+
+  it 'shows error message if deletion for project fails' do
+    project.update_attributes(delete_error: "Something went wrong", pending_delete: false)
+
+    visit project_path(project)
+
+    expect(page).to have_selector('.project-deletion-failed-message')
+    expect(page).to have_content("This project was scheduled for deletion, but failed with the following message: #{project.delete_error}")
+  end
+end
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9a82fee1b5d08a66f0412685bff8b63e5461c087
--- /dev/null
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -0,0 +1,172 @@
+require 'spec_helper'
+
+describe 'Projects > Show > User sees Git instructions' do
+  set(:user) { create(:user) }
+
+  shared_examples_for 'redirects to the sign in page' do
+    it 'redirects to the sign in page' do
+      expect(current_path).to eq(new_user_session_path)
+    end
+  end
+
+  shared_examples_for 'shows details of empty project with no repo' do
+    it 'shows Git command line instructions' do
+      click_link 'Create empty repository'
+
+      page.within '.empty_wrapper' do
+        expect(page).to have_content('Command line instructions')
+      end
+    end
+  end
+
+  shared_examples_for 'shows details of empty project' do
+    let(:user_has_ssh_key) { false }
+
+    it 'shows details' do
+      expect(page).not_to have_content('Git global setup')
+
+      page.all(:css, '.git-empty .clone').each do |element|
+        expect(element.text).to include(project.http_url_to_repo)
+      end
+
+      expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
+    end
+  end
+
+  shared_examples_for 'shows details of non empty project' do
+    let(:user_has_ssh_key) { false }
+
+    it 'shows details' do
+      page.within('.breadcrumbs .breadcrumb-item-text') do
+        expect(page).to have_content(project.title)
+      end
+
+      expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
+    end
+  end
+
+  context 'when project is public' do
+    context 'when project has no repo' do
+      set(:project) { create(:project, :public) }
+
+      before do
+        sign_in(project.owner)
+        visit project_path(project)
+      end
+
+      include_examples 'shows details of empty project with no repo'
+    end
+
+    context 'when project is empty' do
+      set(:project) { create(:project_empty_repo, :public) }
+
+      context 'when not signed in' do
+        before do
+          visit(project_path(project))
+        end
+
+        include_examples 'shows details of empty project'
+      end
+
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
+
+        context 'when user does not have ssh keys' do
+          before do
+            visit(project_path(project))
+          end
+
+          include_examples 'shows details of empty project'
+        end
+
+        context 'when user has ssh keys' do
+          before do
+            create(:personal_key, user: user)
+
+            visit(project_path(project))
+          end
+
+          include_examples 'shows details of empty project' do
+            let(:user_has_ssh_key) { true }
+          end
+        end
+      end
+    end
+
+    context 'when project is not empty' do
+      set(:project) { create(:project, :public, :repository) }
+
+      before do
+        visit(project_path(project))
+      end
+
+      context 'when not signed in' do
+        before do
+          allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com')
+        end
+
+        include_examples 'shows details of non empty project'
+      end
+
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
+
+        context 'when user does not have ssh keys' do
+          before do
+            visit(project_path(project))
+          end
+
+          include_examples 'shows details of non empty project'
+        end
+
+        context 'when user has ssh keys' do
+          before do
+            create(:personal_key, user: user)
+
+            visit(project_path(project))
+          end
+
+          include_examples 'shows details of non empty project' do
+            let(:user_has_ssh_key) { true }
+          end
+        end
+      end
+    end
+  end
+
+  context 'when project is internal' do
+    set(:project) { create(:project, :internal, :repository) }
+
+    context 'when not signed in' do
+      before do
+        visit(project_path(project))
+      end
+
+      include_examples 'redirects to the sign in page'
+    end
+
+    context 'when signed in' do
+      before do
+        sign_in(user)
+
+        visit(project_path(project))
+      end
+
+      include_examples 'shows details of non empty project'
+    end
+  end
+
+  context 'when project is private' do
+    set(:project) { create(:project, :private) }
+
+    before do
+      visit(project_path(project))
+    end
+
+    include_examples 'redirects to the sign in page'
+  end
+end
diff --git a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e277bfb8011cea0370b12d7cb0c858ec8888c3bc
--- /dev/null
+++ b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe 'Projects > Show > User sees last commit CI status' do
+  set(:project) { create(:project, :repository, :public) }
+
+  it 'shows the project README', :js do
+    project.enable_ci
+    pipeline = create(:ci_pipeline, project: project, sha: project.commit.sha, ref: 'master')
+    pipeline.skip
+
+    visit project_path(project)
+
+    page.within '.blob-commit-info' do
+      expect(page).to have_content(project.commit.sha[0..6])
+      expect(page).to have_link('Commit: skipped')
+    end
+  end
+end
diff --git a/spec/features/projects/show/user_sees_readme_spec.rb b/spec/features/projects/show/user_sees_readme_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d80606c1c236221664ebce10826283abc207d0dd
--- /dev/null
+++ b/spec/features/projects/show/user_sees_readme_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe 'Projects > Show > User sees README' do
+  set(:user) { create(:user) }
+
+  set(:project) { create(:project, :repository, :public) }
+
+  it 'shows the project README', :js do
+    visit project_path(project)
+    wait_for_requests
+
+    page.within('.readme-holder') do
+      expect(page).to have_content 'testme'
+    end
+  end
+end
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a906fa20233b50f405af686c62f75aa64b9a3e1e
--- /dev/null
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -0,0 +1,318 @@
+require 'spec_helper'
+
+describe 'Projects > Show > User sees setup shortcut buttons' do
+  # For "New file", "Add License" functionality,
+  # see spec/features/projects/files/project_owner_creates_license_file_spec.rb
+  # see spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+
+  let(:user) { create(:user) }
+
+  describe 'empty project' do
+    let(:project) { create(:project, :public, :empty_repo) }
+    let(:presenter) { project.present(current_user: user) }
+
+    describe 'as a normal user' do
+      before do
+        sign_in(user)
+
+        visit project_path(project)
+      end
+
+      it 'no Auto DevOps button if can not manage pipelines' do
+        page.within('.project-stats') do
+          expect(page).not_to have_link('Enable Auto DevOps')
+          expect(page).not_to have_link('Auto DevOps enabled')
+        end
+      end
+
+      it '"Auto DevOps enabled" button not linked' do
+        project.create_auto_devops!(enabled: true)
+
+        visit project_path(project)
+
+        page.within('.project-stats') do
+          expect(page).to have_text('Auto DevOps enabled')
+        end
+      end
+    end
+
+    describe 'as a master' do
+      before do
+        project.add_master(user)
+        sign_in(user)
+
+        visit project_path(project)
+      end
+
+      it '"New file" button linked to new file page' do
+        page.within('.project-stats') do
+          expect(page).to have_link('New file', href: project_new_blob_path(project, project.default_branch || 'master'))
+        end
+      end
+
+      it '"Add Readme" button linked to new file populated for a readme' do
+        page.within('.project-stats') do
+          expect(page).to have_link('Add Readme', href: presenter.add_readme_path)
+        end
+      end
+
+      it '"Add License" button linked to new file populated for a license' do
+        page.within('.project-stats') do
+          expect(page).to have_link('Add License', href: presenter.add_license_path)
+        end
+      end
+
+      describe 'Auto DevOps button' do
+        it '"Enable Auto DevOps" button linked to settings page' do
+          page.within('.project-stats') do
+            expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
+          end
+        end
+
+        it '"Auto DevOps enabled" anchor linked to settings page' do
+          project.create_auto_devops!(enabled: true)
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
+          end
+        end
+      end
+
+      describe 'Kubernetes cluster button' do
+        it '"Add Kubernetes cluster" button linked to clusters page' do
+          page.within('.project-stats') do
+            expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
+          end
+        end
+
+        it '"Kubernetes cluster" anchor linked to cluster page' do
+          cluster = create(:cluster, :provided_by_gcp, projects: [project])
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
+          end
+        end
+      end
+    end
+  end
+
+  describe 'populated project' do
+    let(:project) { create(:project, :public, :repository) }
+    let(:presenter) { project.present(current_user: user) }
+
+    describe 'as a normal user' do
+      before do
+        sign_in(user)
+
+        visit project_path(project)
+      end
+
+      it 'no Auto DevOps button if can not manage pipelines' do
+        page.within('.project-stats') do
+          expect(page).not_to have_link('Enable Auto DevOps')
+          expect(page).not_to have_link('Auto DevOps enabled')
+        end
+      end
+
+      it '"Auto DevOps enabled" button not linked' do
+        project.create_auto_devops!(enabled: true)
+
+        visit project_path(project)
+
+        page.within('.project-stats') do
+          expect(page).to have_text('Auto DevOps enabled')
+        end
+      end
+
+      it 'no Kubernetes cluster button if can not manage clusters' do
+        page.within('.project-stats') do
+          expect(page).not_to have_link('Add Kubernetes cluster')
+          expect(page).not_to have_link('Kubernetes configured')
+        end
+      end
+    end
+
+    describe 'as a master' do
+      before do
+        allow_any_instance_of(AutoDevopsHelper).to receive(:show_auto_devops_callout?).and_return(false)
+        project.add_master(user)
+        sign_in(user)
+
+        visit project_path(project)
+      end
+
+      it 'no "Add Changelog" button if the project already has a changelog' do
+        expect(project.repository.changelog).not_to be_nil
+
+        page.within('.project-stats') do
+          expect(page).not_to have_link('Add Changelog')
+        end
+      end
+
+      it 'no "Add License" button if the project already has a license' do
+        expect(project.repository.license_blob).not_to be_nil
+
+        page.within('.project-stats') do
+          expect(page).not_to have_link('Add License')
+        end
+      end
+
+      it 'no "Add Contribution guide" button if the project already has a contribution guide' do
+        expect(project.repository.contribution_guide).not_to be_nil
+
+        page.within('.project-stats') do
+          expect(page).not_to have_link('Add Contribution guide')
+        end
+      end
+
+      describe 'GitLab CI configuration button' do
+        it '"Set up CI/CD" button linked to new file populated for a .gitlab-ci.yml' do
+          expect(project.repository.gitlab_ci_yml).to be_nil
+
+          page.within('.project-stats') do
+            expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path)
+          end
+        end
+
+        it 'no "Set up CI/CD" button if the project already has a .gitlab-ci.yml' do
+          Files::CreateService.new(
+            project,
+            project.creator,
+            start_branch: 'master',
+            branch_name: 'master',
+            commit_message: "Add .gitlab-ci.yml",
+            file_path: '.gitlab-ci.yml',
+            file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+          ).execute
+
+          expect(project.repository.gitlab_ci_yml).not_to be_nil
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).not_to have_link('Set up CI/CD')
+          end
+        end
+
+        it 'no "Set up CI/CD" button if the project has Auto DevOps enabled' do
+          project.create_auto_devops!(enabled: true)
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).not_to have_link('Set up CI/CD')
+          end
+        end
+      end
+
+      describe 'Auto DevOps button' do
+        it '"Enable Auto DevOps" button linked to settings page' do
+          page.within('.project-stats') do
+            expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
+          end
+        end
+
+        it '"Enable Auto DevOps" button linked to settings page' do
+          project.create_auto_devops!(enabled: true)
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
+          end
+        end
+
+        it 'no Auto DevOps button if Auto DevOps callout is shown' do
+          allow_any_instance_of(AutoDevopsHelper).to receive(:show_auto_devops_callout?).and_return(true)
+
+          visit project_path(project)
+
+          expect(page).to have_selector('.js-autodevops-banner')
+
+          page.within('.project-stats') do
+            expect(page).not_to have_link('Enable Auto DevOps')
+            expect(page).not_to have_link('Auto DevOps enabled')
+          end
+        end
+
+        it 'no "Enable Auto DevOps" button when .gitlab-ci.yml already exists' do
+          Files::CreateService.new(
+            project,
+            project.creator,
+            start_branch: 'master',
+            branch_name: 'master',
+            commit_message: "Add .gitlab-ci.yml",
+            file_path: '.gitlab-ci.yml',
+            file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+          ).execute
+
+          expect(project.repository.gitlab_ci_yml).not_to be_nil
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).not_to have_link('Enable Auto DevOps')
+            expect(page).not_to have_link('Auto DevOps enabled')
+          end
+        end
+      end
+
+      describe 'Kubernetes cluster button' do
+        it '"Add Kubernetes cluster" button linked to clusters page' do
+          page.within('.project-stats') do
+            expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
+          end
+        end
+
+        it '"Kubernetes cluster" button linked to cluster page' do
+          cluster = create(:cluster, :provided_by_gcp, projects: [project])
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
+          end
+        end
+      end
+
+      describe '"Set up Koding" button' do
+        it 'no "Set up Koding" button if Koding disabled' do
+          stub_application_setting(koding_enabled?: false)
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).not_to have_link('Set up Koding')
+          end
+        end
+
+        it 'no "Set up Koding" button if the project already has a .koding.yml' do
+          stub_application_setting(koding_enabled?: true)
+          allow(Gitlab::CurrentSettings.current_application_settings).to receive(:koding_url).and_return('http://koding.example.com')
+          expect(project.repository.changelog).not_to be_nil
+          allow_any_instance_of(Repository).to receive(:koding_yml).and_return(project.repository.changelog)
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).not_to have_link('Set up Koding')
+          end
+        end
+
+        it '"Set up Koding" button linked to new file populated for a .koding.yml' do
+          stub_application_setting(koding_enabled?: true)
+
+          visit project_path(project)
+
+          page.within('.project-stats') do
+            expect(page).to have_link('Set up Koding', href: presenter.add_koding_stack_path)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/show_project_spec.rb b/spec/features/projects/show_project_spec.rb
deleted file mode 100644
index 0a014e9f080392edbd7a61245a64525f6a68dd01..0000000000000000000000000000000000000000
--- a/spec/features/projects/show_project_spec.rb
+++ /dev/null
@@ -1,337 +0,0 @@
-require 'spec_helper'
-
-describe 'Project show page', :feature do
-  context 'when project pending delete' do
-    let(:project) { create(:project, :empty_repo, pending_delete: true) }
-
-    before do
-      sign_in(project.owner)
-    end
-
-    it 'shows error message if deletion for project fails' do
-      project.update_attributes(delete_error: "Something went wrong", pending_delete: false)
-
-      visit project_path(project)
-
-      expect(page).to have_selector('.project-deletion-failed-message')
-      expect(page).to have_content("This project was scheduled for deletion, but failed with the following message: #{project.delete_error}")
-    end
-  end
-
-  describe 'stat button existence' do
-    # For "New file", "Add License" functionality,
-    # see spec/features/projects/files/project_owner_creates_license_file_spec.rb
-    # see spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
-
-    let(:user) { create(:user) }
-
-    describe 'empty project' do
-      let(:project) { create(:project, :public, :empty_repo) }
-      let(:presenter) { project.present(current_user: user) }
-
-      describe 'as a normal user' do
-        before do
-          sign_in(user)
-
-          visit project_path(project)
-        end
-
-        it 'no Auto DevOps button if can not manage pipelines' do
-          page.within('.project-stats') do
-            expect(page).not_to have_link('Enable Auto DevOps')
-            expect(page).not_to have_link('Auto DevOps enabled')
-          end
-        end
-
-        it '"Auto DevOps enabled" button not linked' do
-          project.create_auto_devops!(enabled: true)
-
-          visit project_path(project)
-
-          page.within('.project-stats') do
-            expect(page).to have_text('Auto DevOps enabled')
-          end
-        end
-      end
-
-      describe 'as a master' do
-        before do
-          project.add_master(user)
-          sign_in(user)
-
-          visit project_path(project)
-        end
-
-        it '"New file" button linked to new file page' do
-          page.within('.project-stats') do
-            expect(page).to have_link('New file', href: project_new_blob_path(project, project.default_branch || 'master'))
-          end
-        end
-
-        it '"Add Readme" button linked to new file populated for a readme' do
-          page.within('.project-stats') do
-            expect(page).to have_link('Add Readme', href: presenter.add_readme_path)
-          end
-        end
-
-        it '"Add License" button linked to new file populated for a license' do
-          page.within('.project-stats') do
-            expect(page).to have_link('Add License', href: presenter.add_license_path)
-          end
-        end
-
-        describe 'Auto DevOps button' do
-          it '"Enable Auto DevOps" button linked to settings page' do
-            page.within('.project-stats') do
-              expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
-            end
-          end
-
-          it '"Auto DevOps enabled" anchor linked to settings page' do
-            project.create_auto_devops!(enabled: true)
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
-            end
-          end
-        end
-
-        describe 'Kubernetes cluster button' do
-          it '"Add Kubernetes cluster" button linked to clusters page' do
-            page.within('.project-stats') do
-              expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
-            end
-          end
-
-          it '"Kubernetes cluster" anchor linked to cluster page' do
-            cluster = create(:cluster, :provided_by_gcp, projects: [project])
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
-            end
-          end
-        end
-      end
-    end
-
-    describe 'populated project' do
-      let(:project) { create(:project, :public, :repository) }
-      let(:presenter) { project.present(current_user: user) }
-
-      describe 'as a normal user' do
-        before do
-          sign_in(user)
-
-          visit project_path(project)
-        end
-
-        it 'no Auto DevOps button if can not manage pipelines' do
-          page.within('.project-stats') do
-            expect(page).not_to have_link('Enable Auto DevOps')
-            expect(page).not_to have_link('Auto DevOps enabled')
-          end
-        end
-
-        it '"Auto DevOps enabled" button not linked' do
-          project.create_auto_devops!(enabled: true)
-
-          visit project_path(project)
-
-          page.within('.project-stats') do
-            expect(page).to have_text('Auto DevOps enabled')
-          end
-        end
-
-        it 'no Kubernetes cluster button if can not manage clusters' do
-          page.within('.project-stats') do
-            expect(page).not_to have_link('Add Kubernetes cluster')
-            expect(page).not_to have_link('Kubernetes configured')
-          end
-        end
-      end
-
-      describe 'as a master' do
-        before do
-          allow_any_instance_of(AutoDevopsHelper).to receive(:show_auto_devops_callout?).and_return(false)
-          project.add_master(user)
-          sign_in(user)
-
-          visit project_path(project)
-        end
-
-        it 'no "Add Changelog" button if the project already has a changelog' do
-          expect(project.repository.changelog).not_to be_nil
-
-          page.within('.project-stats') do
-            expect(page).not_to have_link('Add Changelog')
-          end
-        end
-
-        it 'no "Add License" button if the project already has a license' do
-          expect(project.repository.license_blob).not_to be_nil
-
-          page.within('.project-stats') do
-            expect(page).not_to have_link('Add License')
-          end
-        end
-
-        it 'no "Add Contribution guide" button if the project already has a contribution guide' do
-          expect(project.repository.contribution_guide).not_to be_nil
-
-          page.within('.project-stats') do
-            expect(page).not_to have_link('Add Contribution guide')
-          end
-        end
-
-        describe 'GitLab CI configuration button' do
-          it '"Set up CI/CD" button linked to new file populated for a .gitlab-ci.yml' do
-            expect(project.repository.gitlab_ci_yml).to be_nil
-
-            page.within('.project-stats') do
-              expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path)
-            end
-          end
-
-          it 'no "Set up CI/CD" button if the project already has a .gitlab-ci.yml' do
-            Files::CreateService.new(
-              project,
-              project.creator,
-              start_branch: 'master',
-              branch_name: 'master',
-              commit_message: "Add .gitlab-ci.yml",
-              file_path: '.gitlab-ci.yml',
-              file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
-            ).execute
-
-            expect(project.repository.gitlab_ci_yml).not_to be_nil
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).not_to have_link('Set up CI/CD')
-            end
-          end
-
-          it 'no "Set up CI/CD" button if the project has Auto DevOps enabled' do
-            project.create_auto_devops!(enabled: true)
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).not_to have_link('Set up CI/CD')
-            end
-          end
-        end
-
-        describe 'Auto DevOps button' do
-          it '"Enable Auto DevOps" button linked to settings page' do
-            page.within('.project-stats') do
-              expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
-            end
-          end
-
-          it '"Enable Auto DevOps" button linked to settings page' do
-            project.create_auto_devops!(enabled: true)
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))
-            end
-          end
-
-          it 'no Auto DevOps button if Auto DevOps callout is shown' do
-            allow_any_instance_of(AutoDevopsHelper).to receive(:show_auto_devops_callout?).and_return(true)
-
-            visit project_path(project)
-
-            expect(page).to have_selector('.js-autodevops-banner')
-
-            page.within('.project-stats') do
-              expect(page).not_to have_link('Enable Auto DevOps')
-              expect(page).not_to have_link('Auto DevOps enabled')
-            end
-          end
-
-          it 'no "Enable Auto DevOps" button when .gitlab-ci.yml already exists' do
-            Files::CreateService.new(
-              project,
-              project.creator,
-              start_branch: 'master',
-              branch_name: 'master',
-              commit_message: "Add .gitlab-ci.yml",
-              file_path: '.gitlab-ci.yml',
-              file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
-            ).execute
-
-            expect(project.repository.gitlab_ci_yml).not_to be_nil
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).not_to have_link('Enable Auto DevOps')
-              expect(page).not_to have_link('Auto DevOps enabled')
-            end
-          end
-        end
-
-        describe 'Kubernetes cluster button' do
-          it '"Add Kubernetes cluster" button linked to clusters page' do
-            page.within('.project-stats') do
-              expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
-            end
-          end
-
-          it '"Kubernetes cluster" button linked to cluster page' do
-            cluster = create(:cluster, :provided_by_gcp, projects: [project])
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
-            end
-          end
-        end
-
-        describe '"Set up Koding" button' do
-          it 'no "Set up Koding" button if Koding disabled' do
-            stub_application_setting(koding_enabled?: false)
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).not_to have_link('Set up Koding')
-            end
-          end
-
-          it 'no "Set up Koding" button if the project already has a .koding.yml' do
-            stub_application_setting(koding_enabled?: true)
-            allow(Gitlab::CurrentSettings.current_application_settings).to receive(:koding_url).and_return('http://koding.example.com')
-            expect(project.repository.changelog).not_to be_nil
-            allow_any_instance_of(Repository).to receive(:koding_yml).and_return(project.repository.changelog)
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).not_to have_link('Set up Koding')
-            end
-          end
-
-          it '"Set up Koding" button linked to new file populated for a .koding.yml' do
-            stub_application_setting(koding_enabled?: true)
-
-            visit project_path(project)
-
-            page.within('.project-stats') do
-              expect(page).to have_link('Set up Koding', href: presenter.add_koding_stack_path)
-            end
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 3466a3dfb778cb7381a22c965073cb19b187d06c..2388feeb98026e87c00daf2c79324b5f59c5f889 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-feature 'Create Snippet', :js do
+describe 'Projects > Snippets > Create Snippet', :js do
   include DropzoneHelper
 
   let(:user) { create(:user) }
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index 216f2af7c886d832ea2757cc846185df51c7299d..004ac55b65607554eb97ad9cf4d6b9202b1fbd92 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-feature 'Project snippet', :js do
+describe 'Projects > Snippets > Project snippet', :js do
   let(:user) { create(:user) }
   let(:project) { create(:project, :repository) }
   let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) }
diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
index 1bd2098af6ddc289efc89599a3f5ef519618b454..01cf9740d1f5cf454c98f64b795ec7cf27800c34 100644
--- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'User comments on a snippet', :js do
+describe 'Projects > Snippets > User comments on a snippet', :js do
   let(:project) { create(:project) }
   let!(:snippet) { create(:project_snippet, project: project, author: user) }
   let(:user) { create(:user) }
@@ -22,4 +22,16 @@ describe 'User comments on a snippet', :js do
 
     expect(page).to have_content('Good snippet!')
   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
+
+  it 'should have zen mode' do
+    find('.js-zen-enter').click()
+    expect(page).to have_selector('.fullscreen')
+  end
 end
diff --git a/spec/features/projects/snippets/user_deletes_snippet_spec.rb b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
index ca5f7981c3365dc2bcb4e6bb0d9e42cbc3158179..e64837ad59ec6ac24d67d57c2af5d8e4ea6704aa 100644
--- a/spec/features/projects/snippets/user_deletes_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'User deletes a snippet' do
+describe 'Projects > Snippets > User deletes a snippet' do
   let(:project) { create(:project) }
   let!(:snippet) { create(:project_snippet, project: project, author: user) }
   let(:user) { create(:user) }
diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb
index 09a390443cfa166e53787919d48cf9624bb904eb..eaedbbf32b6ce977fa17be73bd1dc00940b2562e 100644
--- a/spec/features/projects/snippets/user_updates_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'User updates a snippet' do
+describe 'Projects > Snippets > User updates a snippet' do
   let(:project) { create(:project) }
   let!(:snippet) { create(:project_snippet, project: project, author: user) }
   let(:user) { create(:user) }
diff --git a/spec/features/projects/snippets/user_views_snippets_spec.rb b/spec/features/projects/snippets/user_views_snippets_spec.rb
index e9992e00ca8c551d66d70000ecce6c6a6079daa1..376b76e0001a259b1b3fd5b3bd913b3c94ea046f 100644
--- a/spec/features/projects/snippets/user_views_snippets_spec.rb
+++ b/spec/features/projects/snippets/user_views_snippets_spec.rb
@@ -1,9 +1,10 @@
 require 'spec_helper'
 
-describe 'User views snippets' do
+describe 'Projects > Snippets > User views snippets' do
   let(:project) { create(:project) }
   let!(:project_snippet) { create(:project_snippet, project: project, author: user) }
   let!(:snippet) { create(:snippet, author: user) }
+  let(:snippets) { [project_snippet, snippet] } # Used by the shared examples
   let(:user) { create(:user) }
 
   before do
@@ -13,6 +14,17 @@ describe 'User views snippets' do
     visit(project_snippets_path(project))
   end
 
+  context 'pagination' do
+    before do
+      create(:project_snippet, project: project, author: user)
+      allow(Snippet).to receive(:default_per_page).and_return(1)
+
+      visit project_snippets_path(project)
+    end
+
+    it_behaves_like 'paginated snippets'
+  end
+
   it 'shows snippets' do
     expect(page).to have_content(project_snippet.title)
     expect(page).not_to have_content(snippet.title)
diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb
deleted file mode 100644
index 0fa7ca9afd46e999c016425c003f8ac13fb57e73..0000000000000000000000000000000000000000
--- a/spec/features/projects/snippets_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'spec_helper'
-
-describe 'Project snippets', :js do
-  context 'when the project has snippets' do
-    let(:project) { create(:project, :public) }
-    let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
-    let!(:other_snippet) { create(:project_snippet) }
-
-    context 'pagination' do
-      before do
-        allow(Snippet).to receive(:default_per_page).and_return(1)
-
-        visit project_snippets_path(project)
-      end
-
-      it_behaves_like 'paginated snippets'
-    end
-
-    context 'list content' do
-      it 'contains all project snippets' do
-        visit project_snippets_path(project)
-
-        expect(page).to have_selector('.snippet-row', count: 2)
-
-        expect(page).to have_content(snippets[0].title)
-        expect(page).to have_content(snippets[1].title)
-      end
-    end
-
-    context 'when submitting a note' do
-      before do
-        sign_in(create(: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
-
-      it 'should have zen mode' do
-        find('.js-zen-enter').click()
-        expect(page).to have_selector('.fullscreen')
-      end
-    end
-  end
-end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 0c67196f53eccc645bebda02c47118b9f256c1da..b242e41df1ce01507ab507cea85adeabc6529602 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -8,8 +8,6 @@ feature 'Multi-file editor new directory', :js do
     project.add_master(user)
     sign_in(user)
 
-    set_cookie('new_repo', 'true')
-
     visit project_tree_path(project, :master)
 
     wait_for_requests
@@ -46,7 +44,7 @@ feature 'Multi-file editor new directory', :js do
 
     wait_for_requests
 
-    find('.multi-file-commit-panel-collapse-btn').click
+    click_button 'Stage all'
 
     fill_in('commit-message', with: 'commit message ide')
 
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index 85f7318c05d0fab68497a9a03691d41f2b992671..7d65456e04965c06501baff9c2786dd98f17d3bb 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -8,9 +8,7 @@ feature 'Multi-file editor new file', :js do
     project.add_master(user)
     sign_in(user)
 
-    set_cookie('new_repo', 'true')
-
-    visit project_tree_path(project, :master)
+    visit project_path(project)
 
     wait_for_requests
 
@@ -36,7 +34,7 @@ feature 'Multi-file editor new file', :js do
 
     wait_for_requests
 
-    find('.multi-file-commit-panel-collapse-btn').click
+    click_button 'Stage all'
 
     fill_in('commit-message', with: 'commit message ide')
 
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index c8a17871508f0429b49361f4cfd0e4f97978b828..c4b3fb9d171623cb9f5e1ff720e743c8dab152fe 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -25,4 +25,18 @@ feature 'Projects tree' do
       expect(page).to have_selector('.label-lfs', text: 'LFS')
     end
   end
+
+  context 'web IDE', :js do
+    before do
+      visit project_tree_path(project, File.join('master', 'bar'))
+
+      click_link 'Web IDE'
+
+      find('.ide-file-list')
+    end
+
+    it 'opens folder in IDE' do
+      expect(page).to have_selector('.is-open', text: 'bar')
+    end
+  end
 end
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
index f81e8677e92f7a9e9c43c56b01ffb177a189eb3a..8e53ae1570062078b9e83ff3e7df2895881ec8a7 100644
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ b/spec/features/projects/tree/upload_file_spec.rb
@@ -10,8 +10,6 @@ feature 'Multi-file editor upload file', :js do
     project.add_master(user)
     sign_in(user)
 
-    set_cookie('new_repo', 'true')
-
     visit project_tree_path(project, :master)
 
     wait_for_requests
diff --git a/spec/features/projects/user_archives_project_spec.rb b/spec/features/projects/user_archives_project_spec.rb
deleted file mode 100644
index 72063d13c2a2db8834b891c289f536f9342fe491..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_archives_project_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe 'User archives a project' do
-  let(:user) { create(:user) }
-
-  before do
-    project.add_master(user)
-
-    sign_in(user)
-  end
-
-  context 'when a project is archived' do
-    let(:project) { create(:project, :archived, namespace: user.namespace) }
-
-    before do
-      visit(edit_project_path(project))
-    end
-
-    it 'unarchives a project' do
-      expect(page).to have_content('Unarchive project')
-
-      click_link('Unarchive')
-
-      expect(page).not_to have_content('Archived project')
-    end
-  end
-
-  context 'when a project is unarchived' do
-    let(:project) { create(:project, :repository, namespace: user.namespace) }
-
-    before do
-      visit(edit_project_path(project))
-    end
-
-    it 'archives a project' do
-      expect(page).to have_content('Archive project')
-
-      click_link('Archive')
-
-      expect(page).to have_content('Archived')
-    end
-  end
-end
diff --git a/spec/features/projects/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb b/spec/features/projects/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
deleted file mode 100644
index a17e65cc5b9fdf1dd26515c252bf469dd1a67a2c..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
-describe 'User browses a tree with a folder containing only a folder' do
-  let(:project) { create(:project, :empty_repo) }
-  let(:user) { project.creator }
-
-  before do
-    # We need to disable the tree.flat_path provided by Gitaly to reproduce the issue
-    allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
-
-    project.repository.create_dir(user, 'foo/bar', branch_name: 'master', message: 'Add the foo/bar folder')
-    sign_in(user)
-    visit(project_tree_path(project, project.repository.root_ref))
-  end
-
-  it 'shows the nested folder on a single row' do
-    expect(page).to have_content('foo/bar')
-  end
-end
diff --git a/spec/features/projects/user_browses_files_spec.rb b/spec/features/projects/user_browses_files_spec.rb
deleted file mode 100644
index 62e6419cc423b46e9a90aa4c2ca9ce0c1e0c8350..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_browses_files_spec.rb
+++ /dev/null
@@ -1,189 +0,0 @@
-require 'spec_helper'
-
-describe 'User browses files' do
-  include DropzoneHelper
-
-  let(:fork_message) do
-    "You're not allowed to make changes to this project directly. "\
-    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
-  end
-  let(:project) { create(:project, :repository, name: 'Shop') }
-  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
-  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
-  let(:tree_path_ref_6d39438) { project_tree_path(project, '6d39438') }
-  let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
-  let(:user) { create(:user) }
-
-  before do
-    project.add_master(user)
-    sign_in(user)
-  end
-
-  context 'when browsing the master branch' do
-    before do
-      visit(tree_path_root_ref)
-    end
-
-    it 'shows files from a repository' do
-      expect(page).to have_content('VERSION')
-      expect(page).to have_content('.gitignore')
-      expect(page).to have_content('LICENSE')
-    end
-
-    it 'shows the "Browse Directory" link' do
-      click_link('files')
-      click_link('History')
-
-      expect(page).to have_link('Browse Directory')
-      expect(page).not_to have_link('Browse Code')
-    end
-
-    it 'shows the "Browse File" link' do
-      page.within('.tree-table') do
-        click_link('README.md')
-      end
-      click_link('History')
-
-      expect(page).to have_link('Browse File')
-      expect(page).not_to have_link('Browse Files')
-    end
-
-    it 'shows the "Browse Code" link' do
-      click_link('History')
-
-      expect(page).to have_link('Browse Files')
-      expect(page).not_to have_link('Browse Directory')
-    end
-
-    it 'redirects to the permalink URL' do
-      click_link('.gitignore')
-      click_link('Permalink')
-
-      permalink_path = project_blob_path(project, "#{project.repository.commit.sha}/.gitignore")
-
-      expect(current_path).to eq(permalink_path)
-    end
-  end
-
-  context 'when browsing a specific ref' do
-    before do
-      visit(tree_path_ref_6d39438)
-    end
-
-    it 'shows files from a repository for "6d39438"' do
-      expect(current_path).to eq(tree_path_ref_6d39438)
-      expect(page).to have_content('.gitignore')
-      expect(page).to have_content('LICENSE')
-    end
-
-    it 'shows files from a repository with apostroph in its name', :js do
-      first('.js-project-refs-dropdown').click
-
-      page.within('.project-refs-form') do
-        click_link("'test'")
-      end
-
-      expect(page).to have_selector('.dropdown-toggle-text', text: "'test'")
-
-      visit(project_tree_path(project, "'test'"))
-
-      expect(page).to have_css('.tree-commit-link', visible: true)
-      expect(page).not_to have_content('Loading commit data...')
-    end
-
-    it 'shows the code with a leading dot in the directory', :js do
-      first('.js-project-refs-dropdown').click
-
-      page.within('.project-refs-form') do
-        click_link('fix')
-      end
-
-      visit(project_tree_path(project, 'fix/.testdir'))
-
-      expect(page).to have_css('.tree-commit-link', visible: true)
-      expect(page).not_to have_content('Loading commit data...')
-    end
-
-    it 'does not show the permalink link' do
-      click_link('.gitignore')
-
-      expect(page).not_to have_link('permalink')
-    end
-  end
-
-  context 'when browsing a file content' do
-    before do
-      visit(tree_path_root_ref)
-      click_link('.gitignore')
-    end
-
-    it 'shows a file content', :js do
-      wait_for_requests
-      expect(page).to have_content('*.rbc')
-    end
-  end
-
-  context 'when browsing a raw file' do
-    before do
-      visit(project_blob_path(project, File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path)))
-    end
-
-    it 'shows a raw file content' do
-      click_link('Open raw')
-      expect(source).to eq('') # Body is filled in by gitlab-workhorse
-    end
-  end
-
-  context 'when browsing an LFS object' do
-    before do
-      allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
-      visit(project_tree_path(project, 'lfs'))
-    end
-
-    it 'shows an LFS object' do
-      click_link('files')
-      click_link('lfs')
-      click_link('lfs_object.iso')
-
-      expect(page).to have_content('Download (1.5 MB)')
-      expect(page).not_to have_content('version https://git-lfs.github.com/spec/v1')
-      expect(page).not_to have_content('oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897')
-      expect(page).not_to have_content('size 1575078')
-
-      page.within('.content') do
-        expect(page).to have_content('Delete')
-        expect(page).to have_content('History')
-        expect(page).to have_content('Permalink')
-        expect(page).to have_content('Replace')
-        expect(page).not_to have_content('Annotate')
-        expect(page).not_to have_content('Blame')
-        expect(page).not_to have_content('Edit')
-        expect(page).to have_link('Download')
-      end
-    end
-  end
-
-  context 'when previewing a file content' do
-    before do
-      visit(tree_path_root_ref)
-    end
-
-    it 'shows a preview of a file content', :js do
-      find('.add-to-tree').click
-      click_link('Upload file')
-      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
-
-      page.within('#modal-upload-blob') do
-        fill_in(:commit_message, with: 'New commit message')
-        fill_in(:branch_name, with: 'new_branch_name', visible: true)
-        click_button('Upload file')
-      end
-
-      wait_for_all_requests
-
-      visit(project_blob_path(project, 'new_branch_name/logo_sample.svg'))
-
-      expect(page).to have_css('.file-content img')
-    end
-  end
-end
diff --git a/spec/features/projects/user_creates_directory_spec.rb b/spec/features/projects/user_creates_directory_spec.rb
deleted file mode 100644
index 00e48f6fabd22bc25e7f104f4f88a924acfa3d5a..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_creates_directory_spec.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'spec_helper'
-
-feature 'User creates a directory', :js do
-  let(:fork_message) do
-    "You're not allowed to make changes to this project directly. "\
-    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
-  end
-  let(:project) { create(:project, :repository) }
-  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
-  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
-  let(:user) { create(:user) }
-
-  before do
-    project.add_developer(user)
-    sign_in(user)
-    visit project_tree_path(project, 'master')
-  end
-
-  context 'with default target branch' do
-    before do
-      first('.add-to-tree').click
-      click_link('New directory')
-    end
-
-    it 'creates the directory in the default branch' do
-      fill_in(:dir_name, with: 'new_directory')
-      click_button('Create directory')
-
-      expect(page).to have_content('master')
-      expect(page).to have_content('The directory has been successfully created')
-      expect(page).to have_content('new_directory')
-    end
-
-    it 'does not create a directory with a name of already existed directory' do
-      fill_in(:dir_name, with: 'files')
-      fill_in(:commit_message, with: 'New commit message', visible: true)
-      click_button('Create directory')
-
-      expect(page).to have_content('A directory with this name already exists')
-      expect(current_path).to eq(project_tree_path(project, 'master'))
-    end
-  end
-
-  context 'with a new target branch' do
-    before do
-      first('.add-to-tree').click
-      click_link('New directory')
-      fill_in(:dir_name, with: 'new_directory')
-      fill_in(:branch_name, with: 'new-feature')
-      click_button('Create directory')
-    end
-
-    it 'creates the directory in the new branch and redirect to the merge request' do
-      expect(page).to have_content('new-feature')
-      expect(page).to have_content('The directory has been successfully created')
-      expect(page).to have_content('New Merge Request')
-      expect(page).to have_content('From new-feature into master')
-      expect(page).to have_content('Add new directory')
-
-      expect(current_path).to eq(project_new_merge_request_path(project))
-    end
-  end
-
-  context 'when an user does not have write access' do
-    before do
-      project2.add_reporter(user)
-      visit(project2_tree_path_root_ref)
-    end
-
-    it 'creates a directory in a forked project' do
-      find('.add-to-tree').click
-      click_link('New directory')
-
-      expect(page).to have_content(fork_message)
-
-      find('.add-to-tree').click
-      click_link('New directory')
-      fill_in(:dir_name, with: 'new_directory')
-      fill_in(:commit_message, with: 'New commit message', visible: true)
-      click_button('Create directory')
-
-      fork = user.fork_of(project2.reload)
-
-      expect(current_path).to eq(project_new_merge_request_path(fork))
-    end
-  end
-end
diff --git a/spec/features/projects/user_creates_files_spec.rb b/spec/features/projects/user_creates_files_spec.rb
deleted file mode 100644
index 7a935dd2477aba819202a723eb7a933c008bfc21..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_creates_files_spec.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-require 'spec_helper'
-
-describe 'User creates files' do
-  let(:fork_message) do
-    "You're not allowed to make changes to this project directly. "\
-    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
-  end
-  let(:project) { create(:project, :repository, name: 'Shop') }
-  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
-  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
-  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
-  let(:user) { create(:user) }
-
-  before do
-    project.add_master(user)
-    sign_in(user)
-  end
-
-  context 'without commiting a new file' do
-    context 'when an user has write access' do
-      before do
-        visit(project_tree_path_root_ref)
-      end
-
-      it 'opens new file page' do
-        find('.add-to-tree').click
-        click_link('New file')
-
-        expect(page).to have_content('New file')
-        expect(page).to have_content('Commit message')
-      end
-    end
-
-    context 'when an user does not have write access' do
-      before do
-        project2.add_reporter(user)
-        visit(project2_tree_path_root_ref)
-      end
-
-      it 'opens new file page on a forked project' do
-        find('.add-to-tree').click
-        click_link('New file')
-
-        expect(page).to have_selector('.file-editor')
-        expect(page).to have_content(fork_message)
-        expect(page).to have_content('New file')
-        expect(page).to have_content('Commit message')
-      end
-    end
-  end
-
-  context 'with commiting a new file' do
-    context 'when an user has write access' do
-      before do
-        visit(project_tree_path_root_ref)
-
-        find('.add-to-tree').click
-        click_link('New file')
-        expect(page).to have_selector('.file-editor')
-      end
-
-      it 'creates and commit a new file', :js do
-        find('#editor')
-        execute_script("ace.edit('editor').setValue('*.rbca')")
-        fill_in(:file_name, with: 'not_a_file.md')
-        fill_in(:commit_message, with: 'New commit message', visible: true)
-        click_button('Commit changes')
-
-        new_file_path = project_blob_path(project, 'master/not_a_file.md')
-
-        expect(current_path).to eq(new_file_path)
-
-        wait_for_requests
-
-        expect(page).to have_content('*.rbca')
-      end
-
-      it 'creates and commit a new file with new lines at the end of file', :js do
-        find('#editor')
-        execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
-        fill_in(:file_name, with: 'not_a_file.md')
-        fill_in(:commit_message, with: 'New commit message', visible: true)
-        click_button('Commit changes')
-
-        new_file_path = project_blob_path(project, 'master/not_a_file.md')
-
-        expect(current_path).to eq(new_file_path)
-
-        find('.js-edit-blob').click
-
-        find('#editor')
-        expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
-      end
-
-      it 'creates and commit a new file with a directory name', :js do
-        fill_in(:file_name, with: 'foo/bar/baz.txt')
-
-        expect(page).to have_selector('.file-editor')
-
-        find('#editor')
-        execute_script("ace.edit('editor').setValue('*.rbca')")
-        fill_in(:commit_message, with: 'New commit message', visible: true)
-        click_button('Commit changes')
-
-        expect(current_path).to eq(project_blob_path(project, 'master/foo/bar/baz.txt'))
-
-        wait_for_requests
-
-        expect(page).to have_content('*.rbca')
-      end
-
-      it 'creates and commit a new file specifying a new branch', :js do
-        expect(page).to have_selector('.file-editor')
-
-        find('#editor')
-        execute_script("ace.edit('editor').setValue('*.rbca')")
-        fill_in(:file_name, with: 'not_a_file.md')
-        fill_in(:commit_message, with: 'New commit message', visible: true)
-        fill_in(:branch_name, with: 'new_branch_name', visible: true)
-        click_button('Commit changes')
-
-        expect(current_path).to eq(project_new_merge_request_path(project))
-
-        click_link('Changes')
-
-        wait_for_requests
-
-        expect(page).to have_content('*.rbca')
-      end
-    end
-
-    context 'when an user does not have write access' do
-      before do
-        project2.add_reporter(user)
-        visit(project2_tree_path_root_ref)
-      end
-
-      it 'creates and commit new file in forked project', :js do
-        find('.add-to-tree').click
-        click_link('New file')
-
-        expect(page).to have_selector('.file-editor')
-
-        find('#editor')
-        execute_script("ace.edit('editor').setValue('*.rbca')")
-
-        fill_in(:file_name, with: 'not_a_file.md')
-        fill_in(:commit_message, with: 'New commit message', visible: true)
-        click_button('Commit changes')
-
-        fork = user.fork_of(project2.reload)
-
-        expect(current_path).to eq(project_new_merge_request_path(fork))
-        expect(page).to have_content('New commit message')
-      end
-    end
-  end
-end
diff --git a/spec/features/projects/user_deletes_files_spec.rb b/spec/features/projects/user_deletes_files_spec.rb
deleted file mode 100644
index 9d55197e719b99c4fc3d6e2f2d2045761d8f1034..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_deletes_files_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-
-describe 'User deletes files' do
-  let(:fork_message) do
-    "You're not allowed to make changes to this project directly. "\
-    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
-  end
-  let(:project) { create(:project, :repository, name: 'Shop') }
-  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
-  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
-  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
-  let(:user) { create(:user) }
-
-  before do
-    sign_in(user)
-  end
-
-  context 'when an user has write access' do
-    before do
-      project.add_master(user)
-      visit(project_tree_path_root_ref)
-    end
-
-    it 'deletes the file', :js do
-      click_link('.gitignore')
-
-      expect(page).to have_content('.gitignore')
-
-      click_on('Delete')
-      fill_in(:commit_message, with: 'New commit message', visible: true)
-      click_button('Delete file')
-
-      expect(current_path).to eq(project_tree_path(project, 'master'))
-      expect(page).not_to have_content('.gitignore')
-    end
-  end
-
-  context 'when an user does not have write access' do
-    before do
-      project2.add_reporter(user)
-      visit(project2_tree_path_root_ref)
-    end
-
-    it 'deletes the file in a forked project', :js do
-      click_link('.gitignore')
-
-      expect(page).to have_content('.gitignore')
-
-      click_on('Delete')
-
-      expect(page).to have_link('Fork')
-      expect(page).to have_button('Cancel')
-
-      click_link('Fork')
-
-      expect(page).to have_content(fork_message)
-
-      click_on('Delete')
-      fill_in(:commit_message, with: 'New commit message', visible: true)
-      click_button('Delete file')
-
-      fork = user.fork_of(project2.reload)
-
-      expect(current_path).to eq(project_new_merge_request_path(fork))
-      expect(page).to have_content('New commit message')
-    end
-  end
-end
diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb
deleted file mode 100644
index 05c2be473da94aa4750f9d9bfa232a294c0585ef..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_edits_files_spec.rb
+++ /dev/null
@@ -1,173 +0,0 @@
-require 'spec_helper'
-
-describe 'User edits files' do
-  include ProjectForksHelper
-  let(:project) { create(:project, :repository, name: 'Shop') }
-  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
-  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
-  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
-  let(:user) { create(:user) }
-
-  before do
-    sign_in(user)
-  end
-
-  context 'when an user has write access' do
-    before do
-      project.add_master(user)
-      visit(project_tree_path_root_ref)
-    end
-
-    it 'inserts a content of a file', :js do
-      click_link('.gitignore')
-      find('.js-edit-blob').click
-      find('.file-editor', match: :first)
-
-      find('#editor')
-      execute_script("ace.edit('editor').setValue('*.rbca')")
-
-      expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
-    end
-
-    it 'does not show the edit link if a file is binary' do
-      binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png')
-      visit(project_blob_path(project, binary_file))
-
-      page.within '.content' do
-        expect(page).not_to have_link('edit')
-      end
-    end
-
-    it 'commits an edited file', :js do
-      click_link('.gitignore')
-      find('.js-edit-blob').click
-      find('.file-editor', match: :first)
-
-      find('#editor')
-      execute_script("ace.edit('editor').setValue('*.rbca')")
-      fill_in(:commit_message, with: 'New commit message', visible: true)
-      click_button('Commit changes')
-
-      expect(current_path).to eq(project_blob_path(project, 'master/.gitignore'))
-
-      wait_for_requests
-
-      expect(page).to have_content('*.rbca')
-    end
-
-    it 'commits an edited file to a new branch', :js do
-      click_link('.gitignore')
-      find('.js-edit-blob').click
-
-      find('.file-editor', match: :first)
-
-      find('#editor')
-      execute_script("ace.edit('editor').setValue('*.rbca')")
-      fill_in(:commit_message, with: 'New commit message', visible: true)
-      fill_in(:branch_name, with: 'new_branch_name', visible: true)
-      click_button('Commit changes')
-
-      expect(current_path).to eq(project_new_merge_request_path(project))
-
-      click_link('Changes')
-
-      expect(page).to have_content('*.rbca')
-    end
-
-    it 'shows the diff of an edited file', :js do
-      click_link('.gitignore')
-      find('.js-edit-blob').click
-      find('.file-editor', match: :first)
-
-      find('#editor')
-      execute_script("ace.edit('editor').setValue('*.rbca')")
-      click_link('Preview changes')
-
-      expect(page).to have_css('.line_holder.new')
-    end
-  end
-
-  context 'when an user does not have write access' do
-    before do
-      project2.add_reporter(user)
-      visit(project2_tree_path_root_ref)
-    end
-
-    it 'inserts a content of a file in a forked project', :js do
-      click_link('.gitignore')
-      find('.js-edit-blob').click
-
-      expect(page).to have_link('Fork')
-      expect(page).to have_button('Cancel')
-
-      click_link('Fork')
-
-      expect(page).to have_content(
-        "You're not allowed to make changes to this project directly. "\
-        "A fork of this project has been created that you can make changes in, so you can submit a merge request."
-      )
-
-      find('.file-editor', match: :first)
-
-      find('#editor')
-      execute_script("ace.edit('editor').setValue('*.rbca')")
-
-      expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
-    end
-
-    it 'commits an edited file in a forked project', :js do
-      click_link('.gitignore')
-      find('.js-edit-blob').click
-
-      expect(page).to have_link('Fork')
-      expect(page).to have_button('Cancel')
-
-      click_link('Fork')
-
-      find('.file-editor', match: :first)
-
-      find('#editor')
-      execute_script("ace.edit('editor').setValue('*.rbca')")
-      fill_in(:commit_message, with: 'New commit message', visible: true)
-      click_button('Commit changes')
-
-      fork = user.fork_of(project2.reload)
-
-      expect(current_path).to eq(project_new_merge_request_path(fork))
-
-      wait_for_requests
-
-      expect(page).to have_content('New commit message')
-    end
-
-    context 'when the user already had a fork of the project', :js do
-      let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) }
-      before do
-        visit(project2_tree_path_root_ref)
-      end
-
-      it 'links to the forked project for editing' do
-        click_link('.gitignore')
-        find('.js-edit-blob').click
-
-        expect(page).not_to have_link('Fork')
-        expect(page).not_to have_button('Cancel')
-
-        find('#editor')
-        execute_script("ace.edit('editor').setValue('*.rbca')")
-        fill_in(:commit_message, with: 'Another commit', visible: true)
-        click_button('Commit changes')
-
-        fork = user.fork_of(project2)
-
-        expect(current_path).to eq(project_new_merge_request_path(fork))
-
-        wait_for_requests
-
-        expect(page).to have_content('Another commit')
-        expect(page).to have_content("From #{forked_project.full_path}")
-        expect(page).to have_content("into #{project2.full_path}")
-      end
-    end
-  end
-end
diff --git a/spec/features/projects/user_interacts_with_stars_spec.rb b/spec/features/projects/user_interacts_with_stars_spec.rb
deleted file mode 100644
index d9d2e0ab1713fd905e638cb679b44a334075b9f4..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_interacts_with_stars_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe 'User interacts with project stars' do
-  let(:project) { create(:project, :public, :repository) }
-
-  context 'when user is signed in', :js do
-    let(:user) { create(:user) }
-
-    before do
-      sign_in(user)
-      visit(project_path(project))
-    end
-
-    it 'toggles the star' do
-      find('.star-btn').click
-
-      expect(page).to have_css('.star-count', text: 1)
-
-      find('.star-btn').click
-
-      expect(page).to have_css('.star-count', text: 0)
-    end
-  end
-
-  context 'when user is not signed in' do
-    before do
-      visit(project_path(project))
-    end
-
-    it 'does not allow to star a project' do
-      expect(page).not_to have_content('.toggle-star')
-
-      find('.star-btn').click
-
-      expect(current_path).to eq(new_user_session_path)
-    end
-  end
-end
diff --git a/spec/features/projects/user_replaces_files_spec.rb b/spec/features/projects/user_replaces_files_spec.rb
deleted file mode 100644
index 74872403b35fae729ea0027c76ab385c85721362..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_replaces_files_spec.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'spec_helper'
-
-describe 'User replaces files' do
-  include DropzoneHelper
-
-  let(:fork_message) do
-    "You're not allowed to make changes to this project directly. "\
-    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
-  end
-  let(:project) { create(:project, :repository, name: 'Shop') }
-  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
-  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
-  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
-  let(:user) { create(:user) }
-
-  before do
-    sign_in(user)
-  end
-
-  context 'when an user has write access' do
-    before do
-      project.add_master(user)
-      visit(project_tree_path_root_ref)
-    end
-
-    it 'replaces an existed file with a new one', :js do
-      click_link('.gitignore')
-
-      expect(page).to have_content('.gitignore')
-
-      click_on('Replace')
-      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
-
-      page.within('#modal-upload-blob') do
-        fill_in(:commit_message, with: 'Replacement file commit message')
-      end
-
-      click_button('Replace file')
-
-      expect(page).to have_content('Lorem ipsum dolor sit amet')
-      expect(page).to have_content('Sed ut perspiciatis unde omnis')
-      expect(page).to have_content('Replacement file commit message')
-    end
-  end
-
-  context 'when an user does not have write access' do
-    before do
-      project2.add_reporter(user)
-      visit(project2_tree_path_root_ref)
-    end
-
-    it 'replaces an existed file with a new one in a forked project', :js do
-      click_link('.gitignore')
-
-      expect(page).to have_content('.gitignore')
-
-      click_on('Replace')
-
-      expect(page).to have_link('Fork')
-      expect(page).to have_button('Cancel')
-
-      click_link('Fork')
-
-      expect(page).to have_content(fork_message)
-
-      click_on('Replace')
-      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
-
-      page.within('#modal-upload-blob') do
-        fill_in(:commit_message, with: 'Replacement file commit message')
-      end
-
-      click_button('Replace file')
-
-      expect(page).to have_content('Replacement file commit message')
-
-      fork = user.fork_of(project2.reload)
-
-      expect(current_path).to eq(project_new_merge_request_path(fork))
-
-      click_link('Changes')
-
-      expect(page).to have_content('Lorem ipsum dolor sit amet')
-      expect(page).to have_content('Sed ut perspiciatis unde omnis')
-    end
-  end
-end
diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cf80517b934e618ff9568d202f1620306f7bbe71
--- /dev/null
+++ b/spec/features/projects/user_sees_sidebar_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe 'Projects > User sees sidebar' do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :private, public_builds: false, namespace: user.namespace) }
+
+  context 'as owner' do
+    before do
+      sign_in(user)
+    end
+
+    context 'when snippets are disabled' do
+      before do
+        project.project_feature.update_attribute('snippets_access_level', ProjectFeature::DISABLED)
+      end
+
+      it 'does not display a "Snippets" link' do
+        visit project_path(project)
+
+        within('.nav-sidebar') do
+          expect(page).not_to have_content 'Snippets'
+        end
+      end
+    end
+  end
+
+  context 'as guest' do
+    let(:guest) { create(:user) }
+
+    before do
+      project.add_guest(guest)
+
+      sign_in(guest)
+    end
+
+    it 'shows allowed tabs only' do
+      visit project_path(project)
+
+      within('.nav-sidebar') do
+        expect(page).to have_content 'Overview'
+        expect(page).to have_content 'Issues'
+        expect(page).to have_content 'Wiki'
+
+        expect(page).not_to have_content 'Repository'
+        expect(page).not_to have_content 'CI / CD'
+        expect(page).not_to have_content 'Merge Requests'
+      end
+    end
+
+    it 'does not show fork button' do
+      visit project_path(project)
+
+      within('.count-buttons') do
+        expect(page).not_to have_link 'Fork'
+      end
+    end
+
+    it 'does not show clone path' do
+      visit project_path(project)
+
+      within('.project-repo-buttons') do
+        expect(page).not_to have_selector '.project-clone-holder'
+      end
+    end
+
+    describe 'project landing page' do
+      before do
+        project.project_feature.update!(
+          issues_access_level: ProjectFeature::DISABLED,
+          wiki_access_level: ProjectFeature::DISABLED
+        )
+      end
+
+      it 'does not show the project file list landing page' do
+        visit project_path(project)
+
+        expect(page).not_to have_selector '.project-stats'
+        expect(page).not_to have_selector '.project-last-commit'
+        expect(page).not_to have_selector '.project-show-files'
+        expect(page).to have_selector '.project-show-customize_workflow'
+      end
+
+      it 'shows the customize workflow when issues and wiki are disabled' do
+        visit project_path(project)
+
+        expect(page).to have_selector '.project-show-customize_workflow'
+      end
+
+      it 'shows the wiki when enabled' do
+        project.project_feature.update!(wiki_access_level: ProjectFeature::PRIVATE)
+
+        visit project_path(project)
+
+        expect(page).to have_selector '.project-show-wiki'
+      end
+
+      it 'shows the issues when enabled' do
+        project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+
+        visit project_path(project)
+
+        expect(page).to have_selector '.issues-list'
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/user_transfers_a_project_spec.rb b/spec/features/projects/user_transfers_a_project_spec.rb
deleted file mode 100644
index 78f72b644ff6329b3fb7f8fcfbdb8d6efe9e4fe2..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_transfers_a_project_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'spec_helper'
-
-feature 'User transfers a project', :js do
-  let(:user) { create(:user) }
-  let(:project) { create(:project, :repository, namespace: user.namespace) }
-
-  before do
-    sign_in user
-  end
-
-  def transfer_project(project, group)
-    visit edit_project_path(project)
-
-    page.within('.js-project-transfer-form') do
-      page.find('.select2-container').click
-    end
-
-    page.find("div[role='option']", text: group.full_name).click
-
-    click_button('Transfer project')
-
-    fill_in 'confirm_name_input', with: project.name
-
-    click_button 'Confirm'
-
-    wait_for_requests
-  end
-
-  it 'allows transferring a project to a subgroup of a namespace' do
-    group = create(:group)
-    group.add_owner(user)
-
-    transfer_project(project, group)
-
-    expect(project.reload.namespace).to eq(group)
-  end
-
-  context 'when nested groups are available', :nested_groups do
-    it 'allows transferring a project to a subgroup' do
-      parent = create(:group)
-      parent.add_owner(user)
-      subgroup = create(:group, parent: parent)
-
-      transfer_project(project, subgroup)
-
-      expect(project.reload.namespace).to eq(subgroup)
-    end
-  end
-end
diff --git a/spec/features/projects/user_uploads_files_spec.rb b/spec/features/projects/user_uploads_files_spec.rb
deleted file mode 100644
index 75898afcda946f55e6c685eac68d46f92212adce..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_uploads_files_spec.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'spec_helper'
-
-describe 'User uploads files' do
-  include DropzoneHelper
-
-  let(:fork_message) do
-    "You're not allowed to make changes to this project directly. "\
-    "A fork of this project has been created that you can make changes in, so you can submit a merge request."
-  end
-  let(:project) { create(:project, :repository, name: 'Shop') }
-  let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
-  let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
-  let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
-  let(:user) { create(:user) }
-
-  before do
-    project.add_master(user)
-    sign_in(user)
-  end
-
-  context 'when an user has write access' do
-    before do
-      visit(project_tree_path_root_ref)
-    end
-
-    it 'uploads and commit a new file', :js do
-      find('.add-to-tree').click
-      click_link('Upload file')
-      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
-
-      page.within('#modal-upload-blob') do
-        fill_in(:commit_message, with: 'New commit message')
-      end
-
-      fill_in(:branch_name, with: 'new_branch_name', visible: true)
-      click_button('Upload file')
-
-      expect(page).to have_content('New commit message')
-      expect(current_path).to eq(project_new_merge_request_path(project))
-
-      click_link('Changes')
-      find("a[data-action='diffs']", text: 'Changes').click
-
-      wait_for_requests
-
-      expect(page).to have_content('Lorem ipsum dolor sit amet')
-      expect(page).to have_content('Sed ut perspiciatis unde omnis')
-    end
-  end
-
-  context 'when an user does not have write access' do
-    before do
-      project2.add_reporter(user)
-      visit(project2_tree_path_root_ref)
-    end
-
-    it 'uploads and commit a new file to a forked project', :js do
-      find('.add-to-tree').click
-      click_link('Upload file')
-
-      expect(page).to have_content(fork_message)
-
-      find('.add-to-tree').click
-      click_link('Upload file')
-      drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
-
-      page.within('#modal-upload-blob') do
-        fill_in(:commit_message, with: 'New commit message')
-      end
-
-      click_button('Upload file')
-
-      expect(page).to have_content('New commit message')
-
-      fork = user.fork_of(project2.reload)
-
-      expect(current_path).to eq(project_new_merge_request_path(fork))
-
-      find("a[data-action='diffs']", text: 'Changes').click
-
-      wait_for_requests
-
-      expect(page).to have_content('Lorem ipsum dolor sit amet')
-      expect(page).to have_content('Sed ut perspiciatis unde omnis')
-    end
-  end
-end
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index fb0d8c766feb5241b0f00a4f525196d35c1737fb..47c5a8161d90d70b00babb5283f8a7818b2bf58d 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -11,12 +11,12 @@ describe 'User uses shortcuts', :js do
     visit(project_path(project))
   end
 
-  context 'when navigating to the Overview pages' do
+  context 'when navigating to the Project pages' do
     it 'redirects to the details page' do
       find('body').native.send_key('g')
       find('body').native.send_key('p')
 
-      expect(page).to have_active_navigation('Overview')
+      expect(page).to have_active_navigation('Project')
       expect(page).to have_active_sub_navigation('Details')
     end
 
@@ -24,7 +24,7 @@ describe 'User uses shortcuts', :js do
       find('body').native.send_key('g')
       find('body').native.send_key('e')
 
-      expect(page).to have_active_navigation('Overview')
+      expect(page).to have_active_navigation('Project')
       expect(page).to have_active_sub_navigation('Activity')
     end
   end
diff --git a/spec/features/projects/user_views_details_spec.rb b/spec/features/projects/user_views_details_spec.rb
deleted file mode 100644
index ffc063654cd1a8a4f86c43d1c17db7b33ede1a34..0000000000000000000000000000000000000000
--- a/spec/features/projects/user_views_details_spec.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require 'spec_helper'
-
-describe 'User views details' do
-  set(:user) { create(:user) }
-
-  shared_examples_for 'redirects to the sign in page' do
-    it 'redirects to the sign in page' do
-      expect(current_path).to eq(new_user_session_path)
-    end
-  end
-
-  shared_examples_for 'shows details of empty project' do
-    let(:user_has_ssh_key) { false }
-
-    it 'shows details' do
-      expect(page).not_to have_content('Git global setup')
-
-      page.all(:css, '.git-empty .clone').each do |element|
-        expect(element.text).to include(project.http_url_to_repo)
-      end
-
-      expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
-    end
-  end
-
-  shared_examples_for 'shows details of non empty project' do
-    let(:user_has_ssh_key) { false }
-
-    it 'shows details' do
-      page.within('.breadcrumbs .breadcrumb-item-text') do
-        expect(page).to have_content(project.title)
-      end
-
-      expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
-    end
-  end
-
-  context 'when project is public' do
-    context 'when project is empty' do
-      set(:project) { create(:project_empty_repo, :public) }
-
-      context 'when not signed in' do
-        before do
-          visit(project_path(project))
-        end
-
-        include_examples 'shows details of empty project'
-      end
-
-      context 'when signed in' do
-        before do
-          sign_in(user)
-        end
-
-        context 'when user does not have ssh keys' do
-          before do
-            visit(project_path(project))
-          end
-
-          include_examples 'shows details of empty project'
-        end
-
-        context 'when user has ssh keys' do
-          before do
-            create(:personal_key, user: user)
-
-            visit(project_path(project))
-          end
-
-          include_examples 'shows details of empty project' do
-            let(:user_has_ssh_key) { true }
-          end
-        end
-      end
-    end
-
-    context 'when project is not empty' do
-      set(:project) { create(:project, :public, :repository) }
-
-      before do
-        visit(project_path(project))
-      end
-
-      context 'when not signed in' do
-        before do
-          allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com')
-        end
-
-        include_examples 'shows details of non empty project'
-      end
-
-      context 'when signed in' do
-        before do
-          sign_in(user)
-        end
-
-        context 'when user does not have ssh keys' do
-          before do
-            visit(project_path(project))
-          end
-
-          include_examples 'shows details of non empty project'
-        end
-
-        context 'when user has ssh keys' do
-          before do
-            create(:personal_key, user: user)
-
-            visit(project_path(project))
-          end
-
-          include_examples 'shows details of non empty project' do
-            let(:user_has_ssh_key) { true }
-          end
-        end
-      end
-    end
-  end
-
-  context 'when project is internal' do
-    set(:project) { create(:project, :internal, :repository) }
-
-    context 'when not signed in' do
-      before do
-        visit(project_path(project))
-      end
-
-      include_examples 'redirects to the sign in page'
-    end
-
-    context 'when signed in' do
-      before do
-        sign_in(user)
-
-        visit(project_path(project))
-      end
-
-      include_examples 'shows details of non empty project'
-    end
-  end
-
-  context 'when project is private' do
-    set(:project) { create(:project, :private) }
-
-    before do
-      visit(project_path(project))
-    end
-
-    include_examples 'redirects to the sign in page'
-  end
-end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index a40848182842c8f47906702542f9ac5080640234..43cabd3b9f23064ba33c44a35cd15dc07bd2483a 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -142,7 +142,10 @@ feature 'Protected Branches', :js do
         set_protected_branch_name('*-stable')
         click_on "Protect"
 
-        within(".protected-branches-list") { expect(page).to have_content("2 matching branches") }
+        within(".protected-branches-list") do
+          expect(page).to have_content("Protected branch (2)")
+          expect(page).to have_content("2 matching branches")
+        end
       end
 
       it "displays all the branches matching the wildcard" do
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 8cc6f17b8d9283de18e75a3c12ed673223dfc086..efccaeaff6c9cd84e97ef113aa11f76797a49ef0 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -65,7 +65,10 @@ feature 'Protected Tags', :js do
       set_protected_tag_name('*-stable')
       click_on "Protect"
 
-      within(".protected-tags-list") { expect(page).to have_content("2 matching tags") }
+      within(".protected-tags-list") do
+        expect(page).to have_content("Protected tag (2)")
+        expect(page).to have_content("2 matching tags")
+      end
     end
 
     it "displays all the tags matching the wildcard" do
diff --git a/spec/features/read_only_spec.rb b/spec/features/read_only_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8bfaf5584669e945f19edfdacf4205324fae38a4
--- /dev/null
+++ b/spec/features/read_only_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+describe 'read-only message' do
+  set(:user) { create(:user) }
+
+  before do
+    sign_in(user)
+  end
+
+  it 'shows read-only banner when database is read-only' do
+    allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+
+    visit root_dashboard_path
+
+    expect(page).to have_content('You are on a read-only GitLab instance.')
+  end
+
+  it 'does not show read-only banner when database is able to read-write' do
+    allow(Gitlab::Database).to receive(:read_only?).and_return(false)
+
+    visit root_dashboard_path
+
+    expect(page).not_to have_content('You are on a read-only GitLab instance.')
+  end
+end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 77212fb105b250c49d50dd3ac8448eba4b102537..9e089c5a6cb2b1da9e68bd57777624a18d0ecd8c 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -35,7 +35,7 @@ describe 'User searches for code' do
         find('.js-search-project-dropdown').click
 
         page.within('.project-filter') do
-          click_link(project.name_with_namespace)
+          click_link(project.full_name)
         end
 
         fill_in('dashboard_search', with: 'rspec')
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index ef9553f2a91d0373e338a31234b1c3f4d34983aa..d6120ff8517ea48282e715060389aa9a8f902f0c 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -34,7 +34,7 @@ describe 'User searches for issues', :js do
         find('.js-search-project-dropdown').click
 
         page.within('.project-filter') do
-          click_link(project.name_with_namespace)
+          click_link(project.full_name)
         end
 
         fill_in('dashboard_search', with: issue1.title)
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index 3b6739aecbd0e32ed1fce4bcef212dbbca87e4b8..68e2f7a857dfd4ed6c9bebf7f8dd4d3aaf829c25 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -33,7 +33,7 @@ describe 'User searches for merge requests', :js do
       find('.js-search-project-dropdown').click
 
       page.within('.project-filter') do
-        click_link(project.name_with_namespace)
+        click_link(project.full_name)
       end
 
       fill_in('dashboard_search', with: merge_request1.title)
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 6e197aee4984892e6daaa05be662a5df6409de7a..fc6cd81eb68a056ce8c74fcdfb221b7afb9f9a32 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -33,7 +33,7 @@ describe 'User searches for milestones', :js do
       find('.js-search-project-dropdown').click
 
       page.within('.project-filter') do
-        click_link(project.name_with_namespace)
+        click_link(project.full_name)
       end
 
       fill_in('dashboard_search', with: milestone1.title)
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 00af625dc86227beaee5812405a96386ff002c39..7934779058f5850d4d4a72757fb5071dbd398190 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -18,7 +18,7 @@ describe 'User searches for wiki pages', :js do
     find('.js-search-project-dropdown').click
 
     page.within('.project-filter') do
-      click_link(project.name_with_namespace)
+      click_link(project.full_name)
     end
 
     fill_in('dashboard_search', with: 'content')
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 5ddea36add5a27ce169a8340fa9ea97b8ab5da60..a9128104b87e0b1456ee6eb508f05c21ace68c9a 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -9,49 +9,25 @@ describe 'User uses header search field' do
   before do
     project.add_reporter(user)
     sign_in(user)
-
-    visit(project_path(project))
-  end
-
-  it 'starts searching by pressing the enter key', :js do
-    fill_in('search', with: 'gitlab')
-    find('#search').native.send_keys(:enter)
-
-    page.within('.breadcrumbs-sub-title') do
-      expect(page).to have_content('Search')
-    end
   end
 
-  it 'contains location badge' do
-    expect(page).to have_selector('.has-location-badge')
-  end
-
-  context 'when clicking the search field', :js do
+  context 'when user is in a global scope', :js do
     before do
+      visit(root_path)
       page.find('#search').click
     end
 
-    it 'shows category search dropdown' do
-      expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
-    end
-
     context 'when clicking issues' do
-      let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
-
       it 'shows assigned issues' do
-        find('.dropdown-menu').click_link('Issues assigned to me')
+        find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
 
-        expect(page).to have_selector('.filtered-search')
-        expect_tokens([assignee_token(user.name)])
-        expect_filtered_search_input_empty
+        expect(find('.js-assignee-search')).to have_content(user.name)
       end
 
       it 'shows created issues' do
-        find('.dropdown-menu').click_link("Issues I've created")
+        find('.search-input-container .dropdown-menu').click_link("Issues I've created")
 
-        expect(page).to have_selector('.filtered-search')
-        expect_tokens([author_token(user.name)])
-        expect_filtered_search_input_empty
+        expect(find('.js-author-search')).to have_content(user.name)
       end
     end
 
@@ -59,32 +35,97 @@ describe 'User uses header search field' do
       let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
 
       it 'shows assigned merge requests' do
-        find('.dropdown-menu').click_link('Merge requests assigned to me')
+        find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
 
-        expect(page).to have_selector('.merge-requests-holder')
-        expect_tokens([assignee_token(user.name)])
-        expect_filtered_search_input_empty
+        expect(find('.js-assignee-search')).to have_content(user.name)
       end
 
       it 'shows created merge requests' do
-        find('.dropdown-menu').click_link("Merge requests I've created")
+        find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
 
-        expect(page).to have_selector('.merge-requests-holder')
-        expect_tokens([author_token(user.name)])
-        expect_filtered_search_input_empty
+        expect(find('.js-author-search')).to have_content(user.name)
       end
     end
   end
 
-  context 'when entering text into the search field', :js do
+  context 'when user is in a project scope' do
     before do
-      page.within('.search-input-wrap') do
-        fill_in('search', with: project.name[0..3])
+      visit(project_path(project))
+    end
+
+    it 'starts searching by pressing the enter key', :js do
+      fill_in('search', with: 'gitlab')
+      find('#search').native.send_keys(:enter)
+
+      page.within('.breadcrumbs-sub-title') do
+        expect(page).to have_content('Search')
       end
     end
 
-    it 'does not display the category search dropdown' do
-      expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+    it 'contains location badge' do
+      expect(page).to have_selector('.has-location-badge')
+    end
+
+    context 'when clicking the search field', :js do
+      before do
+        page.find('#search').click
+      end
+
+      it 'shows category search dropdown' do
+        expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
+      end
+
+      context 'when clicking issues' do
+        let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
+
+        it 'shows assigned issues' do
+          find('.dropdown-menu').click_link('Issues assigned to me')
+
+          expect(page).to have_selector('.filtered-search')
+          expect_tokens([assignee_token(user.name)])
+          expect_filtered_search_input_empty
+        end
+
+        it 'shows created issues' do
+          find('.dropdown-menu').click_link("Issues I've created")
+
+          expect(page).to have_selector('.filtered-search')
+          expect_tokens([author_token(user.name)])
+          expect_filtered_search_input_empty
+        end
+      end
+
+      context 'when clicking merge requests' do
+        let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
+
+        it 'shows assigned merge requests' do
+          find('.dropdown-menu').click_link('Merge requests assigned to me')
+
+          expect(page).to have_selector('.merge-requests-holder')
+          expect_tokens([assignee_token(user.name)])
+          expect_filtered_search_input_empty
+        end
+
+        it 'shows created merge requests' do
+          find('.dropdown-menu').click_link("Merge requests I've created")
+
+          expect(page).to have_selector('.merge-requests-holder')
+          expect_tokens([author_token(user.name)])
+          expect_filtered_search_input_empty
+        end
+      end
+    end
+
+    context 'when entering text into the search field', :js do
+      before do
+        page.within('.search-input-wrap') do
+          fill_in('search', with: project.name[0..3])
+        end
+      end
+
+      it 'does not display the category search dropdown' do
+        expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+      end
     end
   end
 end
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index aa883c964d261e3b63c7eb745a90c476a25b18ef..66afe163447ee39c4d14060870a8bef12e0cc0d7 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -31,7 +31,7 @@ describe 'User uses search filters', :js do
 
         wait_for_requests
 
-        expect(page).to have_link(group_project.name_with_namespace)
+        expect(page).to have_link(group_project.full_name)
       end
     end
   end
@@ -43,10 +43,10 @@ describe 'User uses search filters', :js do
 
         wait_for_requests
 
-        click_link(project.name_with_namespace)
+        click_link(project.full_name)
       end
 
-      expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
+      expect(find('.js-search-project-dropdown')).to have_content(project.full_name)
     end
   end
 end
diff --git a/spec/features/snippets/embedded_snippet_spec.rb b/spec/features/snippets/embedded_snippet_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab661f6fc699a9f8a6628ae5efed80f5ac95b890
--- /dev/null
+++ b/spec/features/snippets/embedded_snippet_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe 'Embedded Snippets' do
+  let(:snippet) { create(:personal_snippet, :public, file_name: 'random_dir.rb', content: content) }
+  let(:content) { "require 'fileutils'\nFileUtils.mkdir_p 'some/random_dir'\n" }
+
+  it 'loads snippet', :js do
+    script_url = "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}/#{snippet_path(snippet, format: 'js')}"
+    embed_body = "<html><body><script src=\"#{script_url}\"></script></body></html>"
+
+    rack_app = proc do
+      ['200', { 'Content-Type' => 'text/html' }, [embed_body]]
+    end
+
+    server = Capybara::Server.new(rack_app)
+    server.boot
+
+    visit("http://#{server.host}:#{server.port}/embedded_snippet.html")
+
+    expect(page).to have_content("random_dir.rb")
+    expect(page).to have_content("require 'fileutils'")
+    expect(page).to have_link('Open raw')
+    expect(page).to have_link('Download')
+  end
+end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index 975c157bcf549cac77ed7dd26eac0ecaf48ce3f4..e069c2fddd1b23efc56bb6e6b9e35e8792137628 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe 'User can display performance bar', :js do
   shared_examples 'performance bar cannot be displayed' do
     it 'does not show the performance bar by default' do
-      expect(page).not_to have_css('#peek')
+      expect(page).not_to have_css('#js-peek')
     end
 
     context 'when user press `pb`' do
@@ -12,14 +12,14 @@ describe 'User can display performance bar', :js do
       end
 
       it 'does not show the performance bar by default' do
-        expect(page).not_to have_css('#peek')
+        expect(page).not_to have_css('#js-peek')
       end
     end
   end
 
   shared_examples 'performance bar can be displayed' do
     it 'does not show the performance bar by default' do
-      expect(page).not_to have_css('#peek')
+      expect(page).not_to have_css('#js-peek')
     end
 
     context 'when user press `pb`' do
@@ -28,7 +28,7 @@ describe 'User can display performance bar', :js do
       end
 
       it 'shows the performance bar' do
-        expect(page).to have_css('#peek')
+        expect(page).to have_css('#js-peek')
       end
     end
   end
@@ -41,7 +41,7 @@ describe 'User can display performance bar', :js do
     it 'shows the performance bar by default' do
       refresh # Because we're stubbing Rails.env after the 1st visit to root_path
 
-      expect(page).to have_css('#peek')
+      expect(page).to have_css('#js-peek')
     end
   end
 
diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..69ebdddaeecda38e07d6b035875f0ddf64dc88c9
--- /dev/null
+++ b/spec/features/user_sorts_things_spec.rb
@@ -0,0 +1,57 @@
+require "spec_helper"
+
+# The main goal of this spec is not to check whether the sorting UI works, but
+# to check if the sorting option set by user is being kept persisted while going through pages.
+# The `it`s are named here by convention `starting point -> some pages -> final point`.
+# All those specs are moved out to this spec intentionally to keep them all in one place.
+describe "User sorts things" do
+  include Spec::Support::Helpers::Features::SortingHelpers
+  include Helpers::DashboardHelper
+
+  set(:project) { create(:project_empty_repo, :public) }
+  set(:current_user) { create(:user) } # Using `current_user` instead of just `user` because of the hardoced call in `assigned_mrs_dashboard_path` which is used below.
+  set(:issue) { create(:issue, project: project, author: current_user) }
+  set(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: current_user) }
+
+  before do
+    project.add_developer(current_user)
+    sign_in(current_user)
+  end
+
+  it "issues -> project home page -> issues" do
+    sort_option = "Last updated"
+
+    visit(project_issues_path(project))
+
+    sort_by(sort_option)
+
+    visit(project_path(project))
+    visit(project_issues_path(project))
+
+    expect(find(".issues-filters")).to have_content(sort_option)
+  end
+
+  it "issues -> merge requests" do
+    sort_option = "Last updated"
+
+    visit(project_issues_path(project))
+
+    sort_by(sort_option)
+
+    visit(project_merge_requests_path(project))
+
+    expect(find(".issues-filters")).to have_content(sort_option)
+  end
+
+  it "merge requests -> dashboard merge requests" do
+    sort_option = "Last updated"
+
+    visit(project_merge_requests_path(project))
+
+    sort_by(sort_option)
+
+    visit(assigned_mrs_dashboard_path)
+
+    expect(find(".issues-filters")).to have_content(sort_option)
+  end
+end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 6ef235cf870004ffa98c1a2b71dcbe57b66e909b..9e10bfb2adc927333136d1793bb6633be4e85300 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -145,6 +145,18 @@ feature 'Login' do
             expect { enter_code(codes.sample) }
               .to change { user.reload.otp_backup_codes.size }.by(-1)
           end
+
+          it 'invalidates backup codes twice in a row' do
+            random_code = codes.delete(codes.sample)
+            expect { enter_code(random_code) }
+              .to change { user.reload.otp_backup_codes.size }.by(-1)
+
+            gitlab_sign_out
+            gitlab_sign_in(user)
+
+            expect { enter_code(codes.sample) }
+              .to change { user.reload.otp_backup_codes.size }.by(-1)
+          end
         end
 
         context 'with invalid code' do
@@ -380,7 +392,7 @@ feature 'Login' do
     end
 
     def ensure_one_active_tab
-      expect(page).to have_selector('.nav-tabs > li.active', count: 1)
+      expect(page).to have_selector('ul.new-session-tabs > li.active', count: 1)
     end
 
     def ensure_one_active_pane
diff --git a/spec/finders/clusters_finder_spec.rb b/spec/finders/clusters_finder_spec.rb
index c10efac2432b0ec60e4d470765eb2065a202c948..da529e0670ff0dfc91c5e743380e4fd46193a0a4 100644
--- a/spec/finders/clusters_finder_spec.rb
+++ b/spec/finders/clusters_finder_spec.rb
@@ -6,7 +6,7 @@ describe ClustersFinder do
 
   describe '#execute' do
     let(:enabled_cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
-    let(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, projects: [project]) }
+    let(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, :production_environment, projects: [project]) }
 
     subject { described_class.new(project, user, scope).execute }
 
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index 375bcc9087e8bfeb028c0f5b4e664c00eda9bad8..796d40cb625fef795ca982ebbafc99109a1e9044 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -35,15 +35,6 @@ describe GroupDescendantsFinder do
       expect(finder.execute).to contain_exactly(project)
     end
 
-    it 'does not include projects shared with the group' do
-      project = create(:project, namespace: group)
-      other_project = create(:project)
-      other_project.project_group_links.create(group: group,
-                                               group_access: ProjectGroupLink::MASTER)
-
-      expect(finder.execute).to contain_exactly(project)
-    end
-
     context 'when archived is `true`' do
       let(:params) { { archived: 'true' } }
 
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index abb7631d7d7c4dd4fc91e39c68155dc4d824b258..45439640ea3b2f3bbf015e65316bd9b0d67892bf 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -10,9 +10,9 @@ describe IssuesFinder do
   set(:project3) { create(:project, group: subgroup) }
   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', 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', created_at: 1.week.from_now) }
+  set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) }
+  set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+  set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) }
   set(:issue4) { create(:issue, project: project3) }
   set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
   set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
@@ -275,12 +275,46 @@ describe IssuesFinder do
         end
 
         context 'through created_before' do
-          let(:params) { { created_before: issue1.created_at + 1.second } }
+          let(:params) { { created_before: issue1.created_at } }
 
           it 'returns issues created on or before the given date' do
             expect(issues).to contain_exactly(issue1)
           end
         end
+
+        context 'through created_after and created_before' do
+          let(:params) { { created_after: issue2.created_at, created_before: issue3.created_at } }
+
+          it 'returns issues created between the given dates' do
+            expect(issues).to contain_exactly(issue2, issue3)
+          end
+        end
+      end
+
+      context 'filtering by updated_at' do
+        context 'through updated_after' do
+          let(:params) { { updated_after: issue3.updated_at } }
+
+          it 'returns issues updated on or after the given date' do
+            expect(issues).to contain_exactly(issue3)
+          end
+        end
+
+        context 'through updated_before' do
+          let(:params) { { updated_before: issue1.updated_at } }
+
+          it 'returns issues updated on or before the given date' do
+            expect(issues).to contain_exactly(issue1)
+          end
+        end
+
+        context 'through updated_after and updated_before' do
+          let(:params) { { updated_after: issue2.updated_at, updated_before: issue3.updated_at } }
+
+          it 'returns issues updated between the given dates' do
+            expect(issues).to contain_exactly(issue2, issue3)
+          end
+        end
       end
 
       context 'filtering by reaction name' do
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index d434c5011107d825d0cc2b388e2e1e965b8898ba..899d0d22819dc98a62cec1f58ffb94670ee372fa 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -71,6 +71,24 @@ describe LabelsFinder do
         end
       end
 
+      context 'when group has no projects' do
+        let(:empty_group) { create(:group) }
+        let!(:empty_group_label_1) { create(:group_label, group: empty_group, title: 'Label 1 (empty group)') }
+        let!(:empty_group_label_2) { create(:group_label, group: empty_group, title: 'Label 2 (empty group)') }
+
+        before do
+          empty_group.add_developer(user)
+        end
+
+        context 'when only group labels is false' do
+          it 'returns group labels' do
+            finder = described_class.new(user, group_id: empty_group.id)
+
+            expect(finder.execute).to eq [empty_group_label_1, empty_group_label_2]
+          end
+        end
+      end
+
       context 'when including labels from group ancestors', :nested_groups do
         it 'returns labels from group and its ancestors' do
           private_group_1.add_developer(user)
@@ -110,7 +128,21 @@ describe LabelsFinder do
       end
     end
 
-    context 'filtering by project_id' do
+    context 'filtering by project_id', :nested_groups do
+      context 'when include_ancestor_groups is true' do
+        let!(:sub_project) { create(:project, namespace: private_subgroup_1 ) }
+        let!(:project_label) { create(:label, project: sub_project, title: 'Label 5') }
+        let(:finder) { described_class.new(user, project_id: sub_project.id, include_ancestor_groups: true) }
+
+        before do
+          private_group_1.add_developer(user)
+        end
+
+        it 'returns all ancestor labels' do
+          expect(finder.execute).to match_array([private_subgroup_label_1, private_group_label_1, project_label])
+        end
+      end
+
       it 'returns labels available for the project' do
         finder = described_class.new(user, project_id: project_1.id)
 
diff --git a/spec/finders/merge_request_target_project_finder_spec.rb b/spec/finders/merge_request_target_project_finder_spec.rb
index c81bfd7932cffbf183e633a1dd68a33ad630803c..f302cf80ce8b0171d329f1899652bba7d909bdb8 100644
--- a/spec/finders/merge_request_target_project_finder_spec.rb
+++ b/spec/finders/merge_request_target_project_finder_spec.rb
@@ -19,6 +19,12 @@ describe MergeRequestTargetProjectFinder do
 
       expect(finder.execute).to contain_exactly(forked_project)
     end
+
+    it 'does not contain archived projects' do
+      base_project.update!(archived: true)
+
+      expect(finder.execute).to contain_exactly(other_fork, forked_project)
+    end
   end
 
   context 'public projects' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 7917a00fc50e7f13822083b221296a4d6170aa9a..c8a43ddf410876ceedbf45341a9c21c6bb470df2 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -109,7 +109,7 @@ describe MergeRequestsFinder do
       end
     end
 
-    context 'with created_after and created_before params' do
+    context 'filtering by created_at/updated_at' do
       let(:new_project) { create(:project, forked_from_project: project1) }
 
       let!(:new_merge_request) do
@@ -117,15 +117,18 @@ describe MergeRequestsFinder do
                :simple,
                author: user,
                created_at: 1.week.from_now,
+               updated_at: 1.week.from_now,
                source_project: new_project,
-               target_project: project1)
+               target_project: new_project)
       end
 
       let!(:old_merge_request) do
         create(:merge_request,
                :simple,
                author: user,
+               source_branch: 'feature_1',
                created_at: 1.week.ago,
+               updated_at: 1.week.ago,
                source_project: new_project,
                target_project: new_project)
       end
@@ -135,7 +138,7 @@ describe MergeRequestsFinder do
       end
 
       it 'filters by created_after' do
-        params = { project_id: project1.id, created_after: new_merge_request.created_at }
+        params = { project_id: new_project.id, created_after: new_merge_request.created_at }
 
         merge_requests = described_class.new(user, params).execute
 
@@ -143,12 +146,52 @@ describe MergeRequestsFinder do
       end
 
       it 'filters by created_before' do
-        params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second }
+        params = { project_id: new_project.id, created_before: old_merge_request.created_at }
 
         merge_requests = described_class.new(user, params).execute
 
         expect(merge_requests).to contain_exactly(old_merge_request)
       end
+
+      it 'filters by created_after and created_before' do
+        params = {
+          project_id: new_project.id,
+          created_after: old_merge_request.created_at,
+          created_before: new_merge_request.created_at
+        }
+
+        merge_requests = described_class.new(user, params).execute
+
+        expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+      end
+
+      it 'filters by updated_after' do
+        params = { project_id: new_project.id, updated_after: new_merge_request.updated_at }
+
+        merge_requests = described_class.new(user, params).execute
+
+        expect(merge_requests).to contain_exactly(new_merge_request)
+      end
+
+      it 'filters by updated_before' do
+        params = { project_id: new_project.id, updated_before: old_merge_request.updated_at }
+
+        merge_requests = described_class.new(user, params).execute
+
+        expect(merge_requests).to contain_exactly(old_merge_request)
+      end
+
+      it 'filters by updated_after and updated_before' do
+        params = {
+          project_id: new_project.id,
+          updated_after: old_merge_request.updated_at,
+          updated_before: new_merge_request.updated_at
+        }
+
+        merge_requests = described_class.new(user, params).execute
+
+        expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+      end
     end
   end
 
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 7b43494eea2fff9d631f81f64648664f7f731649..f1ae2c7ab6527ab4cedfb3c6908d75679b7d6687 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -75,6 +75,18 @@ describe NotesFinder do
       end
     end
 
+    context 'for target type' do
+      let(:project) { create(:project, :repository) }
+      let!(:note1) { create :note_on_issue, project: project }
+      let!(:note2) { create :note_on_commit, project: project }
+
+      it 'finds only notes for the selected type' do
+        notes = described_class.new(project, user, target_type: 'issue').execute
+
+        expect(notes).to eq([note1])
+      end
+    end
+
     context 'for target' do
       let(:project) { create(:project, :repository) }
       let(:note1) { create :note_on_commit, project: project }
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 90eb0fe21e4f3c8103c91c75ee02b819d8cd922a..9747b9402a7fb5e73661f16abcffa58bdfe3fae4 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -2,12 +2,13 @@ require 'spec_helper'
 
 describe TodosFinder do
   describe '#execute' do
-    let(:user)          { create(:user) }
-    let(:project)       { create(:project) }
-    let(:finder)        { described_class }
+    let(:user) { create(:user) }
+    let(:group) { create(:group) }
+    let(:project) { create(:project, namespace: group) }
+    let(:finder) { described_class }
 
     before do
-      project.add_developer(user)
+      group.add_developer(user)
     end
 
     describe '#sort' do
@@ -34,17 +35,20 @@ describe TodosFinder do
       end
 
       it "sorts by priority" do
+        project_2       = create(:project)
+
         label_1         = create(:label, title: 'label_1', project: project, priority: 1)
         label_2         = create(:label, title: 'label_2', project: project, priority: 2)
         label_3         = create(:label, title: 'label_3', project: project, priority: 3)
+        label_1_2       = create(:label, title: 'label_1', project: project_2, priority: 1)
 
         issue_1         = create(:issue, title: 'issue_1', project: project)
         issue_2         = create(:issue, title: 'issue_2', project: project)
         issue_3         = create(:issue, title: 'issue_3', project: project)
         issue_4         = create(:issue, title: 'issue_4', project: project)
-        merge_request_1 = create(:merge_request, source_project: project)
+        merge_request_1 = create(:merge_request, source_project: project_2)
 
-        merge_request_1.labels << label_1
+        merge_request_1.labels << label_1_2
 
         # Covers the case where Todo has more than one label
         issue_3.labels         << label_1
@@ -57,15 +61,14 @@ describe TodosFinder do
         todo_2 = create(:todo, user: user, project: project, target: issue_2)
         todo_3 = create(:todo, user: user, project: project, target: issue_3, created_at: 2.hours.ago)
         todo_4 = create(:todo, user: user, project: project, target: issue_1)
-        todo_5 = create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago)
+        todo_5 = create(:todo, user: user, project: project_2, target: merge_request_1, created_at: 1.hour.ago)
+
+        project_2.add_developer(user)
 
         todos = finder.new(user, { sort: 'priority' }).execute
 
-        expect(todos.first).to eq(todo_3)
-        expect(todos.second).to eq(todo_5)
-        expect(todos.third).to eq(todo_4)
-        expect(todos.fourth).to eq(todo_2)
-        expect(todos.fifth).to eq(todo_1)
+        puts todos.to_sql
+        expect(todos).to eq([todo_3, todo_5, todo_4, todo_2, todo_1])
       end
     end
   end
diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json
index f1199468d53d0e841fcbd64af374a7be151a23dc..46031961ccafe8bc3f937093673269dd6714e9a7 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_basic.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json
@@ -12,7 +12,8 @@
     "rebase_in_progress": { "type": "boolean" },
     "assignee_id": { "type": ["integer", "null"] },
     "subscribed": { "type": ["boolean", "null"] },
-    "participants": { "type": "array" }
+    "participants": { "type": "array" },
+    "allow_maintainer_to_push": { "type": "boolean"}
   },
   "additionalProperties": false
 }
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index cfbeec58a45dd0f52ea17953ab3a25f86a58fb40..a622bf88b1399ce3b2ec7eb8e2f11aadea2fb8e2 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -30,6 +30,7 @@
     "source_project_id": { "type": "integer" },
     "target_branch": { "type": "string" },
     "target_project_id": { "type": "integer" },
+    "allow_maintainer_to_push": { "type": "boolean"},
     "metrics": {
       "oneOf": [
         { "type": "null" },
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index b579e32c9aa1d0ccb94f29e63ebfc72899d5ba7b..8833825e3fb774bd3ab744bd0fd4008431a0a941 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -15,6 +15,8 @@
     "relative_position": { "type": "integer" },
     "issue_sidebar_endpoint": { "type": "string" },
     "toggle_subscription_endpoint": { "type": "string" },
+    "reference_path": { "type": "string" },
+    "real_path": { "type": "string" },
     "project": {
       "id": { "type": "integer" },
       "path": { "type": "string" }
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
index e86176e5316b3d43a2da4c862a595a248a1d93f5..0dc2eabec5d49306d0cd0fd85f433f3e26026514 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -80,7 +80,8 @@
         "total_time_spent": { "type": "integer" },
         "human_time_estimate": { "type": ["string", "null"] },
         "human_total_time_spent": { "type": ["string", "null"] }
-      }
+      },
+      "allow_maintainer_to_push": { "type": ["boolean", "null"] }
     },
     "required": [
       "id", "iid", "project_id", "title", "description",
diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json
index 6525f7c2c800463e513aff57b08636a42350d8e7..4c4ca3b582f84d13127f8270a73693e8e7293252 100644
--- a/spec/fixtures/api/schemas/public_api/v4/notes.json
+++ b/spec/fixtures/api/schemas/public_api/v4/notes.json
@@ -4,6 +4,7 @@
     "type": "object",
     "properties" : {
       "id": { "type": "integer" },
+      "type": { "type": ["string", "null"] },
       "body": { "type": "string" },
       "attachment": { "type": ["string", "null"] },
       "author": {
diff --git a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
new file mode 100644
index 0000000000000000000000000000000000000000..81c8815caf6f10d7b27ac2829e2b67f5196edb9b
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
@@ -0,0 +1,24 @@
+{
+  "type": "object",
+  "allOf": [
+    {
+      "$ref": "identity.json"
+    },
+    {
+      "required": [
+        "export_status"
+      ],
+      "properties": {
+        "export_status": {
+          "type": "string",
+          "enum": [
+            "none",
+            "started",
+            "finished",
+            "after_export_action"
+          ]
+        }
+      }
+    }
+  ]
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/project/identity.json b/spec/fixtures/api/schemas/public_api/v4/project/identity.json
new file mode 100644
index 0000000000000000000000000000000000000000..e35ab023d4471bfa25819366a3db7768d9343fc7
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/project/identity.json
@@ -0,0 +1,21 @@
+{
+  "type": "object",
+  "required": [
+    "id",
+    "description",
+    "name",
+    "name_with_namespace",
+    "path",
+    "path_with_namespace",
+    "created_at"
+  ],
+  "properties": {
+    "id": { "type": "integer" },
+    "description": { "type": ["string", "null"] },
+    "name": { "type": "string" },
+    "name_with_namespace": { "type": "string" },
+    "path": { "type": "string" },
+    "path_with_namespace": { "type": "string" },
+    "created_at": { "type": "date" }
+  }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/tag.json b/spec/fixtures/api/schemas/public_api/v4/tag.json
index 52cfe86aeebd6833b0c715173a17299288507e0d..10d4edb7ffb6dd2044149e194151b2e331818fc8 100644
--- a/spec/fixtures/api/schemas/public_api/v4/tag.json
+++ b/spec/fixtures/api/schemas/public_api/v4/tag.json
@@ -10,6 +10,7 @@
     "name": { "type": "string" },
     "message": { "type": ["string", "null"] },
     "commit": { "$ref": "commit/basic.json" },
+    "target": { "type": "string" },
     "release": {
       "oneOf": [
         { "type": "null" },
diff --git a/spec/fixtures/big-image.png b/spec/fixtures/big-image.png
new file mode 100644
index 0000000000000000000000000000000000000000..a333363ac36377ac72fc13bf88a732a6ea482355
Binary files /dev/null and b/spec/fixtures/big-image.png differ
diff --git a/spec/fixtures/emails/update_commands_only_reply.eml b/spec/fixtures/emails/update_commands_only_reply.eml
new file mode 100644
index 0000000000000000000000000000000000000000..bb0d2b0e03ae0ff9d643c0dc4cac935958d2f16a
--- /dev/null
+++ b/spec/fixtures/emails/update_commands_only_reply.eml
@@ -0,0 +1,38 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+In-Reply-To: <issue_1@localhost>
+References: <issue_1@localhost> <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost>
+Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+/close
+
+On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
+>
+>
+>
+> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
+>
+> ---
+> hey guys everyone knows adventure time sucks!
+>
+> ---
+> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
+>
+> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
+>
diff --git a/spec/fixtures/exported-project.gz b/spec/fixtures/exported-project.gz
new file mode 100644
index 0000000000000000000000000000000000000000..352384f16c83737e4c4bdfb55b8d413e0aeb3208
Binary files /dev/null and b/spec/fixtures/exported-project.gz differ
diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace
index 55fcb9d275686556fa3360f244c9bcb3e0aa8371..c65cf05d5ca74e9fad5bc1a4a08ad40513f35015 100644
--- a/spec/fixtures/trace/sample_trace
+++ b/spec/fixtures/trace/sample_trace
@@ -1,24 +1,24 @@
-Running with gitlab-runner 10.4.0 (857480b6)
-  on docker-auto-scale-com (9a6801bd)
-Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ...
-Starting service postgres:9.2 ...
-Pulling docker image postgres:9.2 ...
-Using docker image postgres:9.2 ID=sha256:18cdbca56093c841d28e629eb8acd4224afe0aa4c57c839351fc181888b8a470 for postgres service...
+Running with gitlab-runner 10.6.0 (a3543a27)
+  on docker-auto-scale-com 30d62d59
+Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ...
+Starting service mysql:latest ...
+Pulling docker image mysql:latest ...
+Using docker image sha256:5195076672a7e30525705a18f7d352c920bbd07a5ae72b30e374081fe660a011 for mysql:latest ...
 Starting service redis:alpine ...
 Pulling docker image redis:alpine ...
-Using docker image redis:alpine ID=sha256:cb1ec54b370d4a91dff57d00f91fd880dc710160a58440adaa133e0f84ae999d for redis service...
+Using docker image sha256:98bd7cfc43b8ef0ff130465e3d5427c0771002c2f35a6a9b62cb2d04602bed0a for redis:alpine ...
 Waiting for services to be up and running...
-Using docker image sha256:3006a02a5a6f0a116358a13bbc46ee46fb2471175efd5b7f9b1c22345ec2a8e9 for predefined container...
-Pulling docker image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ...
-Using docker image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ID=sha256:1f59be408f12738509ffe4177d65e9de6391f32461de83d9d45f58517b30af99 for build container...
-section_start:1517486886:prepare_script
-Running on runner-9a6801bd-project-13083-concurrent-0 via runner-9a6801bd-gsrm-1517484168-a8449153...
-section_end:1517486887:prepare_script
-section_start:1517486887:get_sources
-Fetching changes for 42624-gitaly-bundle-isolation-not-working-in-ci with git depth set to 20...
+Pulling docker image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ...
+Using docker image sha256:1b06077bb03d9d42d801b53f45701bb6a7e862ca02e1e75f30ca7fcf1270eb02 for dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ...
+section_start:1522927103:prepare_script
+Running on runner-30d62d59-project-13083-concurrent-0 via runner-30d62d59-prm-1522922015-ddc29478...
+section_end:1522927104:prepare_script
+section_start:1522927104:get_sources
+Fetching changes for master with git depth set to 20...
 Removing .gitlab_shell_secret
 Removing .gitlab_workhorse_secret
 Removing .yarn-cache/
+Removing builds/2018_04/
 Removing config/database.yml
 Removing config/gitlab.yml
 Removing config/redis.cache.yml
@@ -26,1160 +26,3420 @@ Removing config/redis.queues.yml
 Removing config/redis.shared_state.yml
 Removing config/resque.yml
 Removing config/secrets.yml
-Removing coverage/
-Removing knapsack/
 Removing log/api_json.log
 Removing log/application.log
 Removing log/gitaly-test.log
-Removing log/githost.log
 Removing log/grpc.log
 Removing log/test_json.log
-Removing node_modules/
-Removing public/assets/
-Removing rspec_flaky/
-Removing shared/tmp/
 Removing tmp/tests/
 Removing vendor/ruby/
-HEAD is now at 4cea24f Converted todos.js to axios
+HEAD is now at b7cbff3d Add `direct_upload` setting for artifacts
 From https://gitlab.com/gitlab-org/gitlab-ce
- * [new branch]      42624-gitaly-bundle-isolation-not-working-in-ci -> origin/42624-gitaly-bundle-isolation-not-working-in-ci
-Checking out f42a5e24 as 42624-gitaly-bundle-isolation-not-working-in-ci...
+   2dbcb9cb..641bb13b  master     -> origin/master
+Checking out 21488c74 as master...
 Skipping Git submodules setup
-section_end:1517486896:get_sources
-section_start:1517486896:restore_cache
+section_end:1522927113:get_sources
+section_start:1522927113:restore_cache
 Checking cache for ruby-2.3.6-with-yarn...
 Downloading cache.zip from http://runners-cache-5-internal.gitlab.com:444/runner/project/13083/ruby-2.3.6-with-yarn 
 Successfully extracted cache
-section_end:1517486919:restore_cache
-section_start:1517486919:download_artifacts
-Downloading artifacts for retrieve-tests-metadata (50551658)...
-Downloading artifacts from coordinator... ok        id=50551658 responseStatus=200 OK token=HhF7y_1X
-Downloading artifacts for compile-assets (50551659)...
-Downloading artifacts from coordinator... ok        id=50551659 responseStatus=200 OK token=wTz6JrCP
-Downloading artifacts for setup-test-env (50551660)...
-Downloading artifacts from coordinator... ok        id=50551660 responseStatus=200 OK token=DTGgeVF5
+section_end:1522927128:restore_cache
+section_start:1522927128:download_artifacts
+Downloading artifacts for retrieve-tests-metadata (61303215)...
+Downloading artifacts from coordinator... ok        id=61303215 responseStatus=200 OK token=AdWPNg2R
+Downloading artifacts for compile-assets (61303216)...
+Downloading artifacts from coordinator... ok        id=61303216 responseStatus=200 OK token=iy2yYbq8
+Downloading artifacts for setup-test-env (61303217)...
+Downloading artifacts from coordinator... ok        id=61303217 responseStatus=200 OK token=ur1g79-4
 WARNING: tmp/tests/gitlab-shell/.gitlab_shell_secret: chmod tmp/tests/gitlab-shell/.gitlab_shell_secret: no such file or directory (suppressing repeats) 
-section_end:1517486934:download_artifacts
-section_start:1517486934:build_script
+section_end:1522927141:download_artifacts
+section_start:1522927141:build_script
 $ bundle --version
 Bundler version 1.16.1
+$ date
+Thu Apr  5 11:19:01 UTC 2018
 $ source scripts/utils.sh
+$ date
+Thu Apr  5 11:19:01 UTC 2018
 $ source scripts/prepare_build.sh
 The Gemfile's dependencies are satisfied
-Successfully installed knapsack-1.15.0
+Successfully installed knapsack-1.16.0
 1 gem installed
-NOTICE:  database "gitlabhq_test" does not exist, skipping
-DROP DATABASE
-CREATE DATABASE
-CREATE ROLE
-GRANT
 -- enable_extension("plpgsql")
-   -> 0.0156s
+   -> 0.0010s
 -- enable_extension("pg_trgm")
-   -> 0.0156s
+   -> 0.0000s
 -- create_table("abuse_reports", {:force=>:cascade})
-   -> 0.0119s
+   -> 0.0401s
 -- create_table("appearances", {:force=>:cascade})
-   -> 0.0065s
+   -> 0.1035s
 -- create_table("application_settings", {:force=>:cascade})
-   -> 0.0382s
+   -> 0.0871s
 -- create_table("audit_events", {:force=>:cascade})
-   -> 0.0056s
+   -> 0.0539s
 -- add_index("audit_events", ["entity_id", "entity_type"], {:name=>"index_audit_events_on_entity_id_and_entity_type", :using=>:btree})
-   -> 0.0040s
+   -> 0.0647s
 -- create_table("award_emoji", {:force=>:cascade})
-   -> 0.0058s
+   -> 0.0134s
 -- add_index("award_emoji", ["awardable_type", "awardable_id"], {:name=>"index_award_emoji_on_awardable_type_and_awardable_id", :using=>:btree})
-   -> 0.0068s
+   -> 0.0074s
 -- add_index("award_emoji", ["user_id", "name"], {:name=>"index_award_emoji_on_user_id_and_name", :using=>:btree})
-   -> 0.0043s
+   -> 0.0072s
+-- create_table("badges", {:force=>:cascade})
+   -> 0.0122s
+-- add_index("badges", ["group_id"], {:name=>"index_badges_on_group_id", :using=>:btree})
+   -> 0.0086s
+-- add_index("badges", ["project_id"], {:name=>"index_badges_on_project_id", :using=>:btree})
+   -> 0.0069s
 -- create_table("boards", {:force=>:cascade})
-   -> 0.0049s
+   -> 0.0075s
+-- add_index("boards", ["group_id"], {:name=>"index_boards_on_group_id", :using=>:btree})
+   -> 0.0050s
 -- add_index("boards", ["project_id"], {:name=>"index_boards_on_project_id", :using=>:btree})
-   -> 0.0056s
+   -> 0.0051s
 -- create_table("broadcast_messages", {:force=>:cascade})
-   -> 0.0056s
+   -> 0.0082s
 -- add_index("broadcast_messages", ["starts_at", "ends_at", "id"], {:name=>"index_broadcast_messages_on_starts_at_and_ends_at_and_id", :using=>:btree})
-   -> 0.0041s
+   -> 0.0063s
 -- create_table("chat_names", {:force=>:cascade})
-   -> 0.0056s
+   -> 0.0084s
 -- add_index("chat_names", ["service_id", "team_id", "chat_id"], {:name=>"index_chat_names_on_service_id_and_team_id_and_chat_id", :unique=>true, :using=>:btree})
-   -> 0.0039s
+   -> 0.0088s
 -- add_index("chat_names", ["user_id", "service_id"], {:name=>"index_chat_names_on_user_id_and_service_id", :unique=>true, :using=>:btree})
-   -> 0.0036s
+   -> 0.0077s
 -- create_table("chat_teams", {:force=>:cascade})
-   -> 0.0068s
+   -> 0.0120s
 -- add_index("chat_teams", ["namespace_id"], {:name=>"index_chat_teams_on_namespace_id", :unique=>true, :using=>:btree})
-   -> 0.0098s
+   -> 0.0135s
 -- create_table("ci_build_trace_section_names", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0125s
 -- add_index("ci_build_trace_section_names", ["project_id", "name"], {:name=>"index_ci_build_trace_section_names_on_project_id_and_name", :unique=>true, :using=>:btree})
-   -> 0.0035s
+   -> 0.0087s
 -- create_table("ci_build_trace_sections", {:force=>:cascade})
-   -> 0.0040s
+   -> 0.0094s
 -- add_index("ci_build_trace_sections", ["build_id", "section_name_id"], {:name=>"index_ci_build_trace_sections_on_build_id_and_section_name_id", :unique=>true, :using=>:btree})
-   -> 0.0035s
+   -> 0.0916s
 -- add_index("ci_build_trace_sections", ["project_id"], {:name=>"index_ci_build_trace_sections_on_project_id", :using=>:btree})
-   -> 0.0033s
+   -> 0.0089s
+-- add_index("ci_build_trace_sections", ["section_name_id"], {:name=>"index_ci_build_trace_sections_on_section_name_id", :using=>:btree})
+   -> 0.0132s
 -- create_table("ci_builds", {:force=>:cascade})
-   -> 0.0062s
+   -> 0.0140s
+-- add_index("ci_builds", ["artifacts_expire_at"], {:name=>"index_ci_builds_on_artifacts_expire_at", :where=>"(artifacts_file <> ''::text)", :using=>:btree})
+   -> 0.0325s
 -- add_index("ci_builds", ["auto_canceled_by_id"], {:name=>"index_ci_builds_on_auto_canceled_by_id", :using=>:btree})
-   -> 0.0035s
+   -> 0.0081s
 -- add_index("ci_builds", ["commit_id", "stage_idx", "created_at"], {:name=>"index_ci_builds_on_commit_id_and_stage_idx_and_created_at", :using=>:btree})
-   -> 0.0032s
+   -> 0.0114s
 -- add_index("ci_builds", ["commit_id", "status", "type"], {:name=>"index_ci_builds_on_commit_id_and_status_and_type", :using=>:btree})
-   -> 0.0032s
+   -> 0.0119s
 -- add_index("ci_builds", ["commit_id", "type", "name", "ref"], {:name=>"index_ci_builds_on_commit_id_and_type_and_name_and_ref", :using=>:btree})
-   -> 0.0035s
+   -> 0.0116s
 -- add_index("ci_builds", ["commit_id", "type", "ref"], {:name=>"index_ci_builds_on_commit_id_and_type_and_ref", :using=>:btree})
-   -> 0.0042s
+   -> 0.0144s
 -- add_index("ci_builds", ["project_id", "id"], {:name=>"index_ci_builds_on_project_id_and_id", :using=>:btree})
-   -> 0.0031s
+   -> 0.0136s
 -- add_index("ci_builds", ["protected"], {:name=>"index_ci_builds_on_protected", :using=>:btree})
-   -> 0.0031s
+   -> 0.0113s
 -- add_index("ci_builds", ["runner_id"], {:name=>"index_ci_builds_on_runner_id", :using=>:btree})
-   -> 0.0033s
+   -> 0.0082s
 -- add_index("ci_builds", ["stage_id"], {:name=>"index_ci_builds_on_stage_id", :using=>:btree})
-   -> 0.0035s
+   -> 0.0086s
 -- add_index("ci_builds", ["status", "type", "runner_id"], {:name=>"index_ci_builds_on_status_and_type_and_runner_id", :using=>:btree})
-   -> 0.0031s
+   -> 0.0091s
 -- add_index("ci_builds", ["status"], {:name=>"index_ci_builds_on_status", :using=>:btree})
-   -> 0.0032s
+   -> 0.0081s
 -- add_index("ci_builds", ["token"], {:name=>"index_ci_builds_on_token", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0103s
 -- add_index("ci_builds", ["updated_at"], {:name=>"index_ci_builds_on_updated_at", :using=>:btree})
-   -> 0.0047s
+   -> 0.0149s
 -- add_index("ci_builds", ["user_id"], {:name=>"index_ci_builds_on_user_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0156s
+-- create_table("ci_builds_metadata", {:force=>:cascade})
+   -> 0.0134s
+-- add_index("ci_builds_metadata", ["build_id"], {:name=>"index_ci_builds_metadata_on_build_id", :unique=>true, :using=>:btree})
+   -> 0.0067s
+-- add_index("ci_builds_metadata", ["project_id"], {:name=>"index_ci_builds_metadata_on_project_id", :using=>:btree})
+   -> 0.0061s
 -- create_table("ci_group_variables", {:force=>:cascade})
-   -> 0.0055s
+   -> 0.0088s
 -- add_index("ci_group_variables", ["group_id", "key"], {:name=>"index_ci_group_variables_on_group_id_and_key", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0073s
 -- create_table("ci_job_artifacts", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0089s
+-- add_index("ci_job_artifacts", ["expire_at", "job_id"], {:name=>"index_ci_job_artifacts_on_expire_at_and_job_id", :using=>:btree})
+   -> 0.0061s
 -- add_index("ci_job_artifacts", ["job_id", "file_type"], {:name=>"index_ci_job_artifacts_on_job_id_and_file_type", :unique=>true, :using=>:btree})
-   -> 0.0027s
+   -> 0.0077s
 -- add_index("ci_job_artifacts", ["project_id"], {:name=>"index_ci_job_artifacts_on_project_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0071s
 -- create_table("ci_pipeline_schedule_variables", {:force=>:cascade})
-   -> 0.0044s
+   -> 0.0512s
 -- add_index("ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], {:name=>"index_ci_pipeline_schedule_variables_on_schedule_id_and_key", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0144s
 -- create_table("ci_pipeline_schedules", {:force=>:cascade})
-   -> 0.0047s
+   -> 0.0603s
 -- add_index("ci_pipeline_schedules", ["next_run_at", "active"], {:name=>"index_ci_pipeline_schedules_on_next_run_at_and_active", :using=>:btree})
-   -> 0.0029s
+   -> 0.0247s
 -- add_index("ci_pipeline_schedules", ["project_id"], {:name=>"index_ci_pipeline_schedules_on_project_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0082s
 -- create_table("ci_pipeline_variables", {:force=>:cascade})
-   -> 0.0045s
+   -> 0.0112s
 -- add_index("ci_pipeline_variables", ["pipeline_id", "key"], {:name=>"index_ci_pipeline_variables_on_pipeline_id_and_key", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0075s
 -- create_table("ci_pipelines", {:force=>:cascade})
-   -> 0.0057s
+   -> 0.0111s
 -- add_index("ci_pipelines", ["auto_canceled_by_id"], {:name=>"index_ci_pipelines_on_auto_canceled_by_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0074s
 -- add_index("ci_pipelines", ["pipeline_schedule_id"], {:name=>"index_ci_pipelines_on_pipeline_schedule_id", :using=>:btree})
-   -> 0.0031s
+   -> 0.0086s
 -- add_index("ci_pipelines", ["project_id", "ref", "status", "id"], {:name=>"index_ci_pipelines_on_project_id_and_ref_and_status_and_id", :using=>:btree})
-   -> 0.0032s
+   -> 0.0104s
 -- add_index("ci_pipelines", ["project_id", "sha"], {:name=>"index_ci_pipelines_on_project_id_and_sha", :using=>:btree})
-   -> 0.0032s
+   -> 0.0107s
 -- add_index("ci_pipelines", ["project_id"], {:name=>"index_ci_pipelines_on_project_id", :using=>:btree})
-   -> 0.0035s
+   -> 0.0084s
 -- add_index("ci_pipelines", ["status"], {:name=>"index_ci_pipelines_on_status", :using=>:btree})
-   -> 0.0032s
+   -> 0.0065s
 -- add_index("ci_pipelines", ["user_id"], {:name=>"index_ci_pipelines_on_user_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0071s
 -- create_table("ci_runner_projects", {:force=>:cascade})
-   -> 0.0035s
+   -> 0.0077s
 -- add_index("ci_runner_projects", ["project_id"], {:name=>"index_ci_runner_projects_on_project_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0072s
 -- add_index("ci_runner_projects", ["runner_id"], {:name=>"index_ci_runner_projects_on_runner_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0064s
 -- create_table("ci_runners", {:force=>:cascade})
-   -> 0.0059s
+   -> 0.0090s
 -- add_index("ci_runners", ["contacted_at"], {:name=>"index_ci_runners_on_contacted_at", :using=>:btree})
-   -> 0.0030s
+   -> 0.0078s
 -- add_index("ci_runners", ["is_shared"], {:name=>"index_ci_runners_on_is_shared", :using=>:btree})
-   -> 0.0030s
+   -> 0.0054s
 -- add_index("ci_runners", ["locked"], {:name=>"index_ci_runners_on_locked", :using=>:btree})
-   -> 0.0030s
+   -> 0.0052s
 -- add_index("ci_runners", ["token"], {:name=>"index_ci_runners_on_token", :using=>:btree})
-   -> 0.0029s
+   -> 0.0057s
 -- create_table("ci_stages", {:force=>:cascade})
-   -> 0.0046s
--- add_index("ci_stages", ["pipeline_id", "name"], {:name=>"index_ci_stages_on_pipeline_id_and_name", :using=>:btree})
-   -> 0.0031s
+   -> 0.0059s
+-- add_index("ci_stages", ["pipeline_id", "name"], {:name=>"index_ci_stages_on_pipeline_id_and_name", :unique=>true, :using=>:btree})
+   -> 0.0054s
 -- add_index("ci_stages", ["pipeline_id"], {:name=>"index_ci_stages_on_pipeline_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0045s
 -- add_index("ci_stages", ["project_id"], {:name=>"index_ci_stages_on_project_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0053s
 -- create_table("ci_trigger_requests", {:force=>:cascade})
-   -> 0.0058s
+   -> 0.0079s
 -- add_index("ci_trigger_requests", ["commit_id"], {:name=>"index_ci_trigger_requests_on_commit_id", :using=>:btree})
-   -> 0.0031s
+   -> 0.0059s
 -- create_table("ci_triggers", {:force=>:cascade})
-   -> 0.0043s
+   -> 0.0100s
 -- add_index("ci_triggers", ["project_id"], {:name=>"index_ci_triggers_on_project_id", :using=>:btree})
-   -> 0.0033s
--- create_table("ci_variables", {:force=>:cascade})
    -> 0.0059s
+-- create_table("ci_variables", {:force=>:cascade})
+   -> 0.0110s
 -- 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})
-   -> 0.0031s
+   -> 0.0066s
 -- create_table("cluster_platforms_kubernetes", {:force=>:cascade})
-   -> 0.0053s
+   -> 0.0082s
 -- add_index("cluster_platforms_kubernetes", ["cluster_id"], {:name=>"index_cluster_platforms_kubernetes_on_cluster_id", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0047s
 -- create_table("cluster_projects", {:force=>:cascade})
-   -> 0.0032s
+   -> 0.0079s
 -- add_index("cluster_projects", ["cluster_id"], {:name=>"index_cluster_projects_on_cluster_id", :using=>:btree})
-   -> 0.0035s
+   -> 0.0045s
 -- add_index("cluster_projects", ["project_id"], {:name=>"index_cluster_projects_on_project_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0044s
 -- create_table("cluster_providers_gcp", {:force=>:cascade})
-   -> 0.0051s
+   -> 0.0247s
 -- add_index("cluster_providers_gcp", ["cluster_id"], {:name=>"index_cluster_providers_gcp_on_cluster_id", :unique=>true, :using=>:btree})
-   -> 0.0034s
+   -> 0.0088s
 -- create_table("clusters", {:force=>:cascade})
-   -> 0.0052s
+   -> 0.0767s
 -- add_index("clusters", ["enabled"], {:name=>"index_clusters_on_enabled", :using=>:btree})
-   -> 0.0031s
+   -> 0.0162s
 -- add_index("clusters", ["user_id"], {:name=>"index_clusters_on_user_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0216s
 -- create_table("clusters_applications_helm", {:force=>:cascade})
-   -> 0.0045s
+   -> 0.0379s
 -- create_table("clusters_applications_ingress", {:force=>:cascade})
-   -> 0.0044s
+   -> 0.0409s
 -- create_table("clusters_applications_prometheus", {:force=>:cascade})
-   -> 0.0047s
+   -> 0.0178s
+-- create_table("clusters_applications_runners", {:force=>:cascade})
+   -> 0.0471s
+-- add_index("clusters_applications_runners", ["cluster_id"], {:name=>"index_clusters_applications_runners_on_cluster_id", :unique=>true, :using=>:btree})
+   -> 0.0487s
+-- add_index("clusters_applications_runners", ["runner_id"], {:name=>"index_clusters_applications_runners_on_runner_id", :using=>:btree})
+   -> 0.0094s
 -- create_table("container_repositories", {:force=>:cascade})
-   -> 0.0050s
+   -> 0.0142s
 -- add_index("container_repositories", ["project_id", "name"], {:name=>"index_container_repositories_on_project_id_and_name", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0080s
 -- add_index("container_repositories", ["project_id"], {:name=>"index_container_repositories_on_project_id", :using=>:btree})
-   -> 0.0032s
+   -> 0.0070s
 -- create_table("conversational_development_index_metrics", {:force=>:cascade})
-   -> 0.0076s
+   -> 0.0204s
 -- create_table("deploy_keys_projects", {:force=>:cascade})
-   -> 0.0037s
+   -> 0.0154s
 -- add_index("deploy_keys_projects", ["project_id"], {:name=>"index_deploy_keys_projects_on_project_id", :using=>:btree})
-   -> 0.0032s
+   -> 0.0471s
 -- create_table("deployments", {:force=>:cascade})
-   -> 0.0049s
+   -> 0.0191s
 -- add_index("deployments", ["created_at"], {:name=>"index_deployments_on_created_at", :using=>:btree})
-   -> 0.0034s
+   -> 0.0552s
 -- add_index("deployments", ["environment_id", "id"], {:name=>"index_deployments_on_environment_id_and_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0294s
 -- add_index("deployments", ["environment_id", "iid", "project_id"], {:name=>"index_deployments_on_environment_id_and_iid_and_project_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0408s
 -- add_index("deployments", ["project_id", "iid"], {:name=>"index_deployments_on_project_id_and_iid", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0094s
 -- create_table("emails", {:force=>:cascade})
-   -> 0.0046s
+   -> 0.0127s
 -- add_index("emails", ["confirmation_token"], {:name=>"index_emails_on_confirmation_token", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0082s
 -- add_index("emails", ["email"], {:name=>"index_emails_on_email", :unique=>true, :using=>:btree})
-   -> 0.0035s
+   -> 0.0110s
 -- add_index("emails", ["user_id"], {:name=>"index_emails_on_user_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0079s
 -- create_table("environments", {:force=>:cascade})
-   -> 0.0052s
+   -> 0.0106s
 -- add_index("environments", ["project_id", "name"], {:name=>"index_environments_on_project_id_and_name", :unique=>true, :using=>:btree})
-   -> 0.0031s
+   -> 0.0086s
 -- add_index("environments", ["project_id", "slug"], {:name=>"index_environments_on_project_id_and_slug", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0076s
 -- create_table("events", {:force=>:cascade})
-   -> 0.0046s
+   -> 0.0122s
 -- add_index("events", ["action"], {:name=>"index_events_on_action", :using=>:btree})
-   -> 0.0032s
--- add_index("events", ["author_id"], {:name=>"index_events_on_author_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0068s
+-- add_index("events", ["author_id", "project_id"], {:name=>"index_events_on_author_id_and_project_id", :using=>:btree})
+   -> 0.0081s
 -- add_index("events", ["project_id", "id"], {:name=>"index_events_on_project_id_and_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0064s
 -- add_index("events", ["target_type", "target_id"], {:name=>"index_events_on_target_type_and_target_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0087s
 -- create_table("feature_gates", {:force=>:cascade})
-   -> 0.0046s
+   -> 0.0105s
 -- add_index("feature_gates", ["feature_key", "key", "value"], {:name=>"index_feature_gates_on_feature_key_and_key_and_value", :unique=>true, :using=>:btree})
-   -> 0.0031s
+   -> 0.0080s
 -- create_table("features", {:force=>:cascade})
-   -> 0.0041s
+   -> 0.0086s
 -- add_index("features", ["key"], {:name=>"index_features_on_key", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0058s
 -- create_table("fork_network_members", {:force=>:cascade})
-   -> 0.0033s
+   -> 0.0081s
 -- add_index("fork_network_members", ["fork_network_id"], {:name=>"index_fork_network_members_on_fork_network_id", :using=>:btree})
-   -> 0.0033s
+   -> 0.0056s
 -- add_index("fork_network_members", ["project_id"], {:name=>"index_fork_network_members_on_project_id", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0053s
 -- create_table("fork_networks", {:force=>:cascade})
-   -> 0.0049s
+   -> 0.0081s
 -- add_index("fork_networks", ["root_project_id"], {:name=>"index_fork_networks_on_root_project_id", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0051s
 -- create_table("forked_project_links", {:force=>:cascade})
-   -> 0.0032s
+   -> 0.0070s
 -- add_index("forked_project_links", ["forked_to_project_id"], {:name=>"index_forked_project_links_on_forked_to_project_id", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0061s
 -- create_table("gcp_clusters", {:force=>:cascade})
-   -> 0.0074s
+   -> 0.0090s
 -- add_index("gcp_clusters", ["project_id"], {:name=>"index_gcp_clusters_on_project_id", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0073s
 -- create_table("gpg_key_subkeys", {:force=>:cascade})
-   -> 0.0042s
+   -> 0.0092s
 -- add_index("gpg_key_subkeys", ["fingerprint"], {:name=>"index_gpg_key_subkeys_on_fingerprint", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0063s
 -- add_index("gpg_key_subkeys", ["gpg_key_id"], {:name=>"index_gpg_key_subkeys_on_gpg_key_id", :using=>:btree})
-   -> 0.0032s
+   -> 0.0603s
 -- add_index("gpg_key_subkeys", ["keyid"], {:name=>"index_gpg_key_subkeys_on_keyid", :unique=>true, :using=>:btree})
-   -> 0.0027s
+   -> 0.0705s
 -- create_table("gpg_keys", {:force=>:cascade})
-   -> 0.0042s
+   -> 0.0235s
 -- add_index("gpg_keys", ["fingerprint"], {:name=>"index_gpg_keys_on_fingerprint", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0220s
 -- add_index("gpg_keys", ["primary_keyid"], {:name=>"index_gpg_keys_on_primary_keyid", :unique=>true, :using=>:btree})
-   -> 0.0026s
+   -> 0.0329s
 -- add_index("gpg_keys", ["user_id"], {:name=>"index_gpg_keys_on_user_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0087s
 -- create_table("gpg_signatures", {:force=>:cascade})
-   -> 0.0054s
+   -> 0.0126s
 -- add_index("gpg_signatures", ["commit_sha"], {:name=>"index_gpg_signatures_on_commit_sha", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0105s
 -- add_index("gpg_signatures", ["gpg_key_id"], {:name=>"index_gpg_signatures_on_gpg_key_id", :using=>:btree})
-   -> 0.0026s
+   -> 0.0094s
 -- add_index("gpg_signatures", ["gpg_key_primary_keyid"], {:name=>"index_gpg_signatures_on_gpg_key_primary_keyid", :using=>:btree})
-   -> 0.0029s
+   -> 0.0100s
 -- add_index("gpg_signatures", ["gpg_key_subkey_id"], {:name=>"index_gpg_signatures_on_gpg_key_subkey_id", :using=>:btree})
-   -> 0.0032s
+   -> 0.0079s
 -- add_index("gpg_signatures", ["project_id"], {:name=>"index_gpg_signatures_on_project_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0081s
 -- create_table("group_custom_attributes", {:force=>:cascade})
-   -> 0.0044s
+   -> 0.0092s
 -- add_index("group_custom_attributes", ["group_id", "key"], {:name=>"index_group_custom_attributes_on_group_id_and_key", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0086s
 -- add_index("group_custom_attributes", ["key", "value"], {:name=>"index_group_custom_attributes_on_key_and_value", :using=>:btree})
-   -> 0.0028s
+   -> 0.0071s
 -- create_table("identities", {:force=>:cascade})
-   -> 0.0043s
+   -> 0.0114s
 -- add_index("identities", ["user_id"], {:name=>"index_identities_on_user_id", :using=>:btree})
-   -> 0.0034s
+   -> 0.0064s
+-- create_table("internal_ids", {:id=>:bigserial, :force=>:cascade})
+   -> 0.0097s
+-- add_index("internal_ids", ["usage", "project_id"], {:name=>"index_internal_ids_on_usage_and_project_id", :unique=>true, :using=>:btree})
+   -> 0.0073s
 -- create_table("issue_assignees", {:id=>false, :force=>:cascade})
-   -> 0.0013s
+   -> 0.0127s
 -- add_index("issue_assignees", ["issue_id", "user_id"], {:name=>"index_issue_assignees_on_issue_id_and_user_id", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0110s
 -- add_index("issue_assignees", ["user_id"], {:name=>"index_issue_assignees_on_user_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0079s
 -- create_table("issue_metrics", {:force=>:cascade})
-   -> 0.0032s
+   -> 0.0098s
 -- add_index("issue_metrics", ["issue_id"], {:name=>"index_issue_metrics", :using=>:btree})
-   -> 0.0029s
+   -> 0.0053s
 -- create_table("issues", {:force=>:cascade})
-   -> 0.0051s
+   -> 0.0090s
 -- add_index("issues", ["author_id"], {:name=>"index_issues_on_author_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0056s
 -- add_index("issues", ["confidential"], {:name=>"index_issues_on_confidential", :using=>:btree})
-   -> 0.0029s
+   -> 0.0055s
 -- add_index("issues", ["description"], {:name=>"index_issues_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
-   -> 0.0022s
+   -> 0.0006s
 -- add_index("issues", ["milestone_id"], {:name=>"index_issues_on_milestone_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0061s
 -- add_index("issues", ["moved_to_id"], {:name=>"index_issues_on_moved_to_id", :where=>"(moved_to_id IS NOT NULL)", :using=>:btree})
-   -> 0.0030s
+   -> 0.0051s
 -- add_index("issues", ["project_id", "created_at", "id", "state"], {:name=>"index_issues_on_project_id_and_created_at_and_id_and_state", :using=>:btree})
-   -> 0.0039s
+   -> 0.0069s
 -- add_index("issues", ["project_id", "due_date", "id", "state"], {:name=>"idx_issues_on_project_id_and_due_date_and_id_and_state_partial", :where=>"(due_date IS NOT NULL)", :using=>:btree})
-   -> 0.0031s
+   -> 0.0073s
 -- add_index("issues", ["project_id", "iid"], {:name=>"index_issues_on_project_id_and_iid", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0060s
 -- add_index("issues", ["project_id", "updated_at", "id", "state"], {:name=>"index_issues_on_project_id_and_updated_at_and_id_and_state", :using=>:btree})
-   -> 0.0035s
+   -> 0.0094s
 -- add_index("issues", ["relative_position"], {:name=>"index_issues_on_relative_position", :using=>:btree})
-   -> 0.0030s
+   -> 0.0070s
 -- add_index("issues", ["state"], {:name=>"index_issues_on_state", :using=>:btree})
-   -> 0.0027s
+   -> 0.0078s
 -- add_index("issues", ["title"], {:name=>"index_issues_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
-   -> 0.0021s
+   -> 0.0007s
 -- add_index("issues", ["updated_at"], {:name=>"index_issues_on_updated_at", :using=>:btree})
-   -> 0.0030s
+   -> 0.0068s
 -- add_index("issues", ["updated_by_id"], {:name=>"index_issues_on_updated_by_id", :where=>"(updated_by_id IS NOT NULL)", :using=>:btree})
-   -> 0.0028s
+   -> 0.0066s
 -- create_table("keys", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0087s
 -- add_index("keys", ["fingerprint"], {:name=>"index_keys_on_fingerprint", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0066s
 -- add_index("keys", ["user_id"], {:name=>"index_keys_on_user_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0063s
 -- create_table("label_links", {:force=>:cascade})
-   -> 0.0041s
+   -> 0.0073s
 -- add_index("label_links", ["label_id"], {:name=>"index_label_links_on_label_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0050s
 -- add_index("label_links", ["target_id", "target_type"], {:name=>"index_label_links_on_target_id_and_target_type", :using=>:btree})
-   -> 0.0028s
+   -> 0.0062s
 -- create_table("label_priorities", {:force=>:cascade})
-   -> 0.0031s
+   -> 0.0073s
 -- add_index("label_priorities", ["priority"], {:name=>"index_label_priorities_on_priority", :using=>:btree})
-   -> 0.0028s
+   -> 0.0058s
 -- add_index("label_priorities", ["project_id", "label_id"], {:name=>"index_label_priorities_on_project_id_and_label_id", :unique=>true, :using=>:btree})
-   -> 0.0027s
+   -> 0.0056s
 -- create_table("labels", {:force=>:cascade})
-   -> 0.0046s
+   -> 0.0087s
 -- add_index("labels", ["group_id", "project_id", "title"], {:name=>"index_labels_on_group_id_and_project_id_and_title", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0074s
 -- add_index("labels", ["project_id"], {:name=>"index_labels_on_project_id", :using=>:btree})
-   -> 0.0032s
+   -> 0.0061s
 -- add_index("labels", ["template"], {:name=>"index_labels_on_template", :where=>"template", :using=>:btree})
-   -> 0.0027s
+   -> 0.0060s
 -- add_index("labels", ["title"], {:name=>"index_labels_on_title", :using=>:btree})
-   -> 0.0030s
+   -> 0.0076s
 -- add_index("labels", ["type", "project_id"], {:name=>"index_labels_on_type_and_project_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0061s
+-- create_table("lfs_file_locks", {:force=>:cascade})
+   -> 0.0078s
+-- add_index("lfs_file_locks", ["project_id", "path"], {:name=>"index_lfs_file_locks_on_project_id_and_path", :unique=>true, :using=>:btree})
+   -> 0.0067s
+-- add_index("lfs_file_locks", ["user_id"], {:name=>"index_lfs_file_locks_on_user_id", :using=>:btree})
+   -> 0.0060s
 -- create_table("lfs_objects", {:force=>:cascade})
-   -> 0.0040s
+   -> 0.0109s
 -- add_index("lfs_objects", ["oid"], {:name=>"index_lfs_objects_on_oid", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0059s
 -- create_table("lfs_objects_projects", {:force=>:cascade})
-   -> 0.0035s
+   -> 0.0091s
 -- add_index("lfs_objects_projects", ["project_id"], {:name=>"index_lfs_objects_projects_on_project_id", :using=>:btree})
-   -> 0.0025s
+   -> 0.0060s
 -- create_table("lists", {:force=>:cascade})
-   -> 0.0033s
+   -> 0.0115s
 -- add_index("lists", ["board_id", "label_id"], {:name=>"index_lists_on_board_id_and_label_id", :unique=>true, :using=>:btree})
-   -> 0.0026s
+   -> 0.0055s
 -- add_index("lists", ["label_id"], {:name=>"index_lists_on_label_id", :using=>:btree})
-   -> 0.0026s
+   -> 0.0055s
 -- create_table("members", {:force=>:cascade})
-   -> 0.0046s
+   -> 0.0140s
 -- add_index("members", ["access_level"], {:name=>"index_members_on_access_level", :using=>:btree})
-   -> 0.0028s
+   -> 0.0067s
 -- add_index("members", ["invite_token"], {:name=>"index_members_on_invite_token", :unique=>true, :using=>:btree})
-   -> 0.0027s
+   -> 0.0069s
 -- add_index("members", ["requested_at"], {:name=>"index_members_on_requested_at", :using=>:btree})
-   -> 0.0025s
+   -> 0.0057s
 -- add_index("members", ["source_id", "source_type"], {:name=>"index_members_on_source_id_and_source_type", :using=>:btree})
-   -> 0.0027s
+   -> 0.0057s
 -- add_index("members", ["user_id"], {:name=>"index_members_on_user_id", :using=>:btree})
-   -> 0.0026s
+   -> 0.0073s
 -- create_table("merge_request_diff_commits", {:id=>false, :force=>:cascade})
-   -> 0.0027s
+   -> 0.0087s
 -- add_index("merge_request_diff_commits", ["merge_request_diff_id", "relative_order"], {:name=>"index_merge_request_diff_commits_on_mr_diff_id_and_order", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0151s
 -- add_index("merge_request_diff_commits", ["sha"], {:name=>"index_merge_request_diff_commits_on_sha", :using=>:btree})
-   -> 0.0029s
+   -> 0.0057s
 -- create_table("merge_request_diff_files", {:id=>false, :force=>:cascade})
-   -> 0.0027s
+   -> 0.0094s
 -- 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})
-   -> 0.0027s
+   -> 0.0138s
 -- create_table("merge_request_diffs", {:force=>:cascade})
-   -> 0.0042s
+   -> 0.0077s
 -- add_index("merge_request_diffs", ["merge_request_id", "id"], {:name=>"index_merge_request_diffs_on_merge_request_id_and_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0060s
 -- create_table("merge_request_metrics", {:force=>:cascade})
-   -> 0.0034s
+   -> 0.0098s
 -- add_index("merge_request_metrics", ["first_deployed_to_production_at"], {:name=>"index_merge_request_metrics_on_first_deployed_to_production_at", :using=>:btree})
-   -> 0.0028s
+   -> 0.0060s
 -- add_index("merge_request_metrics", ["merge_request_id"], {:name=>"index_merge_request_metrics", :using=>:btree})
-   -> 0.0025s
+   -> 0.0050s
 -- add_index("merge_request_metrics", ["pipeline_id"], {:name=>"index_merge_request_metrics_on_pipeline_id", :using=>:btree})
-   -> 0.0026s
+   -> 0.0045s
 -- create_table("merge_requests", {:force=>:cascade})
    -> 0.0066s
 -- add_index("merge_requests", ["assignee_id"], {:name=>"index_merge_requests_on_assignee_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0072s
 -- add_index("merge_requests", ["author_id"], {:name=>"index_merge_requests_on_author_id", :using=>:btree})
-   -> 0.0026s
+   -> 0.0050s
 -- add_index("merge_requests", ["created_at"], {:name=>"index_merge_requests_on_created_at", :using=>:btree})
-   -> 0.0026s
+   -> 0.0053s
 -- add_index("merge_requests", ["description"], {:name=>"index_merge_requests_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
-   -> 0.0020s
+   -> 0.0008s
 -- add_index("merge_requests", ["head_pipeline_id"], {:name=>"index_merge_requests_on_head_pipeline_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0053s
 -- add_index("merge_requests", ["latest_merge_request_diff_id"], {:name=>"index_merge_requests_on_latest_merge_request_diff_id", :using=>:btree})
-   -> 0.0025s
+   -> 0.0048s
 -- add_index("merge_requests", ["merge_user_id"], {:name=>"index_merge_requests_on_merge_user_id", :where=>"(merge_user_id IS NOT NULL)", :using=>:btree})
-   -> 0.0029s
+   -> 0.0051s
 -- add_index("merge_requests", ["milestone_id"], {:name=>"index_merge_requests_on_milestone_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0055s
 -- add_index("merge_requests", ["source_branch"], {:name=>"index_merge_requests_on_source_branch", :using=>:btree})
-   -> 0.0026s
+   -> 0.0055s
 -- add_index("merge_requests", ["source_project_id", "source_branch"], {:name=>"index_merge_requests_on_source_project_and_branch_state_opened", :where=>"((state)::text = 'opened'::text)", :using=>:btree})
-   -> 0.0029s
+   -> 0.0061s
 -- add_index("merge_requests", ["source_project_id", "source_branch"], {:name=>"index_merge_requests_on_source_project_id_and_source_branch", :using=>:btree})
-   -> 0.0031s
+   -> 0.0068s
 -- add_index("merge_requests", ["target_branch"], {:name=>"index_merge_requests_on_target_branch", :using=>:btree})
-   -> 0.0028s
+   -> 0.0054s
 -- add_index("merge_requests", ["target_project_id", "iid"], {:name=>"index_merge_requests_on_target_project_id_and_iid", :unique=>true, :using=>:btree})
-   -> 0.0027s
+   -> 0.0061s
 -- add_index("merge_requests", ["target_project_id", "merge_commit_sha", "id"], {:name=>"index_merge_requests_on_tp_id_and_merge_commit_sha_and_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0077s
 -- add_index("merge_requests", ["title"], {:name=>"index_merge_requests_on_title", :using=>:btree})
-   -> 0.0026s
+   -> 0.0105s
 -- add_index("merge_requests", ["title"], {:name=>"index_merge_requests_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
-   -> 0.0020s
+   -> 0.0008s
 -- add_index("merge_requests", ["updated_by_id"], {:name=>"index_merge_requests_on_updated_by_id", :where=>"(updated_by_id IS NOT NULL)", :using=>:btree})
-   -> 0.0029s
+   -> 0.0074s
 -- create_table("merge_requests_closing_issues", {:force=>:cascade})
-   -> 0.0031s
+   -> 0.0125s
 -- add_index("merge_requests_closing_issues", ["issue_id"], {:name=>"index_merge_requests_closing_issues_on_issue_id", :using=>:btree})
-   -> 0.0026s
+   -> 0.0064s
 -- add_index("merge_requests_closing_issues", ["merge_request_id"], {:name=>"index_merge_requests_closing_issues_on_merge_request_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0061s
 -- create_table("milestones", {:force=>:cascade})
-   -> 0.0044s
+   -> 0.0064s
 -- add_index("milestones", ["description"], {:name=>"index_milestones_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
-   -> 0.0022s
+   -> 0.0007s
 -- add_index("milestones", ["due_date"], {:name=>"index_milestones_on_due_date", :using=>:btree})
-   -> 0.0033s
+   -> 0.0053s
 -- add_index("milestones", ["group_id"], {:name=>"index_milestones_on_group_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0068s
 -- add_index("milestones", ["project_id", "iid"], {:name=>"index_milestones_on_project_id_and_iid", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0057s
 -- add_index("milestones", ["title"], {:name=>"index_milestones_on_title", :using=>:btree})
-   -> 0.0026s
+   -> 0.0051s
 -- add_index("milestones", ["title"], {:name=>"index_milestones_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
-   -> 0.0021s
+   -> 0.0006s
 -- create_table("namespaces", {:force=>:cascade})
-   -> 0.0068s
+   -> 0.0083s
 -- add_index("namespaces", ["created_at"], {:name=>"index_namespaces_on_created_at", :using=>:btree})
-   -> 0.0030s
+   -> 0.0061s
 -- add_index("namespaces", ["name", "parent_id"], {:name=>"index_namespaces_on_name_and_parent_id", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0062s
 -- add_index("namespaces", ["name"], {:name=>"index_namespaces_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}})
-   -> 0.0020s
+   -> 0.0006s
 -- add_index("namespaces", ["owner_id"], {:name=>"index_namespaces_on_owner_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0061s
 -- add_index("namespaces", ["parent_id", "id"], {:name=>"index_namespaces_on_parent_id_and_id", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0072s
 -- add_index("namespaces", ["path"], {:name=>"index_namespaces_on_path", :using=>:btree})
-   -> 0.0031s
+   -> 0.0056s
 -- add_index("namespaces", ["path"], {:name=>"index_namespaces_on_path_trigram", :using=>:gin, :opclasses=>{"path"=>"gin_trgm_ops"}})
-   -> 0.0019s
+   -> 0.0006s
 -- add_index("namespaces", ["require_two_factor_authentication"], {:name=>"index_namespaces_on_require_two_factor_authentication", :using=>:btree})
-   -> 0.0029s
+   -> 0.0061s
 -- add_index("namespaces", ["type"], {:name=>"index_namespaces_on_type", :using=>:btree})
-   -> 0.0032s
--- create_table("notes", {:force=>:cascade})
    -> 0.0055s
+-- create_table("notes", {:force=>:cascade})
+   -> 0.0092s
 -- add_index("notes", ["author_id"], {:name=>"index_notes_on_author_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0072s
 -- add_index("notes", ["commit_id"], {:name=>"index_notes_on_commit_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0057s
 -- add_index("notes", ["created_at"], {:name=>"index_notes_on_created_at", :using=>:btree})
-   -> 0.0029s
+   -> 0.0065s
 -- add_index("notes", ["discussion_id"], {:name=>"index_notes_on_discussion_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0064s
 -- add_index("notes", ["line_code"], {:name=>"index_notes_on_line_code", :using=>:btree})
-   -> 0.0029s
+   -> 0.0078s
 -- add_index("notes", ["note"], {:name=>"index_notes_on_note_trigram", :using=>:gin, :opclasses=>{"note"=>"gin_trgm_ops"}})
-   -> 0.0024s
+   -> 0.0006s
 -- add_index("notes", ["noteable_id", "noteable_type"], {:name=>"index_notes_on_noteable_id_and_noteable_type", :using=>:btree})
-   -> 0.0029s
+   -> 0.0102s
 -- add_index("notes", ["noteable_type"], {:name=>"index_notes_on_noteable_type", :using=>:btree})
-   -> 0.0030s
+   -> 0.0092s
 -- add_index("notes", ["project_id", "noteable_type"], {:name=>"index_notes_on_project_id_and_noteable_type", :using=>:btree})
-   -> 0.0027s
+   -> 0.0082s
 -- add_index("notes", ["updated_at"], {:name=>"index_notes_on_updated_at", :using=>:btree})
-   -> 0.0026s
+   -> 0.0062s
 -- create_table("notification_settings", {:force=>:cascade})
-   -> 0.0053s
+   -> 0.0088s
 -- add_index("notification_settings", ["source_id", "source_type"], {:name=>"index_notification_settings_on_source_id_and_source_type", :using=>:btree})
-   -> 0.0028s
+   -> 0.0405s
 -- add_index("notification_settings", ["user_id", "source_id", "source_type"], {:name=>"index_notifications_on_user_id_and_source_id_and_source_type", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0677s
 -- add_index("notification_settings", ["user_id"], {:name=>"index_notification_settings_on_user_id", :using=>:btree})
-   -> 0.0031s
+   -> 0.1199s
 -- create_table("oauth_access_grants", {:force=>:cascade})
-   -> 0.0042s
+   -> 0.0140s
 -- add_index("oauth_access_grants", ["token"], {:name=>"index_oauth_access_grants_on_token", :unique=>true, :using=>:btree})
-   -> 0.0031s
+   -> 0.0076s
 -- create_table("oauth_access_tokens", {:force=>:cascade})
-   -> 0.0051s
+   -> 0.0167s
 -- add_index("oauth_access_tokens", ["refresh_token"], {:name=>"index_oauth_access_tokens_on_refresh_token", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0098s
 -- add_index("oauth_access_tokens", ["resource_owner_id"], {:name=>"index_oauth_access_tokens_on_resource_owner_id", :using=>:btree})
-   -> 0.0025s
+   -> 0.0074s
 -- add_index("oauth_access_tokens", ["token"], {:name=>"index_oauth_access_tokens_on_token", :unique=>true, :using=>:btree})
-   -> 0.0026s
+   -> 0.0078s
 -- create_table("oauth_applications", {:force=>:cascade})
-   -> 0.0049s
+   -> 0.0112s
 -- add_index("oauth_applications", ["owner_id", "owner_type"], {:name=>"index_oauth_applications_on_owner_id_and_owner_type", :using=>:btree})
-   -> 0.0030s
+   -> 0.0079s
 -- add_index("oauth_applications", ["uid"], {:name=>"index_oauth_applications_on_uid", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0114s
 -- create_table("oauth_openid_requests", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0102s
 -- create_table("pages_domains", {:force=>:cascade})
-   -> 0.0052s
+   -> 0.0102s
 -- add_index("pages_domains", ["domain"], {:name=>"index_pages_domains_on_domain", :unique=>true, :using=>:btree})
-   -> 0.0027s
+   -> 0.0067s
+-- add_index("pages_domains", ["project_id", "enabled_until"], {:name=>"index_pages_domains_on_project_id_and_enabled_until", :using=>:btree})
+   -> 0.0114s
 -- add_index("pages_domains", ["project_id"], {:name=>"index_pages_domains_on_project_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0066s
+-- add_index("pages_domains", ["verified_at", "enabled_until"], {:name=>"index_pages_domains_on_verified_at_and_enabled_until", :using=>:btree})
+   -> 0.0073s
+-- add_index("pages_domains", ["verified_at"], {:name=>"index_pages_domains_on_verified_at", :using=>:btree})
+   -> 0.0063s
 -- create_table("personal_access_tokens", {:force=>:cascade})
-   -> 0.0056s
+   -> 0.0084s
 -- add_index("personal_access_tokens", ["token"], {:name=>"index_personal_access_tokens_on_token", :unique=>true, :using=>:btree})
-   -> 0.0032s
+   -> 0.0075s
 -- add_index("personal_access_tokens", ["user_id"], {:name=>"index_personal_access_tokens_on_user_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0066s
 -- create_table("project_authorizations", {:id=>false, :force=>:cascade})
-   -> 0.0018s
+   -> 0.0087s
 -- add_index("project_authorizations", ["project_id"], {:name=>"index_project_authorizations_on_project_id", :using=>:btree})
-   -> 0.0033s
+   -> 0.0056s
 -- add_index("project_authorizations", ["user_id", "project_id", "access_level"], {:name=>"index_project_authorizations_on_user_id_project_id_access_level", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0075s
 -- create_table("project_auto_devops", {:force=>:cascade})
-   -> 0.0043s
+   -> 0.0079s
 -- add_index("project_auto_devops", ["project_id"], {:name=>"index_project_auto_devops_on_project_id", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0067s
 -- create_table("project_custom_attributes", {:force=>:cascade})
-   -> 0.0047s
+   -> 0.0071s
 -- add_index("project_custom_attributes", ["key", "value"], {:name=>"index_project_custom_attributes_on_key_and_value", :using=>:btree})
-   -> 0.0030s
+   -> 0.0060s
 -- add_index("project_custom_attributes", ["project_id", "key"], {:name=>"index_project_custom_attributes_on_project_id_and_key", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0069s
 -- create_table("project_features", {:force=>:cascade})
-   -> 0.0038s
+   -> 0.0100s
 -- add_index("project_features", ["project_id"], {:name=>"index_project_features_on_project_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0069s
 -- create_table("project_group_links", {:force=>:cascade})
-   -> 0.0036s
+   -> 0.0117s
 -- add_index("project_group_links", ["group_id"], {:name=>"index_project_group_links_on_group_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0121s
 -- add_index("project_group_links", ["project_id"], {:name=>"index_project_group_links_on_project_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0076s
 -- create_table("project_import_data", {:force=>:cascade})
-   -> 0.0049s
+   -> 0.0084s
 -- add_index("project_import_data", ["project_id"], {:name=>"index_project_import_data_on_project_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0058s
 -- create_table("project_statistics", {:force=>:cascade})
-   -> 0.0046s
+   -> 0.0075s
 -- add_index("project_statistics", ["namespace_id"], {:name=>"index_project_statistics_on_namespace_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0054s
 -- add_index("project_statistics", ["project_id"], {:name=>"index_project_statistics_on_project_id", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0054s
 -- create_table("projects", {:force=>:cascade})
-   -> 0.0090s
+   -> 0.0077s
 -- add_index("projects", ["ci_id"], {:name=>"index_projects_on_ci_id", :using=>:btree})
-   -> 0.0033s
+   -> 0.0070s
 -- add_index("projects", ["created_at"], {:name=>"index_projects_on_created_at", :using=>:btree})
-   -> 0.0030s
+   -> 0.0060s
 -- add_index("projects", ["creator_id"], {:name=>"index_projects_on_creator_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0071s
 -- add_index("projects", ["description"], {:name=>"index_projects_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
-   -> 0.0022s
+   -> 0.0009s
+-- add_index("projects", ["id"], {:name=>"index_projects_on_id_partial_for_visibility", :unique=>true, :where=>"(visibility_level = ANY (ARRAY[10, 20]))", :using=>:btree})
+   -> 0.0062s
 -- add_index("projects", ["last_activity_at"], {:name=>"index_projects_on_last_activity_at", :using=>:btree})
-   -> 0.0032s
+   -> 0.0060s
 -- add_index("projects", ["last_repository_check_failed"], {:name=>"index_projects_on_last_repository_check_failed", :using=>:btree})
-   -> 0.0030s
+   -> 0.0063s
 -- add_index("projects", ["last_repository_updated_at"], {:name=>"index_projects_on_last_repository_updated_at", :using=>:btree})
-   -> 0.0031s
+   -> 0.0633s
 -- add_index("projects", ["name"], {:name=>"index_projects_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}})
-   -> 0.0022s
+   -> 0.0012s
 -- add_index("projects", ["namespace_id"], {:name=>"index_projects_on_namespace_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0167s
 -- add_index("projects", ["path"], {:name=>"index_projects_on_path", :using=>:btree})
-   -> 0.0028s
+   -> 0.0222s
 -- add_index("projects", ["path"], {:name=>"index_projects_on_path_trigram", :using=>:gin, :opclasses=>{"path"=>"gin_trgm_ops"}})
-   -> 0.0023s
+   -> 0.0010s
 -- add_index("projects", ["pending_delete"], {:name=>"index_projects_on_pending_delete", :using=>:btree})
-   -> 0.0029s
+   -> 0.0229s
 -- add_index("projects", ["repository_storage"], {:name=>"index_projects_on_repository_storage", :using=>:btree})
-   -> 0.0026s
+   -> 0.0173s
 -- add_index("projects", ["runners_token"], {:name=>"index_projects_on_runners_token", :using=>:btree})
-   -> 0.0034s
+   -> 0.0167s
 -- add_index("projects", ["star_count"], {:name=>"index_projects_on_star_count", :using=>:btree})
-   -> 0.0028s
+   -> 0.0491s
 -- add_index("projects", ["visibility_level"], {:name=>"index_projects_on_visibility_level", :using=>:btree})
-   -> 0.0027s
+   -> 0.0598s
 -- create_table("protected_branch_merge_access_levels", {:force=>:cascade})
-   -> 0.0042s
+   -> 0.1964s
 -- add_index("protected_branch_merge_access_levels", ["protected_branch_id"], {:name=>"index_protected_branch_merge_access", :using=>:btree})
-   -> 0.0029s
+   -> 0.1112s
 -- create_table("protected_branch_push_access_levels", {:force=>:cascade})
-   -> 0.0037s
+   -> 0.0195s
 -- add_index("protected_branch_push_access_levels", ["protected_branch_id"], {:name=>"index_protected_branch_push_access", :using=>:btree})
-   -> 0.0030s
+   -> 0.0069s
 -- create_table("protected_branches", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0113s
 -- add_index("protected_branches", ["project_id"], {:name=>"index_protected_branches_on_project_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0071s
 -- create_table("protected_tag_create_access_levels", {:force=>:cascade})
-   -> 0.0037s
+   -> 0.0180s
 -- add_index("protected_tag_create_access_levels", ["protected_tag_id"], {:name=>"index_protected_tag_create_access", :using=>:btree})
-   -> 0.0029s
+   -> 0.0068s
 -- add_index("protected_tag_create_access_levels", ["user_id"], {:name=>"index_protected_tag_create_access_levels_on_user_id", :using=>:btree})
-   -> 0.0029s
+   -> 0.0077s
 -- create_table("protected_tags", {:force=>:cascade})
-   -> 0.0051s
+   -> 0.0115s
 -- add_index("protected_tags", ["project_id"], {:name=>"index_protected_tags_on_project_id", :using=>:btree})
-   -> 0.0034s
+   -> 0.0081s
 -- create_table("push_event_payloads", {:id=>false, :force=>:cascade})
-   -> 0.0030s
+   -> 0.0108s
 -- add_index("push_event_payloads", ["event_id"], {:name=>"index_push_event_payloads_on_event_id", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0189s
 -- create_table("redirect_routes", {:force=>:cascade})
-   -> 0.0049s
+   -> 0.0106s
 -- add_index("redirect_routes", ["path"], {:name=>"index_redirect_routes_on_path", :unique=>true, :using=>:btree})
-   -> 0.0031s
+   -> 0.0075s
 -- add_index("redirect_routes", ["source_type", "source_id"], {:name=>"index_redirect_routes_on_source_type_and_source_id", :using=>:btree})
-   -> 0.0034s
+   -> 0.0099s
 -- create_table("releases", {:force=>:cascade})
-   -> 0.0043s
+   -> 0.0126s
 -- add_index("releases", ["project_id", "tag"], {:name=>"index_releases_on_project_id_and_tag", :using=>:btree})
-   -> 0.0032s
+   -> 0.0066s
 -- add_index("releases", ["project_id"], {:name=>"index_releases_on_project_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0060s
 -- create_table("routes", {:force=>:cascade})
-   -> 0.0055s
+   -> 0.0091s
 -- add_index("routes", ["path"], {:name=>"index_routes_on_path", :unique=>true, :using=>:btree})
-   -> 0.0028s
+   -> 0.0073s
 -- add_index("routes", ["path"], {:name=>"index_routes_on_path_text_pattern_ops", :using=>:btree, :opclasses=>{"path"=>"varchar_pattern_ops"}})
-   -> 0.0026s
+   -> 0.0004s
 -- add_index("routes", ["source_type", "source_id"], {:name=>"index_routes_on_source_type_and_source_id", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0111s
 -- create_table("sent_notifications", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0093s
 -- add_index("sent_notifications", ["reply_key"], {:name=>"index_sent_notifications_on_reply_key", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0060s
 -- create_table("services", {:force=>:cascade})
-   -> 0.0091s
+   -> 0.0099s
 -- add_index("services", ["project_id"], {:name=>"index_services_on_project_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0068s
 -- add_index("services", ["template"], {:name=>"index_services_on_template", :using=>:btree})
-   -> 0.0031s
+   -> 0.0076s
 -- create_table("snippets", {:force=>:cascade})
-   -> 0.0050s
+   -> 0.0073s
 -- add_index("snippets", ["author_id"], {:name=>"index_snippets_on_author_id", :using=>:btree})
-   -> 0.0030s
+   -> 0.0055s
 -- add_index("snippets", ["file_name"], {:name=>"index_snippets_on_file_name_trigram", :using=>:gin, :opclasses=>{"file_name"=>"gin_trgm_ops"}})
-   -> 0.0020s
+   -> 0.0006s
 -- add_index("snippets", ["project_id"], {:name=>"index_snippets_on_project_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0058s
 -- add_index("snippets", ["title"], {:name=>"index_snippets_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
-   -> 0.0020s
+   -> 0.0005s
 -- add_index("snippets", ["updated_at"], {:name=>"index_snippets_on_updated_at", :using=>:btree})
-   -> 0.0026s
+   -> 0.0100s
 -- add_index("snippets", ["visibility_level"], {:name=>"index_snippets_on_visibility_level", :using=>:btree})
-   -> 0.0026s
+   -> 0.0091s
 -- create_table("spam_logs", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0129s
 -- create_table("subscriptions", {:force=>:cascade})
-   -> 0.0041s
+   -> 0.0094s
 -- add_index("subscriptions", ["subscribable_id", "subscribable_type", "user_id", "project_id"], {:name=>"index_subscriptions_on_subscribable_and_user_id_and_project_id", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0107s
 -- create_table("system_note_metadata", {:force=>:cascade})
-   -> 0.0040s
+   -> 0.0138s
 -- add_index("system_note_metadata", ["note_id"], {:name=>"index_system_note_metadata_on_note_id", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0060s
 -- create_table("taggings", {:force=>:cascade})
-   -> 0.0047s
+   -> 0.0121s
 -- add_index("taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], {:name=>"taggings_idx", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0078s
+-- add_index("taggings", ["tag_id"], {:name=>"index_taggings_on_tag_id", :using=>:btree})
+   -> 0.0058s
 -- add_index("taggings", ["taggable_id", "taggable_type", "context"], {:name=>"index_taggings_on_taggable_id_and_taggable_type_and_context", :using=>:btree})
-   -> 0.0025s
+   -> 0.0059s
+-- add_index("taggings", ["taggable_id", "taggable_type"], {:name=>"index_taggings_on_taggable_id_and_taggable_type", :using=>:btree})
+   -> 0.0056s
 -- create_table("tags", {:force=>:cascade})
-   -> 0.0044s
+   -> 0.0063s
 -- add_index("tags", ["name"], {:name=>"index_tags_on_name", :unique=>true, :using=>:btree})
-   -> 0.0026s
+   -> 0.0055s
 -- create_table("timelogs", {:force=>:cascade})
-   -> 0.0033s
+   -> 0.0061s
 -- add_index("timelogs", ["issue_id"], {:name=>"index_timelogs_on_issue_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0063s
 -- add_index("timelogs", ["merge_request_id"], {:name=>"index_timelogs_on_merge_request_id", :using=>:btree})
-   -> 0.0033s
+   -> 0.0052s
 -- add_index("timelogs", ["user_id"], {:name=>"index_timelogs_on_user_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0055s
 -- create_table("todos", {:force=>:cascade})
-   -> 0.0043s
+   -> 0.0065s
 -- add_index("todos", ["author_id"], {:name=>"index_todos_on_author_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0081s
 -- add_index("todos", ["commit_id"], {:name=>"index_todos_on_commit_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0085s
 -- add_index("todos", ["note_id"], {:name=>"index_todos_on_note_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0083s
 -- add_index("todos", ["project_id"], {:name=>"index_todos_on_project_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0094s
 -- add_index("todos", ["target_type", "target_id"], {:name=>"index_todos_on_target_type_and_target_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0070s
+-- add_index("todos", ["user_id", "id"], {:name=>"index_todos_on_user_id_and_id_done", :where=>"((state)::text = 'done'::text)", :using=>:btree})
+   -> 0.0099s
+-- add_index("todos", ["user_id", "id"], {:name=>"index_todos_on_user_id_and_id_pending", :where=>"((state)::text = 'pending'::text)", :using=>:btree})
+   -> 0.0080s
 -- add_index("todos", ["user_id"], {:name=>"index_todos_on_user_id", :using=>:btree})
-   -> 0.0026s
+   -> 0.0061s
 -- create_table("trending_projects", {:force=>:cascade})
-   -> 0.0030s
--- add_index("trending_projects", ["project_id"], {:name=>"index_trending_projects_on_project_id", :using=>:btree})
-   -> 0.0027s
+   -> 0.0081s
+-- add_index("trending_projects", ["project_id"], {:name=>"index_trending_projects_on_project_id", :unique=>true, :using=>:btree})
+   -> 0.0046s
 -- create_table("u2f_registrations", {:force=>:cascade})
-   -> 0.0048s
+   -> 0.0063s
 -- add_index("u2f_registrations", ["key_handle"], {:name=>"index_u2f_registrations_on_key_handle", :using=>:btree})
-   -> 0.0029s
+   -> 0.0052s
 -- add_index("u2f_registrations", ["user_id"], {:name=>"index_u2f_registrations_on_user_id", :using=>:btree})
-   -> 0.0028s
+   -> 0.0072s
 -- create_table("uploads", {:force=>:cascade})
-   -> 0.0044s
+   -> 0.0067s
 -- add_index("uploads", ["checksum"], {:name=>"index_uploads_on_checksum", :using=>:btree})
-   -> 0.0028s
+   -> 0.0046s
 -- add_index("uploads", ["model_id", "model_type"], {:name=>"index_uploads_on_model_id_and_model_type", :using=>:btree})
-   -> 0.0027s
--- add_index("uploads", ["path"], {:name=>"index_uploads_on_path", :using=>:btree})
-   -> 0.0028s
+   -> 0.0049s
+-- add_index("uploads", ["uploader", "path"], {:name=>"index_uploads_on_uploader_and_path", :using=>:btree})
+   -> 0.0052s
 -- create_table("user_agent_details", {:force=>:cascade})
-   -> 0.0051s
+   -> 0.0059s
 -- add_index("user_agent_details", ["subject_id", "subject_type"], {:name=>"index_user_agent_details_on_subject_id_and_subject_type", :using=>:btree})
-   -> 0.0028s
+   -> 0.0052s
+-- create_table("user_callouts", {:force=>:cascade})
+   -> 0.0059s
+-- add_index("user_callouts", ["user_id", "feature_name"], {:name=>"index_user_callouts_on_user_id_and_feature_name", :unique=>true, :using=>:btree})
+   -> 0.0094s
+-- add_index("user_callouts", ["user_id"], {:name=>"index_user_callouts_on_user_id", :using=>:btree})
+   -> 0.0064s
 -- create_table("user_custom_attributes", {:force=>:cascade})
-   -> 0.0044s
+   -> 0.0086s
 -- add_index("user_custom_attributes", ["key", "value"], {:name=>"index_user_custom_attributes_on_key_and_value", :using=>:btree})
-   -> 0.0027s
+   -> 0.0080s
 -- add_index("user_custom_attributes", ["user_id", "key"], {:name=>"index_user_custom_attributes_on_user_id_and_key", :unique=>true, :using=>:btree})
-   -> 0.0026s
--- create_table("user_synced_attributes_metadata", {:force=>:cascade})
+   -> 0.0066s
+-- create_table("user_interacted_projects", {:id=>false, :force=>:cascade})
+   -> 0.0108s
+-- add_index("user_interacted_projects", ["project_id", "user_id"], {:name=>"index_user_interacted_projects_on_project_id_and_user_id", :unique=>true, :using=>:btree})
+   -> 0.0114s
+-- add_index("user_interacted_projects", ["user_id"], {:name=>"index_user_interacted_projects_on_user_id", :using=>:btree})
    -> 0.0056s
+-- create_table("user_synced_attributes_metadata", {:force=>:cascade})
+   -> 0.0115s
 -- add_index("user_synced_attributes_metadata", ["user_id"], {:name=>"index_user_synced_attributes_metadata_on_user_id", :unique=>true, :using=>:btree})
-   -> 0.0027s
+   -> 0.0054s
 -- create_table("users", {:force=>:cascade})
-   -> 0.0134s
+   -> 0.0111s
 -- add_index("users", ["admin"], {:name=>"index_users_on_admin", :using=>:btree})
-   -> 0.0030s
+   -> 0.0065s
 -- add_index("users", ["confirmation_token"], {:name=>"index_users_on_confirmation_token", :unique=>true, :using=>:btree})
-   -> 0.0029s
+   -> 0.0065s
 -- add_index("users", ["created_at"], {:name=>"index_users_on_created_at", :using=>:btree})
-   -> 0.0034s
+   -> 0.0068s
 -- add_index("users", ["email"], {:name=>"index_users_on_email", :unique=>true, :using=>:btree})
-   -> 0.0030s
+   -> 0.0066s
 -- add_index("users", ["email"], {:name=>"index_users_on_email_trigram", :using=>:gin, :opclasses=>{"email"=>"gin_trgm_ops"}})
-   -> 0.0431s
+   -> 0.0011s
 -- add_index("users", ["ghost"], {:name=>"index_users_on_ghost", :using=>:btree})
-   -> 0.0051s
+   -> 0.0063s
 -- add_index("users", ["incoming_email_token"], {:name=>"index_users_on_incoming_email_token", :using=>:btree})
-   -> 0.0044s
+   -> 0.0057s
 -- add_index("users", ["name"], {:name=>"index_users_on_name", :using=>:btree})
-   -> 0.0044s
+   -> 0.0056s
 -- add_index("users", ["name"], {:name=>"index_users_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}})
-   -> 0.0034s
+   -> 0.0011s
 -- add_index("users", ["reset_password_token"], {:name=>"index_users_on_reset_password_token", :unique=>true, :using=>:btree})
-   -> 0.0044s
+   -> 0.0055s
 -- add_index("users", ["rss_token"], {:name=>"index_users_on_rss_token", :using=>:btree})
-   -> 0.0046s
+   -> 0.0068s
 -- add_index("users", ["state"], {:name=>"index_users_on_state", :using=>:btree})
-   -> 0.0040s
+   -> 0.0067s
 -- add_index("users", ["username"], {:name=>"index_users_on_username", :using=>:btree})
-   -> 0.0046s
+   -> 0.0072s
 -- add_index("users", ["username"], {:name=>"index_users_on_username_trigram", :using=>:gin, :opclasses=>{"username"=>"gin_trgm_ops"}})
-   -> 0.0044s
+   -> 0.0012s
 -- create_table("users_star_projects", {:force=>:cascade})
-   -> 0.0055s
+   -> 0.0100s
 -- add_index("users_star_projects", ["project_id"], {:name=>"index_users_star_projects_on_project_id", :using=>:btree})
-   -> 0.0037s
+   -> 0.0061s
 -- add_index("users_star_projects", ["user_id", "project_id"], {:name=>"index_users_star_projects_on_user_id_and_project_id", :unique=>true, :using=>:btree})
-   -> 0.0044s
+   -> 0.0068s
 -- create_table("web_hook_logs", {:force=>:cascade})
-   -> 0.0060s
+   -> 0.0097s
 -- add_index("web_hook_logs", ["web_hook_id"], {:name=>"index_web_hook_logs_on_web_hook_id", :using=>:btree})
-   -> 0.0034s
+   -> 0.0057s
 -- create_table("web_hooks", {:force=>:cascade})
-   -> 0.0120s
+   -> 0.0080s
 -- add_index("web_hooks", ["project_id"], {:name=>"index_web_hooks_on_project_id", :using=>:btree})
-   -> 0.0038s
+   -> 0.0062s
 -- add_index("web_hooks", ["type"], {:name=>"index_web_hooks_on_type", :using=>:btree})
-   -> 0.0036s
+   -> 0.0065s
+-- add_foreign_key("badges", "namespaces", {:column=>"group_id", :on_delete=>:cascade})
+   -> 0.0158s
+-- add_foreign_key("badges", "projects", {:on_delete=>:cascade})
+   -> 0.0140s
+-- add_foreign_key("boards", "namespaces", {:column=>"group_id", :on_delete=>:cascade})
+   -> 0.0138s
 -- add_foreign_key("boards", "projects", {:name=>"fk_f15266b5f9", :on_delete=>:cascade})
-   -> 0.0030s
+   -> 0.0118s
 -- add_foreign_key("chat_teams", "namespaces", {:on_delete=>:cascade})
-   -> 0.0021s
+   -> 0.0130s
 -- add_foreign_key("ci_build_trace_section_names", "projects", {:on_delete=>:cascade})
-   -> 0.0022s
+   -> 0.0131s
 -- add_foreign_key("ci_build_trace_sections", "ci_build_trace_section_names", {:column=>"section_name_id", :name=>"fk_264e112c66", :on_delete=>:cascade})
-   -> 0.0018s
+   -> 0.0210s
 -- add_foreign_key("ci_build_trace_sections", "ci_builds", {:column=>"build_id", :name=>"fk_4ebe41f502", :on_delete=>:cascade})
-   -> 0.0024s
+   -> 0.0823s
 -- add_foreign_key("ci_build_trace_sections", "projects", {:on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0942s
 -- add_foreign_key("ci_builds", "ci_pipelines", {:column=>"auto_canceled_by_id", :name=>"fk_a2141b1522", :on_delete=>:nullify})
-   -> 0.0023s
+   -> 0.1346s
 -- add_foreign_key("ci_builds", "ci_stages", {:column=>"stage_id", :name=>"fk_3a9eaa254d", :on_delete=>:cascade})
-   -> 0.0020s
+   -> 0.0506s
 -- add_foreign_key("ci_builds", "projects", {:name=>"fk_befce0568a", :on_delete=>:cascade})
-   -> 0.0024s
+   -> 0.0403s
+-- add_foreign_key("ci_builds_metadata", "ci_builds", {:column=>"build_id", :on_delete=>:cascade})
+   -> 0.0160s
+-- add_foreign_key("ci_builds_metadata", "projects", {:on_delete=>:cascade})
+   -> 0.0165s
 -- add_foreign_key("ci_group_variables", "namespaces", {:column=>"group_id", :name=>"fk_33ae4d58d8", :on_delete=>:cascade})
-   -> 0.0024s
+   -> 0.0153s
 -- add_foreign_key("ci_job_artifacts", "ci_builds", {:column=>"job_id", :on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0160s
 -- add_foreign_key("ci_job_artifacts", "projects", {:on_delete=>:cascade})
-   -> 0.0020s
+   -> 0.0278s
 -- add_foreign_key("ci_pipeline_schedule_variables", "ci_pipeline_schedules", {:column=>"pipeline_schedule_id", :name=>"fk_41c35fda51", :on_delete=>:cascade})
-   -> 0.0027s
+   -> 0.0193s
 -- add_foreign_key("ci_pipeline_schedules", "projects", {:name=>"fk_8ead60fcc4", :on_delete=>:cascade})
-   -> 0.0022s
+   -> 0.0184s
 -- add_foreign_key("ci_pipeline_schedules", "users", {:column=>"owner_id", :name=>"fk_9ea99f58d2", :on_delete=>:nullify})
-   -> 0.0025s
+   -> 0.0158s
 -- add_foreign_key("ci_pipeline_variables", "ci_pipelines", {:column=>"pipeline_id", :name=>"fk_f29c5f4380", :on_delete=>:cascade})
-   -> 0.0018s
+   -> 0.0097s
 -- add_foreign_key("ci_pipelines", "ci_pipeline_schedules", {:column=>"pipeline_schedule_id", :name=>"fk_3d34ab2e06", :on_delete=>:nullify})
-   -> 0.0019s
+   -> 0.0693s
 -- add_foreign_key("ci_pipelines", "ci_pipelines", {:column=>"auto_canceled_by_id", :name=>"fk_262d4c2d19", :on_delete=>:nullify})
-   -> 0.0029s
+   -> 0.1599s
 -- add_foreign_key("ci_pipelines", "projects", {:name=>"fk_86635dbd80", :on_delete=>:cascade})
-   -> 0.0023s
+   -> 0.1505s
 -- add_foreign_key("ci_runner_projects", "projects", {:name=>"fk_4478a6f1e4", :on_delete=>:cascade})
-   -> 0.0036s
+   -> 0.0984s
 -- add_foreign_key("ci_stages", "ci_pipelines", {:column=>"pipeline_id", :name=>"fk_fb57e6cc56", :on_delete=>:cascade})
-   -> 0.0017s
+   -> 0.1152s
 -- add_foreign_key("ci_stages", "projects", {:name=>"fk_2360681d1d", :on_delete=>:cascade})
-   -> 0.0020s
+   -> 0.1062s
 -- add_foreign_key("ci_trigger_requests", "ci_triggers", {:column=>"trigger_id", :name=>"fk_b8ec8b7245", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0455s
 -- add_foreign_key("ci_triggers", "projects", {:name=>"fk_e3e63f966e", :on_delete=>:cascade})
-   -> 0.0021s
+   -> 0.0725s
 -- add_foreign_key("ci_triggers", "users", {:column=>"owner_id", :name=>"fk_e8e10d1964", :on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0774s
 -- add_foreign_key("ci_variables", "projects", {:name=>"fk_ada5eb64b3", :on_delete=>:cascade})
-   -> 0.0021s
+   -> 0.0626s
 -- add_foreign_key("cluster_platforms_kubernetes", "clusters", {:on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0529s
 -- add_foreign_key("cluster_projects", "clusters", {:on_delete=>:cascade})
-   -> 0.0018s
+   -> 0.0678s
 -- add_foreign_key("cluster_projects", "projects", {:on_delete=>:cascade})
-   -> 0.0020s
+   -> 0.0391s
 -- add_foreign_key("cluster_providers_gcp", "clusters", {:on_delete=>:cascade})
-   -> 0.0017s
+   -> 0.0328s
 -- add_foreign_key("clusters", "users", {:on_delete=>:nullify})
-   -> 0.0018s
+   -> 0.1266s
 -- add_foreign_key("clusters_applications_helm", "clusters", {:on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0489s
+-- add_foreign_key("clusters_applications_ingress", "clusters", {:name=>"fk_753a7b41c1", :on_delete=>:cascade})
+   -> 0.0565s
+-- add_foreign_key("clusters_applications_prometheus", "clusters", {:name=>"fk_557e773639", :on_delete=>:cascade})
+   -> 0.0174s
+-- add_foreign_key("clusters_applications_runners", "ci_runners", {:column=>"runner_id", :name=>"fk_02de2ded36", :on_delete=>:nullify})
+   -> 0.0182s
+-- add_foreign_key("clusters_applications_runners", "clusters", {:on_delete=>:cascade})
+   -> 0.0208s
 -- add_foreign_key("container_repositories", "projects")
-   -> 0.0020s
+   -> 0.0186s
 -- add_foreign_key("deploy_keys_projects", "projects", {:name=>"fk_58a901ca7e", :on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0140s
 -- add_foreign_key("deployments", "projects", {:name=>"fk_b9a3851b82", :on_delete=>:cascade})
-   -> 0.0021s
+   -> 0.0328s
 -- add_foreign_key("environments", "projects", {:name=>"fk_d1c8c1da6a", :on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0221s
 -- add_foreign_key("events", "projects", {:on_delete=>:cascade})
-   -> 0.0020s
+   -> 0.0212s
 -- add_foreign_key("events", "users", {:column=>"author_id", :name=>"fk_edfd187b6f", :on_delete=>:cascade})
-   -> 0.0020s
+   -> 0.0150s
 -- add_foreign_key("fork_network_members", "fork_networks", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0134s
 -- add_foreign_key("fork_network_members", "projects", {:column=>"forked_from_project_id", :name=>"fk_b01280dae4", :on_delete=>:nullify})
-   -> 0.0019s
+   -> 0.0200s
 -- add_foreign_key("fork_network_members", "projects", {:on_delete=>:cascade})
-   -> 0.0018s
+   -> 0.0162s
 -- add_foreign_key("fork_networks", "projects", {:column=>"root_project_id", :name=>"fk_e7b436b2b5", :on_delete=>:nullify})
-   -> 0.0018s
+   -> 0.0138s
 -- add_foreign_key("forked_project_links", "projects", {:column=>"forked_to_project_id", :name=>"fk_434510edb0", :on_delete=>:cascade})
-   -> 0.0018s
+   -> 0.0137s
 -- add_foreign_key("gcp_clusters", "projects", {:on_delete=>:cascade})
-   -> 0.0029s
+   -> 0.0148s
 -- add_foreign_key("gcp_clusters", "services", {:on_delete=>:nullify})
-   -> 0.0022s
+   -> 0.0216s
 -- add_foreign_key("gcp_clusters", "users", {:on_delete=>:nullify})
-   -> 0.0019s
+   -> 0.0156s
 -- add_foreign_key("gpg_key_subkeys", "gpg_keys", {:on_delete=>:cascade})
-   -> 0.0017s
+   -> 0.0139s
 -- add_foreign_key("gpg_keys", "users", {:on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0142s
 -- add_foreign_key("gpg_signatures", "gpg_key_subkeys", {:on_delete=>:nullify})
-   -> 0.0016s
+   -> 0.0216s
 -- add_foreign_key("gpg_signatures", "gpg_keys", {:on_delete=>:nullify})
-   -> 0.0016s
+   -> 0.0211s
 -- add_foreign_key("gpg_signatures", "projects", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0215s
 -- add_foreign_key("group_custom_attributes", "namespaces", {:column=>"group_id", :on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0174s
+-- add_foreign_key("internal_ids", "projects", {:on_delete=>:cascade})
+   -> 0.0143s
 -- add_foreign_key("issue_assignees", "issues", {:name=>"fk_b7d881734a", :on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0139s
 -- add_foreign_key("issue_assignees", "users", {:name=>"fk_5e0c8d9154", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0138s
 -- add_foreign_key("issue_metrics", "issues", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0106s
 -- add_foreign_key("issues", "issues", {:column=>"moved_to_id", :name=>"fk_a194299be1", :on_delete=>:nullify})
-   -> 0.0014s
+   -> 0.0366s
 -- add_foreign_key("issues", "milestones", {:name=>"fk_96b1dd429c", :on_delete=>:nullify})
-   -> 0.0016s
+   -> 0.0309s
 -- add_foreign_key("issues", "projects", {:name=>"fk_899c8f3231", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0314s
 -- add_foreign_key("issues", "users", {:column=>"author_id", :name=>"fk_05f1e72feb", :on_delete=>:nullify})
-   -> 0.0015s
+   -> 0.0504s
+-- add_foreign_key("issues", "users", {:column=>"closed_by_id", :name=>"fk_c63cbf6c25", :on_delete=>:nullify})
+   -> 0.0428s
 -- add_foreign_key("issues", "users", {:column=>"updated_by_id", :name=>"fk_ffed080f01", :on_delete=>:nullify})
-   -> 0.0017s
+   -> 0.0333s
 -- add_foreign_key("label_priorities", "labels", {:on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0143s
 -- add_foreign_key("label_priorities", "projects", {:on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0160s
 -- add_foreign_key("labels", "namespaces", {:column=>"group_id", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0176s
 -- add_foreign_key("labels", "projects", {:name=>"fk_7de4989a69", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0216s
+-- add_foreign_key("lfs_file_locks", "projects", {:on_delete=>:cascade})
+   -> 0.0144s
+-- add_foreign_key("lfs_file_locks", "users", {:on_delete=>:cascade})
+   -> 0.0178s
 -- add_foreign_key("lists", "boards", {:name=>"fk_0d3f677137", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0161s
 -- add_foreign_key("lists", "labels", {:name=>"fk_7a5553d60f", :on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0137s
 -- add_foreign_key("members", "users", {:name=>"fk_2e88fb7ce9", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0171s
 -- add_foreign_key("merge_request_diff_commits", "merge_request_diffs", {:on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0143s
 -- add_foreign_key("merge_request_diff_files", "merge_request_diffs", {:on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0106s
 -- add_foreign_key("merge_request_diffs", "merge_requests", {:name=>"fk_8483f3258f", :on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0119s
 -- add_foreign_key("merge_request_metrics", "ci_pipelines", {:column=>"pipeline_id", :on_delete=>:cascade})
-   -> 0.0017s
+   -> 0.0163s
 -- add_foreign_key("merge_request_metrics", "merge_requests", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0204s
 -- add_foreign_key("merge_request_metrics", "users", {:column=>"latest_closed_by_id", :name=>"fk_ae440388cc", :on_delete=>:nullify})
-   -> 0.0015s
+   -> 0.0196s
 -- add_foreign_key("merge_request_metrics", "users", {:column=>"merged_by_id", :name=>"fk_7f28d925f3", :on_delete=>:nullify})
-   -> 0.0015s
+   -> 0.0202s
 -- add_foreign_key("merge_requests", "ci_pipelines", {:column=>"head_pipeline_id", :name=>"fk_fd82eae0b9", :on_delete=>:nullify})
-   -> 0.0014s
+   -> 0.0394s
 -- add_foreign_key("merge_requests", "merge_request_diffs", {:column=>"latest_merge_request_diff_id", :name=>"fk_06067f5644", :on_delete=>:nullify})
-   -> 0.0014s
+   -> 0.0532s
 -- add_foreign_key("merge_requests", "milestones", {:name=>"fk_6a5165a692", :on_delete=>:nullify})
-   -> 0.0015s
+   -> 0.0291s
 -- add_foreign_key("merge_requests", "projects", {:column=>"source_project_id", :name=>"fk_3308fe130c", :on_delete=>:nullify})
-   -> 0.0017s
+   -> 0.0278s
 -- add_foreign_key("merge_requests", "projects", {:column=>"target_project_id", :name=>"fk_a6963e8447", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0367s
 -- add_foreign_key("merge_requests", "users", {:column=>"assignee_id", :name=>"fk_6149611a04", :on_delete=>:nullify})
-   -> 0.0016s
+   -> 0.0327s
 -- add_foreign_key("merge_requests", "users", {:column=>"author_id", :name=>"fk_e719a85f8a", :on_delete=>:nullify})
-   -> 0.0017s
+   -> 0.0337s
 -- add_foreign_key("merge_requests", "users", {:column=>"merge_user_id", :name=>"fk_ad525e1f87", :on_delete=>:nullify})
-   -> 0.0018s
+   -> 0.0517s
 -- add_foreign_key("merge_requests", "users", {:column=>"updated_by_id", :name=>"fk_641731faff", :on_delete=>:nullify})
-   -> 0.0017s
+   -> 0.0335s
 -- add_foreign_key("merge_requests_closing_issues", "issues", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0167s
 -- add_foreign_key("merge_requests_closing_issues", "merge_requests", {:on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0191s
 -- add_foreign_key("milestones", "namespaces", {:column=>"group_id", :name=>"fk_95650a40d4", :on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0206s
 -- add_foreign_key("milestones", "projects", {:name=>"fk_9bd0a0c791", :on_delete=>:cascade})
-   -> 0.0017s
+   -> 0.0221s
 -- add_foreign_key("notes", "projects", {:name=>"fk_99e097b079", :on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0332s
 -- add_foreign_key("oauth_openid_requests", "oauth_access_grants", {:column=>"access_grant_id", :name=>"fk_oauth_openid_requests_oauth_access_grants_access_grant_id"})
-   -> 0.0014s
+   -> 0.0128s
 -- add_foreign_key("pages_domains", "projects", {:name=>"fk_ea2f6dfc6f", :on_delete=>:cascade})
-   -> 0.0021s
+   -> 0.0220s
 -- add_foreign_key("personal_access_tokens", "users")
-   -> 0.0016s
+   -> 0.0187s
 -- add_foreign_key("project_authorizations", "projects", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0149s
 -- add_foreign_key("project_authorizations", "users", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0167s
 -- add_foreign_key("project_auto_devops", "projects", {:on_delete=>:cascade})
-   -> 0.0026s
+   -> 0.0142s
 -- add_foreign_key("project_custom_attributes", "projects", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0218s
 -- add_foreign_key("project_features", "projects", {:name=>"fk_18513d9b92", :on_delete=>:cascade})
-   -> 0.0020s
+   -> 0.0204s
 -- add_foreign_key("project_group_links", "projects", {:name=>"fk_daa8cee94c", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0174s
 -- add_foreign_key("project_import_data", "projects", {:name=>"fk_ffb9ee3a10", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0138s
 -- add_foreign_key("project_statistics", "projects", {:on_delete=>:cascade})
-   -> 0.0021s
+   -> 0.0125s
 -- add_foreign_key("protected_branch_merge_access_levels", "protected_branches", {:name=>"fk_8a3072ccb3", :on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0157s
 -- add_foreign_key("protected_branch_push_access_levels", "protected_branches", {:name=>"fk_9ffc86a3d9", :on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0112s
 -- add_foreign_key("protected_branches", "projects", {:name=>"fk_7a9c6d93e7", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0122s
 -- add_foreign_key("protected_tag_create_access_levels", "namespaces", {:column=>"group_id"})
-   -> 0.0016s
+   -> 0.0131s
 -- add_foreign_key("protected_tag_create_access_levels", "protected_tags", {:name=>"fk_f7dfda8c51", :on_delete=>:cascade})
-   -> 0.0013s
+   -> 0.0168s
 -- add_foreign_key("protected_tag_create_access_levels", "users")
-   -> 0.0018s
+   -> 0.0221s
 -- add_foreign_key("protected_tags", "projects", {:name=>"fk_8e4af87648", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0135s
 -- add_foreign_key("push_event_payloads", "events", {:name=>"fk_36c74129da", :on_delete=>:cascade})
-   -> 0.0013s
+   -> 0.0107s
 -- add_foreign_key("releases", "projects", {:name=>"fk_47fe2a0596", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0131s
 -- add_foreign_key("services", "projects", {:name=>"fk_71cce407f9", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0142s
 -- add_foreign_key("snippets", "projects", {:name=>"fk_be41fd4bb7", :on_delete=>:cascade})
-   -> 0.0017s
+   -> 0.0178s
 -- add_foreign_key("subscriptions", "projects", {:on_delete=>:cascade})
-   -> 0.0018s
+   -> 0.0160s
 -- add_foreign_key("system_note_metadata", "notes", {:name=>"fk_d83a918cb1", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0156s
 -- add_foreign_key("timelogs", "issues", {:name=>"fk_timelogs_issues_issue_id", :on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0183s
 -- add_foreign_key("timelogs", "merge_requests", {:name=>"fk_timelogs_merge_requests_merge_request_id", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0198s
+-- add_foreign_key("todos", "notes", {:name=>"fk_91d1f47b13", :on_delete=>:cascade})
+   -> 0.0276s
 -- add_foreign_key("todos", "projects", {:name=>"fk_45054f9c45", :on_delete=>:cascade})
-   -> 0.0018s
+   -> 0.0175s
+-- add_foreign_key("todos", "users", {:column=>"author_id", :name=>"fk_ccf0373936", :on_delete=>:cascade})
+   -> 0.0182s
+-- add_foreign_key("todos", "users", {:name=>"fk_d94154aa95", :on_delete=>:cascade})
+   -> 0.0184s
 -- add_foreign_key("trending_projects", "projects", {:on_delete=>:cascade})
-   -> 0.0015s
+   -> 0.0338s
 -- add_foreign_key("u2f_registrations", "users")
-   -> 0.0017s
+   -> 0.0176s
+-- add_foreign_key("user_callouts", "users", {:on_delete=>:cascade})
+   -> 0.0160s
 -- add_foreign_key("user_custom_attributes", "users", {:on_delete=>:cascade})
-   -> 0.0019s
+   -> 0.0191s
+-- add_foreign_key("user_interacted_projects", "projects", {:name=>"fk_722ceba4f7", :on_delete=>:cascade})
+   -> 0.0171s
+-- add_foreign_key("user_interacted_projects", "users", {:name=>"fk_0894651f08", :on_delete=>:cascade})
+   -> 0.0155s
 -- add_foreign_key("user_synced_attributes_metadata", "users", {:on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0164s
 -- add_foreign_key("users_star_projects", "projects", {:name=>"fk_22cd27ddfc", :on_delete=>:cascade})
-   -> 0.0016s
+   -> 0.0180s
 -- add_foreign_key("web_hook_logs", "web_hooks", {:on_delete=>:cascade})
-   -> 0.0014s
+   -> 0.0164s
 -- add_foreign_key("web_hooks", "projects", {:name=>"fk_0c8ca6d9d1", :on_delete=>:cascade})
-   -> 0.0017s
+   -> 0.0172s
 -- initialize_schema_migrations_table()
-   -> 0.0112s
+   -> 0.0212s
+Adding limits to schema.rb for mysql
+-- column_exists?(:merge_request_diffs, :st_commits)
+   -> 0.0010s
+-- column_exists?(:merge_request_diffs, :st_diffs)
+   -> 0.0006s
+-- change_column(:snippets, :content, :text, {:limit=>2147483647})
+   -> 0.0308s
+-- change_column(:notes, :st_diff, :text, {:limit=>2147483647})
+   -> 0.0366s
+-- change_column(:snippets, :content_html, :text, {:limit=>2147483647})
+   -> 0.0272s
+-- change_column(:merge_request_diff_files, :diff, :text, {:limit=>2147483647})
+   -> 0.0170s
+$ date
+Thu Apr  5 11:19:41 UTC 2018
 $ JOB_NAME=( $CI_JOB_NAME )
 $ export CI_NODE_INDEX=${JOB_NAME[-2]}
 $ export CI_NODE_TOTAL=${JOB_NAME[-1]}
 $ export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
 $ export KNAPSACK_GENERATE_REPORT=true
+$ export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH}
+$ export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${JOB_NAME[0]}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+$ export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${JOB_NAME[0]}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+$ export FLAKY_RSPEC_GENERATE_REPORT=true
 $ export CACHE_CLASSES=true
-$ cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
+$ cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
+$ [[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}
+$ [[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}
 $ scripts/gitaly-test-spawn
-Gem.path: ["/root/.gem/ruby/2.3.0", "/usr/local/lib/ruby/gems/2.3.0", "/usr/local/bundle"]
-ENV['BUNDLE_GEMFILE']: nil
-ENV['RUBYOPT']: nil
-bundle config in /builds/gitlab-org/gitlab-ce
-scripts/gitaly-test-spawn:10:in `<main>': undefined local variable or method `gitaly_dir' for main:Object (NameError)
-Did you mean?  gitaly_dir
-Settings are listed in order of priority. The top value will be used.
-retry
-Set for your local app (/usr/local/bundle/config): 3
+59
+$ knapsack rspec "--color --format documentation"
+
+Report specs:
+spec/services/todo_service_spec.rb
+spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+spec/controllers/projects/merge_requests_controller_spec.rb
+spec/controllers/groups_controller_spec.rb
+spec/features/projects/import_export/import_file_spec.rb
+spec/lib/gitlab/middleware/go_spec.rb
+spec/services/groups/transfer_service_spec.rb
+spec/features/projects/blobs/edit_spec.rb
+spec/services/boards/lists/move_service_spec.rb
+spec/services/create_deployment_service_spec.rb
+spec/controllers/groups/milestones_controller_spec.rb
+spec/helpers/groups_helper_spec.rb
+spec/requests/api/v3/todos_spec.rb
+spec/models/project_services/teamcity_service_spec.rb
+spec/lib/gitlab/conflict/file_spec.rb
+spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+spec/finders/autocomplete_users_finder_spec.rb
+spec/models/service_spec.rb
+spec/services/test_hooks/project_service_spec.rb
+spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb
+spec/finders/runner_jobs_finder_spec.rb
+spec/features/projects/snippets_spec.rb
+spec/requests/api/v3/environments_spec.rb
+spec/requests/api/namespaces_spec.rb
+spec/services/merge_requests/get_urls_service_spec.rb
+spec/models/lfs_file_lock_spec.rb
+spec/lib/gitlab/ci/config/entry/boolean_spec.rb
+
+Leftover specs:
+
+Knapsack report generator started!
+
+==> Setting up GitLab Shell...
+    GitLab Shell setup in 0.307428917 seconds...
+
+==> Setting up Gitaly...
+    Gitaly setup in 0.000135767 seconds...
+
+TodoService
+  updates cached counts when a todo is created
+  Issues
+    #new_issue
+      creates a todo if assigned
+      does not create a todo if unassigned
+      creates a todo if assignee is the current user
+      creates a todo for each valid mentioned user
+      creates a directly addressed todo for each valid addressed user
+      creates correct todos for each valid user based on the type of mention
+      does not create todo if user can not see the issue when issue is confidential
+      does not create directly addressed todo if user cannot see the issue when issue is confidential
+      when a private group is mentioned
+        creates a todo for group members
+    #update_issue
+      creates a todo for each valid mentioned user not included in skip_users
+      creates a todo for each valid user not included in skip_users based on the type of mention
+      creates a directly addressed todo for each valid addressed user not included in skip_users
+      does not create a todo if user was already mentioned and todo is pending
+      does not create a todo if user was already mentioned and todo is done
+      does not create a directly addressed todo if user was already mentioned or addressed and todo is pending
+      does not create a directly addressed todo if user was already mentioned or addressed and todo is done
+      does not create todo if user can not see the issue when issue is confidential
+      does not create a directly addressed todo if user can not see the issue when issue is confidential
+      issues with a task list
+        does not create todo when tasks are marked as completed
+        does not create directly addressed todo when tasks are marked as completed
+        does not raise an error when description not change
+    #close_issue
+      marks related pending todos to the target for the user as done
+    #destroy_target
+      refreshes the todos count cache for users with todos on the target
+      does not refresh the todos count cache for users with only done todos on the target
+      yields the target to the caller
+    #reassigned_issue
+      creates a pending todo for new assignee
+      does not create a todo if unassigned
+      creates a todo if new assignee is the current user
+    #mark_pending_todos_as_done
+      marks related pending todos to the target for the user as done
+      cached counts
+        updates when todos change
+    #mark_todos_as_done
+      behaves like updating todos state
+        updates related todos for the user with the new_state
+        returns the updated ids
+        cached counts
+          updates when todos change
+    #mark_todos_as_done_by_ids
+      behaves like updating todos state
+        updates related todos for the user with the new_state
+        returns the updated ids
+        cached counts
+          updates when todos change
+    #mark_todos_as_pending
+      behaves like updating todos state
+        updates related todos for the user with the new_state
+        returns the updated ids
+        cached counts
+          updates when todos change
+    #mark_todos_as_pending_by_ids
+      behaves like updating todos state
+        updates related todos for the user with the new_state
+        returns the updated ids
+        cached counts
+          updates when todos change
+    #new_note
+      mark related pending todos to the noteable for the note author as done
+      does not mark related pending todos it is a system note
+      creates a todo for each valid mentioned user
+      creates a todo for each valid user based on the type of mention
+      creates a directly addressed todo for each valid addressed user
+      does not create todo if user can not see the issue when leaving a note on a confidential issue
+      does not create a directly addressed todo if user can not see the issue when leaving a note on a confidential issue
+      does not create todo when leaving a note on snippet
+      on commit
+        creates a todo for each valid mentioned user when leaving a note on commit
+        creates a directly addressed todo for each valid mentioned user when leaving a note on commit
+    #mark_todo
+      creates a todo from a issue
+    #todo_exists?
+      returns false when no todo exist for the given issuable
+      returns true when a todo exist for the given issuable
+  Merge Requests
+    #new_merge_request
+      creates a pending todo if assigned
+      does not create a todo if unassigned
+      does not create a todo if assignee is the current user
+      creates a todo for each valid mentioned user
+      creates a todo for each valid user based on the type of mention
+      creates a directly addressed todo for each valid addressed user
+    #update_merge_request
+      creates a todo for each valid mentioned user not included in skip_users
+      creates a todo for each valid user not included in skip_users based on the type of mention
+      creates a directly addressed todo for each valid addressed user not included in skip_users
+      does not create a todo if user was already mentioned and todo is pending
+      does not create a todo if user was already mentioned and todo is done
+      does not create a directly addressed todo if user was already mentioned or addressed and todo is pending
+      does not create a directly addressed todo if user was already mentioned or addressed and todo is done
+      with a task list
+        does not create todo when tasks are marked as completed
+        does not create directly addressed todo when tasks are marked as completed
+        does not raise an error when description not change
+    #close_merge_request
+      marks related pending todos to the target for the user as done
+    #reassigned_merge_request
+      creates a pending todo for new assignee
+      does not create a todo if unassigned
+      creates a todo if new assignee is the current user
+      does not create a todo for guests
+      does not create a directly addressed todo for guests
+    #merge_merge_request
+      marks related pending todos to the target for the user as done
+      does not create todo for guests
+      does not create directly addressed todo for guests
+    #new_award_emoji
+      marks related pending todos to the target for the user as done
+    #merge_request_build_failed
+      creates a pending todo for the merge request author
+      creates a pending todo for merge_user
+    #merge_request_push
+      marks related pending todos to the target for the user as done
+    #merge_request_became_unmergeable
+      creates a pending todo for a merge_user
+    #mark_todo
+      creates a todo from a merge request
+    #new_note
+      creates a todo for mentioned user on new diff note
+      creates a directly addressed todo for addressed user on new diff note
+      creates a todo for mentioned user on legacy diff note
+      does not create todo for guests
+  #update_note
+    creates a todo for each valid mentioned user not included in skip_users
+    creates a todo for each valid user not included in skip_users based on the type of mention
+    creates a directly addressed todo for each valid addressed user not included in skip_users
+    does not create a todo if user was already mentioned and todo is pending
+    does not create a todo if user was already mentioned and todo is done
+    does not create a directly addressed todo if user was already mentioned or addressed and todo is pending
+    does not create a directly addressed todo if user was already mentioned or addressed and todo is done
+  #mark_todos_as_done
+    marks a relation of todos as done
+    marks an array of todos as done
+    returns the ids of updated todos
+    when some of the todos are done already
+      returns the ids of those still pending
+      returns an empty array if all are done
+  #mark_todos_as_done_by_ids
+    marks an array of todo ids as done
+    marks a single todo id as done
+    caches the number of todos of a user
+
+Gitlab::ImportExport::ProjectTreeSaver
+  saves the project tree into a json object
+    saves project successfully
+    JSON
+      saves the correct json
+      has milestones
+      has merge requests
+      has merge request's milestones
+      has merge request's source branch SHA
+      has merge request's target branch SHA
+      has events
+      has snippets
+      has snippet notes
+      has releases
+      has issues
+      has issue comments
+      has issue assignees
+      has author on issue comments
+      has project members
+      has merge requests diffs
+      has merge request diff files
+      has merge request diff commits
+      has merge requests comments
+      has author on merge requests comments
+      has pipeline stages
+      has pipeline statuses
+      has pipeline builds
+      has no when YML attributes but only the DB column
+      has pipeline commits
+      has ci pipeline notes
+      has labels with no associations
+      has labels associated to records
+      has project and group labels
+      has priorities associated to labels
+      saves the correct service type
+      saves the properties for a service
+      has project feature
+      has custom attributes
+      has badges
+      does not complain about non UTF-8 characters in MR diff files
+      with description override
+        overrides the project description
+      group members
+        does not export group members if it has no permission
+        does not export group members as master
+        exports group members as group owner
+        as admin
+          exports group members as admin
+          exports group members as project members
+      project attributes
+        contains the html description
+        does not contain the runners token
+
+Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits
+  #perform
+    when the diff IDs passed do not exist
+      does not raise
+    when the merge request diff has no serialised commits or diffs
+      does not raise
+    processing multiple merge request diffs
+      when BUFFER_ROWS is exceeded
+        inserts commit rows in chunks of BUFFER_ROWS
+        inserts diff rows in chunks of DIFF_FILE_BUFFER_ROWS
+      when BUFFER_ROWS is not exceeded
+        only updates once
+      when some rows were already inserted due to a previous failure
+        does not raise
+        logs a message
+        ends up with the correct rows
+      when the merge request diff update fails
+        raises an error
+        logs the error
+        still adds diff commits
+        still adds diff files
+    when the merge request diff has valid commits and diffs
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diff has diffs but no commits
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diffs do not have too_large set
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diffs do not have a_mode and b_mode set
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diffs have binary content
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diff has commits, but no diffs
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diffs have invalid content
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diffs are Rugged::Patch instances
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+    when the merge request diffs are Rugged::Diff::Delta instances
+      creates correct entries in the merge_request_diff_commits table
+      creates correct entries in the merge_request_diff_files table
+      sets the st_commits and st_diffs columns to nil
+
+Projects::MergeRequestsController
+  GET commit_change_content
+    renders commit_change_content template
+  GET show
+    behaves like loads labels
+      loads labels into the @labels variable
+    as html
+      renders merge request page
+      loads notes
+        with special_role FIRST_TIME_CONTRIBUTOR
+    as json
+      with basic serializer param
+        renders basic MR entity as json
+      with widget serializer param
+        renders widget MR entity as json
+      when no serialiser was passed
+        renders widget MR entity as json
+    as diff
+      triggers workhorse to serve the request
+    as patch
+      triggers workhorse to serve the request
+  GET index
+    behaves like issuables list meta-data
+      creates indexed meta-data object for issuable notes and votes count
+      when given empty collection
+        doesn't execute any queries with false conditions
+    when page param
+      redirects to last_page if page number is larger than number of pages
+      redirects to specified page
+      does not redirect to external sites when provided a host field
+    when filtering by opened state
+      with opened merge requests
+        lists those merge requests
+      with reopened merge requests
+        lists those merge requests
+  PUT update
+    changing the assignee
+      limits the attributes exposed on the assignee
+    when user does not have access to update issue
+      responds with 404
+    there is no source project
+      closes MR without errors
+      allows editing of a closed merge request
+      does not allow to update target branch closed merge request
+      behaves like update invalid issuable
+        when updating causes conflicts
+          renders edit when format is html
+          renders json error message when format is json
+        when updating an invalid issuable
+          renders edit when merge request is invalid
+  POST merge
+    when user cannot access
+      returns 404
+    when the merge request is not mergeable
+      returns :failed
+    when the sha parameter does not match the source SHA
+      returns :sha_mismatch
+    when the sha parameter matches the source SHA
+      returns :success
+      starts the merge immediately
+      when the pipeline succeeds is passed
+        returns :merge_when_pipeline_succeeds
+        sets the MR to merge when the pipeline succeeds
+        when project.only_allow_merge_if_pipeline_succeeds? is true
+          returns :merge_when_pipeline_succeeds
+          and head pipeline is not the current one
+            returns :failed
+      only_allow_merge_if_all_discussions_are_resolved? setting
+        when enabled
+          with unresolved discussion
+            returns :failed
+          with all discussions resolved
+            returns :success
+        when disabled
+          with unresolved discussion
+            returns :success
+          with all discussions resolved
+            returns :success
+  DELETE destroy
+    denies access to users unless they're admin or project owner
+    when the user is owner
+      deletes the merge request
+      delegates the update of the todos count cache to TodoService
+  GET commits
+    renders the commits template to a string
+  GET pipelines
+    responds with serialized pipelines
+  POST remove_wip
+    removes the wip status
+    renders MergeRequest as JSON
+  POST cancel_merge_when_pipeline_succeeds
+    calls MergeRequests::MergeWhenPipelineSucceedsService
+    should respond with numeric status code success
+    renders MergeRequest as JSON
+  POST assign_related_issues
+    shows a flash message on success
+    correctly pluralizes flash message on success
+    calls MergeRequests::AssignIssuesService
+    is skipped when not signed in
+  GET ci_environments_status
+    the environment is from a forked project
+      links to the environment on that project
+  GET pipeline_status.json
+    when head_pipeline exists
+      return a detailed head_pipeline status in json
+    when head_pipeline does not exist
+      return empty
+  POST #rebase
+    successfully
+      enqeues a RebaseWorker
+    with a forked project
+      user cannot push to source branch
+        returns 404
+      user can push to source branch
+        returns 200
+
+GroupsController
+  GET #show
+    as html
+      assigns whether or not a group has children
+    as atom
+      assigns events for all the projects in the group
+  GET #new
+    when creating subgroups
+      and can_create_group is true
+        and logged in as Admin
+          behaves like member with ability to create subgroups
+            renders the new page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Owner
+          behaves like member with ability to create subgroups
+            renders the new page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Guest
+          behaves like member without ability to create subgroups
+            renders the 404 page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Developer
+          behaves like member without ability to create subgroups
+            renders the 404 page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Master
+          behaves like member without ability to create subgroups
+            renders the 404 page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+      and can_create_group is false
+        and logged in as Admin
+          behaves like member with ability to create subgroups
+            renders the new page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Owner
+          behaves like member with ability to create subgroups
+            renders the new page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Guest
+          behaves like member without ability to create subgroups
+            renders the 404 page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Developer
+          behaves like member without ability to create subgroups
+            renders the 404 page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Master
+          behaves like member without ability to create subgroups
+            renders the 404 page (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+  GET #activity
+    as json
+      includes all projects in event feed
+  POST #create
+    when creating subgroups
+      and can_create_group is true
+        and logged in as Owner
+          creates the subgroup (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Developer
+          renders the new template (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+      and can_create_group is false
+        and logged in as Owner
+          creates the subgroup (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+        and logged in as Developer
+          renders the new template (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    when creating a top level group
+      and can_create_group is enabled
+        creates the Group
+      and can_create_group is disabled
+        does not create the Group
+  GET #index
+    as a user
+      redirects to Groups Dashboard
+    as a guest
+      redirects to Explore Groups
+  GET #issues
+    sorting by votes
+      sorts most popular issues
+      sorts least popular issues
+  GET #merge_requests
+    sorting by votes
+      sorts most popular merge requests
+      sorts least popular merge requests
+  DELETE #destroy
+    as another user
+      returns 404
+    as the group owner
+      schedules a group destroy
+      redirects to the root path
+  PUT update
+    updates the path successfully
+    does not update the path on error
+  #ensure_canonical_path
+    for a GET request
+      when requesting groups at the root path
+        when requesting the canonical path with different casing
+          redirects to the correct casing
+        when requesting a redirected path
+          redirects to the canonical path
+          when the old group path is a substring of the scheme or host
+            does not modify the requested host
+          when the old group path is substring of groups
+            does not modify the /groups part of the path
+      when requesting groups under the /groups path
+        when requesting the canonical path
+          non-show path
+            with exactly matching casing
+              does not redirect
+            with different casing
+              redirects to the correct casing
+          show path
+            with exactly matching casing
+              does not redirect
+            with different casing
+              redirects to the correct casing at the root path
+        when requesting a redirected path
+          redirects to the canonical path
+          when the old group path is a substring of the scheme or host
+            does not modify the requested host
+          when the old group path is substring of groups
+            does not modify the /groups part of the path
+          when the old group path is substring of groups plus the new path
+            does not modify the /groups part of the path
+      for a POST request
+        when requesting the canonical path with different casing
+          does not 404
+          does not redirect to the correct casing
+        when requesting a redirected path
+          returns not found
+      for a DELETE request
+        when requesting the canonical path with different casing
+          does not 404
+          does not redirect to the correct casing
+        when requesting a redirected path
+          returns not found
+  PUT transfer
+    when transfering to a subgroup goes right
+      should return a notice (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      should redirect to the new path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+    when converting to a root group goes right
+      should return a notice (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      should redirect to the new path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+    When the transfer goes wrong
+      should return an alert (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      should redirect to the current path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+    when the user is not allowed to transfer the group
+      should be denied (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+
+Import/Export - project import integration test
+Starting the Capybara driver server...
+  invalid project
+  when selecting the namespace
+    prefilled the path
+      user imports an exported project successfully
+    path is not prefilled
+      user imports an exported project successfully
+
+Gitlab::Middleware::Go
+  #call
+    when go-get=0
+      skips go-import generation
+    when go-get=1
+      with SSH disabled
+        with simple 2-segment project path
+          with subpackages
+            returns the full project path
+          without subpackages
+            returns the full project path
+        with a nested project path
+          with subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          with a subpackage that is not a valid project path
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          without subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+        with a bogus path
+          skips go-import generation
+      with HTTP disabled
+        with simple 2-segment project path
+          with subpackages
+            returns the full project path
+          without subpackages
+            returns the full project path
+        with a nested project path
+          with subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          with a subpackage that is not a valid project path
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          without subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+        with a bogus path
+          skips go-import generation
+      with nothing disabled
+        with simple 2-segment project path
+          with subpackages
+            returns the full project path
+          without subpackages
+            returns the full project path
+        with a nested project path
+          with subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          with a subpackage that is not a valid project path
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          without subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+        with a bogus path
+          skips go-import generation
+      with nothing disabled (blank string)
+        with simple 2-segment project path
+          with subpackages
+            returns the full project path
+          without subpackages
+            returns the full project path
+        with a nested project path
+          with subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          with a subpackage that is not a valid project path
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+          without subpackages
+            behaves like a nested project
+              when the project is public
+                returns the full project path
+              when the project is private
+                when not authenticated
+                  behaves like unauthorized
+                    returns the 2-segment group path
+                when authenticated
+                  using warden
+                    when active
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    when blocked
+                      behaves like unauthorized
+                        returns the 2-segment group path
+                  using a personal access token
+                    with api scope
+                      behaves like authenticated
+                        with access to the project
+                          returns the full project path
+                        without access to the project
+                          behaves like unauthorized
+                            returns the 2-segment group path
+                    with read_user scope
+                      behaves like unauthorized
+                        returns the 2-segment group path
+        with a bogus path
+          skips go-import generation
+
+Groups::TransferService
+  #execute
+    when transforming a group into a root group
+      behaves like ensuring allowed transfer for a group
+        with other database than PostgreSQL
+          should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+          should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when there's an exception on Gitlab shell directories
+          should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+          should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the group is already a root group
+        should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the user does not have the right policies
+        should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when there is a group with the same path
+        should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the group is a subgroup and the transfer is valid
+        should update group attributes (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should update group children path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should update group projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+    when transferring a subgroup into another group
+      behaves like ensuring allowed transfer for a group
+        with other database than PostgreSQL
+          should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+          should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when there's an exception on Gitlab shell directories
+          should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+          should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the new parent group is the same as the previous parent group
+        should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the user does not have the right policies
+        should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the parent has a group with the same path
+        should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the parent group has a project with the same path
+        should return false (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should add an error on group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when the group is allowed to be transferred
+        should update visibility for the group based on the parent group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should update parent group to the new parent (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should return the group as children of the new parent (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should create a redirect for the group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when the group has a lower visibility than the parent group
+          should not update the visibility for the group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when the group has a higher visibility than the parent group
+          should update visibility level based on the parent group (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when transferring a group with group descendants
+        should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should create redirects for the subgroups (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when the new parent has a higher visibility than the children
+          should not update the children visibility (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when the new parent has a lower visibility than the children
+          should update children visibility to match the new parent (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when transferring a group with project descendants
+        should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should create permanent redirects for the projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when the new parent has a higher visibility than the projects
+          should not update projects visibility (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        when the new parent has a lower visibility than the projects
+          should update projects visibility to match the new parent (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when transferring a group with subgroups & projects descendants
+        should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when transfering a group with nested groups and projects
+        should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+        should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+      when updating the group goes wrong
+        should restore group and projects visibility (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
+
+Editing file blob
+  as a developer
+    from MR diff
+      returns me to the mr
+    from blob file path
+      updates content
+      previews content
+  visit blob edit
+    redirects to sign in and returns
+      as developer
+        redirects to sign in and returns
+      as guest
+        redirects to sign in and returns
+    as developer
+      on some branch
+        shows blob editor with same branch
+      with protected branch
+        shows blob editor with patch branch
+    as master
+      shows blob editor with same branch
+
+Boards::Lists::MoveService
+  #execute
+    when board parent is a project
+      behaves like lists move service
+        keeps position of lists when list type is closed
+        when list type is set to label
+          keeps position of lists when new position is nil
+          keeps position of lists when new positon is equal to old position
+          keeps position of lists when new positon is negative
+          keeps position of lists when new positon is equal to number of labels lists
+          keeps position of lists when new positon is greater than number of labels lists
+          increments position of intermediate lists when new positon is equal to first position
+          decrements position of intermediate lists when new positon is equal to last position
+          decrements position of intermediate lists when new position is greater than old position
+          increments position of intermediate lists when new position is lower than old position
+    when board parent is a group
+      behaves like lists move service
+        keeps position of lists when list type is closed
+        when list type is set to label
+          keeps position of lists when new position is nil
+          keeps position of lists when new positon is equal to old position
+          keeps position of lists when new positon is negative
+          keeps position of lists when new positon is equal to number of labels lists
+          keeps position of lists when new positon is greater than number of labels lists
+          increments position of intermediate lists when new positon is equal to first position
+          decrements position of intermediate lists when new positon is equal to last position
+          decrements position of intermediate lists when new position is greater than old position
+          increments position of intermediate lists when new position is lower than old position
+
+CreateDeploymentService
+  #execute
+    when environment exists
+      creates a deployment
+    when environment does not exist
+      does not create a deployment
+    when start action is defined
+      and environment is stopped
+        makes environment available
+        creates a deployment
+    when stop action is defined
+      and environment is available
+        makes environment stopped
+        does not create a deployment
+    when variables are used
+      creates a new deployment
+      does not create a new environment
+      updates external url
+    when project was removed
+      does not create deployment or environment
+  #expanded_environment_url
+    when yaml environment uses $CI_COMMIT_REF_NAME
+      should eq "http://review/master"
+    when yaml environment uses $CI_ENVIRONMENT_SLUG
+      should eq "http://review/prod-slug"
+    when yaml environment uses yaml_variables containing symbol keys
+      should eq "http://review/host"
+    when yaml environment does not have url
+      returns the external_url from persisted environment
+  processing of builds
+    without environment specified
+      behaves like does not create deployment
+        does not create a new deployment
+        does not call a service
+    when environment is specified
+      when job succeeds
+        behaves like creates deployment
+          creates a new deployment
+          calls a service
+          is set as deployable
+          updates environment URL
+      when job fails
+        behaves like does not create deployment
+          does not create a new deployment
+          does not call a service
+      when job is retried
+        behaves like creates deployment
+          creates a new deployment
+          calls a service
+          is set as deployable
+          updates environment URL
+  merge request metrics
+    while updating the 'first_deployed_to_production_at' time
+      for merge requests merged before the current deploy
+        sets the time if the deploy's environment is 'production'
+        doesn't set the time if the deploy's environment is not 'production'
+        does not raise errors if the merge request does not have a metrics record
+      for merge requests merged before the previous deploy
+        if the 'first_deployed_to_production_at' time is already set
+          does not overwrite the older 'first_deployed_to_production_at' time
+        if the 'first_deployed_to_production_at' time is not already set
+          does not overwrite the older 'first_deployed_to_production_at' time
+
+Groups::MilestonesController
+  #index
+    shows group milestones page
+    as JSON
+      lists legacy group milestones and group milestones
+  #show
+    when there is a title parameter
+      searchs for a legacy group milestone
+    when there is not a title parameter
+      searchs for a group milestone
+  behaves like milestone tabs
+    #merge_requests
+      as html
+        redirects to milestone#show
+      as json
+        renders the merge requests tab template to a string
+    #participants
+      as html
+        redirects to milestone#show
+      as json
+        renders the participants tab template to a string
+    #labels
+      as html
+        redirects to milestone#show
+      as json
+        renders the labels tab template to a string
+  #create
+    creates group milestone with Chinese title
+  #update
+    updates group milestone
+    legacy group milestones
+      updates only group milestones state
+  #ensure_canonical_path
+    for a GET request
+      when requesting the canonical path
+        non-show path
+          with exactly matching casing
+            does not redirect
+          with different casing
+            redirects to the correct casing
+        show path
+          with exactly matching casing
+            does not redirect
+          with different casing
+            redirects to the correct casing
+      when requesting a redirected path
+        redirects to the canonical path
+        when the old group path is a substring of the scheme or host
+          does not modify the requested host
+        when the old group path is substring of groups
+          does not modify the /groups part of the path
+        when the old group path is substring of groups plus the new path
+          does not modify the /groups part of the path
+  for a non-GET request
+    when requesting the canonical path with different casing
+      does not 404
+      does not redirect to the correct casing
+    when requesting a redirected path
+      returns not found
+
+GroupsHelper
+  group_icon
+    returns an url for the avatar
+  group_icon_url
+    returns an url for the avatar
+    gives default avatar_icon when no avatar is present
+  group_lfs_status
+    only one project in group
+      returns all projects as enabled
+      returns all projects as disabled
+    more than one project in group
+      LFS enabled in group
+        returns both projects as enabled
+        returns only one as enabled
+      LFS disabled in group
+        returns both projects as disabled
+        returns only one as disabled
+  group_title
+    outputs the groups in the correct order (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+  #share_with_group_lock_help_text
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :subgroup
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :subgroup
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :subgroup
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :ancestor_locked_but_you_can_override, linked_ancestor: :subgroup
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :ancestor_locked_but_you_can_override, linked_ancestor: :subgroup
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :ancestor_locked_so_ask_the_owner, linked_ancestor: :subgroup
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :root_group
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :root_group
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :root_group
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :ancestor_locked_but_you_can_override, linked_ancestor: :root_group
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :ancestor_locked_so_ask_the_owner, linked_ancestor: :root_group
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :ancestor_locked_so_ask_the_owner, linked_ancestor: :root_group
+      has the correct help text with correct ancestor links (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+  #group_sidebar_links
+    returns all the expected links
+    includes settings when the user can admin the group
+    excludes cross project features when the user cannot read cross project
+
+API::V3::Todos
+  DELETE /todos/:id
+    when unauthenticated
+      returns authentication error
+    when authenticated
+      marks a todo as done
+      updates todos cache
+      returns 404 if the todo does not belong to the current user
+  DELETE /todos
+    when unauthenticated
+      returns authentication error
+    when authenticated
+      marks all todos as done
+      updates todos cache
+
+TeamcityService
+  Associations
+    should belong to project
+    should have one service_hook
+  Validations
+    when service is active
+      should validate that :build_type cannot be empty/falsy
+      should validate that :teamcity_url cannot be empty/falsy
+      behaves like issue tracker service URL attribute
+        should allow :teamcity_url to be 鈥�"https://example.com"鈥�
+        should not allow :teamcity_url to be 鈥�"example.com"鈥�
+        should not allow :teamcity_url to be 鈥�"ftp://example.com"鈥�
+        should not allow :teamcity_url to be 鈥�"herp-and-derp"鈥�
+      #username
+        does not validate the presence of username if password is nil
+        validates the presence of username if password is present
+      #password
+        does not validate the presence of password if username is nil
+        validates the presence of password if username is present
+    when service is inactive
+      should not validate that :build_type cannot be empty/falsy
+      should not validate that :teamcity_url cannot be empty/falsy
+      should not validate that :username cannot be empty/falsy
+      should not validate that :password cannot be empty/falsy
+  Callbacks
+    before_update :reset_password
+      saves password if new url is set together with password when no password was previously set
+      when a password was previously set
+        resets password if url changed
+        does not reset password if username changed
+        does not reset password if new url is set together with password, even if it's the same password
+  #build_page
+    returns the contents of the reactive cache
+  #commit_status
+    returns the contents of the reactive cache
+  #calculate_reactive_cache
+    build_page
+      returns a specific URL when status is 500
+      returns a build URL when teamcity_url has no trailing slash
+      teamcity_url has trailing slash
+        returns a build URL
+    commit_status
+      sets commit status to :error when status is 500
+      sets commit status to "pending" when status is 404
+      sets commit status to "success" when build status contains SUCCESS
+      sets commit status to "failed" when build status contains FAILURE
+      sets commit status to "pending" when build status contains Pending
+      sets commit status to :error when build status is unknown
+
+Gitlab::Conflict::File
+  #resolve_lines
+    raises ResolutionError when passed a hash without resolutions for all sections
+    when resolving everything to the same side
+      has the correct number of lines
+      has content matching the chosen lines
+    with mixed resolutions
+      has the correct number of lines
+      returns a file containing only the chosen parts of the resolved sections
+  #highlight_lines!
+    modifies the existing lines
+    is called implicitly when rich_text is accessed on a line
+    sets the rich_text of the lines matching the text content
+    highlights the lines correctly
+  #sections
+    only inserts match lines when there is a gap between sections
+    sets conflict to false for sections with only unchanged lines
+    only includes a maximum of CONTEXT_LINES (plus an optional match line) in context sections
+    sets conflict to true for sections with only changed lines
+    adds unique IDs to conflict sections, and not to other sections
+    with an example file
+      sets the correct match line headers
+      does not add match lines where they are not needed
+      creates context sections of the correct length
+  #as_json
+    includes the blob path for the file
+    includes the blob icon for the file
+    with the full_content option passed
+      includes the full content of the conflict
+      includes the detected language of the conflict file
+
+Banzai::Filter::SnippetReferenceFilter
+  requires project context
+  ignores valid references contained inside 'pre' element
+  ignores valid references contained inside 'code' element
+  ignores valid references contained inside 'a' element
+  ignores valid references contained inside 'style' element
+  internal reference
+    links to a valid reference
+    links with adjacent text
+    ignores invalid snippet IDs
+    includes a title attribute
+    escapes the title attribute
+    includes default classes
+    includes a data-project attribute
+    includes a data-snippet attribute
+    supports an :only_path context
+  cross-project / cross-namespace complete reference
+    links to a valid reference
+    link has valid text
+    has valid text
+    ignores invalid snippet IDs on the referenced project
+  cross-project / same-namespace complete reference
+    links to a valid reference
+    link has valid text
+    has valid text
+    ignores invalid snippet IDs on the referenced project
+  cross-project shorthand reference
+    links to a valid reference
+    link has valid text
+    has valid text
+    ignores invalid snippet IDs on the referenced project
+  cross-project URL reference
+    links to a valid reference
+    links with adjacent text
+    ignores invalid snippet IDs on the referenced project
+  group context
+    links to a valid reference
+
+AutocompleteUsersFinder
+  #execute
+    should contain exactly #<User id:2126 @johndoe>, #<User id:2128 @user2119>, #<User id:2129 @user2120>, and #<User id:2130 @user2121>
+    when current_user not passed or nil
+      should contain exactly
+    when project passed
+      should contain exactly #<User id:2140 @user2127>
+      when author_id passed
+        should contain exactly #<User id:2146 @user2131> and #<User id:2142 @notsorandom>
+    when group passed and project not passed
+      should contain exactly #<User id:2147 @johndoe>
+    when passed a subgroup
+      includes users from parent groups as well (PENDING: around hook at ./spec/spec_helper.rb:186 did not execute the example)
+    when filtered by search
+      should contain exactly #<User id:2152 @johndoe>
+    when filtered by skip_users
+      should contain exactly #<User id:2157 @johndoe> and #<User id:2159 @user2138>
+    when todos exist
+      when filtered by todo_filter without todo_state_filter
+        should contain exactly
+      when filtered by todo_filter with pending todo_state_filter
+        should contain exactly #<User id:2175 @johndoe>
+      when filtered by todo_filter with done todo_state_filter
+        should contain exactly #<User id:2190 @user2163>
+    when filtered by current_user
+      should contain exactly #<User id:2202 @notsorandom>, #<User id:2201 @johndoe>, #<User id:2203 @user2174>, and #<User id:2204 @user2175>
+    when filtered by author_id
+      should contain exactly #<User id:2206 @notsorandom>, #<User id:2205 @johndoe>, #<User id:2207 @user2176>, #<User id:2208 @user2177>, and #<User id:2209 @user2178>
+
+Service
+  Associations
+    should belong to project
+    should have one service_hook
+  Validations
+    should validate that :type cannot be empty/falsy
+  Scopes
+    .confidential_note_hooks
+      includes services where confidential_note_events is true
+      excludes services where confidential_note_events is false
+  Test Button
+    #can_test?
+      when repository is not empty
+        returns true
+      when repository is empty
+        returns true
+    #test
+      when repository is not empty
+        test runs execute
+      when repository is empty
+        test runs execute
+  Template
+    .build_from_template
+      when template is invalid
+        sets service template to inactive when template is invalid
+    for pushover service
+      is prefilled for projects pushover service
+        has all fields prefilled
+  {property}_changed?
+    returns false when the property has not been assigned a new value
+    returns true when the property has been assigned a different value
+    returns true when the property has been assigned a different value twice
+    returns false when the property has been re-assigned the same value
+    returns false when the property has been assigned a new value then saved
+  {property}_touched?
+    returns false when the property has not been assigned a new value
+    returns true when the property has been assigned a different value
+    returns true when the property has been assigned a different value twice
+    returns true when the property has been re-assigned the same value
+    returns false when the property has been assigned a new value then saved
+  {property}_was
+    returns nil when the property has not been assigned a new value
+    returns the previous value when the property has been assigned a different value
+    returns initial value when the property has been re-assigned the same value
+    returns initial value when the property has been assigned multiple values
+    returns nil when the property has been assigned a new value then saved
+  initialize service with no properties
+    does not raise error
+    creates the properties
+  callbacks
+    on create
+      updates the has_external_issue_tracker boolean
+    on update
+      updates the has_external_issue_tracker boolean
+  #deprecated?
+    should return false by default
+  #deprecation_message
+    should be empty by default
+  .find_by_template
+    returns service template
+  #api_field_names
+    filters out sensitive fields
+
+TestHooks::ProjectService
+  #execute
+    hook with not implemented test
+      returns error message
+    push_events
+      returns error message if not enough data
+      executes hook
+    tag_push_events
+      returns error message if not enough data
+      executes hook
+    note_events
+      returns error message if not enough data
+      executes hook
+    issues_events
+      returns error message if not enough data
+      executes hook
+    confidential_issues_events
+      returns error message if not enough data
+      executes hook
+    merge_requests_events
+      returns error message if not enough data
+      executes hook
+    job_events
+      returns error message if not enough data
+      executes hook
+    pipeline_events
+      returns error message if not enough data
+      executes hook
+    wiki_page_events
+      returns error message if wiki disabled
+      returns error message if not enough data
+      executes hook
+
+User views an open merge request
+  when a merge request does not have repository
+    renders both the title and the description
+  when a merge request has repository
+    when rendering description preview
+      renders empty description preview
+      renders description preview
+    when the branch is rebased on the target
+      does not show diverged commits count
+    when the branch is diverged on the target
+      shows diverged commits count
+
+RunnerJobsFinder
+  #execute
+    when params is empty
+      returns all jobs assigned to Runner
+    when params contains status
+      when status is created
+        returns matched job
+      when status is pending
+        returns matched job
+      when status is running
+        returns matched job
+      when status is success
+        returns matched job
+      when status is failed
+        returns matched job
+      when status is canceled
+        returns matched job
+      when status is skipped
+        returns matched job
+      when status is manual
+        returns matched job
+
+Project snippets
+  when the project has snippets
+    pagination
+      behaves like paginated snippets
+        is limited to 20 items per page
+        clicking on the link to the second page
+          shows the remaining snippets
+    list content
+      contains all project snippets
+    when submitting a note
+      should have autocomplete
+      should have zen mode
+
+API::V3::Environments
+  GET /projects/:id/environments
+    as member of the project
+      returns project environments
+      behaves like a paginated resources
+        has pagination headers
+    as non member
+      returns a 404 status code
+  POST /projects/:id/environments
+    as a member
+      creates a environment with valid params
+      requires name to be passed
+      returns a 400 if environment already exists
+      returns a 400 if slug is specified
+    a non member
+      rejects the request
+      returns a 400 when the required params are missing
+  PUT /projects/:id/environments/:environment_id
+    returns a 200 if name and external_url are changed
+    won't allow slug to be changed
+    won't update the external_url if only the name is passed
+    returns a 404 if the environment does not exist
+  DELETE /projects/:id/environments/:environment_id
+    as a master
+      returns a 200 for an existing environment
+      returns a 404 for non existing id
+    a non member
+      rejects the request
+
+API::Namespaces
+  GET /namespaces
+    when unauthenticated
+      returns authentication error
+    when authenticated as admin
+      returns correct attributes
+      admin: returns an array of all namespaces
+      admin: returns an array of matched namespaces
+    when authenticated as a regular user
+      returns correct attributes when user can admin group
+      returns correct attributes when user cannot admin group
+      user: returns an array of namespaces
+      admin: returns an array of matched namespaces
+  GET /namespaces/:id
+    when unauthenticated
+      returns authentication error
+    when authenticated as regular user
+      when requested namespace is not owned by user
+        when requesting group
+          returns not-found
+        when requesting personal namespace
+          returns not-found
+      when requested namespace is owned by user
+        behaves like namespace reader
+          when namespace exists
+            when requested by ID
+              when requesting group
+                behaves like can access namespace
+                  returns namespace details
+              when requesting personal namespace
+                behaves like can access namespace
+                  returns namespace details
+            when requested by path
+              when requesting group
+                behaves like can access namespace
+                  returns namespace details
+              when requesting personal namespace
+                behaves like can access namespace
+                  returns namespace details
+          when namespace doesn't exist
+            returns not-found
+    when authenticated as admin
+      when requested namespace is not owned by user
+        when requesting group
+          behaves like can access namespace
+            returns namespace details
+        when requesting personal namespace
+          behaves like can access namespace
+            returns namespace details
+      when requested namespace is owned by user
+        behaves like namespace reader
+          when namespace exists
+            when requested by ID
+              when requesting group
+                behaves like can access namespace
+                  returns namespace details
+              when requesting personal namespace
+                behaves like can access namespace
+                  returns namespace details
+            when requested by path
+              when requesting group
+                behaves like can access namespace
+                  returns namespace details
+              when requesting personal namespace
+                behaves like can access namespace
+                  returns namespace details
+          when namespace doesn't exist
+            returns not-found
+
+MergeRequests::GetUrlsService
+  #execute
+    pushing to default branch
+      behaves like no_merge_request_url
+        returns no URL
+    pushing to project with MRs disabled
+      behaves like no_merge_request_url
+        returns no URL
+    pushing one completely new branch
+      behaves like new_merge_request_link
+        returns url to create new merge request
+    pushing to existing branch but no merge request
+      behaves like new_merge_request_link
+        returns url to create new merge request
+    pushing to deleted branch
+      behaves like no_merge_request_url
+        returns no URL
+    pushing to existing branch and merge request opened
+      behaves like show_merge_request_url
+        returns url to view merge request
+    pushing to existing branch and merge request is reopened
+      behaves like show_merge_request_url
+        returns url to view merge request
+    pushing to existing branch from forked project
+      behaves like show_merge_request_url
+        returns url to view merge request
+    pushing to existing branch and merge request is closed
+      behaves like new_merge_request_link
+        returns url to create new merge request
+    pushing to existing branch and merge request is merged
+      behaves like new_merge_request_link
+        returns url to create new merge request
+    pushing new branch and existing branch (with merge request created) at once
+      returns 2 urls for both creating new and showing merge request
+    when printing_merge_request_link_enabled is false
+      returns empty array
+
+LfsFileLock
+  should belong to project
+  should belong to user
+  should validate that :project_id cannot be empty/falsy
+  should validate that :user_id cannot be empty/falsy
+  should validate that :path cannot be empty/falsy
+  #can_be_unlocked_by?
+    when it's forced
+      can be unlocked by the author
+      can be unlocked by a master
+      can't be unlocked by other user
+    when it isn't forced
+      can be unlocked by the author
+      can't be unlocked by a master
+      can't be unlocked by other user
+
+Gitlab::Ci::Config::Entry::Boolean
+  validations
+    when entry config value is valid
+      #value
+        returns key value
+      #valid?
+        is valid
+    when entry value is not valid
+      #errors
+        saves errors
+Knapsack report was generated. Preview:
+{
+  "spec/services/todo_service_spec.rb": 53.71851348876953,
+  "spec/lib/gitlab/import_export/project_tree_saver_spec.rb": 48.39624857902527,
+  "spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb": 35.17360734939575,
+  "spec/controllers/projects/merge_requests_controller_spec.rb": 25.50887441635132,
+  "spec/controllers/groups_controller_spec.rb": 13.007296323776245,
+  "spec/features/projects/import_export/import_file_spec.rb": 16.827879428863525,
+  "spec/lib/gitlab/middleware/go_spec.rb": 12.497276306152344,
+  "spec/features/projects/blobs/edit_spec.rb": 11.511932134628296,
+  "spec/services/boards/lists/move_service_spec.rb": 8.695446491241455,
+  "spec/services/create_deployment_service_spec.rb": 6.754847526550293,
+  "spec/controllers/groups/milestones_controller_spec.rb": 6.8740551471710205,
+  "spec/helpers/groups_helper_spec.rb": 0.9002459049224854,
+  "spec/requests/api/v3/todos_spec.rb": 6.5924904346466064,
+  "spec/models/project_services/teamcity_service_spec.rb": 2.9881808757781982,
+  "spec/lib/gitlab/conflict/file_spec.rb": 5.294132709503174,
+  "spec/lib/banzai/filter/snippet_reference_filter_spec.rb": 4.118850469589233,
+  "spec/finders/autocomplete_users_finder_spec.rb": 3.864232063293457,
+  "spec/models/service_spec.rb": 3.1697962284088135,
+  "spec/services/test_hooks/project_service_spec.rb": 4.167759656906128,
+  "spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb": 4.707003355026245,
+  "spec/finders/runner_jobs_finder_spec.rb": 3.2137575149536133,
+  "spec/features/projects/snippets_spec.rb": 3.631467580795288,
+  "spec/requests/api/v3/environments_spec.rb": 2.314746856689453,
+  "spec/requests/api/namespaces_spec.rb": 2.352935314178467,
+  "spec/services/merge_requests/get_urls_service_spec.rb": 2.8039824962615967,
+  "spec/models/lfs_file_lock_spec.rb": 0.7295050621032715,
+  "spec/lib/gitlab/ci/config/entry/boolean_spec.rb": 0.007024049758911133
+}
+
+Knapsack global time execution for tests: 04m 49s
+
+Pending: (Failures listed here are expected and do not affect your suite's status)
+
+  1) GroupsController GET #new when creating subgroups and can_create_group is true and logged in as Admin behaves like member with ability to create subgroups renders the new page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:15
+
+  2) GroupsController GET #new when creating subgroups and can_create_group is true and logged in as Owner behaves like member with ability to create subgroups renders the new page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:15
+
+  3) GroupsController GET #new when creating subgroups and can_create_group is true and logged in as Guest behaves like member without ability to create subgroups renders the 404 page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:25
+
+  4) GroupsController GET #new when creating subgroups and can_create_group is true and logged in as Developer behaves like member without ability to create subgroups renders the 404 page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:25
+
+  5) GroupsController GET #new when creating subgroups and can_create_group is true and logged in as Master behaves like member without ability to create subgroups renders the 404 page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:25
+
+  6) GroupsController GET #new when creating subgroups and can_create_group is false and logged in as Admin behaves like member with ability to create subgroups renders the new page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:15
+
+  7) GroupsController GET #new when creating subgroups and can_create_group is false and logged in as Owner behaves like member with ability to create subgroups renders the new page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:15
+
+  8) GroupsController GET #new when creating subgroups and can_create_group is false and logged in as Guest behaves like member without ability to create subgroups renders the 404 page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:25
+
+  9) GroupsController GET #new when creating subgroups and can_create_group is false and logged in as Developer behaves like member without ability to create subgroups renders the 404 page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:25
+
+  10) GroupsController GET #new when creating subgroups and can_create_group is false and logged in as Master behaves like member without ability to create subgroups renders the 404 page
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:25
+
+  11) GroupsController POST #create when creating subgroups and can_create_group is true and logged in as Owner creates the subgroup
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:117
+
+  12) GroupsController POST #create when creating subgroups and can_create_group is true and logged in as Developer renders the new template
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:129
+
+  13) GroupsController POST #create when creating subgroups and can_create_group is false and logged in as Owner creates the subgroup
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:117
+
+  14) GroupsController POST #create when creating subgroups and can_create_group is false and logged in as Developer renders the new template
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:129
+
+  15) GroupsController PUT transfer when transfering to a subgroup goes right should return a notice
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:516
+
+  16) GroupsController PUT transfer when transfering to a subgroup goes right should redirect to the new path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:520
+
+  17) GroupsController PUT transfer when converting to a root group goes right should return a notice
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:535
+
+  18) GroupsController PUT transfer when converting to a root group goes right should redirect to the new path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:539
+
+  19) GroupsController PUT transfer When the transfer goes wrong should return an alert
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:557
+
+  20) GroupsController PUT transfer When the transfer goes wrong should redirect to the current path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:561
+
+  21) GroupsController PUT transfer when the user is not allowed to transfer the group should be denied
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/controllers/groups_controller_spec.rb:577
+
+  22) Groups::TransferService#execute when transforming a group into a root group behaves like ensuring allowed transfer for a group with other database than PostgreSQL should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:15
+
+  23) Groups::TransferService#execute when transforming a group into a root group behaves like ensuring allowed transfer for a group with other database than PostgreSQL should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:19
+
+  24) Groups::TransferService#execute when transforming a group into a root group behaves like ensuring allowed transfer for a group when there's an exception on Gitlab shell directories should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:33
+
+  25) Groups::TransferService#execute when transforming a group into a root group behaves like ensuring allowed transfer for a group when there's an exception on Gitlab shell directories should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:37
+
+  26) Groups::TransferService#execute when transforming a group into a root group when the group is already a root group should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:53
+
+  27) Groups::TransferService#execute when transforming a group into a root group when the user does not have the right policies should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:62
+
+  28) Groups::TransferService#execute when transforming a group into a root group when the user does not have the right policies should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:66
+
+  29) Groups::TransferService#execute when transforming a group into a root group when there is a group with the same path should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:79
+
+  30) Groups::TransferService#execute when transforming a group into a root group when there is a group with the same path should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:83
+
+  31) Groups::TransferService#execute when transforming a group into a root group when the group is a subgroup and the transfer is valid should update group attributes
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:99
+
+  32) Groups::TransferService#execute when transforming a group into a root group when the group is a subgroup and the transfer is valid should update group children path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:103
+
+  33) Groups::TransferService#execute when transforming a group into a root group when the group is a subgroup and the transfer is valid should update group projects path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:109
+
+  34) Groups::TransferService#execute when transferring a subgroup into another group behaves like ensuring allowed transfer for a group with other database than PostgreSQL should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:15
+
+  35) Groups::TransferService#execute when transferring a subgroup into another group behaves like ensuring allowed transfer for a group with other database than PostgreSQL should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:19
+
+  36) Groups::TransferService#execute when transferring a subgroup into another group behaves like ensuring allowed transfer for a group when there's an exception on Gitlab shell directories should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:33
+
+  37) Groups::TransferService#execute when transferring a subgroup into another group behaves like ensuring allowed transfer for a group when there's an exception on Gitlab shell directories should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:37
+
+  38) Groups::TransferService#execute when transferring a subgroup into another group when the new parent group is the same as the previous parent group should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:125
+
+  39) Groups::TransferService#execute when transferring a subgroup into another group when the new parent group is the same as the previous parent group should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:129
+
+  40) Groups::TransferService#execute when transferring a subgroup into another group when the user does not have the right policies should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:138
+
+  41) Groups::TransferService#execute when transferring a subgroup into another group when the user does not have the right policies should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:142
+
+  42) Groups::TransferService#execute when transferring a subgroup into another group when the parent has a group with the same path should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:155
+
+  43) Groups::TransferService#execute when transferring a subgroup into another group when the parent has a group with the same path should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:159
+
+  44) Groups::TransferService#execute when transferring a subgroup into another group when the parent group has a project with the same path should return false
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:174
+
+  45) Groups::TransferService#execute when transferring a subgroup into another group when the parent group has a project with the same path should add an error on group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:178
+
+  46) Groups::TransferService#execute when transferring a subgroup into another group when the group is allowed to be transferred should update visibility for the group based on the parent group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:212
+
+  47) Groups::TransferService#execute when transferring a subgroup into another group when the group is allowed to be transferred should update parent group to the new parent 
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:216
+
+  48) Groups::TransferService#execute when transferring a subgroup into another group when the group is allowed to be transferred should return the group as children of the new parent
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:220
+
+  49) Groups::TransferService#execute when transferring a subgroup into another group when the group is allowed to be transferred should create a redirect for the group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:225
+
+  50) Groups::TransferService#execute when transferring a subgroup into another group when the group is allowed to be transferred when the group has a lower visibility than the parent group should not update the visibility for the group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:194
+
+  51) Groups::TransferService#execute when transferring a subgroup into another group when the group is allowed to be transferred when the group has a higher visibility than the parent group should update visibility level based on the parent group
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:205
+
+  52) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with group descendants should update subgroups path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:239
+
+  53) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with group descendants should create redirects for the subgroups
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:246
+
+  54) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with group descendants when the new parent has a higher visibility than the children should not update the children visibility
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:253
+
+  55) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with group descendants when the new parent has a lower visibility than the children should update children visibility to match the new parent
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:264
+
+  56) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with project descendants should update projects path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:282
+
+  57) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with project descendants should create permanent redirects for the projects
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:289
+
+  58) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with project descendants when the new parent has a higher visibility than the projects should not update projects visibility
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:296
+
+  59) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with project descendants when the new parent has a lower visibility than the projects should update projects visibility to match the new parent
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:307
+
+  60) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with subgroups & projects descendants should update subgroups path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:327
+
+  61) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with subgroups & projects descendants should update projects path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:334
+
+  62) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with subgroups & projects descendants should create redirect for the subgroups and projects
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:341
+
+  63) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update subgroups path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:363
+
+  64) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update projects path
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:375
+
+  65) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should create redirect for the subgroups and projects
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:383
+
+  66) Groups::TransferService#execute when transferring a subgroup into another group when updating the group goes wrong should restore group and projects visibility
+     # around hook at ./spec/spec_helper.rb:190 did not execute the example
+     # ./spec/services/groups/transfer_service_spec.rb:405
+
+  67) GroupsHelper group_title outputs the groups in the correct order
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:106
+
+  68) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  69) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  70) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  71) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  72) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  73) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  74) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :subgroup has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  75) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :subgroup has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  76) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :subgroup has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  77) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :ancestor_locked_but_you_can_override, linked_ancestor: :subgroup has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  78) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :ancestor_locked_but_you_can_override, linked_ancestor: :subgroup has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  79) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: false, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :ancestor_locked_so_ask_the_owner, linked_ancestor: :subgroup has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  80) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  81) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  82) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  83) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
+
+  84) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-path
-Set for your local app (/usr/local/bundle/config): "vendor"
-Set via BUNDLE_PATH: "/usr/local/bundle"
+  85) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: false, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :default_help, linked_ancestor: nil has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-jobs
-Set for your local app (/usr/local/bundle/config): "2"
+  86) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :root_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :root_group has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-clean
-Set for your local app (/usr/local/bundle/config): "true"
+  87) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :root_group has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-without
-Set for your local app (/usr/local/bundle/config): [:production]
+  88) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: false, current_user: :sub_sub_owner, help_text: :ancestor_locked_and_has_been_overridden, linked_ancestor: :root_group has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-silence_root_warning
-Set via BUNDLE_SILENCE_ROOT_WARNING: true
+  89) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :root_owner, help_text: :ancestor_locked_but_you_can_override, linked_ancestor: :root_group has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-app_config
-Set via BUNDLE_APP_CONFIG: "/usr/local/bundle"
+  90) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_owner, help_text: :ancestor_locked_so_ask_the_owner, linked_ancestor: :root_group has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-install_flags
-Set via BUNDLE_INSTALL_FLAGS: "--without=production --jobs=2 --path=vendor --retry=3 --quiet"
+  91) GroupsHelper#share_with_group_lock_help_text root_share_with_group_locked: true, subgroup_share_with_group_locked: true, sub_subgroup_share_with_group_locked: true, current_user: :sub_sub_owner, help_text: :ancestor_locked_so_ask_the_owner, linked_ancestor: :root_group has the correct help text with correct ancestor links
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/helpers/groups_helper_spec.rb:198
 
-bin
-Set via BUNDLE_BIN: "/usr/local/bundle/bin"
+  92) AutocompleteUsersFinder#execute when passed a subgroup includes users from parent groups as well
+     # around hook at ./spec/spec_helper.rb:186 did not execute the example
+     # ./spec/finders/autocomplete_users_finder_spec.rb:55
 
-gemfile
-Set via BUNDLE_GEMFILE: "/builds/gitlab-org/gitlab-ce/Gemfile"
+Finished in 5 minutes 7 seconds (files took 16.6 seconds to load)
+819 examples, 0 failures, 92 pending
 
-section_end:1517486961:build_script
-section_start:1517486961:after_script
-section_end:1517486962:after_script
-section_start:1517486962:upload_artifacts
+section_end:1522927514:build_script
+section_start:1522927514:after_script
+Running after script...
+$ date
+Thu Apr  5 11:25:14 UTC 2018
+section_end:1522927515:after_script
+section_start:1522927515:archive_cache
+Not uploading cache ruby-2.3.6-with-yarn due to policy
+section_end:1522927516:archive_cache
+section_start:1522927516:upload_artifacts
 Uploading artifacts...
-WARNING: coverage/: no matching files              
+coverage/: found 5 matching files                  
 knapsack/: found 5 matching files                  
+rspec_flaky/: found 4 matching files               
 WARNING: tmp/capybara/: no matching files          
-Uploading artifacts to coordinator... ok            id=50551722 responseStatus=201 Created token=XkN753rp
-section_end:1517486963:upload_artifacts
-ERROR: Job failed: exit code 1
-
\ No newline at end of file
+Uploading artifacts to coordinator... ok            id=61303283 responseStatus=201 Created token=rusBKvxM
+section_end:1522927520:upload_artifacts
+Job succeeded
+
diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index a3c5ab99c8701d2ea8a0fd5f34b8f378dafd721e..b22947911b9d813d99b31035f0ea2310f2e2f60a 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -1,6 +1,34 @@
 require 'spec_helper'
 
 describe BoardsHelper do
+  describe '#build_issue_link_base' do
+    context 'project board' do
+      it 'returns correct path for project board' do
+        @project = create(:project)
+        @board = create(:board, project: @project)
+
+        expect(build_issue_link_base).to eq("/#{@project.namespace.path}/#{@project.path}/issues")
+      end
+    end
+
+    context 'group board' do
+      let(:base_group) { create(:group, path: 'base') }
+
+      it 'returns correct path for base group' do
+        @board = create(:board, group: base_group)
+
+        expect(build_issue_link_base).to eq('/base/:project_path/issues')
+      end
+
+      it 'returns correct path for subgroup' do
+        subgroup = create(:group, parent: base_group, path: 'sub')
+        @board = create(:board, group: subgroup)
+
+        expect(build_issue_link_base).to eq('/base/sub/:project_path/issues')
+      end
+    end
+  end
+
   describe '#board_data' do
     let(:user) { create(:user) }
     let(:project) { create(:project) }
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 15cbe36ae766df292b2da8d8aa481f4423029122..53c010fa0db2bea19e67d727327468ac79aa990b 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -135,11 +135,37 @@ describe DiffHelper do
     it "returns strings with marked inline diffs" do
       marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
 
-      expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">'def'</span>})
+      expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">&#39;def&#39;</span>})
       expect(marked_old_line).to be_html_safe
-      expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">"def"</span>})
+      expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">&quot;def&quot;</span>})
       expect(marked_new_line).to be_html_safe
     end
+
+    context 'when given HTML' do
+      it 'sanitizes it' do
+        old_line = %{test.txt}
+        new_line = %{<img src=x onerror=alert(document.domain)>}
+
+        marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
+
+        expect(marked_old_line).to eq(%q{<span class="idiff left right deletion">test.txt</span>})
+        expect(marked_old_line).to be_html_safe
+        expect(marked_new_line).to eq(%q{<span class="idiff left right addition">&lt;img src=x onerror=alert(document.domain)&gt;</span>})
+        expect(marked_new_line).to be_html_safe
+      end
+
+      it 'sanitizes the entire line, not just the changes' do
+        old_line = %{<img src=x onerror=alert(document.domain)>}
+        new_line = %{<img src=y onerror=alert(document.domain)>}
+
+        marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
+
+        expect(marked_old_line).to eq(%q{&lt;img src=<span class="idiff left right deletion">x</span> onerror=alert(document.domain)&gt;})
+        expect(marked_old_line).to be_html_safe
+        expect(marked_new_line).to eq(%q{&lt;img src=<span class="idiff left right addition">y</span> onerror=alert(document.domain)&gt;})
+        expect(marked_new_line).to be_html_safe
+      end
+    end
   end
 
   describe '#parallel_diff_discussions' do
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 2f23ed55d99e87a183910772bf3097a16ccb1a55..93d8e672f8c4613268370958af8d9afcd2c94cd0 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -162,4 +162,11 @@ describe IconsHelper do
       expect(file_type_icon_class('file', 0, 'CHANGELOG')).to eq 'file-text-o'
     end
   end
+
+  describe '#external_snippet_icon' do
+    it 'returns external snippet icon' do
+      expect(external_snippet_icon('download').to_s)
+        .to eq("<span class=\"gl-snippet-icon gl-snippet-icon-download\"></span>")
+    end
+  end
 end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index 9afff47f4e9e1ec3de2b4cf58a2eae4dfd074db8..033155617c6c2954c516e158f68cf47937eead1d 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -27,25 +27,46 @@ describe ImportHelper do
 
   describe '#provider_project_link' do
     context 'when provider is "github"' do
+      let(:github_server_url) { nil }
+      let(:provider) { OpenStruct.new(name: 'github', url: github_server_url) }
+
+      before do
+        stub_omniauth_setting(providers: [provider])
+      end
+
       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')])
-
           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
+        let(:github_server_url) { 'https://github.company.com' }
+
         it 'uses custom URL' do
-          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"')
+        end
+      end
+
+      context "when custom URL contains a '/' char at the end" do
+        let(:github_server_url) { 'https://github.company.com/' }
 
+        it "doesn't render double slash" do
           expect(helper.provider_project_link('github', 'octocat/Hello-World'))
           .to include('href="https://github.company.com/octocat/Hello-World"')
         end
       end
+
+      context 'when provider is missing' do
+        it 'uses the default URL' do
+          allow(Gitlab.config.omniauth).to receive(:providers).and_return([])
+
+          expect(helper.provider_project_link('github', 'octocat/Hello-World'))
+          .to include('href="https://github.com/octocat/Hello-World"')
+        end
+      end
     end
 
     context 'when provider is "gitea"' do
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 2fecd1a3d274e1c85ae898a2051e5523b3ee7992..7b59fde999d1e1f7102f3c50d6caa9f0929a4766 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -22,11 +22,15 @@ describe IssuablesHelper do
   end
 
   describe '#issuable_labels_tooltip' do
-    it 'returns label text' do
+    it 'returns label text with no labels' do
+      expect(issuable_labels_tooltip([])).to eq("Labels")
+    end
+
+    it 'returns label text with labels within max limit' do
       expect(issuable_labels_tooltip([label])).to eq(label.title)
     end
 
-    it 'returns label text' do
+    it 'returns label text with labels exceeding max limit' do
       expect(issuable_labels_tooltip([label, label2], limit: 1)).to eq("#{label.title}, and 1 more")
     end
   end
@@ -40,22 +44,22 @@ describe IssuablesHelper do
       end
 
       it 'returns "Open" when state is :opened' do
-        expect(helper.issuables_state_counter_text(:issues, :opened))
+        expect(helper.issuables_state_counter_text(:issues, :opened, true))
           .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))
+        expect(helper.issuables_state_counter_text(:issues, :closed, true))
           .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))
+        expect(helper.issuables_state_counter_text(:merge_requests, :merged, true))
           .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))
+        expect(helper.issuables_state_counter_text(:merge_requests, :all, true))
           .to eq('<span>All</span> <span class="badge">42</span>')
       end
     end
@@ -101,27 +105,6 @@ describe IssuablesHelper do
     end
   end
 
-  describe '#issuable_filter_present?' do
-    it 'returns true when any key is present' do
-      allow(helper).to receive(:params).and_return(
-        ActionController::Parameters.new(milestone_title: 'Velit consectetur asperiores natus delectus.',
-                                         project_id: 'gitlabhq',
-                                         scope: 'all')
-      )
-
-      expect(helper.issuable_filter_present?).to be_truthy
-    end
-
-    it 'returns false when no key is present' do
-      allow(helper).to receive(:params).and_return(
-        ActionController::Parameters.new(project_id: 'gitlabhq',
-                                         scope: 'all')
-      )
-
-      expect(helper.issuable_filter_present?).to be_falsey
-    end
-  end
-
   describe '#updated_at_by' do
     let(:user) { create(:user) }
     let(:unedited_issuable) { create(:issue) }
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index aeef5352333a574021aec2d8337a5a02496bb647..8bb2e234e9aa36278410596fe1d8f783b6a11f83 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -96,13 +96,32 @@ describe IssuesHelper do
 
   describe '#award_state_class' do
     let!(:upvote) { create(:award_emoji) }
+    let(:awardable) { upvote.awardable }
+    let(:user) { upvote.user }
+
+    before do
+      allow(helper).to receive(:can?) do |*args|
+        Ability.allowed?(*args)
+      end
+    end
 
     it "returns disabled string for unauthenticated user" do
-      expect(award_state_class(AwardEmoji.all, nil)).to eq("disabled")
+      expect(helper.award_state_class(awardable, AwardEmoji.all, nil)).to eq("disabled")
+    end
+
+    it "returns disabled for a user that does not have access to the awardable" do
+      expect(helper.award_state_class(awardable, AwardEmoji.all, build(:user))).to eq("disabled")
     end
 
     it "returns active string for author" do
-      expect(award_state_class(AwardEmoji.all, upvote.user)).to eq("active")
+      expect(helper.award_state_class(awardable, AwardEmoji.all, upvote.user)).to eq("active")
+    end
+
+    it "is blank for a user that has access to the awardable" do
+      user = build(:user)
+      expect(helper).to receive(:can?).with(user, :award_emoji, awardable).and_return(true)
+
+      expect(helper.award_state_class(awardable, AwardEmoji.all, user)).to be_blank
     end
   end
 
@@ -144,4 +163,26 @@ describe IssuesHelper do
       end
     end
   end
+
+  describe '#show_new_issue_link?' do
+    before do
+      allow(helper).to receive(:current_user)
+    end
+
+    it 'is false when no project there is no project' do
+      expect(helper.show_new_issue_link?(nil)).to be_falsey
+    end
+
+    it 'is true when there is a project and no logged in user' do
+      expect(helper.show_new_issue_link?(build(:project))).to be_truthy
+    end
+
+    it 'is true when the current user does not have access to the project' do
+      project = build(:project)
+      allow(helper).to receive(:current_user).and_return(project.owner)
+
+      expect(helper).to receive(:can?).with(project.owner, :create_issue, project).and_return(true)
+      expect(helper.show_new_issue_link?(project)).to be_truthy
+    end
+  end
 end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 619baa78bfab9617c099a6c4616bee5e6222550c..a2cda58e5d24d656b725909fea376a73bdae2d4e 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -139,4 +139,76 @@ describe LabelsHelper do
       expect(text_color_for_bg('#000')).to eq '#FFFFFF'
     end
   end
+
+  describe 'create_label_title' do
+    set(:group) { create(:group) }
+
+    context 'with a group as subject' do
+      it 'returns "Create group label"' do
+        expect(create_label_title(group)).to eq 'Create group label'
+      end
+    end
+
+    context 'with a project as subject' do
+      set(:project) { create(:project, namespace: group) }
+
+      it 'returns "Create project label"' do
+        expect(create_label_title(project)).to eq 'Create project label'
+      end
+    end
+
+    context 'with no subject' do
+      it 'returns "Create new label"' do
+        expect(create_label_title(nil)).to eq 'Create new label'
+      end
+    end
+  end
+
+  describe 'manage_labels_title' do
+    set(:group) { create(:group) }
+
+    context 'with a group as subject' do
+      it 'returns "Manage group labels"' do
+        expect(manage_labels_title(group)).to eq 'Manage group labels'
+      end
+    end
+
+    context 'with a project as subject' do
+      set(:project) { create(:project, namespace: group) }
+
+      it 'returns "Manage project labels"' do
+        expect(manage_labels_title(project)).to eq 'Manage project labels'
+      end
+    end
+
+    context 'with no subject' do
+      it 'returns "Manage labels"' do
+        expect(manage_labels_title(nil)).to eq 'Manage labels'
+      end
+    end
+  end
+
+  describe 'view_labels_title' do
+    set(:group) { create(:group) }
+
+    context 'with a group as subject' do
+      it 'returns "View group labels"' do
+        expect(view_labels_title(group)).to eq 'View group labels'
+      end
+    end
+
+    context 'with a project as subject' do
+      set(:project) { create(:project, namespace: group) }
+
+      it 'returns "View project labels"' do
+        expect(view_labels_title(project)).to eq 'View project labels'
+      end
+    end
+
+    context 'with no subject' do
+      it 'returns "View labels"' do
+        expect(view_labels_title(nil)).to eq 'View labels'
+      end
+    end
+  end
 end
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index 45ffbeb27a45ca8c93db95214965504e68ee5bd7..4590904c93d177c46a5784db18155a68b62b9384 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -12,10 +12,10 @@ describe MembersHelper do
     let(:group_member_invite) { build(:group_member, group: group).tap { |m| m.generate_invite_token! } }
     let(:group_member_request) { group.request_access(requester) }
 
-    it { expect(remove_member_message(project_member)).to eq "Are you sure you want to remove #{project_member.user.name} from the #{project.name_with_namespace} project?" }
-    it { expect(remove_member_message(project_member_invite)).to eq "Are you sure you want to revoke the invitation for #{project_member_invite.invite_email} to join the #{project.name_with_namespace} project?" }
-    it { expect(remove_member_message(project_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{project.name_with_namespace} project?" }
-    it { expect(remove_member_message(project_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{project.name_with_namespace} project?" }
+    it { expect(remove_member_message(project_member)).to eq "Are you sure you want to remove #{project_member.user.name} from the #{project.full_name} project?" }
+    it { expect(remove_member_message(project_member_invite)).to eq "Are you sure you want to revoke the invitation for #{project_member_invite.invite_email} to join the #{project.full_name} project?" }
+    it { expect(remove_member_message(project_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{project.full_name} project?" }
+    it { expect(remove_member_message(project_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{project.full_name} project?" }
     it { expect(remove_member_message(group_member)).to eq "Are you sure you want to remove #{group_member.user.name} from the #{group.name} group?" }
     it { expect(remove_member_message(group_member_invite)).to eq "Are you sure you want to revoke the invitation for #{group_member_invite.invite_email} to join the #{group.name} group?" }
     it { expect(remove_member_message(group_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{group.name} group?" }
@@ -42,7 +42,7 @@ describe MembersHelper do
     let(:group) { build_stubbed(:group) }
     let(:user) { build_stubbed(:user) }
 
-    it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.name_with_namespace}\" project?" }
+    it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.full_name}\" project?" }
     it { expect(leave_confirmation_message(group)).to eq "Are you sure you want to leave the \"#{group.name}\" group?" }
   end
 end
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index 70b4a89cb86228186cd1f7e727066bb179cc9a1e..f5185cb2857e73554cd8a9d19fa1201d7e219737 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -83,58 +83,4 @@ describe MilestonesHelper do
       end
     end
   end
-
-  describe '#milestone_remaining_days' do
-    around do |example|
-      Timecop.freeze(Time.utc(2017, 3, 17)) { example.run }
-    end
-
-    context 'when less than 31 days remaining' do
-      let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 12.days.from_now.utc)) }
-
-      it 'returns days remaining' do
-        expect(milestone_remaining).to eq("<strong>12</strong> days remaining")
-      end
-    end
-
-    context 'when less than 1 year and more than 30 days remaining' do
-      let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 2.months.from_now.utc)) }
-
-      it 'returns months remaining' do
-        expect(milestone_remaining).to eq("<strong>2</strong> months remaining")
-      end
-    end
-
-    context 'when more than 1 year remaining' do
-      let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: (1.year.from_now + 2.days).utc)) }
-
-      it 'returns years remaining' do
-        expect(milestone_remaining).to eq("<strong>1</strong> year remaining")
-      end
-    end
-
-    context 'when milestone is expired' do
-      let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 2.days.ago.utc)) }
-
-      it 'returns "Past due"' do
-        expect(milestone_remaining).to eq("<strong>Past due</strong>")
-      end
-    end
-
-    context 'when milestone has start_date in the future' do
-      let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, start_date: 2.days.from_now.utc)) }
-
-      it 'returns "Upcoming"' do
-        expect(milestone_remaining).to eq("<strong>Upcoming</strong>")
-      end
-    end
-
-    context 'when milestone has start_date in the past' do
-      let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, start_date: 2.days.ago.utc)) }
-
-      it 'returns days elapsed' do
-        expect(milestone_remaining).to eq("<strong>2</strong> days elapsed")
-      end
-    end
-  end
 end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index baf927a9acc9af87451442c714a1ef55654b6feb..b77114a8152edd4026c3c5104b7229ac9564a0cc 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -50,6 +50,11 @@ describe PageLayoutHelper do
       allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
       expect(helper.favicon).to eq 'favicon-blue.ico'
     end
+
+    it 'has yellow favicon for canary' do
+      stub_env('CANARY', 'true')
+      expect(helper.favicon).to eq 'favicon-yellow.ico'
+    end
   end
 
   describe 'page_image' do
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index e2a0c4322ff3ba8c27252ae2cbe5bd4201db934c..c9d2ec8a4aee59456f04c65f020f1f1b5cbfb810 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -21,7 +21,9 @@ describe PreferencesHelper do
         ["Your Projects' Activity", 'project_activity'],
         ["Starred Projects' Activity", 'starred_project_activity'],
         ["Your Groups", 'groups'],
-        ["Your Todos", 'todos']
+        ["Your Todos", 'todos'],
+        ["Assigned Issues", 'issues'],
+        ["Assigned Merge Requests", 'merge_requests']
       ]
     end
   end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index ce96e90e2d74ac3ae8db2f3a3320449472dc8a64..46c55da24f86b1c514cfcdea11803ff5ed3ca469 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -322,74 +322,6 @@ describe ProjectsHelper do
     end
   end
 
-  describe "#project_feature_access_select" do
-    let(:project) { create(:project, :public) }
-    let(:user)    { create(:user) }
-
-    context "when project is internal or public" do
-      it "shows all options" do
-        helper.instance_variable_set(:@project, project)
-        result = helper.project_feature_access_select(:issues_access_level)
-        expect(result).to include("Disabled")
-        expect(result).to include("Only team members")
-        expect(result).to include("Everyone with access")
-      end
-    end
-
-    context "when project is private" do
-      before do
-        project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
-      end
-
-      it "shows only allowed options" do
-        helper.instance_variable_set(:@project, project)
-        result = helper.project_feature_access_select(:issues_access_level)
-        expect(result).to include("Disabled")
-        expect(result).to include("Only team members")
-        expect(result).to have_selector('option[disabled]', text: "Everyone with access")
-      end
-    end
-
-    context "when project moves from public to private" do
-      before do
-        project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
-      end
-
-      it "shows the highest allowed level selected" do
-        helper.instance_variable_set(:@project, project)
-        result = helper.project_feature_access_select(:issues_access_level)
-
-        expect(result).to include("Disabled")
-        expect(result).to include("Only team members")
-        expect(result).to have_selector('option[disabled]', text: "Everyone with access")
-        expect(result).to have_selector('option[selected]', text: "Only team members")
-      end
-    end
-  end
-
-  describe "#visibility_select_options" do
-    let(:project) { create(:project, :repository) }
-    let(:user)    { create(:user) }
-
-    before do
-      allow(helper).to receive(:current_user).and_return(user)
-
-      stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
-    end
-
-    it "does not include the Public restricted level" do
-      expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).not_to include('Public')
-    end
-
-    it "includes the Internal level" do
-      expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Internal')
-    end
-
-    it "includes the Private level" do
-      expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private')
-    end
-  end
-
   describe '#get_project_nav_tabs' do
     let(:project) { create(:project) }
     let(:user)    { create(:user) }
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0323ffb641cdabfb0af6e3b1274dd9804c1104b7
--- /dev/null
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe SnippetsHelper do
+  include IconsHelper
+
+  describe '#embedded_snippet_raw_button' do
+    it 'gives view raw button of embedded snippets for project snippets' do
+      @snippet = create(:project_snippet, :public)
+
+      expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet)}\">#{external_snippet_icon('doc_code')}</a>")
+    end
+
+    it 'gives view raw button of embedded snippets for personal snippets' do
+      @snippet = create(:personal_snippet, :public)
+
+      expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_snippet_url(@snippet)}\">#{external_snippet_icon('doc_code')}</a>")
+    end
+  end
+
+  describe '#embedded_snippet_download_button' do
+    it 'gives download button of embedded snippets for project snippets' do
+      @snippet = create(:project_snippet, :public)
+
+      expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
+    end
+
+    it 'gives download button of embedded snippets for personal snippets' do
+      @snippet = create(:personal_snippet, :public)
+
+      expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_snippet_url(@snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
+    end
+  end
+end
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
index f55163c26e9baa6681e90be11b66e42f2dc195b3..63806ef91f30eb7f9827479cec7a53ef87da8657 100644
--- a/spec/helpers/todos_helper_spec.rb
+++ b/spec/helpers/todos_helper_spec.rb
@@ -26,8 +26,8 @@ describe TodosHelper do
 
       expected_results = [
         { 'id' => '', 'text' => 'Any Project' },
-        { 'id' => projects.second.id, 'text' => projects.second.name_with_namespace },
-        { 'id' => projects.first.id, 'text' => projects.first.name_with_namespace }
+        { 'id' => projects.second.id, 'text' => projects.second.full_name },
+        { 'id' => projects.first.id, 'text' => projects.first.full_name }
       ]
 
       expect(JSON.parse(helper.todo_projects_options)).to match_array(expected_results)
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index d3b1be599dd49d386c9d7830e90798147aecf871..ffdf6561a53bb54b6750d2c7f721ad32b3af3cf1 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -8,6 +8,7 @@ describe TreeHelper do
   describe '.render_tree' do
     before do
       @id = sha
+      @path = ""
       @project = project
       @lfs_blob_ids = []
     end
@@ -61,5 +62,23 @@ describe TreeHelper do
         end
       end
     end
+
+    context 'when the root path contains a plus character' do
+      let(:root_path) { 'gtk/C++' }
+      let(:tree_item) { double(flat_path: 'gtk/C++/glade') }
+
+      it 'returns the flattened path' do
+        expect(subject).to eq('glade')
+      end
+    end
+  end
+
+  describe '#commit_in_single_accessible_branch' do
+    it 'escapes HTML from the branch name' do
+      helper.instance_variable_set(:@branch_name, "<script>alert('escape me!');</script>")
+      escaped_branch_name = '&lt;script&gt;alert(&#39;escape me!&#39;);&lt;/script&gt;'
+
+      expect(helper.commit_in_single_accessible_branch).to include(escaped_branch_name)
+    end
   end
 end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index 83283f0394072834af64fb1461ee4f9e7c5b245f..1dc307ea922dc24f6413e3158fffa0e1901fb2c5 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -15,7 +15,7 @@ describe '6_validations' do
   describe 'validate_storages_config' do
     context 'with correct settings' do
       before do
-        mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/d' })
+        mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/d'))
       end
 
       it 'passes through' do
@@ -25,7 +25,7 @@ describe '6_validations' do
 
     context 'when one of the settings is incorrect' do
       before do
-        mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number' })
+        mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number'))
       end
 
       it 'throws an error' do
@@ -35,7 +35,7 @@ describe '6_validations' do
 
     context 'with invalid storage names' do
       before do
-        mock_storages('name with spaces' => { 'path' => 'tmp/tests/paths/a/b/c' })
+        mock_storages('name with spaces' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'))
       end
 
       it 'throws an error' do
@@ -67,7 +67,7 @@ describe '6_validations' do
   describe 'validate_storages_paths' do
     context 'with correct settings' do
       before do
-        mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/d' })
+        mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/d'))
       end
 
       it 'passes through' do
@@ -77,7 +77,7 @@ describe '6_validations' do
 
     context 'with nested storage paths' do
       before do
-        mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c/d' })
+        mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c/d'))
       end
 
       it 'throws an error' do
@@ -87,7 +87,7 @@ describe '6_validations' do
 
     context 'with similar but un-nested storage paths' do
       before do
-        mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c2' })
+        mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c2'))
       end
 
       it 'passes through' do
@@ -97,7 +97,7 @@ describe '6_validations' do
 
     describe 'inaccessible storage' do
       before do
-        mock_storages('foo' => { 'path' => 'tmp/tests/a/path/that/does/not/exist' })
+        mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/a/path/that/does/not/exist'))
       end
 
       it 'passes through with a warning' do
diff --git a/spec/initializers/artifacts_direct_upload_support_spec.rb b/spec/initializers/artifacts_direct_upload_support_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bfb71da3388b4133120a24c450d64978eda0f66d
--- /dev/null
+++ b/spec/initializers/artifacts_direct_upload_support_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe 'Artifacts direct upload support' do
+  subject do
+    load Rails.root.join('config/initializers/artifacts_direct_upload_support.rb')
+  end
+
+  let(:connection) do
+    { provider: provider }
+  end
+
+  before do
+    stub_artifacts_setting(
+      object_store: {
+        enabled: enabled,
+        direct_upload: direct_upload,
+        connection: connection
+      })
+  end
+
+  context 'when object storage is enabled' do
+    let(:enabled) { true }
+
+    context 'when direct upload is enabled' do
+      let(:direct_upload) { true }
+
+      context 'when provider is Google' do
+        let(:provider) { 'Google' }
+
+        it 'succeeds' do
+          expect { subject }.not_to raise_error
+        end
+      end
+
+      context 'when connection is empty' do
+        let(:connection) { nil }
+
+        it 'raises an error' do
+          expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/
+        end
+      end
+
+      context 'when other provider is used' do
+        let(:provider) { 'AWS' }
+
+        it 'raises an error' do
+          expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/
+        end
+      end
+    end
+
+    context 'when direct upload is disabled' do
+      let(:direct_upload) { false }
+      let(:provider) { 'AWS' }
+
+      it 'succeeds' do
+        expect { subject }.not_to raise_error
+      end
+    end
+  end
+
+  context 'when object storage is disabled' do
+    let(:enabled) { false }
+    let(:direct_upload) { false }
+    let(:provider) { 'AWS' }
+
+    it 'succeeds' do
+      expect { subject }.not_to raise_error
+    end
+  end
+end
diff --git a/spec/initializers/fog_google_https_private_urls_spec.rb b/spec/initializers/fog_google_https_private_urls_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..de3c157ab7bd72b45f48d295ff1bfd9ea350138b
--- /dev/null
+++ b/spec/initializers/fog_google_https_private_urls_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe 'Fog::Storage::GoogleXML::File' do
+  let(:storage) do
+    Fog.mock!
+    Fog::Storage.new({
+                       google_storage_access_key_id: "asdf",
+                       google_storage_secret_access_key: "asdf",
+                       provider: "Google"
+                     })
+  end
+
+  let(:file) do
+    directory = storage.directories.create(key: 'data')
+    directory.files.create(
+      body: 'Hello World!',
+      key: 'hello_world.txt'
+    )
+  end
+
+  it 'delegates to #get_https_url' do
+    expect(file.url(Time.now)).to start_with("https://")
+  end
+end
diff --git a/spec/initializers/gollum_spec.rb b/spec/initializers/gollum_spec.rb
deleted file mode 100644
index adf824a8947dd76d000570a0022854fefa8f764e..0000000000000000000000000000000000000000
--- a/spec/initializers/gollum_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'spec_helper'
-
-describe 'gollum' do
-  let(:project) { create(:project) }
-  let(:user) { project.owner }
-  let(:wiki) { ProjectWiki.new(project, user) }
-  let(:gollum_wiki) { Gollum::Wiki.new(wiki.repository.path) }
-
-  before do
-    create_page(page_name, 'content1')
-  end
-
-  after do
-    destroy_page(page_name)
-  end
-
-  context 'with simple paths' do
-    let(:page_name) { 'page1' }
-
-    it 'returns the entry hash if it matches the file name' do
-      expect(tree_entry(page_name)).not_to be_nil
-    end
-
-    it 'returns nil if the path does not fit completely' do
-      expect(tree_entry("foo/#{page_name}")).to be_nil
-    end
-  end
-
-  context 'with complex paths' do
-    let(:page_name) { '/foo/bar/page2' }
-
-    it 'returns the entry hash if it matches the file name' do
-      expect(tree_entry(page_name)).not_to be_nil
-    end
-
-    it 'returns nil if the path does not fit completely' do
-      expect(tree_entry("foo1/bar/page2")).to be_nil
-      expect(tree_entry("foo/bar1/page2")).to be_nil
-    end
-  end
-
-  def tree_entry(name)
-    gollum_wiki.repo.git.tree_entry(wiki_commits[0].commit, name + '.md')
-  end
-
-  def wiki_commits
-    gollum_wiki.repo.commits
-  end
-
-  def commit_details
-    Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
-  end
-
-  def create_page(name, content)
-    wiki.wiki.write_page(name, :markdown, content, commit_details)
-  end
-
-  def destroy_page(name)
-    page = wiki.find_page(name).page
-    wiki.delete_page(page, "test commit")
-  end
-end
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
index 838ca9fabefedcb05e6849055fb33fc489d83351..57f5adbbc40d2a68b584d43e218396dbc431bb9d 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -1,5 +1,5 @@
 require 'spec_helper'
-require_relative '../../config/initializers/1_settings'
+require_relative '../../config/initializers/1_settings' unless defined?(Settings)
 
 describe Settings do
   describe '#ldap' do
diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js
index 7a9c539e9d0206dd30346d3c6a4bfdf335d56db3..909a1bf76bc46f0e85642f63a2435e93e8349c95 100644
--- a/spec/javascripts/activities_spec.js
+++ b/spec/javascripts/activities_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow, max-len */
 
+import $ from 'jquery';
 import 'vendor/jquery.endless-scroll';
 import Activities from '~/activities';
 
diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js
index 95c2c122403020c659655a04d4b3ae54e6c9936a..261375d3a0e2b1b1e5cd779fdeba7c7f98a56c9b 100644
--- a/spec/javascripts/ajax_loading_spinner_spec.js
+++ b/spec/javascripts/ajax_loading_spinner_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import AjaxLoadingSpinner from '~/ajax_loading_spinner';
 
 describe('Ajax Loading Spinner', () => {
@@ -10,7 +11,7 @@ describe('Ajax Loading Spinner', () => {
   });
 
   it('change current icon with spinner icon and disable link while waiting ajax response', (done) => {
-    spyOn(jQuery, 'ajax').and.callFake((req) => {
+    spyOn($, 'ajax').and.callFake((req) => {
       const xhr = new XMLHttpRequest();
       const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
       const icon = ajaxLoadingSpinner.querySelector('i');
@@ -33,7 +34,7 @@ describe('Ajax Loading Spinner', () => {
   });
 
   it('use original icon again and enabled the link after complete the ajax request', (done) => {
-    spyOn(jQuery, 'ajax').and.callFake((req) => {
+    spyOn($, 'ajax').and.callFake((req) => {
       const xhr = new XMLHttpRequest();
       const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
 
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index cf3a76d0d2e85762b35a293454920cf82e80ca03..3d7ccf432be91adfa50d71a50e886cbdc099dd70 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -35,14 +35,14 @@ describe('Api', () => {
   });
 
   describe('group', () => {
-    it('fetches a group', (done) => {
+    it('fetches a group', done => {
       const groupId = '123456';
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
       mock.onGet(expectedUrl).reply(200, {
         name: 'test',
       });
 
-      Api.group(groupId, (response) => {
+      Api.group(groupId, response => {
         expect(response.name).toBe('test');
         done();
       });
@@ -50,15 +50,17 @@ describe('Api', () => {
   });
 
   describe('groups', () => {
-    it('fetches groups', (done) => {
+    it('fetches groups', done => {
       const query = 'dummy query';
       const options = { unused: 'option' };
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
-      mock.onGet(expectedUrl).reply(200, [{
-        name: 'test',
-      }]);
+      mock.onGet(expectedUrl).reply(200, [
+        {
+          name: 'test',
+        },
+      ]);
 
-      Api.groups(query, options, (response) => {
+      Api.groups(query, options, response => {
         expect(response.length).toBe(1);
         expect(response[0].name).toBe('test');
         done();
@@ -67,14 +69,16 @@ describe('Api', () => {
   });
 
   describe('namespaces', () => {
-    it('fetches namespaces', (done) => {
+    it('fetches namespaces', done => {
       const query = 'dummy query';
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
-      mock.onGet(expectedUrl).reply(200, [{
-        name: 'test',
-      }]);
+      mock.onGet(expectedUrl).reply(200, [
+        {
+          name: 'test',
+        },
+      ]);
 
-      Api.namespaces(query, (response) => {
+      Api.namespaces(query, response => {
         expect(response.length).toBe(1);
         expect(response[0].name).toBe('test');
         done();
@@ -83,31 +87,35 @@ describe('Api', () => {
   });
 
   describe('projects', () => {
-    it('fetches projects with membership when logged in', (done) => {
+    it('fetches projects with membership when logged in', done => {
       const query = 'dummy query';
       const options = { unused: 'option' };
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
       window.gon.current_user_id = 1;
-      mock.onGet(expectedUrl).reply(200, [{
-        name: 'test',
-      }]);
+      mock.onGet(expectedUrl).reply(200, [
+        {
+          name: 'test',
+        },
+      ]);
 
-      Api.projects(query, options, (response) => {
+      Api.projects(query, options, response => {
         expect(response.length).toBe(1);
         expect(response[0].name).toBe('test');
         done();
       });
     });
 
-    it('fetches projects without membership when not logged in', (done) => {
+    it('fetches projects without membership when not logged in', done => {
       const query = 'dummy query';
       const options = { unused: 'option' };
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
-      mock.onGet(expectedUrl).reply(200, [{
-        name: 'test',
-      }]);
+      mock.onGet(expectedUrl).reply(200, [
+        {
+          name: 'test',
+        },
+      ]);
 
-      Api.projects(query, options, (response) => {
+      Api.projects(query, options, response => {
         expect(response.length).toBe(1);
         expect(response[0].name).toBe('test');
         done();
@@ -115,8 +123,65 @@ describe('Api', () => {
     });
   });
 
+  describe('mergerequest', () => {
+    it('fetches a merge request', done => {
+      const projectPath = 'abc';
+      const mergeRequestId = '123456';
+      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`;
+      mock.onGet(expectedUrl).reply(200, {
+        title: 'test',
+      });
+
+      Api.mergeRequest(projectPath, mergeRequestId)
+        .then(({ data }) => {
+          expect(data.title).toBe('test');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('mergerequest changes', () => {
+    it('fetches the changes of a merge request', done => {
+      const projectPath = 'abc';
+      const mergeRequestId = '123456';
+      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`;
+      mock.onGet(expectedUrl).reply(200, {
+        title: 'test',
+      });
+
+      Api.mergeRequestChanges(projectPath, mergeRequestId)
+        .then(({ data }) => {
+          expect(data.title).toBe('test');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('mergerequest versions', () => {
+    it('fetches the versions of a merge request', done => {
+      const projectPath = 'abc';
+      const mergeRequestId = '123456';
+      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`;
+      mock.onGet(expectedUrl).reply(200, [
+        {
+          id: 123,
+        },
+      ]);
+
+      Api.mergeRequestVersions(projectPath, mergeRequestId)
+        .then(({ data }) => {
+          expect(data.length).toBe(1);
+          expect(data[0].id).toBe(123);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
   describe('newLabel', () => {
-    it('creates a new label', (done) => {
+    it('creates a new label', done => {
       const namespace = 'some namespace';
       const project = 'some project';
       const labelData = { some: 'data' };
@@ -124,15 +189,42 @@ describe('Api', () => {
       const expectedData = {
         label: labelData,
       };
-      mock.onPost(expectedUrl).reply((config) => {
+      mock.onPost(expectedUrl).reply(config => {
         expect(config.data).toBe(JSON.stringify(expectedData));
 
-        return [200, {
-          name: 'test',
-        }];
+        return [
+          200,
+          {
+            name: 'test',
+          },
+        ];
       });
 
-      Api.newLabel(namespace, project, labelData, (response) => {
+      Api.newLabel(namespace, project, labelData, response => {
+        expect(response.name).toBe('test');
+        done();
+      });
+    });
+
+    it('creates a group label', done => {
+      const namespace = 'group/subgroup';
+      const labelData = { some: 'data' };
+      const expectedUrl = `${dummyUrlRoot}/groups/${namespace}/-/labels`;
+      const expectedData = {
+        label: labelData,
+      };
+      mock.onPost(expectedUrl).reply(config => {
+        expect(config.data).toBe(JSON.stringify(expectedData));
+
+        return [
+          200,
+          {
+            name: 'test',
+          },
+        ];
+      });
+
+      Api.newLabel(namespace, undefined, labelData, response => {
         expect(response.name).toBe('test');
         done();
       });
@@ -140,15 +232,17 @@ describe('Api', () => {
   });
 
   describe('groupProjects', () => {
-    it('fetches group projects', (done) => {
+    it('fetches group projects', done => {
       const groupId = '123456';
       const query = 'dummy query';
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
-      mock.onGet(expectedUrl).reply(200, [{
-        name: 'test',
-      }]);
+      mock.onGet(expectedUrl).reply(200, [
+        {
+          name: 'test',
+        },
+      ]);
 
-      Api.groupProjects(groupId, query, (response) => {
+      Api.groupProjects(groupId, query, response => {
         expect(response.length).toBe(1);
         expect(response[0].name).toBe('test');
         done();
@@ -157,13 +251,13 @@ describe('Api', () => {
   });
 
   describe('licenseText', () => {
-    it('fetches a license text', (done) => {
+    it('fetches a license text', done => {
       const licenseKey = "driver's license";
       const data = { unused: 'option' };
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/licenses/${licenseKey}`;
       mock.onGet(expectedUrl).reply(200, 'test');
 
-      Api.licenseText(licenseKey, data, (response) => {
+      Api.licenseText(licenseKey, data, response => {
         expect(response).toBe('test');
         done();
       });
@@ -171,12 +265,12 @@ describe('Api', () => {
   });
 
   describe('gitignoreText', () => {
-    it('fetches a gitignore text', (done) => {
+    it('fetches a gitignore text', done => {
       const gitignoreKey = 'ignore git';
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitignores/${gitignoreKey}`;
       mock.onGet(expectedUrl).reply(200, 'test');
 
-      Api.gitignoreText(gitignoreKey, (response) => {
+      Api.gitignoreText(gitignoreKey, response => {
         expect(response).toBe('test');
         done();
       });
@@ -184,12 +278,12 @@ describe('Api', () => {
   });
 
   describe('gitlabCiYml', () => {
-    it('fetches a .gitlab-ci.yml', (done) => {
+    it('fetches a .gitlab-ci.yml', done => {
       const gitlabCiYmlKey = 'Y CI ML';
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitlab_ci_ymls/${gitlabCiYmlKey}`;
       mock.onGet(expectedUrl).reply(200, 'test');
 
-      Api.gitlabCiYml(gitlabCiYmlKey, (response) => {
+      Api.gitlabCiYml(gitlabCiYmlKey, response => {
         expect(response).toBe('test');
         done();
       });
@@ -197,12 +291,12 @@ describe('Api', () => {
   });
 
   describe('dockerfileYml', () => {
-    it('fetches a Dockerfile', (done) => {
+    it('fetches a Dockerfile', done => {
       const dockerfileYmlKey = 'a giant whale';
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/dockerfiles/${dockerfileYmlKey}`;
       mock.onGet(expectedUrl).reply(200, 'test');
 
-      Api.dockerfileYml(dockerfileYmlKey, (response) => {
+      Api.dockerfileYml(dockerfileYmlKey, response => {
         expect(response).toBe('test');
         done();
       });
@@ -210,12 +304,14 @@ describe('Api', () => {
   });
 
   describe('issueTemplate', () => {
-    it('fetches an issue template', (done) => {
+    it('fetches an issue template', done => {
       const namespace = 'some namespace';
       const project = 'some project';
       const templateKey = ' template #%?.key ';
       const templateType = 'template type';
-      const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(templateKey)}`;
+      const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(
+        templateKey,
+      )}`;
       mock.onGet(expectedUrl).reply(200, 'test');
 
       Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
@@ -226,13 +322,15 @@ describe('Api', () => {
   });
 
   describe('users', () => {
-    it('fetches users', (done) => {
+    it('fetches users', done => {
       const query = 'dummy query';
       const options = { unused: 'option' };
       const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
-      mock.onGet(expectedUrl).reply(200, [{
-        name: 'test',
-      }]);
+      mock.onGet(expectedUrl).reply(200, [
+        {
+          name: 'test',
+        },
+      ]);
 
       Api.users(query, options)
         .then(({ data }) => {
diff --git a/spec/javascripts/autosave_spec.js b/spec/javascripts/autosave_spec.js
index b568d7fa8b067c2321cba75101651f3de96f4056..38ae5b7e00c96a3a9a6879829f2e5120419f9749 100644
--- a/spec/javascripts/autosave_spec.js
+++ b/spec/javascripts/autosave_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Autosave from '~/autosave';
 import AccessorUtilities from '~/lib/utils/accessor';
 
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 8e4bbb90ccb4803bb59a62057c2127bed37e7953..e81055bc08fc618e0ab549817bf14ba0ac411131 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,5 +1,6 @@
 /* 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 $ from 'jquery';
 import Cookies from 'js-cookie';
 import loadAwardsHandler from '~/awards_handler';
 
diff --git a/spec/javascripts/badges/components/badge_form_spec.js b/spec/javascripts/badges/components/badge_form_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..dd21ec279cb9098c24cbe22c438b81a354eb03fd
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_form_spec.js
@@ -0,0 +1,171 @@
+import Vue from 'vue';
+import store from '~/badges/store';
+import BadgeForm from '~/badges/components/badge_form.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeForm component', () => {
+  const Component = Vue.extend(BadgeForm);
+  let vm;
+
+  beforeEach(() => {
+    setFixtures(`
+      <div id="dummy-element"></div>
+    `);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('methods', () => {
+    beforeEach(() => {
+      vm = mountComponentWithStore(Component, {
+        el: '#dummy-element',
+        store,
+        props: {
+          isEditing: false,
+        },
+      });
+    });
+
+    describe('onCancel', () => {
+      it('calls stopEditing', () => {
+        spyOn(vm, 'stopEditing');
+
+        vm.onCancel();
+
+        expect(vm.stopEditing).toHaveBeenCalled();
+      });
+    });
+
+    describe('onSubmit', () => {
+      describe('if isEditing is true', () => {
+        beforeEach(() => {
+          spyOn(vm, 'saveBadge').and.returnValue(Promise.resolve());
+          store.replaceState({
+            ...store.state,
+            isSaving: false,
+            badgeInEditForm: createDummyBadge(),
+          });
+          vm.isEditing = true;
+        });
+
+        it('returns immediately if imageUrl is empty', () => {
+          store.state.badgeInEditForm.imageUrl = '';
+
+          vm.onSubmit();
+
+          expect(vm.saveBadge).not.toHaveBeenCalled();
+        });
+
+        it('returns immediately if linkUrl is empty', () => {
+          store.state.badgeInEditForm.linkUrl = '';
+
+          vm.onSubmit();
+
+          expect(vm.saveBadge).not.toHaveBeenCalled();
+        });
+
+        it('returns immediately if isSaving is true', () => {
+          store.state.isSaving = true;
+
+          vm.onSubmit();
+
+          expect(vm.saveBadge).not.toHaveBeenCalled();
+        });
+
+        it('calls saveBadge', () => {
+          vm.onSubmit();
+
+          expect(vm.saveBadge).toHaveBeenCalled();
+        });
+      });
+
+      describe('if isEditing is false', () => {
+        beforeEach(() => {
+          spyOn(vm, 'addBadge').and.returnValue(Promise.resolve());
+          store.replaceState({
+            ...store.state,
+            isSaving: false,
+            badgeInAddForm: createDummyBadge(),
+          });
+          vm.isEditing = false;
+        });
+
+        it('returns immediately if imageUrl is empty', () => {
+          store.state.badgeInAddForm.imageUrl = '';
+
+          vm.onSubmit();
+
+          expect(vm.addBadge).not.toHaveBeenCalled();
+        });
+
+        it('returns immediately if linkUrl is empty', () => {
+          store.state.badgeInAddForm.linkUrl = '';
+
+          vm.onSubmit();
+
+          expect(vm.addBadge).not.toHaveBeenCalled();
+        });
+
+        it('returns immediately if isSaving is true', () => {
+          store.state.isSaving = true;
+
+          vm.onSubmit();
+
+          expect(vm.addBadge).not.toHaveBeenCalled();
+        });
+
+        it('calls addBadge', () => {
+          vm.onSubmit();
+
+          expect(vm.addBadge).toHaveBeenCalled();
+        });
+      });
+    });
+  });
+
+  describe('if isEditing is false', () => {
+    beforeEach(() => {
+      vm = mountComponentWithStore(Component, {
+        el: '#dummy-element',
+        store,
+        props: {
+          isEditing: false,
+        },
+      });
+    });
+
+    it('renders one button', () => {
+      const buttons = vm.$el.querySelectorAll('.row-content-block button');
+      expect(buttons.length).toBe(1);
+      const buttonAddElement = buttons[0];
+      expect(buttonAddElement).toBeVisible();
+      expect(buttonAddElement).toHaveText('Add badge');
+    });
+  });
+
+  describe('if isEditing is true', () => {
+    beforeEach(() => {
+      vm = mountComponentWithStore(Component, {
+        el: '#dummy-element',
+        store,
+        props: {
+          isEditing: true,
+        },
+      });
+    });
+
+    it('renders two buttons', () => {
+      const buttons = vm.$el.querySelectorAll('.row-content-block button');
+      expect(buttons.length).toBe(2);
+      const buttonSaveElement = buttons[0];
+      expect(buttonSaveElement).toBeVisible();
+      expect(buttonSaveElement).toHaveText('Save changes');
+      const buttonCancelElement = buttons[1];
+      expect(buttonCancelElement).toBeVisible();
+      expect(buttonCancelElement).toHaveText('Cancel');
+    });
+  });
+});
diff --git a/spec/javascripts/badges/components/badge_list_row_spec.js b/spec/javascripts/badges/components/badge_list_row_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..21bd00d82f05dd51565c7a60d9ef780e5a7331c7
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_list_row_spec.js
@@ -0,0 +1,97 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
+import store from '~/badges/store';
+import BadgeListRow from '~/badges/components/badge_list_row.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeListRow component', () => {
+  const Component = Vue.extend(BadgeListRow);
+  let badge;
+  let vm;
+
+  beforeEach(() => {
+    setFixtures(`
+      <div id="delete-badge-modal" class="modal"></div>
+      <div id="dummy-element"></div>
+    `);
+    store.replaceState({
+      ...store.state,
+      kind: PROJECT_BADGE,
+    });
+    badge = createDummyBadge();
+    vm = mountComponentWithStore(Component, {
+      el: '#dummy-element',
+      store,
+      props: { badge },
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders the badge', () => {
+    const badgeElement = vm.$el.querySelector('.project-badge');
+    expect(badgeElement).not.toBeNull();
+    expect(badgeElement.getAttribute('src')).toBe(badge.renderedImageUrl);
+  });
+
+  it('renders the badge link', () => {
+    expect(vm.$el).toContainText(badge.linkUrl);
+  });
+
+  it('renders the badge kind', () => {
+    expect(vm.$el).toContainText('Project Badge');
+  });
+
+  it('shows edit and delete buttons', () => {
+    const buttons = vm.$el.querySelectorAll('.table-button-footer button');
+    expect(buttons).toHaveLength(2);
+    const buttonEditElement = buttons[0];
+    expect(buttonEditElement).toBeVisible();
+    expect(buttonEditElement).toHaveSpriteIcon('pencil');
+    const buttonDeleteElement = buttons[1];
+    expect(buttonDeleteElement).toBeVisible();
+    expect(buttonDeleteElement).toHaveSpriteIcon('remove');
+  });
+
+  it('calls editBadge when clicking then edit button', () => {
+    spyOn(vm, 'editBadge');
+
+    const editButton = vm.$el.querySelector('.table-button-footer button:first-of-type');
+    editButton.click();
+
+    expect(vm.editBadge).toHaveBeenCalled();
+  });
+
+  it('calls updateBadgeInModal and shows modal when clicking then delete button', done => {
+    spyOn(vm, 'updateBadgeInModal');
+    $('#delete-badge-modal').on('shown.bs.modal', () => done());
+
+    const deleteButton = vm.$el.querySelector('.table-button-footer button:last-of-type');
+    deleteButton.click();
+
+    expect(vm.updateBadgeInModal).toHaveBeenCalled();
+  });
+
+  describe('for a group badge', () => {
+    beforeEach(done => {
+      badge.kind = GROUP_BADGE;
+
+      Vue.nextTick()
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('renders the badge kind', () => {
+      expect(vm.$el).toContainText('Group Badge');
+    });
+
+    it('hides edit and delete buttons', () => {
+      const buttons = vm.$el.querySelectorAll('.table-button-footer button');
+      expect(buttons).toHaveLength(0);
+    });
+  });
+});
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9439c5789734c6e9adf4ed68725bc8fa5c70a039
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -0,0 +1,88 @@
+import Vue from 'vue';
+import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
+import store from '~/badges/store';
+import BadgeList from '~/badges/components/badge_list.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeList component', () => {
+  const Component = Vue.extend(BadgeList);
+  const numberOfDummyBadges = 3;
+  let vm;
+
+  beforeEach(() => {
+    setFixtures('<div id="dummy-element"></div>');
+    const badges = [];
+    for (let id = 0; id < numberOfDummyBadges; id += 1) {
+      badges.push({ id, ...createDummyBadge() });
+    }
+    store.replaceState({
+      ...store.state,
+      badges,
+      kind: PROJECT_BADGE,
+      isLoading: false,
+    });
+    vm = mountComponentWithStore(Component, {
+      el: '#dummy-element',
+      store,
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders a header with the badge count', () => {
+    const header = vm.$el.querySelector('.panel-heading');
+    expect(header).toHaveText(new RegExp(`Your badges\\s+${numberOfDummyBadges}`));
+  });
+
+  it('renders a row for each badge', () => {
+    const rows = vm.$el.querySelectorAll('.gl-responsive-table-row');
+    expect(rows).toHaveLength(numberOfDummyBadges);
+  });
+
+  it('renders a message if no badges exist', done => {
+    store.state.badges = [];
+
+    Vue.nextTick()
+      .then(() => {
+        expect(vm.$el).toContainText('This project has no badges');
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('shows a loading icon when loading', done => {
+    store.state.isLoading = true;
+
+    Vue.nextTick()
+      .then(() => {
+        const loadingIcon = vm.$el.querySelector('.fa-spinner');
+        expect(loadingIcon).toBeVisible();
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  describe('for group badges', () => {
+    beforeEach(done => {
+      store.state.kind = GROUP_BADGE;
+
+      Vue.nextTick()
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('renders a message if no badges exist', done => {
+      store.state.badges = [];
+
+      Vue.nextTick()
+        .then(() => {
+          expect(vm.$el).toContainText('This group has no badges');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/badges/components/badge_settings_spec.js b/spec/javascripts/badges/components/badge_settings_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3db02982ad44dbb4459e5e5c332f474fa53b4ef0
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_settings_spec.js
@@ -0,0 +1,109 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import store from '~/badges/store';
+import BadgeSettings from '~/badges/components/badge_settings.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeSettings component', () => {
+  const Component = Vue.extend(BadgeSettings);
+  let vm;
+
+  beforeEach(() => {
+    setFixtures(`
+      <div id="dummy-element"></div>
+      <button
+        id="dummy-modal-button"
+        type="button"
+        data-toggle="modal"
+        data-target="#delete-badge-modal"
+      >Show modal</button>
+    `);
+    vm = mountComponentWithStore(Component, {
+      el: '#dummy-element',
+      store,
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('displays modal if button is clicked', done => {
+    const badge = createDummyBadge();
+    store.state.badgeInModal = badge;
+    const modal = vm.$el.querySelector('#delete-badge-modal');
+    const button = document.getElementById('dummy-modal-button');
+
+    $(modal).on('shown.bs.modal', () => {
+      expect(modal).toContainText('Delete badge?');
+      const badgeElement = modal.querySelector('img.project-badge');
+      expect(badgeElement).not.toBe(null);
+      expect(badgeElement.getAttribute('src')).toBe(badge.renderedImageUrl);
+
+      done();
+    });
+
+    Vue.nextTick()
+      .then(() => {
+        button.click();
+      })
+      .catch(done.fail);
+  });
+
+  it('displays a form to add a badge', () => {
+    const form = vm.$el.querySelector('form:nth-of-type(2)');
+    expect(form).not.toBe(null);
+    const button = form.querySelector('.btn-success');
+    expect(button).not.toBe(null);
+    expect(button).toHaveText(/Add badge/);
+  });
+
+  it('displays badge list', () => {
+    const badgeListElement = vm.$el.querySelector('.panel');
+    expect(badgeListElement).not.toBe(null);
+    expect(badgeListElement).toBeVisible();
+    expect(badgeListElement).toContainText('Your badges');
+  });
+
+  describe('when editing', () => {
+    beforeEach(done => {
+      store.state.isEditing = true;
+
+      Vue.nextTick()
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('displays a form to edit a badge', () => {
+      const form = vm.$el.querySelector('form:nth-of-type(1)');
+      expect(form).not.toBe(null);
+      const submitButton = form.querySelector('.btn-success');
+      expect(submitButton).not.toBe(null);
+      expect(submitButton).toHaveText(/Save changes/);
+      const cancelButton = form.querySelector('.btn-cancel');
+      expect(cancelButton).not.toBe(null);
+      expect(cancelButton).toHaveText(/Cancel/);
+    });
+
+    it('displays no badge list', () => {
+      const badgeListElement = vm.$el.querySelector('.panel');
+      expect(badgeListElement).toBeHidden();
+    });
+  });
+
+  describe('methods', () => {
+    describe('onSubmitModal', () => {
+      it('triggers ', () => {
+        spyOn(vm, 'deleteBadge').and.callFake(() => Promise.resolve());
+        const modal = vm.$el.querySelector('#delete-badge-modal');
+        const deleteButton = modal.querySelector('.btn-danger');
+
+        deleteButton.click();
+
+        const badge = store.state.badgeInModal;
+        expect(vm.deleteBadge).toHaveBeenCalledWith(badge);
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd1ecc9cdd880af128a516ca96d9414cbe307a5a
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -0,0 +1,147 @@
+import Vue from 'vue';
+import Badge from '~/badges/components/badge.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
+
+describe('Badge component', () => {
+  const Component = Vue.extend(Badge);
+  const dummyProps = {
+    imageUrl: DUMMY_IMAGE_URL,
+    linkUrl: `${TEST_HOST}/badge/link/url`,
+  };
+  let vm;
+
+  const findElements = () => {
+    const buttons = vm.$el.querySelectorAll('button');
+    return {
+      badgeImage: vm.$el.querySelector('img.project-badge'),
+      loadingIcon: vm.$el.querySelector('.fa-spinner'),
+      reloadButton: buttons[buttons.length - 1],
+    };
+  };
+
+  const createComponent = (props, el = null) => {
+    vm = mountComponent(Component, props, el);
+    const { badgeImage } = findElements();
+    return new Promise(resolve => badgeImage.addEventListener('load', resolve)).then(() =>
+      Vue.nextTick(),
+    );
+  };
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('watchers', () => {
+    describe('imageUrl', () => {
+      it('sets isLoading and resets numRetries and hasError', done => {
+        const props = { ...dummyProps };
+        createComponent(props)
+          .then(() => {
+            expect(vm.isLoading).toBe(false);
+            vm.hasError = true;
+            vm.numRetries = 42;
+
+            vm.imageUrl = `${props.imageUrl}#something/else`;
+
+            return Vue.nextTick();
+          })
+          .then(() => {
+            expect(vm.isLoading).toBe(true);
+            expect(vm.numRetries).toBe(0);
+            expect(vm.hasError).toBe(false);
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+    });
+  });
+
+  describe('methods', () => {
+    beforeEach(done => {
+      createComponent({ ...dummyProps })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('onError resets isLoading and sets hasError', () => {
+      vm.hasError = false;
+      vm.isLoading = true;
+
+      vm.onError();
+
+      expect(vm.hasError).toBe(true);
+      expect(vm.isLoading).toBe(false);
+    });
+
+    it('onLoad sets isLoading', () => {
+      vm.isLoading = true;
+
+      vm.onLoad();
+
+      expect(vm.isLoading).toBe(false);
+    });
+
+    it('reloadImage resets isLoading and hasError and increases numRetries', () => {
+      vm.hasError = true;
+      vm.isLoading = false;
+      vm.numRetries = 0;
+
+      vm.reloadImage();
+
+      expect(vm.hasError).toBe(false);
+      expect(vm.isLoading).toBe(true);
+      expect(vm.numRetries).toBe(1);
+    });
+  });
+
+  describe('behavior', () => {
+    beforeEach(done => {
+      setFixtures('<div id="dummy-element"></div>');
+      createComponent({ ...dummyProps }, '#dummy-element')
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('shows a badge image after loading', () => {
+      expect(vm.isLoading).toBe(false);
+      expect(vm.hasError).toBe(false);
+      const { badgeImage, loadingIcon, reloadButton } = findElements();
+      expect(badgeImage).toBeVisible();
+      expect(loadingIcon).toBeHidden();
+      expect(reloadButton).toBeHidden();
+      expect(vm.$el.innerText).toBe('');
+    });
+
+    it('shows a loading icon when loading', done => {
+      vm.isLoading = true;
+
+      Vue.nextTick()
+        .then(() => {
+          const { badgeImage, loadingIcon, reloadButton } = findElements();
+          expect(badgeImage).toBeHidden();
+          expect(loadingIcon).toBeVisible();
+          expect(reloadButton).toBeHidden();
+          expect(vm.$el.innerText).toBe('');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('shows an error and reload button if loading failed', done => {
+      vm.hasError = true;
+
+      Vue.nextTick()
+        .then(() => {
+          const { badgeImage, loadingIcon, reloadButton } = findElements();
+          expect(badgeImage).toBeHidden();
+          expect(loadingIcon).toBeHidden();
+          expect(reloadButton).toBeVisible();
+          expect(reloadButton).toHaveSpriteIcon('retry');
+          expect(vm.$el.innerText.trim()).toBe('No badge image');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/badges/dummy_badge.js b/spec/javascripts/badges/dummy_badge.js
new file mode 100644
index 0000000000000000000000000000000000000000..6aaff21c50303121d92dd1189605cf03e87b0102
--- /dev/null
+++ b/spec/javascripts/badges/dummy_badge.js
@@ -0,0 +1,23 @@
+import { PROJECT_BADGE } from '~/badges/constants';
+import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
+
+export const createDummyBadge = () => {
+  const id = Math.floor(1000 * Math.random());
+  return {
+    id,
+    imageUrl: `${TEST_HOST}/badges/${id}/image/url`,
+    isDeleting: false,
+    linkUrl: `${TEST_HOST}/badges/${id}/link/url`,
+    kind: PROJECT_BADGE,
+    renderedImageUrl: `${DUMMY_IMAGE_URL}?id=${id}`,
+    renderedLinkUrl: `${TEST_HOST}/badges/${id}/rendered/link/url`,
+  };
+};
+
+export const createDummyBadgeResponse = () => ({
+  image_url: `${TEST_HOST}/badge/image/url`,
+  link_url: `${TEST_HOST}/badge/link/url`,
+  kind: PROJECT_BADGE,
+  rendered_image_url: DUMMY_IMAGE_URL,
+  rendered_link_url: `${TEST_HOST}/rendered/badge/link/url`,
+});
diff --git a/spec/javascripts/badges/store/actions_spec.js b/spec/javascripts/badges/store/actions_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb6263c6de419daced04a43441d6b55ffe31cd38
--- /dev/null
+++ b/spec/javascripts/badges/store/actions_spec.js
@@ -0,0 +1,607 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import actions, { transformBackendBadge } from '~/badges/store/actions';
+import mutationTypes from '~/badges/store/mutation_types';
+import createState from '~/badges/store/state';
+import { TEST_HOST } from 'spec/test_constants';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { createDummyBadge, createDummyBadgeResponse } from '../dummy_badge';
+
+describe('Badges store actions', () => {
+  const dummyEndpointUrl = `${TEST_HOST}/badges/endpoint`;
+  const dummyBadges = [{ ...createDummyBadge(), id: 5 }, { ...createDummyBadge(), id: 6 }];
+
+  let axiosMock;
+  let badgeId;
+  let state;
+
+  beforeEach(() => {
+    axiosMock = new MockAdapter(axios);
+    state = {
+      ...createState(),
+      apiEndpointUrl: dummyEndpointUrl,
+      badges: dummyBadges,
+    };
+    badgeId = state.badges[0].id;
+  });
+
+  afterEach(() => {
+    axiosMock.restore();
+  });
+
+  describe('requestNewBadge', () => {
+    it('commits REQUEST_NEW_BADGE', done => {
+      testAction(
+        actions.requestNewBadge,
+        null,
+        state,
+        [{ type: mutationTypes.REQUEST_NEW_BADGE }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveNewBadge', () => {
+    it('commits RECEIVE_NEW_BADGE', done => {
+      const newBadge = createDummyBadge();
+      testAction(
+        actions.receiveNewBadge,
+        newBadge,
+        state,
+        [{ type: mutationTypes.RECEIVE_NEW_BADGE, payload: newBadge }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveNewBadgeError', () => {
+    it('commits RECEIVE_NEW_BADGE_ERROR', done => {
+      testAction(
+        actions.receiveNewBadgeError,
+        null,
+        state,
+        [{ type: mutationTypes.RECEIVE_NEW_BADGE_ERROR }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('addBadge', () => {
+    let badgeInAddForm;
+    let dispatch;
+    let endpointMock;
+
+    beforeEach(() => {
+      endpointMock = axiosMock.onPost(dummyEndpointUrl);
+      dispatch = jasmine.createSpy('dispatch');
+      badgeInAddForm = createDummyBadge();
+      state = {
+        ...state,
+        badgeInAddForm,
+      };
+    });
+
+    it('dispatches requestNewBadge and receiveNewBadge for successful response', done => {
+      const dummyResponse = createDummyBadgeResponse();
+
+      endpointMock.replyOnce(req => {
+        expect(req.data).toBe(
+          JSON.stringify({
+            image_url: badgeInAddForm.imageUrl,
+            link_url: badgeInAddForm.linkUrl,
+          }),
+        );
+        expect(dispatch.calls.allArgs()).toEqual([['requestNewBadge']]);
+        dispatch.calls.reset();
+        return [200, dummyResponse];
+      });
+
+      const dummyBadge = transformBackendBadge(dummyResponse);
+      actions
+        .addBadge({ state, dispatch })
+        .then(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveNewBadge', dummyBadge]]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('dispatches requestNewBadge and receiveNewBadgeError for error response', done => {
+      endpointMock.replyOnce(req => {
+        expect(req.data).toBe(
+          JSON.stringify({
+            image_url: badgeInAddForm.imageUrl,
+            link_url: badgeInAddForm.linkUrl,
+          }),
+        );
+        expect(dispatch.calls.allArgs()).toEqual([['requestNewBadge']]);
+        dispatch.calls.reset();
+        return [500, ''];
+      });
+
+      actions
+        .addBadge({ state, dispatch })
+        .then(() => done.fail('Expected Ajax call to fail!'))
+        .catch(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveNewBadgeError']]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('requestDeleteBadge', () => {
+    it('commits REQUEST_DELETE_BADGE', done => {
+      testAction(
+        actions.requestDeleteBadge,
+        badgeId,
+        state,
+        [{ type: mutationTypes.REQUEST_DELETE_BADGE, payload: badgeId }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveDeleteBadge', () => {
+    it('commits RECEIVE_DELETE_BADGE', done => {
+      testAction(
+        actions.receiveDeleteBadge,
+        badgeId,
+        state,
+        [{ type: mutationTypes.RECEIVE_DELETE_BADGE, payload: badgeId }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveDeleteBadgeError', () => {
+    it('commits RECEIVE_DELETE_BADGE_ERROR', done => {
+      testAction(
+        actions.receiveDeleteBadgeError,
+        badgeId,
+        state,
+        [{ type: mutationTypes.RECEIVE_DELETE_BADGE_ERROR, payload: badgeId }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('deleteBadge', () => {
+    let dispatch;
+    let endpointMock;
+
+    beforeEach(() => {
+      endpointMock = axiosMock.onDelete(`${dummyEndpointUrl}/${badgeId}`);
+      dispatch = jasmine.createSpy('dispatch');
+    });
+
+    it('dispatches requestDeleteBadge and receiveDeleteBadge for successful response', done => {
+      endpointMock.replyOnce(() => {
+        expect(dispatch.calls.allArgs()).toEqual([['requestDeleteBadge', badgeId]]);
+        dispatch.calls.reset();
+        return [200, ''];
+      });
+
+      actions
+        .deleteBadge({ state, dispatch }, { id: badgeId })
+        .then(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveDeleteBadge', badgeId]]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('dispatches requestDeleteBadge and receiveDeleteBadgeError for error response', done => {
+      endpointMock.replyOnce(() => {
+        expect(dispatch.calls.allArgs()).toEqual([['requestDeleteBadge', badgeId]]);
+        dispatch.calls.reset();
+        return [500, ''];
+      });
+
+      actions
+        .deleteBadge({ state, dispatch }, { id: badgeId })
+        .then(() => done.fail('Expected Ajax call to fail!'))
+        .catch(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveDeleteBadgeError', badgeId]]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('editBadge', () => {
+    it('commits START_EDITING', done => {
+      const dummyBadge = createDummyBadge();
+      testAction(
+        actions.editBadge,
+        dummyBadge,
+        state,
+        [{ type: mutationTypes.START_EDITING, payload: dummyBadge }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('requestLoadBadges', () => {
+    it('commits REQUEST_LOAD_BADGES', done => {
+      const dummyData = 'this is not real data';
+      testAction(
+        actions.requestLoadBadges,
+        dummyData,
+        state,
+        [{ type: mutationTypes.REQUEST_LOAD_BADGES, payload: dummyData }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveLoadBadges', () => {
+    it('commits RECEIVE_LOAD_BADGES', done => {
+      const badges = dummyBadges;
+      testAction(
+        actions.receiveLoadBadges,
+        badges,
+        state,
+        [{ type: mutationTypes.RECEIVE_LOAD_BADGES, payload: badges }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveLoadBadgesError', () => {
+    it('commits RECEIVE_LOAD_BADGES_ERROR', done => {
+      testAction(
+        actions.receiveLoadBadgesError,
+        null,
+        state,
+        [{ type: mutationTypes.RECEIVE_LOAD_BADGES_ERROR }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('loadBadges', () => {
+    let dispatch;
+    let endpointMock;
+
+    beforeEach(() => {
+      endpointMock = axiosMock.onGet(dummyEndpointUrl);
+      dispatch = jasmine.createSpy('dispatch');
+    });
+
+    it('dispatches requestLoadBadges and receiveLoadBadges for successful response', done => {
+      const dummyData = 'this is just some data';
+      const dummyReponse = [
+        createDummyBadgeResponse(),
+        createDummyBadgeResponse(),
+        createDummyBadgeResponse(),
+      ];
+      endpointMock.replyOnce(() => {
+        expect(dispatch.calls.allArgs()).toEqual([['requestLoadBadges', dummyData]]);
+        dispatch.calls.reset();
+        return [200, dummyReponse];
+      });
+
+      actions
+        .loadBadges({ state, dispatch }, dummyData)
+        .then(() => {
+          const badges = dummyReponse.map(transformBackendBadge);
+          expect(dispatch.calls.allArgs()).toEqual([['receiveLoadBadges', badges]]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('dispatches requestLoadBadges and receiveLoadBadgesError for error response', done => {
+      const dummyData = 'this is just some data';
+      endpointMock.replyOnce(() => {
+        expect(dispatch.calls.allArgs()).toEqual([['requestLoadBadges', dummyData]]);
+        dispatch.calls.reset();
+        return [500, ''];
+      });
+
+      actions
+        .loadBadges({ state, dispatch }, dummyData)
+        .then(() => done.fail('Expected Ajax call to fail!'))
+        .catch(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveLoadBadgesError']]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('requestRenderedBadge', () => {
+    it('commits REQUEST_RENDERED_BADGE', done => {
+      testAction(
+        actions.requestRenderedBadge,
+        null,
+        state,
+        [{ type: mutationTypes.REQUEST_RENDERED_BADGE }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveRenderedBadge', () => {
+    it('commits RECEIVE_RENDERED_BADGE', done => {
+      const dummyBadge = createDummyBadge();
+      testAction(
+        actions.receiveRenderedBadge,
+        dummyBadge,
+        state,
+        [{ type: mutationTypes.RECEIVE_RENDERED_BADGE, payload: dummyBadge }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveRenderedBadgeError', () => {
+    it('commits RECEIVE_RENDERED_BADGE_ERROR', done => {
+      testAction(
+        actions.receiveRenderedBadgeError,
+        null,
+        state,
+        [{ type: mutationTypes.RECEIVE_RENDERED_BADGE_ERROR }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('renderBadge', () => {
+    let dispatch;
+    let endpointMock;
+    let badgeInForm;
+
+    beforeEach(() => {
+      badgeInForm = createDummyBadge();
+      state = {
+        ...state,
+        badgeInAddForm: badgeInForm,
+      };
+      const urlParameters = [
+        `link_url=${encodeURIComponent(badgeInForm.linkUrl)}`,
+        `image_url=${encodeURIComponent(badgeInForm.imageUrl)}`,
+      ].join('&');
+      endpointMock = axiosMock.onGet(`${dummyEndpointUrl}/render?${urlParameters}`);
+      dispatch = jasmine.createSpy('dispatch');
+    });
+
+    it('returns immediately if imageUrl is empty', done => {
+      spyOn(axios, 'get');
+      badgeInForm.imageUrl = '';
+
+      actions
+        .renderBadge({ state, dispatch })
+        .then(() => {
+          expect(axios.get).not.toHaveBeenCalled();
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('returns immediately if linkUrl is empty', done => {
+      spyOn(axios, 'get');
+      badgeInForm.linkUrl = '';
+
+      actions
+        .renderBadge({ state, dispatch })
+        .then(() => {
+          expect(axios.get).not.toHaveBeenCalled();
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('escapes user input', done => {
+      spyOn(axios, 'get').and.callFake(() => Promise.resolve({ data: createDummyBadgeResponse() }));
+      badgeInForm.imageUrl = '&make-sandwhich=true';
+      badgeInForm.linkUrl = '<script>I am dangerous!</script>';
+
+      actions
+        .renderBadge({ state, dispatch })
+        .then(() => {
+          expect(axios.get.calls.count()).toBe(1);
+          const url = axios.get.calls.argsFor(0)[0];
+          expect(url).toMatch(`^${dummyEndpointUrl}/render?`);
+          expect(url).toMatch('\\?link_url=%3Cscript%3EI%20am%20dangerous!%3C%2Fscript%3E&');
+          expect(url).toMatch('&image_url=%26make-sandwhich%3Dtrue$');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('dispatches requestRenderedBadge and receiveRenderedBadge for successful response', done => {
+      const dummyReponse = createDummyBadgeResponse();
+      endpointMock.replyOnce(() => {
+        expect(dispatch.calls.allArgs()).toEqual([['requestRenderedBadge']]);
+        dispatch.calls.reset();
+        return [200, dummyReponse];
+      });
+
+      actions
+        .renderBadge({ state, dispatch })
+        .then(() => {
+          const renderedBadge = transformBackendBadge(dummyReponse);
+          expect(dispatch.calls.allArgs()).toEqual([['receiveRenderedBadge', renderedBadge]]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('dispatches requestRenderedBadge and receiveRenderedBadgeError for error response', done => {
+      endpointMock.replyOnce(() => {
+        expect(dispatch.calls.allArgs()).toEqual([['requestRenderedBadge']]);
+        dispatch.calls.reset();
+        return [500, ''];
+      });
+
+      actions
+        .renderBadge({ state, dispatch })
+        .then(() => done.fail('Expected Ajax call to fail!'))
+        .catch(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveRenderedBadgeError']]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('requestUpdatedBadge', () => {
+    it('commits REQUEST_UPDATED_BADGE', done => {
+      testAction(
+        actions.requestUpdatedBadge,
+        null,
+        state,
+        [{ type: mutationTypes.REQUEST_UPDATED_BADGE }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveUpdatedBadge', () => {
+    it('commits RECEIVE_UPDATED_BADGE', done => {
+      const updatedBadge = createDummyBadge();
+      testAction(
+        actions.receiveUpdatedBadge,
+        updatedBadge,
+        state,
+        [{ type: mutationTypes.RECEIVE_UPDATED_BADGE, payload: updatedBadge }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('receiveUpdatedBadgeError', () => {
+    it('commits RECEIVE_UPDATED_BADGE_ERROR', done => {
+      testAction(
+        actions.receiveUpdatedBadgeError,
+        null,
+        state,
+        [{ type: mutationTypes.RECEIVE_UPDATED_BADGE_ERROR }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('saveBadge', () => {
+    let badgeInEditForm;
+    let dispatch;
+    let endpointMock;
+
+    beforeEach(() => {
+      badgeInEditForm = createDummyBadge();
+      state = {
+        ...state,
+        badgeInEditForm,
+      };
+      endpointMock = axiosMock.onPut(`${dummyEndpointUrl}/${badgeInEditForm.id}`);
+      dispatch = jasmine.createSpy('dispatch');
+    });
+
+    it('dispatches requestUpdatedBadge and receiveUpdatedBadge for successful response', done => {
+      const dummyResponse = createDummyBadgeResponse();
+
+      endpointMock.replyOnce(req => {
+        expect(req.data).toBe(
+          JSON.stringify({
+            image_url: badgeInEditForm.imageUrl,
+            link_url: badgeInEditForm.linkUrl,
+          }),
+        );
+        expect(dispatch.calls.allArgs()).toEqual([['requestUpdatedBadge']]);
+        dispatch.calls.reset();
+        return [200, dummyResponse];
+      });
+
+      const updatedBadge = transformBackendBadge(dummyResponse);
+      actions
+        .saveBadge({ state, dispatch })
+        .then(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveUpdatedBadge', updatedBadge]]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('dispatches requestUpdatedBadge and receiveUpdatedBadgeError for error response', done => {
+      endpointMock.replyOnce(req => {
+        expect(req.data).toBe(
+          JSON.stringify({
+            image_url: badgeInEditForm.imageUrl,
+            link_url: badgeInEditForm.linkUrl,
+          }),
+        );
+        expect(dispatch.calls.allArgs()).toEqual([['requestUpdatedBadge']]);
+        dispatch.calls.reset();
+        return [500, ''];
+      });
+
+      actions
+        .saveBadge({ state, dispatch })
+        .then(() => done.fail('Expected Ajax call to fail!'))
+        .catch(() => {
+          expect(dispatch.calls.allArgs()).toEqual([['receiveUpdatedBadgeError']]);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('stopEditing', () => {
+    it('commits STOP_EDITING', done => {
+      testAction(
+        actions.stopEditing,
+        null,
+        state,
+        [{ type: mutationTypes.STOP_EDITING }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('updateBadgeInForm', () => {
+    it('commits UPDATE_BADGE_IN_FORM', done => {
+      const dummyBadge = createDummyBadge();
+      testAction(
+        actions.updateBadgeInForm,
+        dummyBadge,
+        state,
+        [{ type: mutationTypes.UPDATE_BADGE_IN_FORM, payload: dummyBadge }],
+        [],
+        done,
+      );
+    });
+
+    describe('updateBadgeInModal', () => {
+      it('commits UPDATE_BADGE_IN_MODAL', done => {
+        const dummyBadge = createDummyBadge();
+        testAction(
+          actions.updateBadgeInModal,
+          dummyBadge,
+          state,
+          [{ type: mutationTypes.UPDATE_BADGE_IN_MODAL, payload: dummyBadge }],
+          [],
+          done,
+        );
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/badges/store/mutations_spec.js b/spec/javascripts/badges/store/mutations_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8d26f83339dda16119c3b142f77f5894991d1441
--- /dev/null
+++ b/spec/javascripts/badges/store/mutations_spec.js
@@ -0,0 +1,418 @@
+import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
+import store from '~/badges/store';
+import types from '~/badges/store/mutation_types';
+import createState from '~/badges/store/state';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('Badges store mutations', () => {
+  let dummyBadge;
+
+  beforeEach(() => {
+    dummyBadge = createDummyBadge();
+    store.replaceState(createState());
+  });
+
+  describe('RECEIVE_DELETE_BADGE', () => {
+    beforeEach(() => {
+      const badges = [
+        { ...dummyBadge, id: dummyBadge.id - 1 },
+        dummyBadge,
+        { ...dummyBadge, id: dummyBadge.id + 1 },
+      ];
+
+      store.replaceState({
+        ...store.state,
+        badges,
+      });
+    });
+
+    it('removes deleted badge', () => {
+      const badgeCount = store.state.badges.length;
+
+      store.commit(types.RECEIVE_DELETE_BADGE, dummyBadge.id);
+
+      expect(store.state.badges.length).toBe(badgeCount - 1);
+      expect(store.state.badges.indexOf(dummyBadge)).toBe(-1);
+    });
+  });
+
+  describe('RECEIVE_DELETE_BADGE_ERROR', () => {
+    beforeEach(() => {
+      const badges = [
+        { ...dummyBadge, id: dummyBadge.id - 1, isDeleting: false },
+        { ...dummyBadge, isDeleting: true },
+        { ...dummyBadge, id: dummyBadge.id + 1, isDeleting: true },
+      ];
+
+      store.replaceState({
+        ...store.state,
+        badges,
+      });
+    });
+
+    it('sets isDeleting to false', () => {
+      const badgeCount = store.state.badges.length;
+
+      store.commit(types.RECEIVE_DELETE_BADGE_ERROR, dummyBadge.id);
+
+      expect(store.state.badges.length).toBe(badgeCount);
+      expect(store.state.badges[0].isDeleting).toBe(false);
+      expect(store.state.badges[1].isDeleting).toBe(false);
+      expect(store.state.badges[2].isDeleting).toBe(true);
+    });
+  });
+
+  describe('RECEIVE_LOAD_BADGES', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isLoading: 'not false',
+      });
+    });
+
+    it('sets badges and isLoading to false', () => {
+      const badges = [createDummyBadge()];
+      store.commit(types.RECEIVE_LOAD_BADGES, badges);
+
+      expect(store.state.isLoading).toBe(false);
+      expect(store.state.badges).toBe(badges);
+    });
+  });
+
+  describe('RECEIVE_LOAD_BADGES_ERROR', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isLoading: 'not false',
+      });
+    });
+
+    it('sets isLoading to false', () => {
+      store.commit(types.RECEIVE_LOAD_BADGES_ERROR);
+
+      expect(store.state.isLoading).toBe(false);
+    });
+  });
+
+  describe('RECEIVE_NEW_BADGE', () => {
+    beforeEach(() => {
+      const badges = [
+        { ...dummyBadge, id: dummyBadge.id - 1, kind: GROUP_BADGE },
+        { ...dummyBadge, id: dummyBadge.id + 1, kind: GROUP_BADGE },
+        { ...dummyBadge, id: dummyBadge.id - 1, kind: PROJECT_BADGE },
+        { ...dummyBadge, id: dummyBadge.id + 1, kind: PROJECT_BADGE },
+      ];
+      store.replaceState({
+        ...store.state,
+        badgeInAddForm: createDummyBadge(),
+        badges,
+        isSaving: 'dummy value',
+        renderedBadge: createDummyBadge(),
+      });
+    });
+
+    it('resets the add form', () => {
+      store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
+
+      expect(store.state.badgeInAddForm).toBe(null);
+      expect(store.state.isSaving).toBe(false);
+      expect(store.state.renderedBadge).toBe(null);
+    });
+
+    it('inserts group badge at correct position', () => {
+      const badgeCount = store.state.badges.length;
+      dummyBadge = { ...dummyBadge, kind: GROUP_BADGE };
+
+      store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
+
+      expect(store.state.badges.length).toBe(badgeCount + 1);
+      expect(store.state.badges.indexOf(dummyBadge)).toBe(1);
+    });
+
+    it('inserts project badge at correct position', () => {
+      const badgeCount = store.state.badges.length;
+      dummyBadge = { ...dummyBadge, kind: PROJECT_BADGE };
+
+      store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
+
+      expect(store.state.badges.length).toBe(badgeCount + 1);
+      expect(store.state.badges.indexOf(dummyBadge)).toBe(3);
+    });
+  });
+
+  describe('RECEIVE_NEW_BADGE_ERROR', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isSaving: 'dummy value',
+      });
+    });
+
+    it('sets isSaving to false', () => {
+      store.commit(types.RECEIVE_NEW_BADGE_ERROR);
+
+      expect(store.state.isSaving).toBe(false);
+    });
+  });
+
+  describe('RECEIVE_RENDERED_BADGE', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isRendering: 'dummy value',
+        renderedBadge: 'dummy value',
+      });
+    });
+
+    it('sets renderedBadge', () => {
+      store.commit(types.RECEIVE_RENDERED_BADGE, dummyBadge);
+
+      expect(store.state.isRendering).toBe(false);
+      expect(store.state.renderedBadge).toBe(dummyBadge);
+    });
+  });
+
+  describe('RECEIVE_RENDERED_BADGE_ERROR', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isRendering: 'dummy value',
+      });
+    });
+
+    it('sets isRendering to false', () => {
+      store.commit(types.RECEIVE_RENDERED_BADGE_ERROR);
+
+      expect(store.state.isRendering).toBe(false);
+    });
+  });
+
+  describe('RECEIVE_UPDATED_BADGE', () => {
+    beforeEach(() => {
+      const badges = [
+        { ...dummyBadge, id: dummyBadge.id - 1 },
+        dummyBadge,
+        { ...dummyBadge, id: dummyBadge.id + 1 },
+      ];
+      store.replaceState({
+        ...store.state,
+        badgeInEditForm: createDummyBadge(),
+        badges,
+        isEditing: 'dummy value',
+        isSaving: 'dummy value',
+        renderedBadge: createDummyBadge(),
+      });
+    });
+
+    it('resets the edit form', () => {
+      store.commit(types.RECEIVE_UPDATED_BADGE, dummyBadge);
+
+      expect(store.state.badgeInAddForm).toBe(null);
+      expect(store.state.isSaving).toBe(false);
+      expect(store.state.renderedBadge).toBe(null);
+    });
+
+    it('replaces the updated badge', () => {
+      const badgeCount = store.state.badges.length;
+      const badgeIndex = store.state.badges.indexOf(dummyBadge);
+      const newBadge = { id: dummyBadge.id, dummy: 'value' };
+
+      store.commit(types.RECEIVE_UPDATED_BADGE, newBadge);
+
+      expect(store.state.badges.length).toBe(badgeCount);
+      expect(store.state.badges[badgeIndex]).toBe(newBadge);
+    });
+  });
+
+  describe('RECEIVE_UPDATED_BADGE_ERROR', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isSaving: 'dummy value',
+      });
+    });
+
+    it('sets isSaving to false', () => {
+      store.commit(types.RECEIVE_NEW_BADGE_ERROR);
+
+      expect(store.state.isSaving).toBe(false);
+    });
+  });
+
+  describe('REQUEST_DELETE_BADGE', () => {
+    beforeEach(() => {
+      const badges = [
+        { ...dummyBadge, id: dummyBadge.id - 1, isDeleting: false },
+        { ...dummyBadge, isDeleting: false },
+        { ...dummyBadge, id: dummyBadge.id + 1, isDeleting: true },
+      ];
+
+      store.replaceState({
+        ...store.state,
+        badges,
+      });
+    });
+
+    it('sets isDeleting to true', () => {
+      const badgeCount = store.state.badges.length;
+
+      store.commit(types.REQUEST_DELETE_BADGE, dummyBadge.id);
+
+      expect(store.state.badges.length).toBe(badgeCount);
+      expect(store.state.badges[0].isDeleting).toBe(false);
+      expect(store.state.badges[1].isDeleting).toBe(true);
+      expect(store.state.badges[2].isDeleting).toBe(true);
+    });
+  });
+
+  describe('REQUEST_LOAD_BADGES', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        apiEndpointUrl: 'some endpoint',
+        docsUrl: 'some url',
+        isLoading: 'dummy value',
+        kind: 'some kind',
+      });
+    });
+
+    it('sets isLoading to true and initializes the store', () => {
+      const dummyData = {
+        apiEndpointUrl: 'dummy endpoint',
+        docsUrl: 'dummy url',
+        kind: 'dummy kind',
+      };
+
+      store.commit(types.REQUEST_LOAD_BADGES, dummyData);
+
+      expect(store.state.isLoading).toBe(true);
+      expect(store.state.apiEndpointUrl).toBe(dummyData.apiEndpointUrl);
+      expect(store.state.docsUrl).toBe(dummyData.docsUrl);
+      expect(store.state.kind).toBe(dummyData.kind);
+    });
+  });
+
+  describe('REQUEST_NEW_BADGE', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isSaving: 'dummy value',
+      });
+    });
+
+    it('sets isSaving to true', () => {
+      store.commit(types.REQUEST_NEW_BADGE);
+
+      expect(store.state.isSaving).toBe(true);
+    });
+  });
+
+  describe('REQUEST_RENDERED_BADGE', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isRendering: 'dummy value',
+      });
+    });
+
+    it('sets isRendering to true', () => {
+      store.commit(types.REQUEST_RENDERED_BADGE);
+
+      expect(store.state.isRendering).toBe(true);
+    });
+  });
+
+  describe('REQUEST_UPDATED_BADGE', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        isSaving: 'dummy value',
+      });
+    });
+
+    it('sets isSaving to true', () => {
+      store.commit(types.REQUEST_NEW_BADGE);
+
+      expect(store.state.isSaving).toBe(true);
+    });
+  });
+
+  describe('START_EDITING', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        badgeInEditForm: 'dummy value',
+        isEditing: 'dummy value',
+        renderedBadge: 'dummy value',
+      });
+    });
+
+    it('initializes the edit form', () => {
+      store.commit(types.START_EDITING, dummyBadge);
+
+      expect(store.state.isEditing).toBe(true);
+      expect(store.state.badgeInEditForm).toEqual(dummyBadge);
+      expect(store.state.renderedBadge).toEqual(dummyBadge);
+    });
+  });
+
+  describe('STOP_EDITING', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        badgeInEditForm: 'dummy value',
+        isEditing: 'dummy value',
+        renderedBadge: 'dummy value',
+      });
+    });
+
+    it('resets the edit form', () => {
+      store.commit(types.STOP_EDITING);
+
+      expect(store.state.isEditing).toBe(false);
+      expect(store.state.badgeInEditForm).toBe(null);
+      expect(store.state.renderedBadge).toBe(null);
+    });
+  });
+
+  describe('UPDATE_BADGE_IN_FORM', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        badgeInAddForm: 'dummy value',
+        badgeInEditForm: 'dummy value',
+      });
+    });
+
+    it('sets badgeInEditForm if isEditing is true', () => {
+      store.state.isEditing = true;
+
+      store.commit(types.UPDATE_BADGE_IN_FORM, dummyBadge);
+
+      expect(store.state.badgeInEditForm).toBe(dummyBadge);
+    });
+
+    it('sets badgeInAddForm if isEditing is false', () => {
+      store.state.isEditing = false;
+
+      store.commit(types.UPDATE_BADGE_IN_FORM, dummyBadge);
+
+      expect(store.state.badgeInAddForm).toBe(dummyBadge);
+    });
+  });
+
+  describe('UPDATE_BADGE_IN_MODAL', () => {
+    beforeEach(() => {
+      store.replaceState({
+        ...store.state,
+        badgeInModal: 'dummy value',
+      });
+    });
+
+    it('sets badgeInModal', () => {
+      store.commit(types.UPDATE_BADGE_IN_MODAL, dummyBadge);
+
+      expect(store.state.badgeInModal).toBe(dummyBadge);
+    });
+  });
+});
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
index 960b731892ae6e11e7eb50156eba0c988ee5c797..c411c5174fbcb4fd5b38697fea8b8d6702e6e86b 100644
--- a/spec/javascripts/behaviors/autosize_spec.js
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import '~/behaviors/autosize';
 
 function load() {
diff --git a/spec/javascripts/behaviors/copy_as_gfm_spec.js b/spec/javascripts/behaviors/copy_as_gfm_spec.js
index b8155144e2ac1b6c85e4c890d41d0f1b09eb6be2..efbe09a10a25c448596997792c6ab4ce1df37051 100644
--- a/spec/javascripts/behaviors/copy_as_gfm_spec.js
+++ b/spec/javascripts/behaviors/copy_as_gfm_spec.js
@@ -1,4 +1,4 @@
-import { CopyAsGFM } from '~/behaviors/copy_as_gfm';
+import { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
 
 describe('CopyAsGFM', () => {
   describe('CopyAsGFM.pasteGFM', () => {
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index d5300d9c63dd2d50f6a8db56d049d2ab79999941..c37c62c63dd8173de96164ed3e818c8ec4615f3d 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import '~/behaviors/quick_submit';
 
 describe('Quick Submit behavior', () => {
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index e500bbe750fbf97ed1b81577d80a22a29319c3ab..a434949b9dab7f3b1cac5a8a43472b586097bc88 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import '~/behaviors/requires_input';
 
 describe('requiresInput', () => {
diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js
index 47de63e6690ecdfc23b3b140eb6861915155b651..0b1de5044350139dd90178cd1e959cad1494cde2 100644
--- a/spec/javascripts/blob/blob_file_dropzone_spec.js
+++ b/spec/javascripts/blob/blob_file_dropzone_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import BlobFileDropzone from '~/blob/blob_file_dropzone';
 
 describe('BlobFileDropzone', () => {
@@ -27,7 +28,7 @@ describe('BlobFileDropzone', () => {
         name: 'some-file.jpg',
         type: 'jpg',
       };
-      const fakeEvent = jQuery.Event('drop', {
+      const fakeEvent = $.Event('drop', {
         dataTransfer: { files: [file] },
       });
 
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index 892411a6a404d13d4e817a3afdd9ef2fbadfdb6a..f920c4ca945b75aeec2703c6c83496caa7d8a758 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-new */
+
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import BlobViewer from '~/blob/viewer/index';
 import axios from '~/lib/utils/axios_utils';
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index f757dadfadab309d572febbcb0318a16a0b71fbf..664ea202e937b74816211dddba5b320ec56cf496 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,7 +1,7 @@
 /* global BoardService */
 import Vue from 'vue';
 import '~/boards/stores/boards_store';
-import boardBlankState from '~/boards/components/board_blank_state';
+import BoardBlankState from '~/boards/components/board_blank_state.vue';
 import { mockBoardService } from './mock_data';
 
 describe('Boards blank state', () => {
@@ -9,7 +9,7 @@ describe('Boards blank state', () => {
   let fail = false;
 
   beforeEach((done) => {
-    const Comp = Vue.extend(boardBlankState);
+    const Comp = Vue.extend(BoardBlankState);
 
     gl.issueBoards.BoardsStore.create();
     gl.boardService = mockBoardService();
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 80a598e63bd255ff58a450c5d80a5521364f4fd0..13d607a06d2fd04993ea502113d27c53f1a5634d 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -9,8 +9,8 @@ import axios from '~/lib/utils/axios_utils';
 import '~/boards/models/assignee';
 
 import eventHub from '~/boards/eventhub';
+import '~/vue_shared/models/label';
 import '~/boards/models/list';
-import '~/boards/models/label';
 import '~/boards/stores/boards_store';
 import boardCard from '~/boards/components/board_card.vue';
 import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 8411f4dd8a6af82c23cbf28e50f60f4f3ec26fa0..0cf9e4c9ba1b6e387a693bd834d7d858b3d3d7dc 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import Cookies from 'js-cookie';
 
+import '~/vue_shared/models/label';
 import '~/boards/models/issue';
-import '~/boards/models/label';
 import '~/boards/models/list';
 import '~/boards/models/assignee';
 import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 278155c585ee14e17fa9b31e6937fe7b4aedd8f7..be1ea0b57b421c595ffa88d5f9a1512b968a319d 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -4,8 +4,8 @@
 
 import Vue from 'vue';
 
+import '~/vue_shared/models/label';
 import '~/boards/models/issue';
-import '~/boards/models/label';
 import '~/boards/models/list';
 import '~/boards/models/assignee';
 import '~/boards/stores/boards_store';
@@ -41,6 +41,8 @@ describe('Issue card component', () => {
       confidential: false,
       labels: [list.label],
       assignees: [],
+      reference_path: '#1',
+      real_path: '/test/1',
     });
 
     component = new Vue({
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index dbbe14fe3e0bb8c33585222d651025e1a116b425..4a11131b55cfecac0e493a9b1720bf5d848b96f4 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -3,8 +3,8 @@
 /* global ListIssue */
 
 import Vue from 'vue';
+import '~/vue_shared/models/label';
 import '~/boards/models/issue';
-import '~/boards/models/label';
 import '~/boards/models/list';
 import '~/boards/models/assignee';
 import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 34964b20b05c6c3f340946fbad029a048beaf436..d9a1d692949c862cbf34fa746880e141524e598a 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -6,8 +6,8 @@
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import _ from 'underscore';
+import '~/vue_shared/models/label';
 import '~/boards/models/issue';
-import '~/boards/models/label';
 import '~/boards/models/list';
 import '~/boards/models/assignee';
 import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 0671facb285611b7827dc4de7f1806c11c4ae3be..81f1a97112fc8b7603f6523018de9eb7bf480853 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,7 +1,4 @@
 /* global BoardService */
-/* eslint-disable comma-dangle, no-unused-vars, quote-props */
-import _ from 'underscore';
-
 export const listObj = {
   id: 300,
   position: 0,
@@ -11,8 +8,8 @@ export const listObj = {
     id: 5000,
     title: 'Testing',
     color: 'red',
-    description: 'testing;'
-  }
+    description: 'testing;',
+  },
 };
 
 export const listObjDuplicate = {
@@ -24,35 +21,37 @@ export const listObjDuplicate = {
     id: listObj.label.id,
     title: 'Testing',
     color: 'red',
-    description: 'testing;'
-  }
+    description: 'testing;',
+  },
 };
 
 export const BoardsMockData = {
-  'GET': {
+  GET: {
     '/test/-/boards/1/lists/300/issues?id=300&page=1&=': {
-      issues: [{
-        title: 'Testing',
-        id: 1,
-        iid: 1,
-        confidential: false,
-        labels: [],
-        assignees: [],
-      }],
-    }
+      issues: [
+        {
+          title: 'Testing',
+          id: 1,
+          iid: 1,
+          confidential: false,
+          labels: [],
+          assignees: [],
+        },
+      ],
+    },
+  },
+  POST: {
+    '/test/-/boards/1/lists': listObj,
   },
-  'POST': {
-    '/test/-/boards/1/lists': listObj
+  PUT: {
+    '/test/issue-boards/board/1/lists{/id}': {},
   },
-  'PUT': {
-    '/test/issue-boards/board/1/lists{/id}': {}
+  DELETE: {
+    '/test/issue-boards/board/1/lists{/id}': {},
   },
-  'DELETE': {
-    '/test/issue-boards/board/1/lists{/id}': {}
-  }
 };
 
-export const boardsMockInterceptor = (config) => {
+export const boardsMockInterceptor = config => {
   const body = BoardsMockData[config.method.toUpperCase()][config.url];
   return [200, body];
 };
diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js
index 7eecb58a4c331166fb1296a74970218b4e2f7225..797693a21aa21478111b878d497dcfed32d1e523 100644
--- a/spec/javascripts/boards/modal_store_spec.js
+++ b/spec/javascripts/boards/modal_store_spec.js
@@ -1,15 +1,14 @@
 /* global ListIssue */
 
+import '~/vue_shared/models/label';
 import '~/boards/models/issue';
-import '~/boards/models/label';
 import '~/boards/models/list';
 import '~/boards/models/assignee';
-import '~/boards/stores/modal_store';
+import Store from '~/boards/stores/modal_store';
 
 describe('Modal store', () => {
   let issue;
   let issue2;
-  const Store = gl.issueBoards.ModalStore;
 
   beforeEach(() => {
     // Setup default state
diff --git a/spec/javascripts/bootstrap_jquery_spec.js b/spec/javascripts/bootstrap_jquery_spec.js
index 48994b7c523308c2b379fe581c7926d952a47ce5..0fd6f9dc81057dd06f77356dadecff260a46e8d7 100644
--- a/spec/javascripts/bootstrap_jquery_spec.js
+++ b/spec/javascripts/bootstrap_jquery_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable space-before-function-paren, no-var */
 
+import $ from 'jquery';
 import '~/commons/bootstrap';
 
 (function() {
diff --git a/spec/javascripts/branches/branches_delete_modal_spec.js b/spec/javascripts/branches/branches_delete_modal_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b223b8e2c0aaec884c04d9dc20a281ba72508983
--- /dev/null
+++ b/spec/javascripts/branches/branches_delete_modal_spec.js
@@ -0,0 +1,40 @@
+import $ from 'jquery';
+import DeleteModal from '~/branches/branches_delete_modal';
+
+describe('branches delete modal', () => {
+  describe('setDisableDeleteButton', () => {
+    let submitSpy;
+    let $deleteButton;
+
+    beforeEach(() => {
+      setFixtures(`
+        <div id="modal-delete-branch">
+          <form>
+            <button type="submit" class="js-delete-branch">Delete</button>
+          </form>
+        </div>
+      `);
+      $deleteButton = $('.js-delete-branch');
+      submitSpy = jasmine.createSpy('submit').and.callFake(event => event.preventDefault());
+      $('#modal-delete-branch form').on('submit', submitSpy);
+      // eslint-disable-next-line no-new
+      new DeleteModal();
+    });
+
+    it('does not submit if button is disabled', () => {
+      $deleteButton.attr('disabled', true);
+
+      $deleteButton.click();
+
+      expect(submitSpy).not.toHaveBeenCalled();
+    });
+
+    it('submits if button is not disabled', () => {
+      $deleteButton.attr('disabled', false);
+
+      $deleteButton.click();
+
+      expect(submitSpy).toHaveBeenCalled();
+    });
+  });
+});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 270f925e699b33d5f2915396c661cca503b109e5..2fa50975f0f143c1a5f49ca3070b5116c9afd922 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import VariableList from '~/ci_variable_list/ci_variable_list';
 import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
 
diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
index eb508a7f059031cb5df7b3b35d636962cc292284..94a0c999d66fab1e440c5a551b55c36bde74977e 100644
--- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
 
 describe('NativeFormVariableList', () => {
@@ -19,7 +20,7 @@ describe('NativeFormVariableList', () => {
     it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
       const $row = $wrapper.find('.js-row');
       expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('schedule[variables_attributes][][key]');
-      expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('schedule[variables_attributes][][value]');
+      expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('schedule[variables_attributes][][secret_value]');
 
       $wrapper.closest('form').trigger('trigger-submit');
 
diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js
index 2abf52a16763f57ecc72f46d7c01b84a485b1fa7..8427e8a0ba73efa4c8b426d3cb1e2ad329783889 100644
--- a/spec/javascripts/collapsed_sidebar_todo_spec.js
+++ b/spec/javascripts/collapsed_sidebar_todo_spec.js
@@ -85,7 +85,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
     setTimeout(() => {
       expect(
         document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
-      ).toBe('Mark done');
+      ).toBe('Mark todo as done');
 
       done();
     });
@@ -97,7 +97,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
     setTimeout(() => {
       expect(
         document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('data-original-title'),
-      ).toBe('Mark done');
+      ).toBe('Mark todo as done');
 
       done();
     });
@@ -128,13 +128,13 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
       .catch(done.fail);
   });
 
-  it('updates aria-label to mark done', (done) => {
+  it('updates aria-label to mark todo as done', (done) => {
     document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
 
     setTimeout(() => {
       expect(
         document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
-      ).toBe('Mark done');
+      ).toBe('Mark todo as done');
 
       done();
     });
@@ -147,7 +147,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
       .then(() => {
         expect(
           document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
-        ).toBe('Mark done');
+        ).toBe('Mark todo as done');
 
         document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
       })
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index 0afe09d87bc561bc2497f094f1cc3c65e6e58f45..53820770f3fb5d530f86364d49d4ed6e56a34de2 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,113 +1,82 @@
-import _ from 'underscore';
 import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
 import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
 
 describe('Pipelines table in Commits and Merge requests', () => {
   const jsonFixtureName = 'pipelines/pipelines.json';
   let pipeline;
   let PipelinesTable;
+  let mock;
+  let vm;
 
   preloadFixtures(jsonFixtureName);
 
   beforeEach(() => {
+    mock = new MockAdapter(axios);
+
     const pipelines = getJSONFixture(jsonFixtureName).pipelines;
 
     PipelinesTable = Vue.extend(pipelinesTable);
     pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
   });
 
+  afterEach(() => {
+    vm.$destroy();
+    mock.restore();
+  });
+
   describe('successful request', () => {
     describe('without pipelines', () => {
-      const pipelinesEmptyResponse = (request, next) => {
-        next(request.respondWith(JSON.stringify([]), {
-          status: 200,
-        }));
-      };
-
       beforeEach(function () {
-        Vue.http.interceptors.push(pipelinesEmptyResponse);
-
-        this.component = new PipelinesTable({
-          propsData: {
-            endpoint: 'endpoint',
-            helpPagePath: 'foo',
-            emptyStateSvgPath: 'foo',
-            errorStateSvgPath: 'foo',
-            autoDevopsHelpPath: 'foo',
-          },
-        }).$mount();
-      });
+        mock.onGet('endpoint.json').reply(200, []);
 
-      afterEach(function () {
-        Vue.http.interceptors = _.without(
-          Vue.http.interceptors, pipelinesEmptyResponse,
-        );
-        this.component.$destroy();
+        vm = mountComponent(PipelinesTable, {
+          endpoint: 'endpoint.json',
+          helpPagePath: 'foo',
+          emptyStateSvgPath: 'foo',
+          errorStateSvgPath: 'foo',
+          autoDevopsHelpPath: 'foo',
+        });
       });
 
       it('should render the empty state', function (done) {
         setTimeout(() => {
-          expect(this.component.$el.querySelector('.empty-state')).toBeDefined();
-          expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
-          expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBe(null);
+          expect(vm.$el.querySelector('.empty-state')).toBeDefined();
+          expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
+          expect(vm.$el.querySelector('.js-pipelines-error-state')).toBe(null);
           done();
-        }, 1);
+        }, 0);
       });
     });
 
     describe('with pipelines', () => {
-      const pipelinesResponse = (request, next) => {
-        next(request.respondWith(JSON.stringify([pipeline]), {
-          status: 200,
-        }));
-      };
-
       beforeEach(() => {
-        Vue.http.interceptors.push(pipelinesResponse);
-
-        this.component = new PipelinesTable({
-          propsData: {
-            endpoint: 'endpoint',
-            helpPagePath: 'foo',
-            emptyStateSvgPath: 'foo',
-            errorStateSvgPath: 'foo',
-            autoDevopsHelpPath: 'foo',
-          },
-        }).$mount();
-      });
-
-      afterEach(() => {
-        Vue.http.interceptors = _.without(
-          Vue.http.interceptors, pipelinesResponse,
-        );
-        this.component.$destroy();
+        mock.onGet('endpoint.json').reply(200, [pipeline]);
+        vm = mountComponent(PipelinesTable, {
+          endpoint: 'endpoint.json',
+          helpPagePath: 'foo',
+          emptyStateSvgPath: 'foo',
+          errorStateSvgPath: 'foo',
+          autoDevopsHelpPath: 'foo',
+        });
       });
 
       it('should render a table with the received pipelines', (done) => {
         setTimeout(() => {
-          expect(this.component.$el.querySelectorAll('.ci-table .commit').length).toEqual(1);
-          expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
-          expect(this.component.$el.querySelector('.empty-state')).toBe(null);
-          expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBe(null);
+          expect(vm.$el.querySelectorAll('.ci-table .commit').length).toEqual(1);
+          expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
+          expect(vm.$el.querySelector('.empty-state')).toBe(null);
+          expect(vm.$el.querySelector('.js-pipelines-error-state')).toBe(null);
           done();
         }, 0);
       });
     });
 
     describe('pipeline badge counts', () => {
-      const pipelinesResponse = (request, next) => {
-        next(request.respondWith(JSON.stringify([pipeline]), {
-          status: 200,
-        }));
-      };
-
       beforeEach(() => {
-        Vue.http.interceptors.push(pipelinesResponse);
-      });
-
-      afterEach(() => {
-        Vue.http.interceptors = _.without(Vue.http.interceptors, pipelinesResponse);
-        this.component.$destroy();
+        mock.onGet('endpoint.json').reply(200, [pipeline]);
       });
 
       it('should receive update-pipelines-count event', (done) => {
@@ -119,54 +88,38 @@ describe('Pipelines table in Commits and Merge requests', () => {
           done();
         });
 
-        this.component = new PipelinesTable({
-          propsData: {
-            endpoint: 'endpoint',
-            helpPagePath: 'foo',
-            emptyStateSvgPath: 'foo',
-            errorStateSvgPath: 'foo',
-            autoDevopsHelpPath: 'foo',
-          },
-        }).$mount();
-        element.appendChild(this.component.$el);
-      });
-    });
-  });
-
-  describe('unsuccessfull request', () => {
-    const pipelinesErrorResponse = (request, next) => {
-      next(request.respondWith(JSON.stringify([]), {
-        status: 500,
-      }));
-    };
-
-    beforeEach(function () {
-      Vue.http.interceptors.push(pipelinesErrorResponse);
-
-      this.component = new PipelinesTable({
-        propsData: {
-          endpoint: 'endpoint',
+        vm = mountComponent(PipelinesTable, {
+          endpoint: 'endpoint.json',
           helpPagePath: 'foo',
           emptyStateSvgPath: 'foo',
           errorStateSvgPath: 'foo',
           autoDevopsHelpPath: 'foo',
-        },
-      }).$mount();
+        });
+
+        element.appendChild(vm.$el);
+      });
     });
+  });
 
-    afterEach(function () {
-      Vue.http.interceptors = _.without(
-        Vue.http.interceptors, pipelinesErrorResponse,
-      );
-      this.component.$destroy();
+  describe('unsuccessfull request', () => {
+    beforeEach(() => {
+      mock.onGet('endpoint.json').reply(500, []);
+
+      vm = mountComponent(PipelinesTable, {
+        endpoint: 'endpoint.json',
+        helpPagePath: 'foo',
+        emptyStateSvgPath: 'foo',
+        errorStateSvgPath: 'foo',
+        autoDevopsHelpPath: 'foo',
+      });
     });
 
     it('should render error state', function (done) {
       setTimeout(() => {
-        expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
-        expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
-        expect(this.component.$el.querySelector('.js-empty-state')).toBe(null);
-        expect(this.component.$el.querySelector('.ci-table')).toBe(null);
+        expect(vm.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
+        expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
+        expect(vm.$el.querySelector('.js-empty-state')).toBe(null);
+        expect(vm.$el.querySelector('.ci-table')).toBe(null);
         done();
       }, 0);
     });
diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js
index 1daccc8dd0230d1215e286946aaec0a4747670ef..977298b922158394787cc1f3100bd38d914244b9 100644
--- a/spec/javascripts/commits_spec.js
+++ b/spec/javascripts/commits_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import 'vendor/jquery.endless-scroll';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js
index 143137c23ecaf91978ae0ec41810924d79832c75..ee26122be12755e95f346195001c52d9ef0393bb 100644
--- a/spec/javascripts/create_item_dropdown_spec.js
+++ b/spec/javascripts/create_item_dropdown_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import CreateItemDropdown from '~/create_item_dropdown';
 
 const DROPDOWN_ITEM_DATA = [{
diff --git a/spec/javascripts/droplab/constants_spec.js b/spec/javascripts/droplab/constants_spec.js
index b9d28db74cc5b6ac8f80b2f864b1230b73ee992a..23b69defec617eb4cb8986ce8658fa1271324c12 100644
--- a/spec/javascripts/droplab/constants_spec.js
+++ b/spec/javascripts/droplab/constants_spec.js
@@ -1,39 +1,37 @@
-/* eslint-disable */
-
 import * as constants from '~/droplab/constants';
 
-describe('constants', function () {
-  describe('DATA_TRIGGER', function () {
+describe('constants', function() {
+  describe('DATA_TRIGGER', function() {
     it('should be `data-dropdown-trigger`', function() {
       expect(constants.DATA_TRIGGER).toBe('data-dropdown-trigger');
     });
   });
 
-  describe('DATA_DROPDOWN', function () {
+  describe('DATA_DROPDOWN', function() {
     it('should be `data-dropdown`', function() {
       expect(constants.DATA_DROPDOWN).toBe('data-dropdown');
     });
   });
 
-  describe('SELECTED_CLASS', function () {
+  describe('SELECTED_CLASS', function() {
     it('should be `droplab-item-selected`', function() {
       expect(constants.SELECTED_CLASS).toBe('droplab-item-selected');
     });
   });
 
-  describe('ACTIVE_CLASS', function () {
+  describe('ACTIVE_CLASS', function() {
     it('should be `droplab-item-active`', function() {
       expect(constants.ACTIVE_CLASS).toBe('droplab-item-active');
     });
   });
 
-  describe('TEMPLATE_REGEX', function () {
+  describe('TEMPLATE_REGEX', function() {
     it('should be a handlebars templating syntax regex', function() {
       expect(constants.TEMPLATE_REGEX).toEqual(/\{\{(.+?)\}\}/g);
     });
   });
 
-  describe('IGNORE_CLASS', function () {
+  describe('IGNORE_CLASS', function() {
     it('should be `droplab-item-ignore`', function() {
       expect(constants.IGNORE_CLASS).toBe('droplab-item-ignore');
     });
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 5bb37304372f9f39c23e6c75f12f2a73b1a50729..e4c3bf2bef17cc8f4e8b2abf023b1f22494f4b86 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -61,6 +61,7 @@ describe('Environment', () => {
     });
 
     describe('with paginated environments', () => {
+      let backupInterceptors;
       const environmentsResponseInterceptor = (request, next) => {
         next((response) => {
           response.headers.set('X-nExt-pAge', '2');
@@ -84,16 +85,16 @@ describe('Environment', () => {
       };
 
       beforeEach(() => {
-        Vue.http.interceptors.push(environmentsResponseInterceptor);
-        Vue.http.interceptors.push(headersInterceptor);
+        backupInterceptors = Vue.http.interceptors;
+        Vue.http.interceptors = [
+          environmentsResponseInterceptor,
+          headersInterceptor,
+        ];
         component = mountComponent(EnvironmentsComponent, mockData);
       });
 
       afterEach(() => {
-        Vue.http.interceptors = _.without(
-          Vue.http.interceptors, environmentsResponseInterceptor,
-        );
-        Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
+        Vue.http.interceptors = backupInterceptors;
       });
 
       it('should render a table with environments', (done) => {
diff --git a/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js b/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
index 1b1f28f3ddb697bca364291c2424cd9770e41ea4..2ab6a0077b55f0313a74057bc5cda32335d61200 100644
--- a/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
+++ b/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
@@ -1,13 +1,13 @@
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import {
   getSelector,
-  togglePopover,
   dismiss,
-  mouseleave,
-  mouseenter,
   inserted,
 } from '~/feature_highlight/feature_highlight_helper';
+import { togglePopover } from '~/shared/popover';
+
 import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
 
 describe('feature highlight helper', () => {
@@ -18,110 +18,6 @@ describe('feature highlight helper', () => {
     });
   });
 
-  describe('togglePopover', () => {
-    describe('togglePopover(true)', () => {
-      it('returns true when popover is shown', () => {
-        const context = {
-          hasClass: () => false,
-          popover: () => {},
-          toggleClass: () => {},
-        };
-
-        expect(togglePopover.call(context, true)).toEqual(true);
-      });
-
-      it('returns false when popover is already shown', () => {
-        const context = {
-          hasClass: () => true,
-        };
-
-        expect(togglePopover.call(context, true)).toEqual(false);
-      });
-
-      it('shows popover', (done) => {
-        const context = {
-          hasClass: () => false,
-          popover: () => {},
-          toggleClass: () => {},
-        };
-
-        spyOn(context, 'popover').and.callFake((method) => {
-          expect(method).toEqual('show');
-          done();
-        });
-
-        togglePopover.call(context, true);
-      });
-
-      it('adds disable-animation and js-popover-show class', (done) => {
-        const context = {
-          hasClass: () => false,
-          popover: () => {},
-          toggleClass: () => {},
-        };
-
-        spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
-          expect(classNames).toEqual('disable-animation js-popover-show');
-          expect(show).toEqual(true);
-          done();
-        });
-
-        togglePopover.call(context, true);
-      });
-    });
-
-    describe('togglePopover(false)', () => {
-      it('returns true when popover is hidden', () => {
-        const context = {
-          hasClass: () => true,
-          popover: () => {},
-          toggleClass: () => {},
-        };
-
-        expect(togglePopover.call(context, false)).toEqual(true);
-      });
-
-      it('returns false when popover is already hidden', () => {
-        const context = {
-          hasClass: () => false,
-        };
-
-        expect(togglePopover.call(context, false)).toEqual(false);
-      });
-
-      it('hides popover', (done) => {
-        const context = {
-          hasClass: () => true,
-          popover: () => {},
-          toggleClass: () => {},
-        };
-
-        spyOn(context, 'popover').and.callFake((method) => {
-          expect(method).toEqual('hide');
-          done();
-        });
-
-        togglePopover.call(context, false);
-      });
-
-      it('removes disable-animation and js-popover-show class', (done) => {
-        const context = {
-          hasClass: () => true,
-          popover: () => {},
-          toggleClass: () => {},
-        };
-
-        spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
-          expect(classNames).toEqual('disable-animation js-popover-show');
-          expect(show).toEqual(false);
-          done();
-        });
-
-        togglePopover.call(context, false);
-      });
-    });
-  });
-
   describe('dismiss', () => {
     let mock;
     const context = {
@@ -162,56 +58,6 @@ describe('feature highlight helper', () => {
     });
   });
 
-  describe('mouseleave', () => {
-    it('calls hide popover if .popover:hover is false', () => {
-      const fakeJquery = {
-        length: 0,
-      };
-
-      spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
-      spyOn(togglePopover, 'call');
-      mouseleave();
-      expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), false);
-    });
-
-    it('does not call hide popover if .popover:hover is true', () => {
-      const fakeJquery = {
-        length: 1,
-      };
-
-      spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
-      spyOn(togglePopover, 'call');
-      mouseleave();
-      expect(togglePopover.call).not.toHaveBeenCalledWith(false);
-    });
-  });
-
-  describe('mouseenter', () => {
-    const context = {};
-
-    it('shows popover', () => {
-      spyOn(togglePopover, 'call').and.returnValue(false);
-      mouseenter.call(context);
-      expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), true);
-    });
-
-    it('registers mouseleave event if popover is showed', (done) => {
-      spyOn(togglePopover, 'call').and.returnValue(true);
-      spyOn($.fn, 'on').and.callFake((eventName) => {
-        expect(eventName).toEqual('mouseleave');
-        done();
-      });
-      mouseenter.call(context);
-    });
-
-    it('does not register mouseleave event if popover is not showed', () => {
-      spyOn(togglePopover, 'call').and.returnValue(false);
-      const spy = spyOn($.fn, 'on').and.callFake(() => {});
-      mouseenter.call(context);
-      expect(spy).not.toHaveBeenCalled();
-    });
-  });
-
   describe('inserted', () => {
     it('registers click event callback', (done) => {
       const context = {
diff --git a/spec/javascripts/feature_highlight/feature_highlight_spec.js b/spec/javascripts/feature_highlight/feature_highlight_spec.js
index f3f80cb377115e24aa0d1c9051ec6d6385220962..ec46d4f905afe6e5f6634bbfacc8d10171625a55 100644
--- a/spec/javascripts/feature_highlight/feature_highlight_spec.js
+++ b/spec/javascripts/feature_highlight/feature_highlight_spec.js
@@ -1,5 +1,6 @@
-import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
+import $ from 'jquery';
 import * as featureHighlight from '~/feature_highlight/feature_highlight';
+import * as popover from '~/shared/popover';
 import axios from '~/lib/utils/axios_utils';
 import MockAdapter from 'axios-mock-adapter';
 
@@ -28,7 +29,6 @@ describe('feature highlight', () => {
       mock = new MockAdapter(axios);
       mock.onGet('/test').reply(200);
       spyOn(window, 'addEventListener');
-      spyOn(window, 'removeEventListener');
       featureHighlight.setupFeatureHighlightPopover('test', 0);
     });
 
@@ -44,14 +44,14 @@ describe('feature highlight', () => {
     });
 
     it('setup mouseenter', () => {
-      const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
+      const toggleSpy = spyOn(popover.togglePopover, 'call');
       $(selector).trigger('mouseenter');
 
       expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), true);
     });
 
     it('setup debounced mouseleave', (done) => {
-      const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
+      const toggleSpy = spyOn(popover.togglePopover, 'call');
       $(selector).trigger('mouseleave');
 
       // Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
@@ -63,12 +63,7 @@ describe('feature highlight', () => {
 
     it('setup show.bs.popover', () => {
       $(selector).trigger('show.bs.popover');
-      expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
-    });
-
-    it('setup hide.bs.popover', () => {
-      $(selector).trigger('hide.bs.popover');
-      expect(window.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
+      expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), { once: true });
     });
 
     it('removes disabled attribute', () => {
@@ -84,7 +79,7 @@ describe('feature highlight', () => {
     it('toggles when clicked', () => {
       $(selector).trigger('mouseenter');
       const popoverId = $(selector).attr('aria-describedby');
-      const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
+      const toggleSpy = spyOn(popover.togglePopover, 'call');
 
       $(`#${popoverId} .dismiss-feature-highlight`).click();
 
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 71c14582329ddbac16aa6a210a723da0fd08fd67..8c5a0961a0253f908d46a3d10c05857c5d73efde 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -1,8 +1,9 @@
+import $ from 'jquery';
 import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
 
 describe('Filtered Search Dropdown Manager', () => {
   beforeEach(() => {
-    spyOn(jQuery, 'ajax');
+    spyOn($, 'ajax');
   });
 
   describe('addWordToInput', () => {
diff --git a/spec/javascripts/fixtures/environments.rb b/spec/javascripts/fixtures/environments.rb
deleted file mode 100644
index d2457d75419b61f0ce2ce79ae5b7d1d5c038f9f3..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/environments.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'spec_helper'
-
-describe Projects::EnvironmentsController, '(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: 'environments-project') }
-  let(:environment) { create(:environment, name: 'production', project: project) }
-
-  render_views
-
-  before(:all) do
-    clean_frontend_fixtures('environments/metrics')
-  end
-
-  before do
-    sign_in(admin)
-  end
-
-  it 'environments/metrics/metrics.html.raw' do |example|
-    get :metrics,
-      namespace_id: project.namespace,
-      project_id: project,
-      id: environment.id
-
-    expect(response).to be_success
-    store_frontend_fixture(response, example.description)
-  end
-end
diff --git a/spec/javascripts/fixtures/gl_dropdown.html.haml b/spec/javascripts/fixtures/gl_dropdown.html.haml
index a20390c08ee39f4c177fee3472f662c59b2ec1e1..43d57c2c4dc1d6753b57099199d052b9d5d8899d 100644
--- a/spec/javascripts/fixtures/gl_dropdown.html.haml
+++ b/spec/javascripts/fixtures/gl_dropdown.html.haml
@@ -1,7 +1,8 @@
 %div
   .dropdown.inline
     %button#js-project-dropdown.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
-      Projects
+      .dropdown-toggle-text
+        Projects
       %i.fa.fa-chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle
     .dropdown-menu.dropdown-select.dropdown-menu-selectable
       .dropdown-title
diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml
index 93c0cf97ff0979b055b9bdee4e8d716984fda08b..c38fe8b1f25aa2a70f74f747617b38c8bc54727b 100644
--- a/spec/javascripts/fixtures/linked_tabs.html.haml
+++ b/spec/javascripts/fixtures/linked_tabs.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-tabs.linked-tabs
+%ul.nav-links.new-session-tabs.linked-tabs
   %li
     %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
       Tab 1
diff --git a/spec/javascripts/fixtures/one_white_pixel.png b/spec/javascripts/fixtures/one_white_pixel.png
new file mode 100644
index 0000000000000000000000000000000000000000..073fcf40a183b676abf6e88f4b49e74326de60a1
Binary files /dev/null and b/spec/javascripts/fixtures/one_white_pixel.png differ
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index b344b389241d5c6e2250178fac59c179307fb8ef..e8865b04874b5132dd1e5e433b43664c3f6b7489 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -17,8 +17,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
   end
 
   before do
-    # EE-specific start
-    # EE specific end
     project.add_master(admin)
     sign_in(admin)
   end
diff --git a/spec/javascripts/fixtures/signin_tabs.html.haml b/spec/javascripts/fixtures/signin_tabs.html.haml
index 12b8d423cbea0221fecd524f71085f6a93886724..2e00fe7865e200ec6430c79601bc035fcf4871f0 100644
--- a/spec/javascripts/fixtures/signin_tabs.html.haml
+++ b/spec/javascripts/fixtures/signin_tabs.html.haml
@@ -1,5 +1,5 @@
-%ul.nav-tabs
+%ul.nav-links.new-session-tabs
+  %li.active
+    %a{ href: '#ldap' } LDAP
   %li
-    %a.active{ id: 'standard', href: '#standard'} Standard
-  %li
-    %a{ id: 'ldap', href: '#ldap'} Ldap
+    %a{ href: '#login-pane'} Standard
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 50a587ef35121f2b41135ac3a0a956bd03694a76..1cb20a1e7ff38a5a56ce2b0714e4bcbb82c932e5 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -1,5 +1,6 @@
 /* eslint no-param-reassign: "off" */
 
+import $ from 'jquery';
 import GfmAutoComplete from '~/gfm_auto_complete';
 
 import 'vendor/jquery.caret';
@@ -80,13 +81,21 @@ describe('GfmAutoComplete', function () {
     });
 
     it('should quote if value contains any non-alphanumeric characters', () => {
-      expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label-20"');
+      expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label\\-20"');
       expect(beforeInsert(atwhoInstance, '~label 20')).toBe('~"label 20"');
     });
 
     it('should quote integer labels', () => {
       expect(beforeInsert(atwhoInstance, '~1234')).toBe('~"1234"');
     });
+
+    it('should escape Markdown emphasis characters, except in the first character', () => {
+      expect(beforeInsert(atwhoInstance, '@_group')).toEqual('@\\_group');
+      expect(beforeInsert(atwhoInstance, '~_bug')).toEqual('~\\_bug');
+      expect(beforeInsert(atwhoInstance, '~a `bug`')).toEqual('~"a \\`bug\\`"');
+      expect(beforeInsert(atwhoInstance, '~a ~bug')).toEqual('~"a \\~bug"');
+      expect(beforeInsert(atwhoInstance, '~a **bug')).toEqual('~"a \\*\\*bug"');
+    });
   });
 
   describe('DefaultOptions.matcher', function () {
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 67b854f61c02c75a7d76d0568169e2770e812b06..5393502196e71ec0c48031fb35d9832e196d81c8 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */
 
+import $ from 'jquery';
 import '~/gl_dropdown';
 import '~/lib/utils/common_utils';
 import * as urlUtils from '~/lib/utils/url_utility';
@@ -255,4 +256,29 @@ describe('glDropdown', function describeDropdown() {
       });
     });
   });
+
+  it('should keep selected item after selecting a second time', () => {
+    const options = {
+      isSelectable(item, $el) {
+        return !$el.hasClass('is-active');
+      },
+      toggleLabel(item) {
+        return item && item.id;
+      },
+    };
+    initDropDown.call(this, false, false, options);
+    const $item = $(`${ITEM_SELECTOR}:first() a`, this.$dropdownMenuElement);
+
+    // select item the first time
+    this.dropdownButtonElement.click();
+    $item.click();
+    expect($item).toHaveClass('is-active');
+    // select item the second time
+    this.dropdownButtonElement.click();
+    $item.click();
+    expect($item).toHaveClass('is-active');
+
+    expect($('.dropdown-toggle-text')).toHaveText(this.projectsData[0].id.toString());
+  });
 });
+
diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js
index 2779686a6f593bce431b02d7b2646ba8ca5ee6f8..4e93fd91751d8a71a94a43de36f7d238eadc5e0a 100644
--- a/spec/javascripts/gl_field_errors_spec.js
+++ b/spec/javascripts/gl_field_errors_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable space-before-function-paren, arrow-body-style */
 
+import $ from 'jquery';
 import GlFieldErrors from '~/gl_field_errors';
 
 describe('GL Style Field Errors', function() {
diff --git a/spec/javascripts/gl_form_spec.js b/spec/javascripts/gl_form_spec.js
index 9c1fc0fda9edaf1148c7e437b025a6dbdbbd428d..74383f901b23ab60dee962004fc14e72cb63c216 100644
--- a/spec/javascripts/gl_form_spec.js
+++ b/spec/javascripts/gl_form_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import autosize from 'autosize';
 import GLForm from '~/gl_form';
 import '~/lib/utils/text_utility';
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 3adc29262f3de8aa6c18d224255550271cb721a6..d8428bd0e08aa0124f9a8c280fe8ddfe7cadb06b 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 
 import * as utils from '~/lib/utils/url_utility';
@@ -129,7 +130,7 @@ describe('AppComponent', () => {
 
         vm.fetchGroups({});
         setTimeout(() => {
-          expect(vm.isLoading).toBeFalsy();
+          expect(vm.isLoading).toBe(false);
           expect($.scrollTo).toHaveBeenCalledWith(0);
           expect(window.Flash).toHaveBeenCalledWith('An error occurred. Please try again.');
           done();
@@ -144,10 +145,10 @@ describe('AppComponent', () => {
         spyOn(vm, 'updateGroups').and.callThrough();
 
         vm.fetchAllGroups();
-        expect(vm.isLoading).toBeTruthy();
+        expect(vm.isLoading).toBe(true);
         expect(vm.fetchGroups).toHaveBeenCalled();
         setTimeout(() => {
-          expect(vm.isLoading).toBeFalsy();
+          expect(vm.isLoading).toBe(false);
           expect(vm.updateGroups).toHaveBeenCalled();
           done();
         }, 0);
@@ -181,7 +182,7 @@ describe('AppComponent', () => {
         spyOn($, 'scrollTo');
 
         vm.fetchPage(2, null, null, true);
-        expect(vm.isLoading).toBeTruthy();
+        expect(vm.isLoading).toBe(true);
         expect(vm.fetchGroups).toHaveBeenCalledWith({
           page: 2,
           filterGroupsBy: null,
@@ -190,7 +191,7 @@ describe('AppComponent', () => {
           archived: true,
         });
         setTimeout(() => {
-          expect(vm.isLoading).toBeFalsy();
+          expect(vm.isLoading).toBe(false);
           expect($.scrollTo).toHaveBeenCalledWith(0);
           expect(utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String));
           expect(window.history.replaceState).toHaveBeenCalledWith({
@@ -216,7 +217,7 @@ describe('AppComponent', () => {
         spyOn(vm.store, 'setGroupChildren');
 
         vm.toggleChildren(groupItem);
-        expect(groupItem.isChildrenLoading).toBeTruthy();
+        expect(groupItem.isChildrenLoading).toBe(true);
         expect(vm.fetchGroups).toHaveBeenCalledWith({
           parentId: groupItem.id,
         });
@@ -232,7 +233,7 @@ describe('AppComponent', () => {
 
         vm.toggleChildren(groupItem);
         expect(vm.fetchGroups).not.toHaveBeenCalled();
-        expect(groupItem.isOpen).toBeTruthy();
+        expect(groupItem.isOpen).toBe(true);
       });
 
       it('should collapse group if it is already expanded', () => {
@@ -241,16 +242,16 @@ describe('AppComponent', () => {
 
         vm.toggleChildren(groupItem);
         expect(vm.fetchGroups).not.toHaveBeenCalled();
-        expect(groupItem.isOpen).toBeFalsy();
+        expect(groupItem.isOpen).toBe(false);
       });
 
       it('should set `isChildrenLoading` back to `false` if load request fails', (done) => {
         spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true));
 
         vm.toggleChildren(groupItem);
-        expect(groupItem.isChildrenLoading).toBeTruthy();
+        expect(groupItem.isChildrenLoading).toBe(true);
         setTimeout(() => {
-          expect(groupItem.isChildrenLoading).toBeFalsy();
+          expect(groupItem.isChildrenLoading).toBe(false);
           done();
         }, 0);
       });
@@ -268,10 +269,10 @@ describe('AppComponent', () => {
 
       it('updates props which show modal confirmation dialog', () => {
         const group = Object.assign({}, mockParentGroupItem);
-        expect(vm.updateModal).toBeFalsy();
+        expect(vm.showModal).toBe(false);
         expect(vm.groupLeaveConfirmationMessage).toBe('');
         vm.showLeaveGroupModal(group, mockParentGroupItem);
-        expect(vm.updateModal).toBeTruthy();
+        expect(vm.showModal).toBe(true);
         expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`);
       });
     });
@@ -280,9 +281,9 @@ describe('AppComponent', () => {
       it('hides modal confirmation which is shown before leaving the group', () => {
         const group = Object.assign({}, mockParentGroupItem);
         vm.showLeaveGroupModal(group, mockParentGroupItem);
-        expect(vm.updateModal).toBeTruthy();
+        expect(vm.showModal).toBe(true);
         vm.hideLeaveGroupModal();
-        expect(vm.updateModal).toBeFalsy();
+        expect(vm.showModal).toBe(false);
       });
     });
 
@@ -307,8 +308,8 @@ describe('AppComponent', () => {
         spyOn($, 'scrollTo');
 
         vm.leaveGroup();
-        expect(vm.updateModal).toBeFalsy();
-        expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+        expect(vm.showModal).toBe(false);
+        expect(vm.targetGroup.isBeingRemoved).toBe(true);
         expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath);
         setTimeout(() => {
           expect($.scrollTo).toHaveBeenCalledWith(0);
@@ -325,12 +326,12 @@ describe('AppComponent', () => {
         spyOn(window, 'Flash');
 
         vm.leaveGroup();
-        expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+        expect(vm.targetGroup.isBeingRemoved).toBe(true);
         expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
         setTimeout(() => {
           expect(vm.store.removeGroup).not.toHaveBeenCalled();
           expect(window.Flash).toHaveBeenCalledWith(message);
-          expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
+          expect(vm.targetGroup.isBeingRemoved).toBe(false);
           done();
         }, 0);
       });
@@ -342,12 +343,12 @@ describe('AppComponent', () => {
         spyOn(window, 'Flash');
 
         vm.leaveGroup(childGroupItem, groupItem);
-        expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+        expect(vm.targetGroup.isBeingRemoved).toBe(true);
         expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
         setTimeout(() => {
           expect(vm.store.removeGroup).not.toHaveBeenCalled();
           expect(window.Flash).toHaveBeenCalledWith(message);
-          expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
+          expect(vm.targetGroup.isBeingRemoved).toBe(false);
           done();
         }, 0);
       });
@@ -379,10 +380,10 @@ describe('AppComponent', () => {
 
       it('should set `isSearchEmpty` prop based on groups count', () => {
         vm.updateGroups(mockGroups);
-        expect(vm.isSearchEmpty).toBeFalsy();
+        expect(vm.isSearchEmpty).toBe(false);
 
         vm.updateGroups([]);
-        expect(vm.isSearchEmpty).toBeTruthy();
+        expect(vm.isSearchEmpty).toBe(true);
       });
     });
   });
@@ -473,13 +474,16 @@ describe('AppComponent', () => {
       });
     });
 
-    it('renders modal confirmation dialog', () => {
+    it('renders modal confirmation dialog', (done) => {
       vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?';
-      vm.updateModal = true;
-      const modalDialogEl = vm.$el.querySelector('.modal');
-      expect(modalDialogEl).not.toBe(null);
-      expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
-      expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
+      vm.showModal = true;
+      Vue.nextTick(() => {
+        const modalDialogEl = vm.$el.querySelector('.modal');
+        expect(modalDialogEl).not.toBe(null);
+        expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
+        expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
+        done();
+      });
     });
   });
 });
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index 2443ffd48f35610282288e887cbb2054f1bb870d..16ac438f7ac120fb1473f064530b2dca78c42d74 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import initTodoToggle from '~/header';
 
 describe('Header', function () {
diff --git a/spec/javascripts/helpers/vue_component_helper.js b/spec/javascripts/helpers/vue_component_helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..e0fe18e5560d35ce005ff4eadaa68a56f826b697
--- /dev/null
+++ b/spec/javascripts/helpers/vue_component_helper.js
@@ -0,0 +1,18 @@
+/**
+ * Replaces line break with an empty space
+ * @param {*} data
+ */
+export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' ');
+
+/**
+ * Removes line breaks, spaces and trims the given text
+ * @param {String} str
+ * @returns {String}
+ */
+export const trimText = str =>
+  str
+    .replace(/\r?\n|\r/g, '')
+    .replace(/\s\s+/g, ' ')
+    .trim();
+
+export const removeWhitespace = str => str.replace(/\s\s+/g, ' ');
diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js
index 34acdfbfba9505877150ac5618b70545add0a119..effacbcff4e4991c7164284bf721ceb55d63e6b2 100644
--- a/spec/javascripts/helpers/vue_mount_component_helper.js
+++ b/spec/javascripts/helpers/vue_mount_component_helper.js
@@ -3,6 +3,12 @@ export const createComponentWithStore = (Component, store, propsData = {}) => ne
   propsData,
 });
 
+export const mountComponentWithStore = (Component, { el, props, store }) =>
+  new Component({
+    store,
+    propsData: props || { },
+  }).$mount(el);
+
 export default (Component, props = {}, el = null) => new Component({
   propsData: props,
 }).$mount(el);
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index 2d386fe1da568811cd17947d1a01e6aed1f6727a..83f29d1b0c266a6c21d975bc37b84e87001ea1dc 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -1,37 +1,71 @@
-/* eslint-disable */
-
 /**
- * helper for testing action with expected mutations
+ * helper for testing action with expected mutations inspired in
  * https://vuex.vuejs.org/en/testing.html
+ *
+ * @example
+ * testAction(
+ *   actions.actionName, // action
+ *   { }, // mocked response
+ *   state, // state
+ *   [
+ *    { type: types.MUTATION}
+ *    { type: types.MUTATION_1, payload: {}}
+ *   ], // mutations
+ *   [
+ *    { type: 'actionName', payload: {}},
+ *    { type: 'actionName1', payload: {}}
+ *   ] //actions
+ *   done,
+ * );
  */
-export default (action, payload, state, expectedMutations, done) => {
-  let count = 0;
+export default (action, payload, state, expectedMutations, expectedActions, done) => {
+  let mutationsCount = 0;
+  let actionsCount = 0;
 
   // mock commit
-  const commit = (type, payload) => {
-    const mutation = expectedMutations[count];
-
-    try {
-      expect(mutation.type).to.equal(type);
-      if (payload) {
-        expect(mutation.payload).to.deep.equal(payload);
-      }
-    } catch (error) {
-      done(error);
+  const commit = (type, mutationPayload) => {
+    const mutation = expectedMutations[mutationsCount];
+
+    expect(mutation.type).toEqual(type);
+
+    if (mutation.payload) {
+      expect(mutation.payload).toEqual(mutationPayload);
     }
 
-    count++;
-    if (count >= expectedMutations.length) {
+    mutationsCount += 1;
+    if (mutationsCount >= expectedMutations.length) {
+      done();
+    }
+  };
+
+  // mock dispatch
+  const dispatch = (type, actionPayload) => {
+    const actionExpected = expectedActions[actionsCount];
+
+    expect(actionExpected.type).toEqual(type);
+
+    if (actionExpected.payload) {
+      expect(actionExpected.payload).toEqual(actionPayload);
+    }
+
+    actionsCount += 1;
+    if (actionsCount >= expectedActions.length) {
       done();
     }
   };
 
   // call the action with mocked store and arguments
-  action({ commit, state }, payload);
+  action({ commit, state, dispatch }, payload);
 
   // check if no mutations should have been dispatched
   if (expectedMutations.length === 0) {
-    expect(count).to.equal(0);
+    expect(mutationsCount).toEqual(0);
+    done();
+  }
+
+  // check if no mutations should have been dispatched
+  if (expectedActions.length === 0) {
+    expect(actionsCount).toEqual(0);
     done();
   }
 };
diff --git a/spec/javascripts/ide/components/changed_file_icon_spec.js b/spec/javascripts/ide/components/changed_file_icon_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..541864e912ecc08d4fe6bcac859468d970512482
--- /dev/null
+++ b/spec/javascripts/ide/components/changed_file_icon_spec.js
@@ -0,0 +1,46 @@
+import Vue from 'vue';
+import changedFileIcon from '~/ide/components/changed_file_icon.vue';
+import createComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('IDE changed file icon', () => {
+  let vm;
+
+  beforeEach(() => {
+    const component = Vue.extend(changedFileIcon);
+
+    vm = createComponent(component, {
+      file: {
+        tempFile: false,
+        changed: true,
+      },
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('changedIcon', () => {
+    it('equals file-modified when not a temp file and has changes', () => {
+      expect(vm.changedIcon).toBe('file-modified');
+    });
+
+    it('equals file-addition when a temp file', () => {
+      vm.file.tempFile = true;
+
+      expect(vm.changedIcon).toBe('file-addition');
+    });
+  });
+
+  describe('changedIconClass', () => {
+    it('includes multi-file-modified when not a temp file', () => {
+      expect(vm.changedIconClass).toContain('multi-file-modified');
+    });
+
+    it('includes multi-file-addition when a temp file', () => {
+      vm.file.tempFile = true;
+
+      expect(vm.changedIconClass).toContain('multi-file-addition');
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..144e78d14b5ed15a44351ad9e0cc8b312f56c7a5
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
@@ -0,0 +1,35 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import commitActions from '~/ide/components/commit_sidebar/actions.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore } from 'spec/ide/helpers';
+
+describe('IDE commit sidebar actions', () => {
+  let vm;
+
+  beforeEach(done => {
+    const Component = Vue.extend(commitActions);
+
+    vm = createComponentWithStore(Component, store);
+
+    vm.$store.state.currentBranchId = 'master';
+
+    vm.$mount();
+
+    Vue.nextTick(done);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('renders 3 groups', () => {
+    expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(3);
+  });
+
+  it('renders current branch text', () => {
+    expect(vm.$el.textContent).toContain('Commit to master branch');
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js b/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b80d08de7b1a892016b8337a0116953b521fbd5a
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js
@@ -0,0 +1,95 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import emptyState from '~/ide/components/commit_sidebar/empty_state.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { resetStore } from '../../helpers';
+
+describe('IDE commit panel empty state', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(emptyState);
+
+    vm = createComponentWithStore(Component, store, {
+      noChangesStateSvgPath: 'no-changes',
+      committedStateSvgPath: 'committed-state',
+    });
+
+    vm.$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  describe('statusSvg', () => {
+    it('uses noChangesStateSvgPath when commit message is empty', () => {
+      expect(vm.statusSvg).toBe('no-changes');
+      expect(vm.$el.querySelector('img').getAttribute('src')).toBe(
+        'no-changes',
+      );
+    });
+
+    it('uses committedStateSvgPath when commit message exists', done => {
+      vm.$store.state.lastCommitMsg = 'testing';
+
+      Vue.nextTick(() => {
+        expect(vm.statusSvg).toBe('committed-state');
+        expect(vm.$el.querySelector('img').getAttribute('src')).toBe(
+          'committed-state',
+        );
+
+        done();
+      });
+    });
+  });
+
+  it('renders no changes text when last commit message is empty', () => {
+    expect(vm.$el.textContent).toContain('No changes');
+  });
+
+  it('renders last commit message when it exists', done => {
+    vm.$store.state.lastCommitMsg = 'testing commit message';
+
+    Vue.nextTick(() => {
+      expect(vm.$el.textContent).toContain('testing commit message');
+
+      done();
+    });
+  });
+
+  describe('toggle button', () => {
+    it('calls store action', () => {
+      spyOn(vm, 'toggleRightPanelCollapsed');
+
+      vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
+
+      expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled();
+    });
+
+    it('renders collapsed class', done => {
+      vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
+
+      Vue.nextTick(() => {
+        expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
+
+        done();
+      });
+    });
+  });
+
+  describe('collapsed state', () => {
+    beforeEach(done => {
+      vm.$store.state.rightPanelCollapsed = true;
+
+      Vue.nextTick(done);
+    });
+
+    it('does not render text & svg', () => {
+      expect(vm.$el.querySelector('img')).toBeNull();
+      expect(vm.$el.textContent).not.toContain('No changes');
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9af3c15a4e3bbc9ffd1b44265069dd5b0c3658cd
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js
@@ -0,0 +1,72 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { file } from '../../helpers';
+import { removeWhitespace } from '../../../helpers/vue_component_helper';
+
+describe('Multi-file editor commit sidebar list collapsed', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(listCollapsed);
+
+    vm = createComponentWithStore(Component, store, {
+      files: [
+        {
+          ...file('file1'),
+          tempFile: true,
+        },
+        file('file2'),
+      ],
+      iconName: 'staged',
+      title: 'Staged',
+    });
+
+    vm.$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders added & modified files count', () => {
+    expect(removeWhitespace(vm.$el.textContent).trim()).toBe('1 1');
+  });
+
+  describe('addedFilesLength', () => {
+    it('returns an length of temp files', () => {
+      expect(vm.addedFilesLength).toBe(1);
+    });
+  });
+
+  describe('modifiedFilesLength', () => {
+    it('returns an length of modified files', () => {
+      expect(vm.modifiedFilesLength).toBe(1);
+    });
+  });
+
+  describe('addedFilesIconClass', () => {
+    it('includes multi-file-addition when addedFiles is not empty', () => {
+      expect(vm.addedFilesIconClass).toContain('multi-file-addition');
+    });
+
+    it('excludes multi-file-addition when addedFiles is empty', () => {
+      vm.files = [];
+
+      expect(vm.addedFilesIconClass).not.toContain('multi-file-addition');
+    });
+  });
+
+  describe('modifiedFilesClass', () => {
+    it('includes multi-file-modified when addedFiles is not empty', () => {
+      expect(vm.modifiedFilesClass).toContain('multi-file-modified');
+    });
+
+    it('excludes multi-file-modified when addedFiles is empty', () => {
+      vm.files = [];
+
+      expect(vm.modifiedFilesClass).not.toContain('multi-file-modified');
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..cc7e0a3f26daa1a3784668054182842184ffb3ff
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
@@ -0,0 +1,92 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import listItem from '~/ide/components/commit_sidebar/list_item.vue';
+import router from '~/ide/ide_router';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { file, resetStore } from '../../helpers';
+
+describe('Multi-file editor commit sidebar list item', () => {
+  let vm;
+  let f;
+
+  beforeEach(() => {
+    const Component = Vue.extend(listItem);
+
+    f = file('test-file');
+
+    store.state.entries[f.path] = f;
+
+    vm = createComponentWithStore(Component, store, {
+      file: f,
+      actionComponent: 'stage-button',
+    }).$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(store);
+  });
+
+  it('renders file path', () => {
+    expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
+  });
+
+  it('renders actionn button', () => {
+    expect(vm.$el.querySelector('.multi-file-discard-btn')).not.toBeNull();
+  });
+
+  it('opens a closed file in the editor when clicking the file path', done => {
+    spyOn(vm, 'openPendingTab').and.callThrough();
+    spyOn(router, 'push');
+
+    vm.$el.querySelector('.multi-file-commit-list-path').click();
+
+    setTimeout(() => {
+      expect(vm.openPendingTab).toHaveBeenCalled();
+      expect(router.push).toHaveBeenCalled();
+
+      done();
+    });
+  });
+
+  it('calls updateViewer with diff when clicking file', done => {
+    spyOn(vm, 'openFileInEditor').and.callThrough();
+    spyOn(vm, 'updateViewer').and.callThrough();
+    spyOn(router, 'push');
+
+    vm.$el.querySelector('.multi-file-commit-list-path').click();
+
+    setTimeout(() => {
+      expect(vm.updateViewer).toHaveBeenCalledWith('diff');
+
+      done();
+    });
+  });
+
+  describe('computed', () => {
+    describe('iconName', () => {
+      it('returns modified when not a tempFile', () => {
+        expect(vm.iconName).toBe('file-modified');
+      });
+
+      it('returns addition when not a tempFile', () => {
+        f.tempFile = true;
+
+        expect(vm.iconName).toBe('file-addition');
+      });
+    });
+
+    describe('iconClass', () => {
+      it('returns modified when not a tempFile', () => {
+        expect(vm.iconClass).toContain('multi-file-modified');
+      });
+
+      it('returns addition when not a tempFile', () => {
+        f.tempFile = true;
+
+        expect(vm.iconClass).toContain('multi-file-addition');
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..62fc3f90ad1754552e18888369c1bafdd1610789
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/list_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { file, resetStore } from '../../helpers';
+
+describe('Multi-file editor commit sidebar list', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(commitSidebarList);
+
+    vm = createComponentWithStore(Component, store, {
+      title: 'Staged',
+      fileList: [],
+      iconName: 'staged',
+      action: 'stageAllChanges',
+      actionBtnText: 'stage all',
+      itemActionComponent: 'stage-button',
+    });
+
+    vm.$store.state.rightPanelCollapsed = false;
+
+    vm.$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  describe('with a list of files', () => {
+    beforeEach(done => {
+      const f = file('file name');
+      f.changed = true;
+      vm.fileList.push(f);
+
+      Vue.nextTick(done);
+    });
+
+    it('renders list', () => {
+      expect(vm.$el.querySelectorAll('li').length).toBe(1);
+    });
+  });
+
+  describe('empty files array', () => {
+    it('renders no changes text when empty', () => {
+      expect(vm.$el.textContent).toContain('No changes');
+    });
+  });
+
+  describe('collapsed', () => {
+    beforeEach(done => {
+      vm.$store.state.rightPanelCollapsed = true;
+
+      Vue.nextTick(done);
+    });
+
+    it('hides list', () => {
+      expect(vm.$el.querySelector('.list-unstyled')).toBeNull();
+      expect(vm.$el.querySelector('.help-block')).toBeNull();
+    });
+  });
+
+  describe('with toggle', () => {
+    beforeEach(done => {
+      spyOn(vm, 'toggleRightPanelCollapsed');
+
+      vm.showToggle = true;
+
+      Vue.nextTick(done);
+    });
+
+    it('calls setPanelCollapsedStatus when clickin toggle', () => {
+      vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
+
+      expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled();
+    });
+  });
+
+  describe('action button', () => {
+    beforeEach(() => {
+      spyOn(vm, 'stageAllChanges');
+    });
+
+    it('calls store action when clicked', () => {
+      vm.$el.querySelector('.ide-staged-action-btn').click();
+
+      expect(vm.stageAllChanges).toHaveBeenCalled();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js b/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..d62d58101d6f3b4df5de7d6091930624fad25998
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js
@@ -0,0 +1,174 @@
+import Vue from 'vue';
+import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue';
+import createComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('IDE commit message field', () => {
+  const Component = Vue.extend(CommitMessageField);
+  let vm;
+
+  beforeEach(() => {
+    setFixtures('<div id="app"></div>');
+
+    vm = createComponent(
+      Component,
+      {
+        text: '',
+      },
+      '#app',
+    );
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('adds is-focused class on focus', done => {
+    vm.$el.querySelector('textarea').focus();
+
+    vm.$nextTick(() => {
+      expect(vm.$el.querySelector('.is-focused')).not.toBeNull();
+
+      done();
+    });
+  });
+
+  it('removed is-focused class on blur', done => {
+    vm.$el.querySelector('textarea').focus();
+
+    vm
+      .$nextTick()
+      .then(() => {
+        expect(vm.$el.querySelector('.is-focused')).not.toBeNull();
+
+        vm.$el.querySelector('textarea').blur();
+
+        return vm.$nextTick();
+      })
+      .then(() => {
+        expect(vm.$el.querySelector('.is-focused')).toBeNull();
+
+        done();
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('emits input event on input', () => {
+    spyOn(vm, '$emit');
+
+    const textarea = vm.$el.querySelector('textarea');
+    textarea.value = 'testing';
+
+    textarea.dispatchEvent(new Event('input'));
+
+    expect(vm.$emit).toHaveBeenCalledWith('input', 'testing');
+  });
+
+  describe('highlights', () => {
+    describe('subject line', () => {
+      it('does not highlight less than 50 characters', done => {
+        vm.text = 'text less than 50 chars';
+
+        vm
+          .$nextTick()
+          .then(() => {
+            expect(vm.$el.querySelector('.highlights span').textContent).toContain(
+              'text less than 50 chars',
+            );
+            expect(vm.$el.querySelector('mark').style.display).toBe('none');
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+
+      it('highlights characters over 50 length', done => {
+        vm.text =
+          'text less than 50 chars that should not highlighted. text more than 50 should be highlighted';
+
+        vm
+          .$nextTick()
+          .then(() => {
+            expect(vm.$el.querySelector('.highlights span').textContent).toContain(
+              'text less than 50 chars that should not highlighte',
+            );
+            expect(vm.$el.querySelector('mark').style.display).not.toBe('none');
+            expect(vm.$el.querySelector('mark').textContent).toBe(
+              'd. text more than 50 should be highlighted',
+            );
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+    });
+
+    describe('body text', () => {
+      it('does not highlight body text less tan 72 characters', done => {
+        vm.text = 'subject line\nbody content';
+
+        vm
+          .$nextTick()
+          .then(() => {
+            expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
+            expect(vm.$el.querySelectorAll('mark')[1].style.display).toBe('none');
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+
+      it('highlights body text more than 72 characters', done => {
+        vm.text =
+          'subject line\nbody content that will be highlighted when it is more than 72 characters in length';
+
+        vm
+          .$nextTick()
+          .then(() => {
+            expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
+            expect(vm.$el.querySelectorAll('mark')[1].style.display).not.toBe('none');
+            expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length');
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+
+      it('highlights body text & subject line', done => {
+        vm.text =
+          'text less than 50 chars that should not highlighted\nbody content that will be highlighted when it is more than 72 characters in length';
+
+        vm
+          .$nextTick()
+          .then(() => {
+            expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
+            expect(vm.$el.querySelectorAll('mark').length).toBe(2);
+
+            expect(vm.$el.querySelectorAll('mark')[0].textContent).toContain('d');
+            expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length');
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+    });
+  });
+
+  describe('scrolling textarea', () => {
+    it('updates transform of highlights', done => {
+      vm.text = 'subject line\n\n\n\n\n\n\n\n\n\n\nbody content';
+
+      vm
+        .$nextTick()
+        .then(() => {
+          vm.$el.querySelector('textarea').scrollTo(0, 50);
+
+          vm.handleScroll();
+        })
+        .then(vm.$nextTick)
+        .then(() => {
+          expect(vm.scrollTop).toBe(50);
+          expect(vm.$el.querySelector('.highlights').style.transform).toBe(
+            'translate3d(0px, -50px, 0px)',
+          );
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..21bfe4be52fd8affe022539363875178c03c6c78
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
@@ -0,0 +1,117 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore } from 'spec/ide/helpers';
+
+describe('IDE commit sidebar radio group', () => {
+  let vm;
+
+  beforeEach(done => {
+    const Component = Vue.extend(radioGroup);
+
+    store.state.commit.commitAction = '2';
+
+    vm = createComponentWithStore(Component, store, {
+      value: '1',
+      label: 'test',
+      checked: true,
+    });
+
+    vm.$mount();
+
+    Vue.nextTick(done);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('uses label if present', () => {
+    expect(vm.$el.textContent).toContain('test');
+  });
+
+  it('uses slot if label is not present', done => {
+    vm.$destroy();
+
+    vm = new Vue({
+      components: {
+        radioGroup,
+      },
+      store,
+      template: `
+        <radio-group
+          value="1"
+        >
+          Testing slot
+        </radio-group>
+      `,
+    });
+
+    vm.$mount();
+
+    Vue.nextTick(() => {
+      expect(vm.$el.textContent).toContain('Testing slot');
+
+      done();
+    });
+  });
+
+  it('updates store when changing radio button', done => {
+    vm.$el.querySelector('input').dispatchEvent(new Event('change'));
+
+    Vue.nextTick(() => {
+      expect(store.state.commit.commitAction).toBe('1');
+
+      done();
+    });
+  });
+
+  describe('with input', () => {
+    beforeEach(done => {
+      vm.$destroy();
+
+      const Component = Vue.extend(radioGroup);
+
+      store.state.commit.commitAction = '1';
+
+      vm = createComponentWithStore(Component, store, {
+        value: '1',
+        label: 'test',
+        checked: true,
+        showInput: true,
+      });
+
+      vm.$mount();
+
+      Vue.nextTick(done);
+    });
+
+    it('renders input box when commitAction matches value', () => {
+      expect(vm.$el.querySelector('.form-control')).not.toBeNull();
+    });
+
+    it('hides input when commitAction doesnt match value', done => {
+      store.state.commit.commitAction = '2';
+
+      Vue.nextTick(() => {
+        expect(vm.$el.querySelector('.form-control')).toBeNull();
+        done();
+      });
+    });
+
+    it('updates branch name in store on input', done => {
+      const input = vm.$el.querySelector('.form-control');
+      input.value = 'testing-123';
+      input.dispatchEvent(new Event('input'));
+
+      Vue.nextTick(() => {
+        expect(store.state.commit.newBranchName).toBe('testing-123');
+
+        done();
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6bf8710bda74a21494982cfe608730a2007524d7
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
@@ -0,0 +1,46 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import stageButton from '~/ide/components/commit_sidebar/stage_button.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { file, resetStore } from '../../helpers';
+
+describe('IDE stage file button', () => {
+  let vm;
+  let f;
+
+  beforeEach(() => {
+    const Component = Vue.extend(stageButton);
+    f = file();
+
+    vm = createComponentWithStore(Component, store, {
+      path: f.path,
+    });
+
+    spyOn(vm, 'stageChange');
+    spyOn(vm, 'discardFileChanges');
+
+    vm.$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('renders button to discard & stage', () => {
+    expect(vm.$el.querySelectorAll('.btn').length).toBe(2);
+  });
+
+  it('calls store with stage button', () => {
+    vm.$el.querySelectorAll('.btn')[0].click();
+
+    expect(vm.stageChange).toHaveBeenCalledWith(f.path);
+  });
+
+  it('calls store with discard button', () => {
+    vm.$el.querySelectorAll('.btn')[1].click();
+
+    expect(vm.discardFileChanges).toHaveBeenCalledWith(f.path);
+  });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/unstage_button_spec.js b/spec/javascripts/ide/components/commit_sidebar/unstage_button_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..917bbb9fb46a111859a8f7d4f2d114d29629a1b1
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/unstage_button_spec.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import unstageButton from '~/ide/components/commit_sidebar/unstage_button.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { file, resetStore } from '../../helpers';
+
+describe('IDE unstage file button', () => {
+  let vm;
+  let f;
+
+  beforeEach(() => {
+    const Component = Vue.extend(unstageButton);
+    f = file();
+
+    vm = createComponentWithStore(Component, store, {
+      path: f.path,
+    });
+
+    spyOn(vm, 'unstageChange');
+
+    vm.$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('renders button to unstage', () => {
+    expect(vm.$el.querySelectorAll('.btn').length).toBe(1);
+  });
+
+  it('calls store with unnstage button', () => {
+    vm.$el.querySelector('.btn').click();
+
+    expect(vm.unstageChange).toHaveBeenCalledWith(f.path);
+  });
+});
diff --git a/spec/javascripts/ide/components/ide_context_bar_spec.js b/spec/javascripts/ide/components/ide_context_bar_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e17b051f137917193da98a5a4d0d225ab33cf17d
--- /dev/null
+++ b/spec/javascripts/ide/components/ide_context_bar_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import ideContextBar from '~/ide/components/ide_context_bar.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+
+describe('Multi-file editor right context bar', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(ideContextBar);
+
+    vm = createComponentWithStore(Component, store, {
+      noChangesStateSvgPath: 'svg',
+      committedStateSvgPath: 'svg',
+    });
+
+    vm.$store.state.rightPanelCollapsed = false;
+
+    vm.$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('collapsed', () => {
+    beforeEach(done => {
+      vm.$store.state.rightPanelCollapsed = true;
+
+      Vue.nextTick(done);
+    });
+
+    it('adds collapsed class', () => {
+      expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/ide_external_links_spec.js b/spec/javascripts/ide/components/ide_external_links_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f6cb459f3b995720cb8739acfaa84ebbb108beb
--- /dev/null
+++ b/spec/javascripts/ide/components/ide_external_links_spec.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import ideExternalLinks from '~/ide/components/ide_external_links.vue';
+import createComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('ide external links component', () => {
+  let vm;
+  let fakeReferrer;
+  let Component;
+
+  const fakeProjectUrl = '/project/';
+
+  beforeEach(() => {
+    Component = Vue.extend(ideExternalLinks);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('goBackUrl', () => {
+    it('renders the Go Back link with the referrer when present', () => {
+      fakeReferrer = '/example/README.md';
+      spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer);
+
+      vm = createComponent(Component, {
+        projectUrl: fakeProjectUrl,
+      }).$mount();
+
+      expect(vm.goBackUrl).toEqual(fakeReferrer);
+    });
+
+    it('renders the Go Back link with the project url when referrer is not present', () => {
+      fakeReferrer = '';
+      spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer);
+
+      vm = createComponent(Component, {
+        projectUrl: fakeProjectUrl,
+      }).$mount();
+
+      expect(vm.goBackUrl).toEqual(fakeProjectUrl);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/ide_file_buttons_spec.js b/spec/javascripts/ide/components/ide_file_buttons_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ac8d1b2acf22a001686eacbd13f2d9bc17e1e41
--- /dev/null
+++ b/spec/javascripts/ide/components/ide_file_buttons_spec.js
@@ -0,0 +1,61 @@
+import Vue from 'vue';
+import repoFileButtons from '~/ide/components/ide_file_buttons.vue';
+import createVueComponent from '../../helpers/vue_mount_component_helper';
+import { file } from '../helpers';
+
+describe('RepoFileButtons', () => {
+  const activeFile = file();
+  let vm;
+
+  function createComponent() {
+    const RepoFileButtons = Vue.extend(repoFileButtons);
+
+    activeFile.rawPath = 'test';
+    activeFile.blamePath = 'test';
+    activeFile.commitsPath = 'test';
+
+    return createVueComponent(RepoFileButtons, {
+      file: activeFile,
+    });
+  }
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders Raw, Blame, History and Permalink', done => {
+    vm = createComponent();
+
+    vm.$nextTick(() => {
+      const raw = vm.$el.querySelector('.raw');
+      const blame = vm.$el.querySelector('.blame');
+      const history = vm.$el.querySelector('.history');
+
+      expect(raw.href).toMatch(`/${activeFile.rawPath}`);
+      expect(raw.getAttribute('data-original-title')).toEqual('Raw');
+      expect(blame.href).toMatch(`/${activeFile.blamePath}`);
+      expect(blame.getAttribute('data-original-title')).toEqual('Blame');
+      expect(history.href).toMatch(`/${activeFile.commitsPath}`);
+      expect(history.getAttribute('data-original-title')).toEqual('History');
+      expect(vm.$el.querySelector('.permalink').getAttribute('data-original-title')).toEqual(
+        'Permalink',
+      );
+
+      done();
+    });
+  });
+
+  it('renders Download', done => {
+    activeFile.binary = true;
+    vm = createComponent();
+
+    vm.$nextTick(() => {
+      const raw = vm.$el.querySelector('.raw');
+
+      expect(raw.href).toMatch(`/${activeFile.rawPath}`);
+      expect(raw.getAttribute('data-original-title')).toEqual('Download');
+
+      done();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/ide_project_tree_spec.js b/spec/javascripts/ide/components/ide_project_tree_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..657682cb39cee882b59016e506817a15b916603b
--- /dev/null
+++ b/spec/javascripts/ide/components/ide_project_tree_spec.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import ProjectTree from '~/ide/components/ide_project_tree.vue';
+import createComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('IDE project tree', () => {
+  const Component = Vue.extend(ProjectTree);
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent(Component, {
+      project: {
+        id: 1,
+        name: 'test',
+        web_url: gl.TEST_HOST,
+        avatar_url: '',
+        branches: [],
+      },
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders identicon when projct has no avatar', () => {
+    expect(vm.$el.querySelector('.identicon')).not.toBeNull();
+  });
+
+  it('renders avatar image if project has avatar', done => {
+    vm.project.avatar_url = gl.TEST_HOST;
+
+    vm.$nextTick(() => {
+      expect(vm.$el.querySelector('.identicon')).toBeNull();
+      expect(vm.$el.querySelector('img.avatar')).not.toBeNull();
+
+      done();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/ide_repo_tree_spec.js b/spec/javascripts/ide/components/ide_repo_tree_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e0fbc90ca6145982427e61ab68f0ce016c1dd339
--- /dev/null
+++ b/spec/javascripts/ide/components/ide_repo_tree_spec.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import ideRepoTree from '~/ide/components/ide_repo_tree.vue';
+import createComponent from '../../helpers/vue_mount_component_helper';
+import { file } from '../helpers';
+
+describe('IdeRepoTree', () => {
+  let vm;
+  let tree;
+
+  beforeEach(() => {
+    const IdeRepoTree = Vue.extend(ideRepoTree);
+
+    tree = {
+      tree: [file()],
+      loading: false,
+    };
+
+    vm = createComponent(IdeRepoTree, {
+      tree,
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders a sidebar', () => {
+    expect(vm.$el.querySelector('.loading-file')).toBeNull();
+    expect(vm.$el.querySelector('.file')).not.toBeNull();
+  });
+
+  it('renders 3 loading files if tree is loading', done => {
+    tree.loading = true;
+
+    vm.$nextTick(() => {
+      expect(
+        vm.$el.querySelectorAll('.multi-file-loading-container').length,
+      ).toEqual(3);
+
+      done();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/ide_side_bar_spec.js b/spec/javascripts/ide/components/ide_side_bar_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..699dae1ce2f6309893842e583b58630dd7962ec3
--- /dev/null
+++ b/spec/javascripts/ide/components/ide_side_bar_spec.js
@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import ideSidebar from '~/ide/components/ide_side_bar.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore } from '../helpers';
+
+describe('IdeSidebar', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(ideSidebar);
+
+    vm = createComponentWithStore(Component, store).$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('renders a sidebar', () => {
+    expect(
+      vm.$el.querySelector('.multi-file-commit-panel-inner'),
+    ).not.toBeNull();
+  });
+
+  it('renders loading icon component', done => {
+    vm.$store.state.loading = true;
+
+    vm.$nextTick(() => {
+      expect(
+        vm.$el.querySelector('.multi-file-loading-container'),
+      ).not.toBeNull();
+      expect(
+        vm.$el.querySelectorAll('.multi-file-loading-container').length,
+      ).toBe(3);
+
+      done();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..5bd890094ccea6787051eed0ba42a32ecda468c8
--- /dev/null
+++ b/spec/javascripts/ide/components/ide_spec.js
@@ -0,0 +1,41 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import ide from '~/ide/components/ide.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { file, resetStore } from '../helpers';
+
+describe('ide component', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(ide);
+
+    vm = createComponentWithStore(Component, store, {
+      emptyStateSvgPath: 'svg',
+      noChangesStateSvgPath: 'svg',
+      committedStateSvgPath: 'svg',
+    }).$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('does not render panel right when no files open', () => {
+    expect(vm.$el.querySelector('.panel-right')).toBeNull();
+  });
+
+  it('renders panel right when files are open', done => {
+    vm.$store.state.trees['abcproject/mybranch'] = {
+      tree: [file()],
+    };
+
+    Vue.nextTick(() => {
+      expect(vm.$el.querySelector('.panel-right')).toBeNull();
+
+      done();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e08abe7d8494b6f84eb176982a89cb746faa0f9a
--- /dev/null
+++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import newDropdown from '~/ide/components/new_dropdown/index.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore } from '../../helpers';
+
+describe('new dropdown component', () => {
+  let vm;
+
+  beforeEach(() => {
+    const component = Vue.extend(newDropdown);
+
+    vm = createComponentWithStore(component, store, {
+      branch: 'master',
+      path: '',
+    });
+
+    vm.$store.state.currentProjectId = 'abcproject';
+    vm.$store.state.path = '';
+    vm.$store.state.trees['abcproject/mybranch'] = {
+      tree: [],
+    };
+
+    vm.$mount();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('renders new file, upload and new directory links', () => {
+    expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
+    expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe(
+      'Upload file',
+    );
+    expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe(
+      'New directory',
+    );
+  });
+
+  describe('createNewItem', () => {
+    it('sets modalType to blob when new file is clicked', () => {
+      vm.$el.querySelectorAll('a')[0].click();
+
+      expect(vm.modalType).toBe('blob');
+    });
+
+    it('sets modalType to tree when new directory is clicked', () => {
+      vm.$el.querySelectorAll('a')[2].click();
+
+      expect(vm.modalType).toBe('tree');
+    });
+
+    it('opens modal when link is clicked', done => {
+      vm.$el.querySelectorAll('a')[0].click();
+
+      Vue.nextTick(() => {
+        expect(vm.$el.querySelector('.modal')).not.toBeNull();
+
+        done();
+      });
+    });
+  });
+
+  describe('hideModal', () => {
+    beforeAll(done => {
+      vm.openModal = true;
+      Vue.nextTick(done);
+    });
+
+    it('closes modal after toggling', done => {
+      vm.hideModal();
+
+      Vue.nextTick()
+        .then(() => {
+          expect(vm.$el.querySelector('.modal')).toBeNull();
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6e1e5a0d3507bb376fc716636e2b9a469df960c
--- /dev/null
+++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
@@ -0,0 +1,82 @@
+import Vue from 'vue';
+import modal from '~/ide/components/new_dropdown/modal.vue';
+import createComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('new file modal component', () => {
+  const Component = Vue.extend(modal);
+  let vm;
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  ['tree', 'blob'].forEach(type => {
+    describe(type, () => {
+      beforeEach(() => {
+        vm = createComponent(Component, {
+          type,
+          branchId: 'master',
+          path: '',
+        });
+
+        vm.entryName = 'testing';
+      });
+
+      it(`sets modal title as ${type}`, () => {
+        const title = type === 'tree' ? 'directory' : 'file';
+
+        expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(
+          `Create new ${title}`,
+        );
+      });
+
+      it(`sets button label as ${type}`, () => {
+        const title = type === 'tree' ? 'directory' : 'file';
+
+        expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(
+          `Create ${title}`,
+        );
+      });
+
+      it(`sets form label as ${type}`, () => {
+        const title = type === 'tree' ? 'Directory' : 'File';
+
+        expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(
+          `${title} name`,
+        );
+      });
+
+      describe('createEntryInStore', () => {
+        it('$emits create', () => {
+          spyOn(vm, '$emit');
+
+          vm.createEntryInStore();
+
+          expect(vm.$emit).toHaveBeenCalledWith('create', {
+            branchId: 'master',
+            name: 'testing',
+            type,
+          });
+        });
+      });
+    });
+  });
+
+  it('focuses field on mount', () => {
+    document.body.innerHTML += '<div class="js-test"></div>';
+
+    vm = createComponent(
+      Component,
+      {
+        type: 'tree',
+        branchId: 'master',
+        path: '',
+      },
+      '.js-test',
+    );
+
+    expect(document.activeElement).toBe(vm.$refs.fieldName);
+
+    vm.$el.remove();
+  });
+});
diff --git a/spec/javascripts/ide/components/new_dropdown/upload_spec.js b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..2bc5d70160161d39be2a76373f85a89d7bff4860
--- /dev/null
+++ b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
@@ -0,0 +1,87 @@
+import Vue from 'vue';
+import upload from '~/ide/components/new_dropdown/upload.vue';
+import createComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('new dropdown upload', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(upload);
+
+    vm = createComponent(Component, {
+      branchId: 'master',
+      path: '',
+    });
+
+    vm.entryName = 'testing';
+
+    spyOn(vm, '$emit');
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('readFile', () => {
+    beforeEach(() => {
+      spyOn(FileReader.prototype, 'readAsText');
+      spyOn(FileReader.prototype, 'readAsDataURL');
+    });
+
+    it('calls readAsText for text files', () => {
+      const file = {
+        type: 'text/html',
+      };
+
+      vm.readFile(file);
+
+      expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file);
+    });
+
+    it('calls readAsDataURL for non-text files', () => {
+      const file = {
+        type: 'images/png',
+      };
+
+      vm.readFile(file);
+
+      expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file);
+    });
+  });
+
+  describe('createFile', () => {
+    const target = {
+      result: 'content',
+    };
+    const binaryTarget = {
+      result: 'base64,base64content',
+    };
+    const file = {
+      name: 'file',
+    };
+
+    it('creates new file', () => {
+      vm.createFile(target, file, true);
+
+      expect(vm.$emit).toHaveBeenCalledWith('create', {
+        name: file.name,
+        branchId: 'master',
+        type: 'blob',
+        content: target.result,
+        base64: false,
+      });
+    });
+
+    it('splits content on base64 if binary', () => {
+      vm.createFile(binaryTarget, file, false);
+
+      expect(vm.$emit).toHaveBeenCalledWith('create', {
+        name: file.name,
+        branchId: 'master',
+        type: 'blob',
+        content: binaryTarget.result.split('base64,')[1],
+        base64: true,
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/repo_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..768f6e99bf170074311d384b41feca374d91ad59
--- /dev/null
+++ b/spec/javascripts/ide/components/repo_commit_section_spec.js
@@ -0,0 +1,264 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import service from '~/ide/services';
+import repoCommitSection from '~/ide/components/repo_commit_section.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
+import { file, resetStore } from '../helpers';
+
+describe('RepoCommitSection', () => {
+  let vm;
+
+  function createComponent() {
+    const Component = Vue.extend(repoCommitSection);
+
+    vm = createComponentWithStore(Component, store, {
+      noChangesStateSvgPath: 'svg',
+      committedStateSvgPath: 'commitsvg',
+    });
+
+    vm.$store.state.currentProjectId = 'abcproject';
+    vm.$store.state.currentBranchId = 'master';
+    vm.$store.state.projects.abcproject = {
+      web_url: '',
+      branches: {
+        master: {
+          workingReference: '1',
+        },
+      },
+    };
+
+    const files = [file('file1'), file('file2')].map(f =>
+      Object.assign(f, {
+        type: 'blob',
+      }),
+    );
+
+    vm.$store.state.rightPanelCollapsed = false;
+    vm.$store.state.currentBranch = 'master';
+    vm.$store.state.changedFiles = [...files];
+    vm.$store.state.changedFiles.forEach(f =>
+      Object.assign(f, {
+        changed: true,
+        content: 'changedFile testing',
+      }),
+    );
+
+    vm.$store.state.stagedFiles = [{ ...files[0] }, { ...files[1] }];
+    vm.$store.state.stagedFiles.forEach(f =>
+      Object.assign(f, {
+        changed: true,
+        content: 'testing',
+      }),
+    );
+
+    vm.$store.state.changedFiles.forEach(f => {
+      vm.$store.state.entries[f.path] = f;
+    });
+
+    return vm.$mount();
+  }
+
+  beforeEach(done => {
+    vm = createComponent();
+
+    spyOn(service, 'getTreeData').and.returnValue(
+      Promise.resolve({
+        headers: {
+          'page-title': 'test',
+        },
+        json: () =>
+          Promise.resolve({
+            last_commit_path: 'last_commit_path',
+            parent_tree_url: 'parent_tree_url',
+            path: '/',
+            trees: [{ name: 'tree' }],
+            blobs: [{ name: 'blob' }],
+            submodules: [{ name: 'submodule' }],
+          }),
+      }),
+    );
+
+    Vue.nextTick(done);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  describe('empty Stage', () => {
+    it('renders no changes text', () => {
+      resetStore(vm.$store);
+      const Component = Vue.extend(repoCommitSection);
+
+      vm = createComponentWithStore(Component, store, {
+        noChangesStateSvgPath: 'nochangessvg',
+        committedStateSvgPath: 'svg',
+      }).$mount();
+
+      expect(
+        vm.$el.querySelector('.js-empty-state').textContent.trim(),
+      ).toContain('No changes');
+      expect(
+        vm.$el.querySelector('.js-empty-state img').getAttribute('src'),
+      ).toBe('nochangessvg');
+    });
+  });
+
+  it('renders a commit section', () => {
+    const changedFileElements = [
+      ...vm.$el.querySelectorAll('.multi-file-commit-list li'),
+    ];
+    const submitCommit = vm.$el.querySelector('form .btn');
+    const allFiles = vm.$store.state.changedFiles.concat(
+      vm.$store.state.stagedFiles,
+    );
+
+    expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
+    expect(changedFileElements.length).toEqual(4);
+
+    changedFileElements.forEach((changedFile, i) => {
+      expect(changedFile.textContent.trim()).toContain(allFiles[i].path);
+    });
+
+    expect(submitCommit.disabled).toBeTruthy();
+    expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
+  });
+
+  it('adds changed files into staged files', done => {
+    vm.$el.querySelector('.ide-staged-action-btn').click();
+
+    Vue.nextTick(() => {
+      expect(
+        vm.$el.querySelector('.ide-commit-list-container').textContent,
+      ).toContain('No changes');
+
+      done();
+    });
+  });
+
+  it('stages a single file', done => {
+    vm.$el.querySelector('.multi-file-discard-btn .btn').click();
+
+    Vue.nextTick(() => {
+      expect(
+        vm.$el
+          .querySelector('.ide-commit-list-container')
+          .querySelectorAll('li').length,
+      ).toBe(1);
+
+      done();
+    });
+  });
+
+  it('discards a single file', done => {
+    vm.$el.querySelectorAll('.multi-file-discard-btn .btn')[1].click();
+
+    Vue.nextTick(() => {
+      expect(
+        vm.$el.querySelector('.ide-commit-list-container').textContent,
+      ).not.toContain('file1');
+      expect(
+        vm.$el
+          .querySelector('.ide-commit-list-container')
+          .querySelectorAll('li').length,
+      ).toBe(1);
+
+      done();
+    });
+  });
+
+  it('removes all staged files', done => {
+    vm.$el.querySelectorAll('.ide-staged-action-btn')[1].click();
+
+    Vue.nextTick(() => {
+      expect(
+        vm.$el.querySelectorAll('.ide-commit-list-container')[1].textContent,
+      ).toContain('No changes');
+
+      done();
+    });
+  });
+
+  it('unstages a single file', done => {
+    vm.$el
+      .querySelectorAll('.multi-file-discard-btn')[2]
+      .querySelector('.btn')
+      .click();
+
+    Vue.nextTick(() => {
+      expect(
+        vm.$el
+          .querySelectorAll('.ide-commit-list-container')[1]
+          .querySelectorAll('li').length,
+      ).toBe(1);
+
+      done();
+    });
+  });
+
+  it('updates commitMessage in store on input', done => {
+    const textarea = vm.$el.querySelector('textarea');
+
+    textarea.value = 'testing commit message';
+
+    textarea.dispatchEvent(new Event('input'));
+
+    getSetTimeoutPromise()
+      .then(() => {
+        expect(vm.$store.state.commit.commitMessage).toBe(
+          'testing commit message',
+        );
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  describe('discard draft button', () => {
+    it('hidden when commitMessage is empty', () => {
+      expect(
+        vm.$el.querySelector('.multi-file-commit-form .btn-default'),
+      ).toBeNull();
+    });
+
+    it('resets commitMessage when clicking discard button', done => {
+      vm.$store.state.commit.commitMessage = 'testing commit message';
+
+      getSetTimeoutPromise()
+        .then(() => {
+          vm.$el.querySelector('.multi-file-commit-form .btn-default').click();
+        })
+        .then(Vue.nextTick)
+        .then(() => {
+          expect(vm.$store.state.commit.commitMessage).not.toBe(
+            'testing commit message',
+          );
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('when submitting', () => {
+    beforeEach(() => {
+      spyOn(vm, 'commitChanges');
+    });
+
+    it('calls commitChanges', done => {
+      vm.$store.state.commit.commitMessage = 'testing commit message';
+
+      getSetTimeoutPromise()
+        .then(() => {
+          vm.$el.querySelector('.multi-file-commit-form .btn-success').click();
+        })
+        .then(Vue.nextTick)
+        .then(() => {
+          expect(vm.commitChanges).toHaveBeenCalled();
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b06a6c62a1c8bef0881fd0d82c19b0f6930d231b
--- /dev/null
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -0,0 +1,298 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import store from '~/ide/stores';
+import repoEditor from '~/ide/components/repo_editor.vue';
+import monacoLoader from '~/ide/monaco_loader';
+import Editor from '~/ide/lib/editor';
+import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
+import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
+import { file, resetStore } from '../helpers';
+
+describe('RepoEditor', () => {
+  let vm;
+
+  beforeEach(done => {
+    const f = file();
+    const RepoEditor = Vue.extend(repoEditor);
+
+    vm = createComponentWithStore(RepoEditor, store, {
+      file: f,
+    });
+
+    f.active = true;
+    f.tempFile = true;
+    vm.$store.state.openFiles.push(f);
+    vm.$store.state.entries[f.path] = f;
+    vm.monaco = true;
+
+    vm.$mount();
+
+    monacoLoader(['vs/editor/editor.main'], () => {
+      setTimeout(done, 0);
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+
+    Editor.editorInstance.dispose();
+  });
+
+  it('renders an ide container', done => {
+    Vue.nextTick(() => {
+      expect(vm.shouldHideEditor).toBeFalsy();
+
+      done();
+    });
+  });
+
+  it('renders only an edit tab', done => {
+    Vue.nextTick(() => {
+      const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li');
+      expect(tabs.length).toBe(1);
+      expect(tabs[0].textContent.trim()).toBe('Edit');
+
+      done();
+    });
+  });
+
+  describe('when file is markdown', () => {
+    beforeEach(done => {
+      vm.file.previewMode = {
+        id: 'markdown',
+        previewTitle: 'Preview Markdown',
+      };
+
+      vm.$nextTick(done);
+    });
+
+    it('renders an Edit and a Preview Tab', done => {
+      Vue.nextTick(() => {
+        const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li');
+        expect(tabs.length).toBe(2);
+        expect(tabs[0].textContent.trim()).toBe('Edit');
+        expect(tabs[1].textContent.trim()).toBe('Preview Markdown');
+
+        done();
+      });
+    });
+  });
+
+  describe('when file is markdown and viewer mode is review', () => {
+    let mock;
+
+    beforeEach(done => {
+      mock = new MockAdapter(axios);
+
+      vm.file.projectId = 'namespace/project';
+      vm.file.previewMode = {
+        id: 'markdown',
+        previewTitle: 'Preview Markdown',
+      };
+      vm.file.content = 'testing 123';
+      vm.$store.state.viewer = 'diff';
+
+      mock.onPost(/(.*)\/preview_markdown/).reply(200, {
+        body: '<p>testing 123</p>',
+      });
+
+      vm.$nextTick(done);
+    });
+
+    afterEach(() => {
+      mock.restore();
+    });
+
+    it('renders an Edit and a Preview Tab', done => {
+      Vue.nextTick(() => {
+        const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li');
+        expect(tabs.length).toBe(2);
+        expect(tabs[0].textContent.trim()).toBe('Review');
+        expect(tabs[1].textContent.trim()).toBe('Preview Markdown');
+
+        done();
+      });
+    });
+
+    it('renders markdown for tempFile', done => {
+      vm.file.tempFile = true;
+      vm.file.path = `${vm.file.path}.md`;
+      vm.$store.state.entries[vm.file.path] = vm.file;
+
+      vm
+        .$nextTick()
+        .then(() => {
+          vm.$el.querySelectorAll('.ide-mode-tabs .nav-links a')[1].click();
+        })
+        .then(setTimeoutPromise)
+        .then(() => {
+          expect(vm.$el.querySelector('.preview-container').innerHTML).toContain(
+            '<p>testing 123</p>',
+          );
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('when open file is binary and not raw', () => {
+    beforeEach(done => {
+      vm.file.binary = true;
+
+      vm.$nextTick(done);
+    });
+
+    it('does not render the IDE', () => {
+      expect(vm.shouldHideEditor).toBeTruthy();
+    });
+  });
+
+  describe('createEditorInstance', () => {
+    it('calls createInstance when viewer is editor', done => {
+      spyOn(vm.editor, 'createInstance');
+
+      vm.createEditorInstance();
+
+      vm.$nextTick(() => {
+        expect(vm.editor.createInstance).toHaveBeenCalled();
+
+        done();
+      });
+    });
+
+    it('calls createDiffInstance when viewer is diff', done => {
+      vm.$store.state.viewer = 'diff';
+
+      spyOn(vm.editor, 'createDiffInstance');
+
+      vm.createEditorInstance();
+
+      vm.$nextTick(() => {
+        expect(vm.editor.createDiffInstance).toHaveBeenCalled();
+
+        done();
+      });
+    });
+
+    it('calls createDiffInstance when viewer is a merge request diff', done => {
+      vm.$store.state.viewer = 'mrdiff';
+
+      spyOn(vm.editor, 'createDiffInstance');
+
+      vm.createEditorInstance();
+
+      vm.$nextTick(() => {
+        expect(vm.editor.createDiffInstance).toHaveBeenCalled();
+
+        done();
+      });
+    });
+  });
+
+  describe('setupEditor', () => {
+    it('creates new model', () => {
+      spyOn(vm.editor, 'createModel').and.callThrough();
+
+      Editor.editorInstance.modelManager.dispose();
+
+      vm.setupEditor();
+
+      expect(vm.editor.createModel).toHaveBeenCalledWith(vm.file, null);
+      expect(vm.model).not.toBeNull();
+    });
+
+    it('attaches model to editor', () => {
+      spyOn(vm.editor, 'attachModel').and.callThrough();
+
+      Editor.editorInstance.modelManager.dispose();
+
+      vm.setupEditor();
+
+      expect(vm.editor.attachModel).toHaveBeenCalledWith(vm.model);
+    });
+
+    it('adds callback methods', () => {
+      spyOn(vm.editor, 'onPositionChange').and.callThrough();
+
+      Editor.editorInstance.modelManager.dispose();
+
+      vm.setupEditor();
+
+      expect(vm.editor.onPositionChange).toHaveBeenCalled();
+      expect(vm.model.events.size).toBe(2);
+    });
+
+    it('updates state when model content changed', done => {
+      vm.model.setValue('testing 123');
+
+      setTimeout(() => {
+        expect(vm.file.content).toBe('testing 123');
+
+        done();
+      });
+    });
+
+    it('sets head model as staged file', () => {
+      spyOn(vm.editor, 'createModel').and.callThrough();
+
+      Editor.editorInstance.modelManager.dispose();
+
+      vm.$store.state.stagedFiles.push({ ...vm.file, key: 'staged' });
+      vm.file.staged = true;
+      vm.file.key = `unstaged-${vm.file.key}`;
+
+      vm.setupEditor();
+
+      expect(vm.editor.createModel).toHaveBeenCalledWith(vm.file, vm.$store.state.stagedFiles[0]);
+    });
+  });
+
+  describe('editor updateDimensions', () => {
+    beforeEach(() => {
+      spyOn(vm.editor, 'updateDimensions').and.callThrough();
+      spyOn(vm.editor, 'updateDiffView');
+    });
+
+    it('calls updateDimensions when rightPanelCollapsed is changed', done => {
+      vm.$store.state.rightPanelCollapsed = true;
+
+      vm.$nextTick(() => {
+        expect(vm.editor.updateDimensions).toHaveBeenCalled();
+        expect(vm.editor.updateDiffView).toHaveBeenCalled();
+
+        done();
+      });
+    });
+
+    it('calls updateDimensions when panelResizing is false', done => {
+      vm.$store.state.panelResizing = true;
+
+      vm
+        .$nextTick()
+        .then(() => {
+          vm.$store.state.panelResizing = false;
+        })
+        .then(vm.$nextTick)
+        .then(() => {
+          expect(vm.editor.updateDimensions).toHaveBeenCalled();
+          expect(vm.editor.updateDiffView).toHaveBeenCalled();
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('does not call updateDimensions when panelResizing is true', done => {
+      vm.$store.state.panelResizing = true;
+
+      vm.$nextTick(() => {
+        expect(vm.editor.updateDimensions).not.toHaveBeenCalled();
+        expect(vm.editor.updateDiffView).not.toHaveBeenCalled();
+
+        done();
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/repo_file_spec.js b/spec/javascripts/ide/components/repo_file_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ff391cb43513626e021d683b111ac9e3c70507bd
--- /dev/null
+++ b/spec/javascripts/ide/components/repo_file_spec.js
@@ -0,0 +1,80 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import repoFile from '~/ide/components/repo_file.vue';
+import router from '~/ide/ide_router';
+import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
+import { file } from '../helpers';
+
+describe('RepoFile', () => {
+  let vm;
+
+  function createComponent(propsData) {
+    const RepoFile = Vue.extend(repoFile);
+
+    vm = createComponentWithStore(RepoFile, store, propsData);
+
+    vm.$mount();
+  }
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders link, icon and name', () => {
+    createComponent({
+      file: file('t4'),
+      level: 0,
+    });
+
+    const name = vm.$el.querySelector('.ide-file-name');
+
+    expect(name.href).toMatch('');
+    expect(name.textContent.trim()).toEqual(vm.file.name);
+  });
+
+  it('fires clickFile when the link is clicked', done => {
+    spyOn(router, 'push');
+    createComponent({
+      file: file('t3'),
+      level: 0,
+    });
+
+    vm.$el.querySelector('.file-name').click();
+
+    setTimeout(() => {
+      expect(router.push).toHaveBeenCalledWith(`/project${vm.file.url}`);
+
+      done();
+    });
+  });
+
+  describe('locked file', () => {
+    let f;
+
+    beforeEach(() => {
+      f = file('locked file');
+      f.file_lock = {
+        user: {
+          name: 'testuser',
+          updated_at: new Date(),
+        },
+      };
+
+      createComponent({
+        file: f,
+        level: 0,
+      });
+    });
+
+    it('renders lock icon', () => {
+      expect(vm.$el.querySelector('.file-status-icon')).not.toBeNull();
+    });
+
+    it('renders a tooltip', () => {
+      expect(
+        vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset
+          .originalTitle,
+      ).toContain('Locked by testuser');
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/repo_loading_file_spec.js b/spec/javascripts/ide/components/repo_loading_file_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..7c20b8302f921f4fa8b9e4c0b12d0870e1b22b25
--- /dev/null
+++ b/spec/javascripts/ide/components/repo_loading_file_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import repoLoadingFile from '~/ide/components/repo_loading_file.vue';
+import { resetStore } from '../helpers';
+
+describe('RepoLoadingFile', () => {
+  let vm;
+
+  function createComponent() {
+    const RepoLoadingFile = Vue.extend(repoLoadingFile);
+
+    return new RepoLoadingFile({
+      store,
+    }).$mount();
+  }
+
+  function assertLines(lines) {
+    lines.forEach((line, n) => {
+      const index = n + 1;
+      expect(line.classList.contains(`skeleton-line-${index}`)).toBeTruthy();
+    });
+  }
+
+  function assertColumns(columns) {
+    columns.forEach(column => {
+      const container = column.querySelector('.animation-container');
+      const lines = [...container.querySelectorAll(':scope > div')];
+
+      expect(container).toBeTruthy();
+      expect(lines.length).toEqual(3);
+      assertLines(lines);
+    });
+  }
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('renders 3 columns of animated LoC', () => {
+    vm = createComponent();
+    const columns = [...vm.$el.querySelectorAll('td')];
+
+    expect(columns.length).toEqual(3);
+    assertColumns(columns);
+  });
+
+  it('renders 1 column of animated LoC if isMini', done => {
+    vm = createComponent();
+    vm.$store.state.leftPanelCollapsed = true;
+    vm.$store.state.openFiles.push('test');
+
+    vm.$nextTick(() => {
+      const columns = [...vm.$el.querySelectorAll('td')];
+
+      expect(columns.length).toEqual(1);
+      assertColumns(columns);
+
+      done();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/repo_tab_spec.js b/spec/javascripts/ide/components/repo_tab_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8cabc6e89353fe21052ee30c9170105201090e7c
--- /dev/null
+++ b/spec/javascripts/ide/components/repo_tab_spec.js
@@ -0,0 +1,165 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import repoTab from '~/ide/components/repo_tab.vue';
+import router from '~/ide/ide_router';
+import { file, resetStore } from '../helpers';
+
+describe('RepoTab', () => {
+  let vm;
+
+  function createComponent(propsData) {
+    const RepoTab = Vue.extend(repoTab);
+
+    return new RepoTab({
+      store,
+      propsData,
+    }).$mount();
+  }
+
+  beforeEach(() => {
+    spyOn(router, 'push');
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+
+    resetStore(vm.$store);
+  });
+
+  it('renders a close link and a name link', () => {
+    vm = createComponent({
+      tab: file(),
+    });
+    vm.$store.state.openFiles.push(vm.tab);
+    const close = vm.$el.querySelector('.multi-file-tab-close');
+    const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`);
+
+    expect(close.innerHTML).toContain('#close');
+    expect(name.textContent.trim()).toEqual(vm.tab.name);
+  });
+
+  it('fires clickFile when the link is clicked', () => {
+    vm = createComponent({
+      tab: file(),
+    });
+
+    spyOn(vm, 'clickFile');
+
+    vm.$el.click();
+
+    expect(vm.clickFile).toHaveBeenCalledWith(vm.tab);
+  });
+
+  it('calls closeFile when clicking close button', () => {
+    vm = createComponent({
+      tab: file(),
+    });
+
+    spyOn(vm, 'closeFile');
+
+    vm.$el.querySelector('.multi-file-tab-close').click();
+
+    expect(vm.closeFile).toHaveBeenCalledWith(vm.tab);
+  });
+
+  it('changes icon on hover', done => {
+    const tab = file();
+    tab.changed = true;
+    vm = createComponent({
+      tab,
+    });
+
+    vm.$el.dispatchEvent(new Event('mouseover'));
+
+    Vue.nextTick()
+      .then(() => {
+        expect(vm.$el.querySelector('.multi-file-modified')).toBeNull();
+
+        vm.$el.dispatchEvent(new Event('mouseout'));
+      })
+      .then(Vue.nextTick)
+      .then(() => {
+        expect(vm.$el.querySelector('.multi-file-modified')).not.toBeNull();
+
+        done();
+      })
+      .catch(done.fail);
+  });
+
+  describe('locked file', () => {
+    let f;
+
+    beforeEach(() => {
+      f = file('locked file');
+      f.file_lock = {
+        user: {
+          name: 'testuser',
+          updated_at: new Date(),
+        },
+      };
+
+      vm = createComponent({
+        tab: f,
+      });
+    });
+
+    afterEach(() => {
+      vm.$destroy();
+    });
+
+    it('renders lock icon', () => {
+      expect(vm.$el.querySelector('.file-status-icon')).not.toBeNull();
+    });
+
+    it('renders a tooltip', () => {
+      expect(
+        vm.$el.querySelector('span:nth-child(2)').dataset.originalTitle,
+      ).toContain('Locked by testuser');
+    });
+  });
+
+  describe('methods', () => {
+    describe('closeTab', () => {
+      it('closes tab if file has changed', done => {
+        const tab = file();
+        tab.changed = true;
+        tab.opened = true;
+        vm = createComponent({
+          tab,
+        });
+        vm.$store.state.openFiles.push(tab);
+        vm.$store.state.changedFiles.push(tab);
+        vm.$store.state.entries[tab.path] = tab;
+        vm.$store.dispatch('setFileActive', tab.path);
+
+        vm.$el.querySelector('.multi-file-tab-close').click();
+
+        vm.$nextTick(() => {
+          expect(tab.opened).toBeFalsy();
+          expect(vm.$store.state.changedFiles.length).toBe(1);
+
+          done();
+        });
+      });
+
+      it('closes tab when clicking close btn', done => {
+        const tab = file('lose');
+        tab.opened = true;
+        vm = createComponent({
+          tab,
+        });
+        vm.$store.state.openFiles.push(tab);
+        vm.$store.state.entries[tab.path] = tab;
+        vm.$store.dispatch('setFileActive', tab.path);
+
+        vm.$el.querySelector('.multi-file-tab-close').click();
+
+        vm.$nextTick(() => {
+          expect(tab.opened).toBeFalsy();
+
+          done();
+        });
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/ide/components/repo_tabs_spec.js b/spec/javascripts/ide/components/repo_tabs_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..cb785ba2cd3c4eeded0965435da48538cc102835
--- /dev/null
+++ b/spec/javascripts/ide/components/repo_tabs_spec.js
@@ -0,0 +1,85 @@
+import Vue from 'vue';
+import repoTabs from '~/ide/components/repo_tabs.vue';
+import createComponent from '../../helpers/vue_mount_component_helper';
+import { file } from '../helpers';
+
+describe('RepoTabs', () => {
+  const openedFiles = [file('open1'), file('open2')];
+  const RepoTabs = Vue.extend(repoTabs);
+  let vm;
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('renders a list of tabs', done => {
+    vm = createComponent(RepoTabs, {
+      files: openedFiles,
+      viewer: 'editor',
+      hasChanges: false,
+      activeFile: file('activeFile'),
+      hasMergeRequest: false,
+    });
+    openedFiles[0].active = true;
+
+    vm.$nextTick(() => {
+      const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
+
+      expect(tabs.length).toEqual(2);
+      expect(tabs[0].classList.contains('active')).toEqual(true);
+      expect(tabs[1].classList.contains('active')).toEqual(false);
+
+      done();
+    });
+  });
+
+  describe('updated', () => {
+    it('sets showShadow as true when scroll width is larger than width', done => {
+      const el = document.createElement('div');
+      el.innerHTML = '<div id="test-app"></div>';
+      document.body.appendChild(el);
+
+      const style = document.createElement('style');
+      style.innerText = `
+        .multi-file-tabs {
+          width: 100px;
+        }
+
+        .multi-file-tabs .list-unstyled {
+          display: flex;
+          overflow-x: auto;
+        }
+      `;
+      document.head.appendChild(style);
+
+      vm = createComponent(
+        RepoTabs,
+        {
+          files: [],
+          viewer: 'editor',
+          hasChanges: false,
+          activeFile: file('activeFile'),
+          hasMergeRequest: false,
+        },
+        '#test-app',
+      );
+
+      vm
+        .$nextTick()
+        .then(() => {
+          expect(vm.showShadow).toEqual(false);
+
+          vm.files = openedFiles;
+        })
+        .then(vm.$nextTick)
+        .then(() => {
+          expect(vm.showShadow).toEqual(true);
+
+          style.remove();
+          el.remove();
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..98db6defc7ad38c9cf81cb0fa2c6ee75773e2f1c
--- /dev/null
+++ b/spec/javascripts/ide/helpers.js
@@ -0,0 +1,22 @@
+import { decorateData } from '~/ide/stores/utils';
+import state from '~/ide/stores/state';
+import commitState from '~/ide/stores/modules/commit/state';
+
+export const resetStore = store => {
+  const newState = {
+    ...state(),
+    commit: commitState(),
+  };
+  store.replaceState(newState);
+};
+
+export const file = (name = 'name', id = name, type = '') =>
+  decorateData({
+    id,
+    type,
+    icon: 'icon',
+    url: 'url',
+    name,
+    path: name,
+    lastCommit: {},
+  });
diff --git a/spec/javascripts/repo/lib/common/disposable_spec.js b/spec/javascripts/ide/lib/common/disposable_spec.js
similarity index 100%
rename from spec/javascripts/repo/lib/common/disposable_spec.js
rename to spec/javascripts/ide/lib/common/disposable_spec.js
diff --git a/spec/javascripts/ide/lib/common/model_manager_spec.js b/spec/javascripts/ide/lib/common/model_manager_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c00d590c580fd5610a23bb987f4eba8c2fd14383
--- /dev/null
+++ b/spec/javascripts/ide/lib/common/model_manager_spec.js
@@ -0,0 +1,132 @@
+/* global monaco */
+import eventHub from '~/ide/eventhub';
+import monacoLoader from '~/ide/monaco_loader';
+import ModelManager from '~/ide/lib/common/model_manager';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library model manager', () => {
+  let instance;
+
+  beforeEach(done => {
+    monacoLoader(['vs/editor/editor.main'], () => {
+      instance = new ModelManager(monaco);
+
+      done();
+    });
+  });
+
+  afterEach(() => {
+    instance.dispose();
+  });
+
+  describe('addModel', () => {
+    it('caches model', () => {
+      instance.addModel(file());
+
+      expect(instance.models.size).toBe(1);
+    });
+
+    it('caches model by file path', () => {
+      const f = file('path-name');
+      instance.addModel(f);
+
+      expect(instance.models.keys().next().value).toBe(f.key);
+    });
+
+    it('adds model into disposable', () => {
+      spyOn(instance.disposable, 'add').and.callThrough();
+
+      instance.addModel(file());
+
+      expect(instance.disposable.add).toHaveBeenCalled();
+    });
+
+    it('returns cached model', () => {
+      spyOn(instance.models, 'get').and.callThrough();
+
+      instance.addModel(file());
+      instance.addModel(file());
+
+      expect(instance.models.get).toHaveBeenCalled();
+    });
+
+    it('adds eventHub listener', () => {
+      const f = file();
+      spyOn(eventHub, '$on').and.callThrough();
+
+      instance.addModel(f);
+
+      expect(eventHub.$on).toHaveBeenCalledWith(
+        `editor.update.model.dispose.${f.key}`,
+        jasmine.anything(),
+      );
+    });
+  });
+
+  describe('hasCachedModel', () => {
+    it('returns false when no models exist', () => {
+      expect(instance.hasCachedModel('path')).toBeFalsy();
+    });
+
+    it('returns true when model exists', () => {
+      const f = file('path-name');
+
+      instance.addModel(f);
+
+      expect(instance.hasCachedModel(f.key)).toBeTruthy();
+    });
+  });
+
+  describe('getModel', () => {
+    it('returns cached model', () => {
+      instance.addModel(file('path-name'));
+
+      expect(instance.getModel('path-name')).not.toBeNull();
+    });
+  });
+
+  describe('removeCachedModel', () => {
+    let f;
+
+    beforeEach(() => {
+      f = file();
+
+      instance.addModel(f);
+    });
+
+    it('clears cached model', () => {
+      instance.removeCachedModel(f);
+
+      expect(instance.models.size).toBe(0);
+    });
+
+    it('removes eventHub listener', () => {
+      spyOn(eventHub, '$off').and.callThrough();
+
+      instance.removeCachedModel(f);
+
+      expect(eventHub.$off).toHaveBeenCalledWith(
+        `editor.update.model.dispose.${f.key}`,
+        jasmine.anything(),
+      );
+    });
+  });
+
+  describe('dispose', () => {
+    it('clears cached models', () => {
+      instance.addModel(file());
+
+      instance.dispose();
+
+      expect(instance.models.size).toBe(0);
+    });
+
+    it('calls disposable dispose', () => {
+      spyOn(instance.disposable, 'dispose').and.callThrough();
+
+      instance.dispose();
+
+      expect(instance.disposable.dispose).toHaveBeenCalled();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/lib/common/model_spec.js b/spec/javascripts/ide/lib/common/model_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..7a6c22b6d272223238222c1f8133dff8ecad21a3
--- /dev/null
+++ b/spec/javascripts/ide/lib/common/model_spec.js
@@ -0,0 +1,139 @@
+/* global monaco */
+import eventHub from '~/ide/eventhub';
+import monacoLoader from '~/ide/monaco_loader';
+import Model from '~/ide/lib/common/model';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library model', () => {
+  let model;
+
+  beforeEach(done => {
+    spyOn(eventHub, '$on').and.callThrough();
+
+    monacoLoader(['vs/editor/editor.main'], () => {
+      const f = file('path');
+      f.mrChange = { diff: 'ABC' };
+      f.baseRaw = 'test';
+      model = new Model(monaco, f);
+
+      done();
+    });
+  });
+
+  afterEach(() => {
+    model.dispose();
+  });
+
+  it('creates original model & base model & new model', () => {
+    expect(model.originalModel).not.toBeNull();
+    expect(model.model).not.toBeNull();
+    expect(model.baseModel).not.toBeNull();
+  });
+
+  it('creates model with head file to compare against', () => {
+    const f = file('path');
+    model.dispose();
+
+    model = new Model(monaco, f, {
+      ...f,
+      content: '123 testing',
+    });
+
+    expect(model.head).not.toBeNull();
+    expect(model.getOriginalModel().getValue()).toBe('123 testing');
+  });
+
+  it('adds eventHub listener', () => {
+    expect(eventHub.$on).toHaveBeenCalledWith(
+      `editor.update.model.dispose.${model.file.key}`,
+      jasmine.anything(),
+    );
+  });
+
+  describe('path', () => {
+    it('returns file path', () => {
+      expect(model.path).toBe(model.file.key);
+    });
+  });
+
+  describe('getModel', () => {
+    it('returns model', () => {
+      expect(model.getModel()).toBe(model.model);
+    });
+  });
+
+  describe('getOriginalModel', () => {
+    it('returns original model', () => {
+      expect(model.getOriginalModel()).toBe(model.originalModel);
+    });
+  });
+
+  describe('getBaseModel', () => {
+    it('returns base model', () => {
+      expect(model.getBaseModel()).toBe(model.baseModel);
+    });
+  });
+
+  describe('setValue', () => {
+    it('updates models value', () => {
+      model.setValue('testing 123');
+
+      expect(model.getModel().getValue()).toBe('testing 123');
+    });
+  });
+
+  describe('onChange', () => {
+    it('calls callback on change', done => {
+      const spy = jasmine.createSpy();
+      model.onChange(spy);
+
+      model.getModel().setValue('123');
+
+      setTimeout(() => {
+        expect(spy).toHaveBeenCalledWith(model, jasmine.anything());
+        done();
+      });
+    });
+  });
+
+  describe('dispose', () => {
+    it('calls disposable dispose', () => {
+      spyOn(model.disposable, 'dispose').and.callThrough();
+
+      model.dispose();
+
+      expect(model.disposable.dispose).toHaveBeenCalled();
+    });
+
+    it('clears events', () => {
+      model.onChange(() => {});
+
+      expect(model.events.size).toBe(1);
+
+      model.dispose();
+
+      expect(model.events.size).toBe(0);
+    });
+
+    it('removes eventHub listener', () => {
+      spyOn(eventHub, '$off').and.callThrough();
+
+      model.dispose();
+
+      expect(eventHub.$off).toHaveBeenCalledWith(
+        `editor.update.model.dispose.${model.file.key}`,
+        jasmine.anything(),
+      );
+    });
+
+    it('calls onDispose callback', () => {
+      const disposeSpy = jasmine.createSpy();
+
+      model.onDispose(disposeSpy);
+
+      model.dispose();
+
+      expect(disposeSpy).toHaveBeenCalled();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/lib/decorations/controller_spec.js b/spec/javascripts/ide/lib/decorations/controller_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1c4ca570b6499c08d305e35e3ee0129b3082f64
--- /dev/null
+++ b/spec/javascripts/ide/lib/decorations/controller_spec.js
@@ -0,0 +1,149 @@
+/* global monaco */
+import monacoLoader from '~/ide/monaco_loader';
+import editor from '~/ide/lib/editor';
+import DecorationsController from '~/ide/lib/decorations/controller';
+import Model from '~/ide/lib/common/model';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library decorations controller', () => {
+  let editorInstance;
+  let controller;
+  let model;
+
+  beforeEach(done => {
+    monacoLoader(['vs/editor/editor.main'], () => {
+      editorInstance = editor.create(monaco);
+      editorInstance.createInstance(document.createElement('div'));
+
+      controller = new DecorationsController(editorInstance);
+      model = new Model(monaco, file('path'));
+
+      done();
+    });
+  });
+
+  afterEach(() => {
+    model.dispose();
+    editorInstance.dispose();
+    controller.dispose();
+  });
+
+  describe('getAllDecorationsForModel', () => {
+    it('returns empty array when no decorations exist for model', () => {
+      const decorations = controller.getAllDecorationsForModel(model);
+
+      expect(decorations).toEqual([]);
+    });
+
+    it('returns decorations by model URL', () => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+      const decorations = controller.getAllDecorationsForModel(model);
+
+      expect(decorations[0]).toEqual({ decoration: 'decorationValue' });
+    });
+  });
+
+  describe('addDecorations', () => {
+    it('caches decorations in a new map', () => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+      expect(controller.decorations.size).toBe(1);
+    });
+
+    it('does not create new cache model', () => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue2' }]);
+
+      expect(controller.decorations.size).toBe(1);
+    });
+
+    it('caches decorations by model URL', () => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+      expect(controller.decorations.size).toBe(1);
+      expect(controller.decorations.keys().next().value).toBe('path--path');
+    });
+
+    it('calls decorate method', () => {
+      spyOn(controller, 'decorate');
+
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+      expect(controller.decorate).toHaveBeenCalled();
+    });
+  });
+
+  describe('decorate', () => {
+    it('sets decorations on editor instance', () => {
+      spyOn(controller.editor.instance, 'deltaDecorations');
+
+      controller.decorate(model);
+
+      expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []);
+    });
+
+    it('caches decorations', () => {
+      spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
+
+      controller.decorate(model);
+
+      expect(controller.editorDecorations.size).toBe(1);
+    });
+
+    it('caches decorations by model URL', () => {
+      spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
+
+      controller.decorate(model);
+
+      expect(controller.editorDecorations.keys().next().value).toBe('path--path');
+    });
+  });
+
+  describe('dispose', () => {
+    it('clears cached decorations', () => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+      controller.dispose();
+
+      expect(controller.decorations.size).toBe(0);
+    });
+
+    it('clears cached editorDecorations', () => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+      controller.dispose();
+
+      expect(controller.editorDecorations.size).toBe(0);
+    });
+  });
+
+  describe('hasDecorations', () => {
+    it('returns true when decorations are cached', () => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+      expect(controller.hasDecorations(model)).toBe(true);
+    });
+
+    it('returns false when no model decorations exist', () => {
+      expect(controller.hasDecorations(model)).toBe(false);
+    });
+  });
+
+  describe('removeDecorations', () => {
+    beforeEach(() => {
+      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+      controller.decorate(model);
+    });
+
+    it('removes cached decorations', () => {
+      expect(controller.decorations.size).not.toBe(0);
+      expect(controller.editorDecorations.size).not.toBe(0);
+
+      controller.removeDecorations(model);
+
+      expect(controller.decorations.size).toBe(0);
+      expect(controller.editorDecorations.size).toBe(0);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/lib/diff/controller_spec.js b/spec/javascripts/ide/lib/diff/controller_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd8ab3b4f1d97f0b4a804fbf4ca4b5bbf7e782f7
--- /dev/null
+++ b/spec/javascripts/ide/lib/diff/controller_spec.js
@@ -0,0 +1,220 @@
+/* global monaco */
+import monacoLoader from '~/ide/monaco_loader';
+import editor from '~/ide/lib/editor';
+import ModelManager from '~/ide/lib/common/model_manager';
+import DecorationsController from '~/ide/lib/decorations/controller';
+import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
+import { computeDiff } from '~/ide/lib/diff/diff';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library dirty diff controller', () => {
+  let editorInstance;
+  let controller;
+  let modelManager;
+  let decorationsController;
+  let model;
+
+  beforeEach(done => {
+    monacoLoader(['vs/editor/editor.main'], () => {
+      editorInstance = editor.create(monaco);
+      editorInstance.createInstance(document.createElement('div'));
+
+      modelManager = new ModelManager(monaco);
+      decorationsController = new DecorationsController(editorInstance);
+
+      model = modelManager.addModel(file('path'));
+
+      controller = new DirtyDiffController(modelManager, decorationsController);
+
+      done();
+    });
+  });
+
+  afterEach(() => {
+    controller.dispose();
+    model.dispose();
+    decorationsController.dispose();
+    editorInstance.dispose();
+  });
+
+  describe('getDiffChangeType', () => {
+    ['added', 'removed', 'modified'].forEach(type => {
+      it(`returns ${type}`, () => {
+        const change = {
+          [type]: true,
+        };
+
+        expect(getDiffChangeType(change)).toBe(type);
+      });
+    });
+  });
+
+  describe('getDecorator', () => {
+    ['added', 'removed', 'modified'].forEach(type => {
+      it(`returns with linesDecorationsClassName for ${type}`, () => {
+        const change = {
+          [type]: true,
+        };
+
+        expect(getDecorator(change).options.linesDecorationsClassName).toBe(
+          `dirty-diff dirty-diff-${type}`,
+        );
+      });
+
+      it('returns with line numbers', () => {
+        const change = {
+          lineNumber: 1,
+          endLineNumber: 2,
+          [type]: true,
+        };
+
+        const range = getDecorator(change).range;
+
+        expect(range.startLineNumber).toBe(1);
+        expect(range.endLineNumber).toBe(2);
+        expect(range.startColumn).toBe(1);
+        expect(range.endColumn).toBe(1);
+      });
+    });
+  });
+
+  describe('attachModel', () => {
+    it('adds change event callback', () => {
+      spyOn(model, 'onChange');
+
+      controller.attachModel(model);
+
+      expect(model.onChange).toHaveBeenCalled();
+    });
+
+    it('adds dispose event callback', () => {
+      spyOn(model, 'onDispose');
+
+      controller.attachModel(model);
+
+      expect(model.onDispose).toHaveBeenCalled();
+    });
+
+    it('calls throttledComputeDiff on change', () => {
+      spyOn(controller, 'throttledComputeDiff');
+
+      controller.attachModel(model);
+
+      model.getModel().setValue('123');
+
+      expect(controller.throttledComputeDiff).toHaveBeenCalled();
+    });
+
+    it('caches model', () => {
+      controller.attachModel(model);
+
+      expect(controller.models.has(model.url)).toBe(true);
+    });
+  });
+
+  describe('computeDiff', () => {
+    it('posts to worker', () => {
+      spyOn(controller.dirtyDiffWorker, 'postMessage');
+
+      controller.computeDiff(model);
+
+      expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({
+        path: model.path,
+        originalContent: '',
+        newContent: '',
+      });
+    });
+  });
+
+  describe('reDecorate', () => {
+    it('calls computeDiff when no decorations are cached', () => {
+      spyOn(controller, 'computeDiff');
+
+      controller.reDecorate(model);
+
+      expect(controller.computeDiff).toHaveBeenCalledWith(model);
+    });
+
+    it('calls decorate when decorations are cached', () => {
+      spyOn(controller.decorationsController, 'decorate');
+
+      controller.decorationsController.decorations.set(model.url, 'test');
+
+      controller.reDecorate(model);
+
+      expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model);
+    });
+  });
+
+  describe('decorate', () => {
+    it('adds decorations into decorations controller', () => {
+      spyOn(controller.decorationsController, 'addDecorations');
+
+      controller.decorate({ data: { changes: [], path: model.path } });
+
+      expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith(
+        model,
+        'dirtyDiff',
+        jasmine.anything(),
+      );
+    });
+
+    it('adds decorations into editor', () => {
+      const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
+
+      controller.decorate({
+        data: { changes: computeDiff('123', '1234'), path: model.path },
+      });
+
+      expect(spy).toHaveBeenCalledWith(
+        [],
+        [
+          {
+            range: new monaco.Range(1, 1, 1, 1),
+            options: {
+              isWholeLine: true,
+              linesDecorationsClassName: 'dirty-diff dirty-diff-modified',
+            },
+          },
+        ],
+      );
+    });
+  });
+
+  describe('dispose', () => {
+    it('calls disposable dispose', () => {
+      spyOn(controller.disposable, 'dispose').and.callThrough();
+
+      controller.dispose();
+
+      expect(controller.disposable.dispose).toHaveBeenCalled();
+    });
+
+    it('terminates worker', () => {
+      spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough();
+
+      controller.dispose();
+
+      expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled();
+    });
+
+    it('removes worker event listener', () => {
+      spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough();
+
+      controller.dispose();
+
+      expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith(
+        'message',
+        jasmine.anything(),
+      );
+    });
+
+    it('clears cached models', () => {
+      controller.attachModel(model);
+
+      model.dispose();
+
+      expect(controller.models.size).toBe(0);
+    });
+  });
+});
diff --git a/spec/javascripts/repo/lib/diff/diff_spec.js b/spec/javascripts/ide/lib/diff/diff_spec.js
similarity index 100%
rename from spec/javascripts/repo/lib/diff/diff_spec.js
rename to spec/javascripts/ide/lib/diff/diff_spec.js
diff --git a/spec/javascripts/ide/lib/editor_options_spec.js b/spec/javascripts/ide/lib/editor_options_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..d149a883166983606a24c36de9d472eb26422339
--- /dev/null
+++ b/spec/javascripts/ide/lib/editor_options_spec.js
@@ -0,0 +1,11 @@
+import editorOptions from '~/ide/lib/editor_options';
+
+describe('Multi-file editor library editor options', () => {
+  it('returns an array', () => {
+    expect(editorOptions).toEqual(jasmine.any(Array));
+  });
+
+  it('contains readOnly option', () => {
+    expect(editorOptions[0].readOnly).toBeDefined();
+  });
+});
diff --git a/spec/javascripts/ide/lib/editor_spec.js b/spec/javascripts/ide/lib/editor_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..530bdfa2759045165e9c3f93cc1dd70b1e524eff
--- /dev/null
+++ b/spec/javascripts/ide/lib/editor_spec.js
@@ -0,0 +1,271 @@
+/* global monaco */
+import monacoLoader from '~/ide/monaco_loader';
+import editor from '~/ide/lib/editor';
+import { file } from '../helpers';
+
+describe('Multi-file editor library', () => {
+  let instance;
+  let el;
+  let holder;
+
+  beforeEach(done => {
+    el = document.createElement('div');
+    holder = document.createElement('div');
+    el.appendChild(holder);
+
+    document.body.appendChild(el);
+
+    monacoLoader(['vs/editor/editor.main'], () => {
+      instance = editor.create(monaco);
+
+      done();
+    });
+  });
+
+  afterEach(() => {
+    instance.dispose();
+
+    el.remove();
+  });
+
+  it('creates instance of editor', () => {
+    expect(editor.editorInstance).not.toBeNull();
+  });
+
+  it('creates instance returns cached instance', () => {
+    expect(editor.create(monaco)).toEqual(instance);
+  });
+
+  describe('createInstance', () => {
+    it('creates editor instance', () => {
+      spyOn(instance.monaco.editor, 'create').and.callThrough();
+
+      instance.createInstance(holder);
+
+      expect(instance.monaco.editor.create).toHaveBeenCalled();
+    });
+
+    it('creates dirty diff controller', () => {
+      instance.createInstance(holder);
+
+      expect(instance.dirtyDiffController).not.toBeNull();
+    });
+
+    it('creates model manager', () => {
+      instance.createInstance(holder);
+
+      expect(instance.modelManager).not.toBeNull();
+    });
+  });
+
+  describe('createDiffInstance', () => {
+    it('creates editor instance', () => {
+      spyOn(instance.monaco.editor, 'createDiffEditor').and.callThrough();
+
+      instance.createDiffInstance(holder);
+
+      expect(instance.monaco.editor.createDiffEditor).toHaveBeenCalledWith(holder, {
+        model: null,
+        contextmenu: true,
+        minimap: {
+          enabled: false,
+        },
+        readOnly: true,
+        scrollBeyondLastLine: false,
+        quickSuggestions: false,
+        occurrencesHighlight: false,
+        renderLineHighlight: 'none',
+        hideCursorInOverviewRuler: true,
+        wordWrap: 'on',
+        renderSideBySide: true,
+      });
+    });
+  });
+
+  describe('createModel', () => {
+    it('calls model manager addModel', () => {
+      spyOn(instance.modelManager, 'addModel');
+
+      instance.createModel('FILE');
+
+      expect(instance.modelManager.addModel).toHaveBeenCalledWith('FILE', null);
+    });
+  });
+
+  describe('attachModel', () => {
+    let model;
+
+    beforeEach(() => {
+      instance.createInstance(document.createElement('div'));
+
+      model = instance.createModel(file());
+    });
+
+    it('sets the current model on the instance', () => {
+      instance.attachModel(model);
+
+      expect(instance.currentModel).toBe(model);
+    });
+
+    it('attaches the model to the current instance', () => {
+      spyOn(instance.instance, 'setModel');
+
+      instance.attachModel(model);
+
+      expect(instance.instance.setModel).toHaveBeenCalledWith(model.getModel());
+    });
+
+    it('sets original & modified when diff editor', () => {
+      spyOn(instance.instance, 'getEditorType').and.returnValue('vs.editor.IDiffEditor');
+      spyOn(instance.instance, 'setModel');
+
+      instance.attachModel(model);
+
+      expect(instance.instance.setModel).toHaveBeenCalledWith({
+        original: model.getOriginalModel(),
+        modified: model.getModel(),
+      });
+    });
+
+    it('attaches the model to the dirty diff controller', () => {
+      spyOn(instance.dirtyDiffController, 'attachModel');
+
+      instance.attachModel(model);
+
+      expect(instance.dirtyDiffController.attachModel).toHaveBeenCalledWith(model);
+    });
+
+    it('re-decorates with the dirty diff controller', () => {
+      spyOn(instance.dirtyDiffController, 'reDecorate');
+
+      instance.attachModel(model);
+
+      expect(instance.dirtyDiffController.reDecorate).toHaveBeenCalledWith(model);
+    });
+  });
+
+  describe('attachMergeRequestModel', () => {
+    let model;
+
+    beforeEach(() => {
+      instance.createDiffInstance(document.createElement('div'));
+
+      const f = file();
+      f.mrChanges = { diff: 'ABC' };
+      f.baseRaw = 'testing';
+
+      model = instance.createModel(f);
+    });
+
+    it('sets original & modified', () => {
+      spyOn(instance.instance, 'setModel');
+
+      instance.attachMergeRequestModel(model);
+
+      expect(instance.instance.setModel).toHaveBeenCalledWith({
+        original: model.getBaseModel(),
+        modified: model.getModel(),
+      });
+    });
+  });
+
+  describe('clearEditor', () => {
+    it('resets the editor model', () => {
+      instance.createInstance(document.createElement('div'));
+
+      spyOn(instance.instance, 'setModel');
+
+      instance.clearEditor();
+
+      expect(instance.instance.setModel).toHaveBeenCalledWith(null);
+    });
+  });
+
+  describe('dispose', () => {
+    it('calls disposble dispose method', () => {
+      spyOn(instance.disposable, 'dispose').and.callThrough();
+
+      instance.dispose();
+
+      expect(instance.disposable.dispose).toHaveBeenCalled();
+    });
+
+    it('resets instance', () => {
+      instance.createInstance(document.createElement('div'));
+
+      expect(instance.instance).not.toBeNull();
+
+      instance.dispose();
+
+      expect(instance.instance).toBeNull();
+    });
+
+    it('does not dispose modelManager', () => {
+      spyOn(instance.modelManager, 'dispose');
+
+      instance.dispose();
+
+      expect(instance.modelManager.dispose).not.toHaveBeenCalled();
+    });
+
+    it('does not dispose decorationsController', () => {
+      spyOn(instance.decorationsController, 'dispose');
+
+      instance.dispose();
+
+      expect(instance.decorationsController.dispose).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('updateDiffView', () => {
+    describe('edit mode', () => {
+      it('does not update options', () => {
+        instance.createInstance(holder);
+
+        spyOn(instance.instance, 'updateOptions');
+
+        instance.updateDiffView();
+
+        expect(instance.instance.updateOptions).not.toHaveBeenCalled();
+      });
+    });
+
+    describe('diff mode', () => {
+      beforeEach(() => {
+        instance.createDiffInstance(holder);
+
+        spyOn(instance.instance, 'updateOptions').and.callThrough();
+      });
+
+      it('sets renderSideBySide to false if el is less than 700 pixels', () => {
+        spyOnProperty(instance.instance.getDomNode(), 'offsetWidth').and.returnValue(600);
+
+        expect(instance.instance.updateOptions).not.toHaveBeenCalledWith({
+          renderSideBySide: false,
+        });
+      });
+
+      it('sets renderSideBySide to false if el is more than 700 pixels', () => {
+        spyOnProperty(instance.instance.getDomNode(), 'offsetWidth').and.returnValue(800);
+
+        expect(instance.instance.updateOptions).not.toHaveBeenCalledWith({
+          renderSideBySide: true,
+        });
+      });
+    });
+  });
+
+  describe('isDiffEditorType', () => {
+    it('returns true when diff editor', () => {
+      instance.createDiffInstance(holder);
+
+      expect(instance.isDiffEditorType).toBe(true);
+    });
+
+    it('returns false when not diff editor', () => {
+      instance.createInstance(holder);
+
+      expect(instance.isDiffEditorType).toBe(false);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/monaco_loader_spec.js b/spec/javascripts/ide/monaco_loader_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ab315aa8c8fefb8f808ce68b464be1ac05200f7
--- /dev/null
+++ b/spec/javascripts/ide/monaco_loader_spec.js
@@ -0,0 +1,15 @@
+import monacoContext from 'monaco-editor/dev/vs/loader';
+import monacoLoader from '~/ide/monaco_loader';
+
+describe('MonacoLoader', () => {
+  it('calls require.config and exports require', () => {
+    expect(monacoContext.require.getConfig()).toEqual(
+      jasmine.objectContaining({
+        paths: {
+          vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
+        },
+      }),
+    );
+    expect(monacoLoader).toBe(monacoContext.require);
+  });
+});
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce5c525bed7d7461be397f930bc49bd0a58ebd0a
--- /dev/null
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -0,0 +1,628 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import * as actions from '~/ide/stores/actions/file';
+import * as types from '~/ide/stores/mutation_types';
+import service from '~/ide/services';
+import router from '~/ide/ide_router';
+import eventHub from '~/ide/eventhub';
+import { file, resetStore } from '../../helpers';
+import testAction from '../../../helpers/vuex_action_helper';
+
+describe('IDE store file actions', () => {
+  beforeEach(() => {
+    spyOn(router, 'push');
+  });
+
+  afterEach(() => {
+    resetStore(store);
+  });
+
+  describe('closeFile', () => {
+    let localFile;
+
+    beforeEach(() => {
+      localFile = file('testFile');
+      localFile.active = true;
+      localFile.opened = true;
+      localFile.parentTreeUrl = 'parentTreeUrl';
+
+      store.state.openFiles.push(localFile);
+      store.state.entries[localFile.path] = localFile;
+    });
+
+    it('closes open files', done => {
+      store
+        .dispatch('closeFile', localFile)
+        .then(() => {
+          expect(localFile.opened).toBeFalsy();
+          expect(localFile.active).toBeFalsy();
+          expect(store.state.openFiles.length).toBe(0);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('closes file even if file has changes', done => {
+      store.state.changedFiles.push(localFile);
+
+      store
+        .dispatch('closeFile', localFile)
+        .then(Vue.nextTick)
+        .then(() => {
+          expect(store.state.openFiles.length).toBe(0);
+          expect(store.state.changedFiles.length).toBe(1);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('closes file & opens next available file', done => {
+      const f = {
+        ...file('newOpenFile'),
+        url: '/newOpenFile',
+      };
+
+      store.state.openFiles.push(f);
+      store.state.entries[f.path] = f;
+
+      store
+        .dispatch('closeFile', localFile)
+        .then(Vue.nextTick)
+        .then(() => {
+          expect(router.push).toHaveBeenCalledWith(`/project${f.url}`);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('removes file if it pending', done => {
+      store.state.openFiles.push({
+        ...localFile,
+        pending: true,
+      });
+
+      store
+        .dispatch('closeFile', localFile)
+        .then(() => {
+          expect(store.state.openFiles.length).toBe(0);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('setFileActive', () => {
+    let localFile;
+    let scrollToTabSpy;
+    let oldScrollToTab;
+
+    beforeEach(() => {
+      scrollToTabSpy = jasmine.createSpy('scrollToTab');
+      oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
+      store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
+
+      localFile = file('setThisActive');
+
+      store.state.entries[localFile.path] = localFile;
+    });
+
+    afterEach(() => {
+      store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
+    });
+
+    it('calls scrollToTab', done => {
+      store
+        .dispatch('setFileActive', localFile.path)
+        .then(() => {
+          expect(scrollToTabSpy).toHaveBeenCalled();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets the file active', done => {
+      store
+        .dispatch('setFileActive', localFile.path)
+        .then(() => {
+          expect(localFile.active).toBeTruthy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('returns early if file is already active', done => {
+      localFile.active = true;
+
+      store
+        .dispatch('setFileActive', localFile.path)
+        .then(() => {
+          expect(scrollToTabSpy).not.toHaveBeenCalled();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets current active file to not active', done => {
+      const f = file('newActive');
+      store.state.entries[f.path] = f;
+      localFile.active = true;
+      store.state.openFiles.push(localFile);
+
+      store
+        .dispatch('setFileActive', f.path)
+        .then(() => {
+          expect(localFile.active).toBeFalsy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('resets location.hash for line highlighting', done => {
+      location.hash = 'test';
+
+      store
+        .dispatch('setFileActive', localFile.path)
+        .then(() => {
+          expect(location.hash).not.toBe('test');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('getFileData', () => {
+    let localFile;
+
+    beforeEach(() => {
+      spyOn(service, 'getFileData').and.returnValue(
+        Promise.resolve({
+          headers: {
+            'page-title': 'testing getFileData',
+          },
+          json: () =>
+            Promise.resolve({
+              blame_path: 'blame_path',
+              commits_path: 'commits_path',
+              permalink: 'permalink',
+              raw_path: 'raw_path',
+              binary: false,
+              html: '123',
+              render_error: '',
+            }),
+        }),
+      );
+
+      localFile = file(`newCreate-${Math.random()}`);
+      localFile.url = 'getFileDataURL';
+      store.state.entries[localFile.path] = localFile;
+    });
+
+    it('calls the service', done => {
+      store
+        .dispatch('getFileData', { path: localFile.path })
+        .then(() => {
+          expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets the file data', done => {
+      store
+        .dispatch('getFileData', { path: localFile.path })
+        .then(() => {
+          expect(localFile.blamePath).toBe('blame_path');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets document title', done => {
+      store
+        .dispatch('getFileData', { path: localFile.path })
+        .then(() => {
+          expect(document.title).toBe('testing getFileData');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets the file as active', done => {
+      store
+        .dispatch('getFileData', { path: localFile.path })
+        .then(() => {
+          expect(localFile.active).toBeTruthy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets the file not as active if we pass makeFileActive false', done => {
+      store
+        .dispatch('getFileData', { path: localFile.path, makeFileActive: false })
+        .then(() => {
+          expect(localFile.active).toBeFalsy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('adds the file to open files', done => {
+      store
+        .dispatch('getFileData', { path: localFile.path })
+        .then(() => {
+          expect(store.state.openFiles.length).toBe(1);
+          expect(store.state.openFiles[0].name).toBe(localFile.name);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('getRawFileData', () => {
+    let tmpFile;
+
+    beforeEach(() => {
+      spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw'));
+
+      tmpFile = file('tmpFile');
+      store.state.entries[tmpFile.path] = tmpFile;
+    });
+
+    it('calls getRawFileData service method', done => {
+      store
+        .dispatch('getRawFileData', { path: tmpFile.path })
+        .then(() => {
+          expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('updates file raw data', done => {
+      store
+        .dispatch('getRawFileData', { path: tmpFile.path })
+        .then(() => {
+          expect(tmpFile.raw).toBe('raw');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('calls also getBaseRawFileData service method', done => {
+      spyOn(service, 'getBaseRawFileData').and.returnValue(Promise.resolve('baseraw'));
+
+      tmpFile.mrChange = { new_file: false };
+
+      store
+        .dispatch('getRawFileData', { path: tmpFile.path, baseSha: 'SHA' })
+        .then(() => {
+          expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
+          expect(tmpFile.baseRaw).toBe('baseraw');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('changeFileContent', () => {
+    let tmpFile;
+
+    beforeEach(() => {
+      tmpFile = file('tmpFile');
+      store.state.entries[tmpFile.path] = tmpFile;
+    });
+
+    it('updates file content', done => {
+      store
+        .dispatch('changeFileContent', {
+          path: tmpFile.path,
+          content: 'content',
+        })
+        .then(() => {
+          expect(tmpFile.content).toBe('content');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('adds file into changedFiles array', done => {
+      store
+        .dispatch('changeFileContent', {
+          path: tmpFile.path,
+          content: 'content',
+        })
+        .then(() => {
+          expect(store.state.changedFiles.length).toBe(1);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('adds file once into changedFiles array', done => {
+      store
+        .dispatch('changeFileContent', {
+          path: tmpFile.path,
+          content: 'content',
+        })
+        .then(() =>
+          store.dispatch('changeFileContent', {
+            path: tmpFile.path,
+            content: 'content 123',
+          }),
+        )
+        .then(() => {
+          expect(store.state.changedFiles.length).toBe(1);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('removes file from changedFiles array if not changed', done => {
+      store
+        .dispatch('changeFileContent', {
+          path: tmpFile.path,
+          content: 'content',
+        })
+        .then(() =>
+          store.dispatch('changeFileContent', {
+            path: tmpFile.path,
+            content: '',
+          }),
+        )
+        .then(() => {
+          expect(store.state.changedFiles.length).toBe(0);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('discardFileChanges', () => {
+    let tmpFile;
+
+    beforeEach(() => {
+      spyOn(eventHub, '$on');
+      spyOn(eventHub, '$emit');
+
+      tmpFile = file();
+      tmpFile.content = 'testing';
+
+      store.state.changedFiles.push(tmpFile);
+      store.state.entries[tmpFile.path] = tmpFile;
+    });
+
+    it('resets file content', done => {
+      store
+        .dispatch('discardFileChanges', tmpFile.path)
+        .then(() => {
+          expect(tmpFile.content).not.toBe('testing');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('removes file from changedFiles array', done => {
+      store
+        .dispatch('discardFileChanges', tmpFile.path)
+        .then(() => {
+          expect(store.state.changedFiles.length).toBe(0);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('closes temp file', done => {
+      tmpFile.tempFile = true;
+      tmpFile.opened = true;
+
+      store
+        .dispatch('discardFileChanges', tmpFile.path)
+        .then(() => {
+          expect(tmpFile.opened).toBeFalsy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('does not re-open a closed temp file', done => {
+      tmpFile.tempFile = true;
+
+      expect(tmpFile.opened).toBeFalsy();
+
+      store
+        .dispatch('discardFileChanges', tmpFile.path)
+        .then(() => {
+          expect(tmpFile.opened).toBeFalsy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('pushes route for active file', done => {
+      tmpFile.active = true;
+      store.state.openFiles.push(tmpFile);
+
+      store
+        .dispatch('discardFileChanges', tmpFile.path)
+        .then(() => {
+          expect(router.push).toHaveBeenCalledWith(`/project${tmpFile.url}`);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('emits eventHub event to dispose cached model', done => {
+      store
+        .dispatch('discardFileChanges', tmpFile.path)
+        .then(() => {
+          expect(eventHub.$emit).toHaveBeenCalled();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('stageChange', () => {
+    it('calls STAGE_CHANGE with file path', done => {
+      testAction(
+        actions.stageChange,
+        'path',
+        store.state,
+        [{ type: types.STAGE_CHANGE, payload: 'path' }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('unstageChange', () => {
+    it('calls UNSTAGE_CHANGE with file path', done => {
+      testAction(
+        actions.unstageChange,
+        'path',
+        store.state,
+        [{ type: types.UNSTAGE_CHANGE, payload: 'path' }],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('openPendingTab', () => {
+    let f;
+
+    beforeEach(() => {
+      f = {
+        ...file(),
+        projectId: '123',
+      };
+
+      store.state.entries[f.path] = f;
+    });
+
+    it('makes file pending in openFiles', done => {
+      store
+        .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
+        .then(() => {
+          expect(store.state.openFiles[0].pending).toBe(true);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('returns true when opened', done => {
+      store
+        .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
+        .then(added => {
+          expect(added).toBe(true);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('pushes router URL when added', done => {
+      store.state.currentBranchId = 'master';
+
+      store
+        .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
+        .then(() => {
+          expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('calls scrollToTab', done => {
+      const scrollToTabSpy = jasmine.createSpy('scrollToTab');
+      const oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
+      store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
+
+      store
+        .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
+        .then(() => {
+          expect(scrollToTabSpy).toHaveBeenCalled();
+          store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('returns false when passed in file is active & viewer is diff', done => {
+      f.active = true;
+      store.state.openFiles.push(f);
+      store.state.viewer = 'diff';
+
+      store
+        .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
+        .then(added => {
+          expect(added).toBe(false);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('removePendingTab', () => {
+    let f;
+
+    beforeEach(() => {
+      spyOn(eventHub, '$emit');
+
+      f = {
+        ...file('pendingFile'),
+        pending: true,
+      };
+    });
+
+    it('removes pending file from open files', done => {
+      store.state.openFiles.push(f);
+
+      store
+        .dispatch('removePendingTab', f)
+        .then(() => {
+          expect(store.state.openFiles.length).toBe(0);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('emits event to dispose model', done => {
+      store
+        .dispatch('removePendingTab', f)
+        .then(() => {
+          expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.${f.key}`);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b4ec4a0b1737fe36bb161fe91b176807a34fe0d8
--- /dev/null
+++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js
@@ -0,0 +1,110 @@
+import store from '~/ide/stores';
+import service from '~/ide/services';
+import { resetStore } from '../../helpers';
+
+describe('IDE store merge request actions', () => {
+  beforeEach(() => {
+    store.state.projects.abcproject = {
+      mergeRequests: {},
+    };
+  });
+
+  afterEach(() => {
+    resetStore(store);
+  });
+
+  describe('getMergeRequestData', () => {
+    beforeEach(() => {
+      spyOn(service, 'getProjectMergeRequestData').and.returnValue(
+        Promise.resolve({ data: { title: 'mergerequest' } }),
+      );
+    });
+
+    it('calls getProjectMergeRequestData service method', done => {
+      store
+        .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+        .then(() => {
+          expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets the Merge Request Object', done => {
+      store
+        .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+        .then(() => {
+          expect(store.state.projects.abcproject.mergeRequests['1'].title).toBe('mergerequest');
+          expect(store.state.currentMergeRequestId).toBe(1);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('getMergeRequestChanges', () => {
+    beforeEach(() => {
+      spyOn(service, 'getProjectMergeRequestChanges').and.returnValue(
+        Promise.resolve({ data: { title: 'mergerequest' } }),
+      );
+
+      store.state.projects.abcproject.mergeRequests['1'] = { changes: [] };
+    });
+
+    it('calls getProjectMergeRequestChanges service method', done => {
+      store
+        .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+        .then(() => {
+          expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith('abcproject', 1);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets the Merge Request Changes Object', done => {
+      store
+        .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+        .then(() => {
+          expect(store.state.projects.abcproject.mergeRequests['1'].changes.title).toBe(
+            'mergerequest',
+          );
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('getMergeRequestVersions', () => {
+    beforeEach(() => {
+      spyOn(service, 'getProjectMergeRequestVersions').and.returnValue(
+        Promise.resolve({ data: [{ id: 789 }] }),
+      );
+
+      store.state.projects.abcproject.mergeRequests['1'] = { versions: [] };
+    });
+
+    it('calls getProjectMergeRequestVersions service method', done => {
+      store
+        .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+        .then(() => {
+          expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith('abcproject', 1);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('sets the Merge Request Versions Object', done => {
+      store
+        .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+        .then(() => {
+          expect(store.state.projects.abcproject.mergeRequests['1'].versions.length).toBe(1);
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e0ef57a39666d59824d922caf1beb4707b2f0cf4
--- /dev/null
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -0,0 +1,166 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import service from '~/ide/services';
+import router from '~/ide/ide_router';
+import { file, resetStore } from '../../helpers';
+
+describe('Multi-file store tree actions', () => {
+  let projectTree;
+
+  const basicCallParameters = {
+    endpoint: 'rootEndpoint',
+    projectId: 'abcproject',
+    branch: 'master',
+    branchId: 'master',
+  };
+
+  beforeEach(() => {
+    spyOn(router, 'push');
+
+    store.state.currentProjectId = 'abcproject';
+    store.state.currentBranchId = 'master';
+    store.state.projects.abcproject = {
+      web_url: '',
+      branches: {
+        master: {
+          workingReference: '1',
+        },
+      },
+    };
+  });
+
+  afterEach(() => {
+    resetStore(store);
+  });
+
+  describe('getFiles', () => {
+    beforeEach(() => {
+      spyOn(service, 'getFiles').and.returnValue(
+        Promise.resolve({
+          json: () =>
+            Promise.resolve([
+              'file.txt',
+              'folder/fileinfolder.js',
+              'folder/subfolder/fileinsubfolder.js',
+            ]),
+        }),
+      );
+    });
+
+    it('calls service getFiles', done => {
+      store
+        .dispatch('getFiles', basicCallParameters)
+        .then(() => {
+          expect(service.getFiles).toHaveBeenCalledWith('', 'master');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('adds data into tree', done => {
+      store
+        .dispatch('getFiles', basicCallParameters)
+        .then(() => {
+          projectTree = store.state.trees['abcproject/master'];
+          expect(projectTree.tree.length).toBe(2);
+          expect(projectTree.tree[0].type).toBe('tree');
+          expect(projectTree.tree[0].tree[1].name).toBe('fileinfolder.js');
+          expect(projectTree.tree[1].type).toBe('blob');
+          expect(projectTree.tree[0].tree[0].tree[0].type).toBe('blob');
+          expect(projectTree.tree[0].tree[0].tree[0].name).toBe('fileinsubfolder.js');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('toggleTreeOpen', () => {
+    let tree;
+
+    beforeEach(() => {
+      tree = file('testing', '1', 'tree');
+      store.state.entries[tree.path] = tree;
+    });
+
+    it('toggles the tree open', done => {
+      store
+        .dispatch('toggleTreeOpen', tree.path)
+        .then(() => {
+          expect(tree.opened).toBeTruthy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('getLastCommitData', () => {
+    beforeEach(() => {
+      spyOn(service, 'getTreeLastCommit').and.returnValue(
+        Promise.resolve({
+          headers: {
+            'more-logs-url': null,
+          },
+          json: () =>
+            Promise.resolve([
+              {
+                type: 'tree',
+                file_name: 'testing',
+                commit: {
+                  message: 'commit message',
+                  authored_date: '123',
+                },
+              },
+            ]),
+        }),
+      );
+
+      store.state.trees['abcproject/mybranch'] = {
+        tree: [],
+      };
+
+      projectTree = store.state.trees['abcproject/mybranch'];
+      projectTree.tree.push(file('testing', '1', 'tree'));
+      projectTree.lastCommitPath = 'lastcommitpath';
+    });
+
+    it('calls service with lastCommitPath', done => {
+      store
+        .dispatch('getLastCommitData', projectTree)
+        .then(() => {
+          expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('updates trees last commit data', done => {
+      store
+        .dispatch('getLastCommitData', projectTree)
+        .then(Vue.nextTick)
+        .then(() => {
+          expect(projectTree.tree[0].lastCommit.message).toBe('commit message');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('does not update entry if not found', done => {
+      projectTree.tree[0].name = 'a';
+
+      store
+        .dispatch('getLastCommitData', projectTree)
+        .then(Vue.nextTick)
+        .then(() => {
+          expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..22a7441ba92eb388d9dbf40cff16c6c296d66093
--- /dev/null
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -0,0 +1,343 @@
+import * as urlUtils from '~/lib/utils/url_utility';
+import store from '~/ide/stores';
+import * as actions from '~/ide/stores/actions';
+import * as types from '~/ide/stores/mutation_types';
+import router from '~/ide/ide_router';
+import { resetStore, file } from '../helpers';
+import testAction from '../../helpers/vuex_action_helper';
+
+describe('Multi-file store actions', () => {
+  beforeEach(() => {
+    spyOn(router, 'push');
+  });
+
+  afterEach(() => {
+    resetStore(store);
+  });
+
+  describe('redirectToUrl', () => {
+    it('calls visitUrl', done => {
+      spyOn(urlUtils, 'visitUrl');
+
+      store
+        .dispatch('redirectToUrl', 'test')
+        .then(() => {
+          expect(urlUtils.visitUrl).toHaveBeenCalledWith('test');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('setInitialData', () => {
+    it('commits initial data', done => {
+      store
+        .dispatch('setInitialData', { canCommit: true })
+        .then(() => {
+          expect(store.state.canCommit).toBeTruthy();
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('discardAllChanges', () => {
+    beforeEach(() => {
+      const f = file('discardAll');
+      f.changed = true;
+
+      store.state.openFiles.push(f);
+      store.state.changedFiles.push(f);
+      store.state.entries[f.path] = f;
+    });
+
+    it('discards changes in file', done => {
+      store
+        .dispatch('discardAllChanges')
+        .then(() => {
+          expect(store.state.openFiles.changed).toBeFalsy();
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('removes all files from changedFiles state', done => {
+      store
+        .dispatch('discardAllChanges')
+        .then(() => {
+          expect(store.state.changedFiles.length).toBe(0);
+          expect(store.state.openFiles.length).toBe(1);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('closeAllFiles', () => {
+    beforeEach(() => {
+      const f = file('closeAll');
+      store.state.openFiles.push(f);
+      store.state.openFiles[0].opened = true;
+      store.state.entries[f.path] = f;
+    });
+
+    it('closes all open files', done => {
+      store
+        .dispatch('closeAllFiles')
+        .then(() => {
+          expect(store.state.openFiles.length).toBe(0);
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('createTempEntry', () => {
+    beforeEach(() => {
+      document.body.innerHTML += '<div class="flash-container"></div>';
+
+      store.state.currentProjectId = 'abcproject';
+      store.state.currentBranchId = 'mybranch';
+
+      store.state.trees['abcproject/mybranch'] = {
+        tree: [],
+      };
+      store.state.projects.abcproject = {
+        web_url: '',
+      };
+    });
+
+    afterEach(() => {
+      document.querySelector('.flash-container').remove();
+    });
+
+    describe('tree', () => {
+      it('creates temp tree', done => {
+        store
+          .dispatch('createTempEntry', {
+            branchId: store.state.currentBranchId,
+            name: 'test',
+            type: 'tree',
+          })
+          .then(() => {
+            const entry = store.state.entries.test;
+
+            expect(entry).not.toBeNull();
+            expect(entry.type).toBe('tree');
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('creates new folder inside another tree', done => {
+        const tree = {
+          type: 'tree',
+          name: 'testing',
+          path: 'testing',
+          tree: [],
+        };
+
+        store.state.entries[tree.path] = tree;
+
+        store
+          .dispatch('createTempEntry', {
+            branchId: store.state.currentBranchId,
+            name: 'testing/test',
+            type: 'tree',
+          })
+          .then(() => {
+            expect(tree.tree[0].tempFile).toBeTruthy();
+            expect(tree.tree[0].name).toBe('test');
+            expect(tree.tree[0].type).toBe('tree');
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('does not create new tree if already exists', done => {
+        const tree = {
+          type: 'tree',
+          path: 'testing',
+          tempFile: false,
+          tree: [],
+        };
+
+        store.state.entries[tree.path] = tree;
+
+        store
+          .dispatch('createTempEntry', {
+            branchId: store.state.currentBranchId,
+            name: 'testing',
+            type: 'tree',
+          })
+          .then(() => {
+            expect(store.state.entries[tree.path].tempFile).toEqual(false);
+            expect(document.querySelector('.flash-alert')).not.toBeNull();
+
+            done();
+          })
+          .catch(done.fail);
+      });
+    });
+
+    describe('blob', () => {
+      it('creates temp file', done => {
+        store
+          .dispatch('createTempEntry', {
+            name: 'test',
+            branchId: 'mybranch',
+            type: 'blob',
+          })
+          .then(f => {
+            expect(f.tempFile).toBeTruthy();
+            expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1);
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('adds tmp file to open files', done => {
+        store
+          .dispatch('createTempEntry', {
+            name: 'test',
+            branchId: 'mybranch',
+            type: 'blob',
+          })
+          .then(f => {
+            expect(store.state.openFiles.length).toBe(1);
+            expect(store.state.openFiles[0].name).toBe(f.name);
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('adds tmp file to changed files', done => {
+        store
+          .dispatch('createTempEntry', {
+            name: 'test',
+            branchId: 'mybranch',
+            type: 'blob',
+          })
+          .then(f => {
+            expect(store.state.changedFiles.length).toBe(1);
+            expect(store.state.changedFiles[0].name).toBe(f.name);
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('sets tmp file as active', done => {
+        store
+          .dispatch('createTempEntry', {
+            name: 'test',
+            branchId: 'mybranch',
+            type: 'blob',
+          })
+          .then(f => {
+            expect(f.active).toBeTruthy();
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('creates flash message if file already exists', done => {
+        const f = file('test', '1', 'blob');
+        store.state.trees['abcproject/mybranch'].tree = [f];
+        store.state.entries[f.path] = f;
+
+        store
+          .dispatch('createTempEntry', {
+            name: 'test',
+            branchId: 'mybranch',
+            type: 'blob',
+          })
+          .then(() => {
+            expect(document.querySelector('.flash-alert')).not.toBeNull();
+
+            done();
+          })
+          .catch(done.fail);
+      });
+    });
+  });
+
+  describe('popHistoryState', () => {});
+
+  describe('scrollToTab', () => {
+    it('focuses the current active element', done => {
+      document.body.innerHTML +=
+        '<div id="tabs"><div class="active"><div class="repo-tab"></div></div></div>';
+      const el = document.querySelector('.repo-tab');
+      spyOn(el, 'focus');
+
+      store
+        .dispatch('scrollToTab')
+        .then(() => {
+          setTimeout(() => {
+            expect(el.focus).toHaveBeenCalled();
+
+            document.getElementById('tabs').remove();
+
+            done();
+          });
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('stageAllChanges', () => {
+    it('adds all files from changedFiles to stagedFiles', done => {
+      store.state.changedFiles.push(file(), file('new'));
+
+      testAction(
+        actions.stageAllChanges,
+        null,
+        store.state,
+        [
+          { type: types.STAGE_CHANGE, payload: store.state.changedFiles[0].path },
+          { type: types.STAGE_CHANGE, payload: store.state.changedFiles[1].path },
+        ],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('unstageAllChanges', () => {
+    it('removes all files from stagedFiles after unstaging', done => {
+      store.state.stagedFiles.push(file(), file('new'));
+
+      testAction(
+        actions.unstageAllChanges,
+        null,
+        store.state,
+        [
+          { type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[0].path },
+          { type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[1].path },
+        ],
+        [],
+        done,
+      );
+    });
+  });
+
+  describe('updateViewer', () => {
+    it('updates viewer state', done => {
+      store
+        .dispatch('updateViewer', 'diff')
+        .then(() => {
+          expect(store.state.viewer).toBe('diff');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8d04b83928ca7a30475637784c40f7d539ae06ae
--- /dev/null
+++ b/spec/javascripts/ide/stores/getters_spec.js
@@ -0,0 +1,67 @@
+import * as getters from '~/ide/stores/getters';
+import state from '~/ide/stores/state';
+import { file } from '../helpers';
+
+describe('IDE store getters', () => {
+  let localState;
+
+  beforeEach(() => {
+    localState = state();
+  });
+
+  describe('activeFile', () => {
+    it('returns the current active file', () => {
+      localState.openFiles.push(file());
+      localState.openFiles.push(file('active'));
+      localState.openFiles[1].active = true;
+
+      expect(getters.activeFile(localState).name).toBe('active');
+    });
+
+    it('returns undefined if no active files are found', () => {
+      localState.openFiles.push(file());
+      localState.openFiles.push(file('active'));
+
+      expect(getters.activeFile(localState)).toBeNull();
+    });
+  });
+
+  describe('modifiedFiles', () => {
+    it('returns a list of modified files', () => {
+      localState.openFiles.push(file());
+      localState.changedFiles.push(file('changed'));
+      localState.changedFiles[0].changed = true;
+
+      const modifiedFiles = getters.modifiedFiles(localState);
+
+      expect(modifiedFiles.length).toBe(1);
+      expect(modifiedFiles[0].name).toBe('changed');
+    });
+
+    it('returns angle left when collapsed', () => {
+      localState.rightPanelCollapsed = true;
+
+      expect(getters.collapseButtonIcon(localState)).toBe('angle-double-left');
+    });
+  });
+
+  describe('currentMergeRequest', () => {
+    it('returns Current Merge Request', () => {
+      localState.currentProjectId = 'abcproject';
+      localState.currentMergeRequestId = 1;
+      localState.projects.abcproject = {
+        mergeRequests: {
+          1: { mergeId: 1 },
+        },
+      };
+
+      expect(getters.currentMergeRequest(localState).mergeId).toBe(1);
+    });
+
+    it('returns null if no active Merge Request was found', () => {
+      localState.currentProjectId = 'otherproject';
+
+      expect(getters.currentMergeRequest(localState)).toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..116967208e0509f2367ebf8ca4271ed9598d96a8
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -0,0 +1,517 @@
+import store from '~/ide/stores';
+import service from '~/ide/services';
+import router from '~/ide/ide_router';
+import * as urlUtils from '~/lib/utils/url_utility';
+import eventHub from '~/ide/eventhub';
+import * as consts from '~/ide/stores/modules/commit/constants';
+import { resetStore, file } from 'spec/ide/helpers';
+
+describe('IDE commit module actions', () => {
+  beforeEach(() => {
+    spyOn(router, 'push');
+  });
+
+  afterEach(() => {
+    resetStore(store);
+  });
+
+  describe('updateCommitMessage', () => {
+    it('updates store with new commit message', done => {
+      store
+        .dispatch('commit/updateCommitMessage', 'testing')
+        .then(() => {
+          expect(store.state.commit.commitMessage).toBe('testing');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('discardDraft', () => {
+    it('resets commit message to blank', done => {
+      store.state.commit.commitMessage = 'testing';
+
+      store
+        .dispatch('commit/discardDraft')
+        .then(() => {
+          expect(store.state.commit.commitMessage).not.toBe('testing');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('updateCommitAction', () => {
+    it('updates store with new commit action', done => {
+      store
+        .dispatch('commit/updateCommitAction', '1')
+        .then(() => {
+          expect(store.state.commit.commitAction).toBe('1');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('updateBranchName', () => {
+    it('updates store with new branch name', done => {
+      store
+        .dispatch('commit/updateBranchName', 'branch-name')
+        .then(() => {
+          expect(store.state.commit.newBranchName).toBe('branch-name');
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('setLastCommitMessage', () => {
+    beforeEach(() => {
+      Object.assign(store.state, {
+        currentProjectId: 'abcproject',
+        projects: {
+          abcproject: {
+            web_url: 'http://testing',
+          },
+        },
+      });
+    });
+
+    it('updates commit message with short_id', done => {
+      store
+        .dispatch('commit/setLastCommitMessage', { short_id: '123' })
+        .then(() => {
+          expect(store.state.lastCommitMsg).toContain(
+            'Your changes have been committed. Commit <a href="http://testing/commit/123" class="commit-sha">123</a>',
+          );
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('updates commit message with stats', done => {
+      store
+        .dispatch('commit/setLastCommitMessage', {
+          short_id: '123',
+          stats: {
+            additions: '1',
+            deletions: '2',
+          },
+        })
+        .then(() => {
+          expect(store.state.lastCommitMsg).toBe(
+            'Your changes have been committed. Commit <a href="http://testing/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.',
+          );
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('checkCommitStatus', () => {
+    beforeEach(() => {
+      store.state.currentProjectId = 'abcproject';
+      store.state.currentBranchId = 'master';
+      store.state.projects.abcproject = {
+        branches: {
+          master: {
+            workingReference: '1',
+          },
+        },
+      };
+    });
+
+    it('calls service', done => {
+      spyOn(service, 'getBranchData').and.returnValue(
+        Promise.resolve({
+          data: {
+            commit: { id: '123' },
+          },
+        }),
+      );
+
+      store
+        .dispatch('commit/checkCommitStatus')
+        .then(() => {
+          expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('returns true if current ref does not equal returned ID', done => {
+      spyOn(service, 'getBranchData').and.returnValue(
+        Promise.resolve({
+          data: {
+            commit: { id: '123' },
+          },
+        }),
+      );
+
+      store
+        .dispatch('commit/checkCommitStatus')
+        .then(val => {
+          expect(val).toBeTruthy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+
+    it('returns false if current ref equals returned ID', done => {
+      spyOn(service, 'getBranchData').and.returnValue(
+        Promise.resolve({
+          data: {
+            commit: { id: '1' },
+          },
+        }),
+      );
+
+      store
+        .dispatch('commit/checkCommitStatus')
+        .then(val => {
+          expect(val).toBeFalsy();
+
+          done();
+        })
+        .catch(done.fail);
+    });
+  });
+
+  describe('updateFilesAfterCommit', () => {
+    const data = {
+      id: '123',
+      message: 'testing commit message',
+      committed_date: '123',
+      committer_name: 'root',
+    };
+    const branch = 'master';
+    let f;
+
+    beforeEach(() => {
+      spyOn(eventHub, '$emit');
+
+      f = file('changedFile');
+      Object.assign(f, {
+        active: true,
+        changed: true,
+        content: 'file content',
+      });
+
+      store.state.currentProjectId = 'abcproject';
+      store.state.currentBranchId = 'master';
+      store.state.projects.abcproject = {
+        web_url: 'web_url',
+        branches: {
+          master: {
+            workingReference: '',
+          },
+        },
+      };
+      store.state.stagedFiles.push(f, {
+        ...file('changedFile2'),
+        changed: true,
+      });
+      store.state.openFiles = store.state.stagedFiles;
+
+      store.state.stagedFiles.forEach(stagedFile => {
+        store.state.entries[stagedFile.path] = stagedFile;
+      });
+    });
+
+    it('updates stores working reference', done => {
+      store
+        .dispatch('commit/updateFilesAfterCommit', {
+          data,
+          branch,
+        })
+        .then(() => {
+          expect(store.state.projects.abcproject.branches.master.workingReference).toBe(data.id);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('resets all files changed status', done => {
+      store
+        .dispatch('commit/updateFilesAfterCommit', {
+          data,
+          branch,
+        })
+        .then(() => {
+          store.state.openFiles.forEach(entry => {
+            expect(entry.changed).toBeFalsy();
+          });
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('sets files commit data', done => {
+      store
+        .dispatch('commit/updateFilesAfterCommit', {
+          data,
+          branch,
+        })
+        .then(() => {
+          expect(f.lastCommit.message).toBe(data.message);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('updates raw content for changed file', done => {
+      store
+        .dispatch('commit/updateFilesAfterCommit', {
+          data,
+          branch,
+        })
+        .then(() => {
+          expect(f.raw).toBe(f.content);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('emits changed event for file', done => {
+      store
+        .dispatch('commit/updateFilesAfterCommit', {
+          data,
+          branch,
+        })
+        .then(() => {
+          expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.content.${f.key}`, {
+            content: f.content,
+            changed: false,
+          });
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('pushes route to new branch if commitAction is new branch', done => {
+      store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+
+      store
+        .dispatch('commit/updateFilesAfterCommit', {
+          data,
+          branch,
+        })
+        .then(() => {
+          expect(router.push).toHaveBeenCalledWith(`/project/abcproject/blob/master/${f.path}`);
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+
+  describe('commitChanges', () => {
+    beforeEach(() => {
+      spyOn(urlUtils, 'visitUrl');
+
+      document.body.innerHTML += '<div class="flash-container"></div>';
+
+      store.state.currentProjectId = 'abcproject';
+      store.state.currentBranchId = 'master';
+      store.state.projects.abcproject = {
+        web_url: 'webUrl',
+        branches: {
+          master: {
+            workingReference: '1',
+          },
+        },
+      };
+
+      const f = {
+        ...file('changed'),
+        type: 'blob',
+        active: true,
+      };
+      store.state.stagedFiles.push(f);
+      store.state.changedFiles = [
+        {
+          ...f,
+        },
+      ];
+      store.state.openFiles = store.state.changedFiles;
+
+      store.state.openFiles.forEach(localF => {
+        store.state.entries[localF.path] = localF;
+      });
+
+      store.state.commit.commitAction = '2';
+      store.state.commit.commitMessage = 'testing 123';
+    });
+
+    afterEach(() => {
+      document.querySelector('.flash-container').remove();
+    });
+
+    describe('success', () => {
+      beforeEach(() => {
+        spyOn(service, 'commit').and.returnValue(
+          Promise.resolve({
+            data: {
+              id: '123456',
+              short_id: '123',
+              message: 'test message',
+              committed_date: 'date',
+              stats: {
+                additions: '1',
+                deletions: '2',
+              },
+            },
+          }),
+        );
+      });
+
+      it('calls service', done => {
+        store
+          .dispatch('commit/commitChanges')
+          .then(() => {
+            expect(service.commit).toHaveBeenCalledWith('abcproject', {
+              branch: jasmine.anything(),
+              commit_message: 'testing 123',
+              actions: [
+                {
+                  action: 'update',
+                  file_path: jasmine.anything(),
+                  content: jasmine.anything(),
+                  encoding: jasmine.anything(),
+                },
+              ],
+              start_branch: 'master',
+            });
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('pushes router to new route', done => {
+        store
+          .dispatch('commit/commitChanges')
+          .then(() => {
+            expect(router.push).toHaveBeenCalledWith(
+              `/project/${store.state.currentProjectId}/blob/${
+                store.getters['commit/newBranchName']
+              }/changed`,
+            );
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('sets last Commit Msg', done => {
+        store
+          .dispatch('commit/commitChanges')
+          .then(() => {
+            expect(store.state.lastCommitMsg).toBe(
+              'Your changes have been committed. Commit <a href="webUrl/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.',
+            );
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('adds commit data to files', done => {
+        store
+          .dispatch('commit/commitChanges')
+          .then(() => {
+            expect(store.state.entries[store.state.openFiles[0].path].lastCommit.message).toBe(
+              'test message',
+            );
+
+            done();
+          })
+          .catch(done.fail);
+      });
+
+      it('resets stores commit actions', done => {
+        store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+
+        store
+          .dispatch('commit/commitChanges')
+          .then(() => {
+            expect(store.state.commit.commitAction).not.toBe(consts.COMMIT_TO_NEW_BRANCH);
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+
+      it('removes all staged files', done => {
+        store
+          .dispatch('commit/commitChanges')
+          .then(() => {
+            expect(store.state.stagedFiles.length).toBe(0);
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+
+      describe('merge request', () => {
+        it('redirects to new merge request page', done => {
+          spyOn(eventHub, '$on');
+
+          store.state.commit.commitAction = '3';
+
+          store
+            .dispatch('commit/commitChanges')
+            .then(() => {
+              expect(urlUtils.visitUrl).toHaveBeenCalledWith(
+                `webUrl/merge_requests/new?merge_request[source_branch]=${
+                  store.getters['commit/newBranchName']
+                }&merge_request[target_branch]=master`,
+              );
+
+              done();
+            })
+            .catch(done.fail);
+        });
+
+        it('resets changed files before redirecting', done => {
+          spyOn(eventHub, '$on');
+
+          store.state.commit.commitAction = '3';
+
+          store
+            .dispatch('commit/commitChanges')
+            .then(() => {
+              expect(store.state.stagedFiles.length).toBe(0);
+
+              done();
+            })
+            .catch(done.fail);
+        });
+      });
+    });
+
+    describe('failed', () => {
+      beforeEach(() => {
+        spyOn(service, 'commit').and.returnValue(
+          Promise.resolve({
+            data: {
+              message: 'failed message',
+            },
+          }),
+        );
+      });
+
+      it('shows failed message', done => {
+        store
+          .dispatch('commit/commitChanges')
+          .then(() => {
+            const alert = document.querySelector('.flash-container');
+
+            expect(alert.textContent.trim()).toBe('failed message');
+
+            done();
+          })
+          .catch(done.fail);
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..55580f046ad54438ae759a1a5cdc0a44eb1bd248
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -0,0 +1,128 @@
+import commitState from '~/ide/stores/modules/commit/state';
+import * as consts from '~/ide/stores/modules/commit/constants';
+import * as getters from '~/ide/stores/modules/commit/getters';
+
+describe('IDE commit module getters', () => {
+  let state;
+
+  beforeEach(() => {
+    state = commitState();
+  });
+
+  describe('discardDraftButtonDisabled', () => {
+    it('returns true when commitMessage is empty', () => {
+      expect(getters.discardDraftButtonDisabled(state)).toBeTruthy();
+    });
+
+    it('returns false when commitMessage is not empty & loading is false', () => {
+      state.commitMessage = 'test';
+      state.submitCommitLoading = false;
+
+      expect(getters.discardDraftButtonDisabled(state)).toBeFalsy();
+    });
+
+    it('returns true when commitMessage is not empty & loading is true', () => {
+      state.commitMessage = 'test';
+      state.submitCommitLoading = true;
+
+      expect(getters.discardDraftButtonDisabled(state)).toBeTruthy();
+    });
+  });
+
+  describe('commitButtonDisabled', () => {
+    const localGetters = {
+      discardDraftButtonDisabled: false,
+    };
+    const rootState = {
+      stagedFiles: ['a'],
+    };
+
+    it('returns false when discardDraftButtonDisabled is false & stagedFiles is not empty', () => {
+      expect(
+        getters.commitButtonDisabled(state, localGetters, rootState),
+      ).toBeFalsy();
+    });
+
+    it('returns true when discardDraftButtonDisabled is false & stagedFiles is empty', () => {
+      rootState.stagedFiles.length = 0;
+
+      expect(
+        getters.commitButtonDisabled(state, localGetters, rootState),
+      ).toBeTruthy();
+    });
+
+    it('returns true when discardDraftButtonDisabled is true', () => {
+      localGetters.discardDraftButtonDisabled = true;
+
+      expect(
+        getters.commitButtonDisabled(state, localGetters, rootState),
+      ).toBeTruthy();
+    });
+
+    it('returns true when discardDraftButtonDisabled is false & changedFiles is not empty', () => {
+      localGetters.discardDraftButtonDisabled = false;
+      rootState.stagedFiles.length = 0;
+
+      expect(
+        getters.commitButtonDisabled(state, localGetters, rootState),
+      ).toBeTruthy();
+    });
+  });
+
+  describe('newBranchName', () => {
+    it('includes username, currentBranchId, patch & random number', () => {
+      gon.current_username = 'username';
+
+      const branch = getters.newBranchName(state, null, {
+        currentBranchId: 'testing',
+      });
+
+      expect(branch).toMatch(/username-testing-patch-\d{5}$/);
+    });
+  });
+
+  describe('branchName', () => {
+    const rootState = {
+      currentBranchId: 'master',
+    };
+    const localGetters = {
+      newBranchName: 'newBranchName',
+    };
+
+    beforeEach(() => {
+      Object.assign(state, {
+        newBranchName: 'state-newBranchName',
+      });
+    });
+
+    it('defualts to currentBranchId', () => {
+      expect(getters.branchName(state, null, rootState)).toBe('master');
+    });
+
+    ['COMMIT_TO_NEW_BRANCH', 'COMMIT_TO_NEW_BRANCH_MR'].forEach(type => {
+      describe(type, () => {
+        beforeEach(() => {
+          Object.assign(state, {
+            commitAction: consts[type],
+          });
+        });
+
+        it('uses newBranchName when not empty', () => {
+          expect(getters.branchName(state, localGetters, rootState)).toBe(
+            'state-newBranchName',
+          );
+        });
+
+        it('uses getters newBranchName when state newBranchName is empty', () => {
+          Object.assign(state, {
+            newBranchName: '',
+          });
+
+          expect(getters.branchName(state, localGetters, rootState)).toBe(
+            'newBranchName',
+          );
+        });
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/modules/commit/mutations_spec.js b/spec/javascripts/ide/stores/modules/commit/mutations_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..5de7a281d34169ff20b502200712821b29d07228
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/commit/mutations_spec.js
@@ -0,0 +1,42 @@
+import commitState from '~/ide/stores/modules/commit/state';
+import mutations from '~/ide/stores/modules/commit/mutations';
+
+describe('IDE commit module mutations', () => {
+  let state;
+
+  beforeEach(() => {
+    state = commitState();
+  });
+
+  describe('UPDATE_COMMIT_MESSAGE', () => {
+    it('updates commitMessage', () => {
+      mutations.UPDATE_COMMIT_MESSAGE(state, 'testing');
+
+      expect(state.commitMessage).toBe('testing');
+    });
+  });
+
+  describe('UPDATE_COMMIT_ACTION', () => {
+    it('updates commitAction', () => {
+      mutations.UPDATE_COMMIT_ACTION(state, 'testing');
+
+      expect(state.commitAction).toBe('testing');
+    });
+  });
+
+  describe('UPDATE_NEW_BRANCH_NAME', () => {
+    it('updates newBranchName', () => {
+      mutations.UPDATE_NEW_BRANCH_NAME(state, 'testing');
+
+      expect(state.newBranchName).toBe('testing');
+    });
+  });
+
+  describe('UPDATE_LOADING', () => {
+    it('updates submitCommitLoading', () => {
+      mutations.UPDATE_LOADING(state, true);
+
+      expect(state.submitCommitLoading).toBeTruthy();
+    });
+  });
+});
diff --git a/spec/javascripts/repo/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js
similarity index 100%
rename from spec/javascripts/repo/stores/mutations/branch_spec.js
rename to spec/javascripts/ide/stores/mutations/branch_spec.js
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6fba934810de5592810077cddf232678d596b42a
--- /dev/null
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -0,0 +1,318 @@
+import mutations from '~/ide/stores/mutations/file';
+import state from '~/ide/stores/state';
+import { file } from '../../helpers';
+
+describe('IDE store file mutations', () => {
+  let localState;
+  let localFile;
+
+  beforeEach(() => {
+    localState = state();
+    localFile = {
+      ...file(),
+      type: 'blob',
+    };
+
+    localState.entries[localFile.path] = localFile;
+  });
+
+  describe('SET_FILE_ACTIVE', () => {
+    it('sets the file active', () => {
+      mutations.SET_FILE_ACTIVE(localState, {
+        path: localFile.path,
+        active: true,
+      });
+
+      expect(localFile.active).toBeTruthy();
+    });
+
+    it('sets pending tab as not active', () => {
+      localState.openFiles.push({
+        ...localFile,
+        pending: true,
+        active: true,
+      });
+
+      mutations.SET_FILE_ACTIVE(localState, {
+        path: localFile.path,
+        active: true,
+      });
+
+      expect(localState.openFiles[0].active).toBe(false);
+    });
+  });
+
+  describe('TOGGLE_FILE_OPEN', () => {
+    beforeEach(() => {
+      mutations.TOGGLE_FILE_OPEN(localState, localFile.path);
+    });
+
+    it('adds into opened files', () => {
+      expect(localFile.opened).toBeTruthy();
+      expect(localState.openFiles.length).toBe(1);
+    });
+
+    it('removes from opened files', () => {
+      mutations.TOGGLE_FILE_OPEN(localState, localFile.path);
+
+      expect(localFile.opened).toBeFalsy();
+      expect(localState.openFiles.length).toBe(0);
+    });
+  });
+
+  describe('SET_FILE_DATA', () => {
+    it('sets extra file data', () => {
+      mutations.SET_FILE_DATA(localState, {
+        data: {
+          blame_path: 'blame',
+          commits_path: 'commits',
+          permalink: 'permalink',
+          raw_path: 'raw',
+          binary: true,
+          render_error: 'render_error',
+        },
+        file: localFile,
+      });
+
+      expect(localFile.blamePath).toBe('blame');
+      expect(localFile.commitsPath).toBe('commits');
+      expect(localFile.permalink).toBe('permalink');
+      expect(localFile.rawPath).toBe('raw');
+      expect(localFile.binary).toBeTruthy();
+      expect(localFile.renderError).toBe('render_error');
+      expect(localFile.raw).toBeNull();
+      expect(localFile.baseRaw).toBeNull();
+    });
+  });
+
+  describe('SET_FILE_RAW_DATA', () => {
+    it('sets raw data', () => {
+      mutations.SET_FILE_RAW_DATA(localState, {
+        file: localFile,
+        raw: 'testing',
+      });
+
+      expect(localFile.raw).toBe('testing');
+    });
+  });
+
+  describe('SET_FILE_BASE_RAW_DATA', () => {
+    it('sets raw data from base branch', () => {
+      mutations.SET_FILE_BASE_RAW_DATA(localState, {
+        file: localFile,
+        baseRaw: 'testing',
+      });
+
+      expect(localFile.baseRaw).toBe('testing');
+    });
+  });
+
+  describe('UPDATE_FILE_CONTENT', () => {
+    beforeEach(() => {
+      localFile.raw = 'test';
+    });
+
+    it('sets content', () => {
+      mutations.UPDATE_FILE_CONTENT(localState, {
+        path: localFile.path,
+        content: 'test',
+      });
+
+      expect(localFile.content).toBe('test');
+    });
+
+    it('sets changed if content does not match raw', () => {
+      mutations.UPDATE_FILE_CONTENT(localState, {
+        path: localFile.path,
+        content: 'testing',
+      });
+
+      expect(localFile.content).toBe('testing');
+      expect(localFile.changed).toBeTruthy();
+    });
+
+    it('sets changed if file is a temp file', () => {
+      localFile.tempFile = true;
+
+      mutations.UPDATE_FILE_CONTENT(localState, {
+        path: localFile.path,
+        content: '',
+      });
+
+      expect(localFile.changed).toBeTruthy();
+    });
+  });
+
+  describe('SET_FILE_MERGE_REQUEST_CHANGE', () => {
+    it('sets file mr change', () => {
+      mutations.SET_FILE_MERGE_REQUEST_CHANGE(localState, {
+        file: localFile,
+        mrChange: { diff: 'ABC' },
+      });
+
+      expect(localFile.mrChange.diff).toBe('ABC');
+    });
+  });
+
+  describe('DISCARD_FILE_CHANGES', () => {
+    beforeEach(() => {
+      localFile.content = 'test';
+      localFile.changed = true;
+    });
+
+    it('resets content and changed', () => {
+      mutations.DISCARD_FILE_CHANGES(localState, localFile.path);
+
+      expect(localFile.content).toBe('');
+      expect(localFile.changed).toBeFalsy();
+    });
+  });
+
+  describe('ADD_FILE_TO_CHANGED', () => {
+    it('adds file into changed files array', () => {
+      mutations.ADD_FILE_TO_CHANGED(localState, localFile.path);
+
+      expect(localState.changedFiles.length).toBe(1);
+    });
+  });
+
+  describe('REMOVE_FILE_FROM_CHANGED', () => {
+    it('removes files from changed files array', () => {
+      localState.changedFiles.push(localFile);
+
+      mutations.REMOVE_FILE_FROM_CHANGED(localState, localFile.path);
+
+      expect(localState.changedFiles.length).toBe(0);
+    });
+  });
+
+  describe('STAGE_CHANGE', () => {
+    it('adds file into stagedFiles array', () => {
+      mutations.STAGE_CHANGE(localState, localFile.path);
+
+      expect(localState.stagedFiles.length).toBe(1);
+      expect(localState.stagedFiles[0]).toEqual(localFile);
+    });
+
+    it('updates stagedFile if it is already staged', () => {
+      mutations.STAGE_CHANGE(localState, localFile.path);
+
+      localFile.raw = 'testing 123';
+
+      mutations.STAGE_CHANGE(localState, localFile.path);
+
+      expect(localState.stagedFiles.length).toBe(1);
+      expect(localState.stagedFiles[0].raw).toEqual('testing 123');
+    });
+  });
+
+  describe('UNSTAGE_CHANGE', () => {
+    let f;
+
+    beforeEach(() => {
+      f = {
+        ...file(),
+        type: 'blob',
+        staged: true,
+      };
+
+      localState.stagedFiles.push(f);
+      localState.changedFiles.push(f);
+      localState.entries[f.path] = f;
+    });
+
+    it('removes from stagedFiles array', () => {
+      mutations.UNSTAGE_CHANGE(localState, f.path);
+
+      expect(localState.stagedFiles.length).toBe(0);
+      expect(localState.changedFiles.length).toBe(1);
+    });
+  });
+
+  describe('TOGGLE_FILE_CHANGED', () => {
+    it('updates file changed status', () => {
+      mutations.TOGGLE_FILE_CHANGED(localState, {
+        file: localFile,
+        changed: true,
+      });
+
+      expect(localFile.changed).toBeTruthy();
+    });
+  });
+
+  describe('SET_FILE_VIEWMODE', () => {
+    it('updates file view mode', () => {
+      mutations.SET_FILE_VIEWMODE(localState, {
+        file: localFile,
+        viewMode: 'preview',
+      });
+
+      expect(localFile.viewMode).toBe('preview');
+    });
+  });
+
+  describe('ADD_PENDING_TAB', () => {
+    beforeEach(() => {
+      const f = {
+        ...file('openFile'),
+        path: 'openFile',
+        active: true,
+        opened: true,
+      };
+
+      localState.entries[f.path] = f;
+      localState.openFiles.push(f);
+    });
+
+    it('adds file into openFiles as pending', () => {
+      mutations.ADD_PENDING_TAB(localState, { file: localFile });
+
+      expect(localState.openFiles.length).toBe(2);
+      expect(localState.openFiles[1].pending).toBe(true);
+      expect(localState.openFiles[1].key).toBe(`pending-${localFile.key}`);
+    });
+
+    it('updates open file to pending', () => {
+      mutations.ADD_PENDING_TAB(localState, { file: localState.openFiles[0] });
+
+      expect(localState.openFiles.length).toBe(1);
+    });
+
+    it('updates pending open file to active', () => {
+      localState.openFiles.push({
+        ...localFile,
+        pending: true,
+      });
+
+      mutations.ADD_PENDING_TAB(localState, { file: localFile });
+
+      expect(localState.openFiles[1].pending).toBe(true);
+      expect(localState.openFiles[1].active).toBe(true);
+    });
+
+    it('sets all openFiles to not active', () => {
+      mutations.ADD_PENDING_TAB(localState, { file: localFile });
+
+      expect(localState.openFiles.length).toBe(2);
+
+      localState.openFiles.forEach(f => {
+        if (f.pending) {
+          expect(f.active).toBe(true);
+        } else {
+          expect(f.active).toBe(false);
+        }
+      });
+    });
+  });
+
+  describe('REMOVE_PENDING_TAB', () => {
+    it('removes pending tab from openFiles', () => {
+      localFile.key = 'testing';
+      localState.openFiles.push(localFile);
+
+      mutations.REMOVE_PENDING_TAB(localState, localFile);
+
+      expect(localState.openFiles.length).toBe(0);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/mutations/merge_request_spec.js b/spec/javascripts/ide/stores/mutations/merge_request_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f724bf464f583b042eb7d161083d17479c1e8e85
--- /dev/null
+++ b/spec/javascripts/ide/stores/mutations/merge_request_spec.js
@@ -0,0 +1,65 @@
+import mutations from '~/ide/stores/mutations/merge_request';
+import state from '~/ide/stores/state';
+
+describe('IDE store merge request mutations', () => {
+  let localState;
+
+  beforeEach(() => {
+    localState = state();
+    localState.projects = { abcproject: { mergeRequests: {} } };
+
+    mutations.SET_MERGE_REQUEST(localState, {
+      projectPath: 'abcproject',
+      mergeRequestId: 1,
+      mergeRequest: {
+        title: 'mr',
+      },
+    });
+  });
+
+  describe('SET_CURRENT_MERGE_REQUEST', () => {
+    it('sets current merge request', () => {
+      mutations.SET_CURRENT_MERGE_REQUEST(localState, 2);
+
+      expect(localState.currentMergeRequestId).toBe(2);
+    });
+  });
+
+  describe('SET_MERGE_REQUEST', () => {
+    it('setsmerge request data', () => {
+      const newMr = localState.projects.abcproject.mergeRequests[1];
+
+      expect(newMr.title).toBe('mr');
+      expect(newMr.active).toBeTruthy();
+    });
+  });
+
+  describe('SET_MERGE_REQUEST_CHANGES', () => {
+    it('sets merge request changes', () => {
+      mutations.SET_MERGE_REQUEST_CHANGES(localState, {
+        projectPath: 'abcproject',
+        mergeRequestId: 1,
+        changes: {
+          diff: 'abc',
+        },
+      });
+
+      const newMr = localState.projects.abcproject.mergeRequests[1];
+      expect(newMr.changes.diff).toBe('abc');
+    });
+  });
+
+  describe('SET_MERGE_REQUEST_VERSIONS', () => {
+    it('sets merge request versions', () => {
+      mutations.SET_MERGE_REQUEST_VERSIONS(localState, {
+        projectPath: 'abcproject',
+        mergeRequestId: 1,
+        versions: [{ id: 123 }],
+      });
+
+      const newMr = localState.projects.abcproject.mergeRequests[1];
+      expect(newMr.versions.length).toBe(1);
+      expect(newMr.versions[0].id).toBe(123);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/mutations/tree_spec.js b/spec/javascripts/ide/stores/mutations/tree_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..67e9f7509da839ae25b27cb69a4f98d2900a58e5
--- /dev/null
+++ b/spec/javascripts/ide/stores/mutations/tree_spec.js
@@ -0,0 +1,79 @@
+import mutations from '~/ide/stores/mutations/tree';
+import state from '~/ide/stores/state';
+import { file } from '../../helpers';
+
+describe('Multi-file store tree mutations', () => {
+  let localState;
+  let localTree;
+
+  beforeEach(() => {
+    localState = state();
+    localTree = file();
+
+    localState.entries[localTree.path] = localTree;
+  });
+
+  describe('TOGGLE_TREE_OPEN', () => {
+    it('toggles tree open', () => {
+      mutations.TOGGLE_TREE_OPEN(localState, localTree.path);
+
+      expect(localTree.opened).toBeTruthy();
+
+      mutations.TOGGLE_TREE_OPEN(localState, localTree.path);
+
+      expect(localTree.opened).toBeFalsy();
+    });
+  });
+
+  describe('SET_DIRECTORY_DATA', () => {
+    const data = [
+      {
+        name: 'tree',
+      },
+      {
+        name: 'submodule',
+      },
+      {
+        name: 'blob',
+      },
+    ];
+
+    it('adds directory data', () => {
+      localState.trees['project/master'] = {
+        tree: [],
+      };
+
+      mutations.SET_DIRECTORY_DATA(localState, {
+        data,
+        treePath: 'project/master',
+      });
+
+      const tree = localState.trees['project/master'];
+
+      expect(tree.tree.length).toBe(3);
+      expect(tree.tree[0].name).toBe('tree');
+      expect(tree.tree[1].name).toBe('submodule');
+      expect(tree.tree[2].name).toBe('blob');
+    });
+
+    it('keeps loading state', () => {
+      mutations.CREATE_TREE(localState, { treePath: 'project/master' });
+      mutations.SET_DIRECTORY_DATA(localState, {
+        data,
+        treePath: 'project/master',
+      });
+
+      expect(localState.trees['project/master'].loading).toBe(true);
+    });
+  });
+
+  describe('REMOVE_ALL_CHANGES_FILES', () => {
+    it('removes all files from changedFiles state', () => {
+      localState.changedFiles.push(file('REMOVE_ALL_CHANGES_FILES'));
+
+      mutations.REMOVE_ALL_CHANGES_FILES(localState);
+
+      expect(localState.changedFiles.length).toBe(0);
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..26e7ed4535e08204e5f11664b0d6b742aa230454
--- /dev/null
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -0,0 +1,89 @@
+import mutations from '~/ide/stores/mutations';
+import state from '~/ide/stores/state';
+import { file } from '../helpers';
+
+describe('Multi-file store mutations', () => {
+  let localState;
+  let entry;
+
+  beforeEach(() => {
+    localState = state();
+    entry = file();
+
+    localState.entries[entry.path] = entry;
+  });
+
+  describe('SET_INITIAL_DATA', () => {
+    it('sets all initial data', () => {
+      mutations.SET_INITIAL_DATA(localState, {
+        test: 'test',
+      });
+
+      expect(localState.test).toBe('test');
+    });
+  });
+
+  describe('TOGGLE_LOADING', () => {
+    it('toggles loading of entry', () => {
+      mutations.TOGGLE_LOADING(localState, { entry });
+
+      expect(entry.loading).toBeTruthy();
+
+      mutations.TOGGLE_LOADING(localState, { entry });
+
+      expect(entry.loading).toBeFalsy();
+    });
+
+    it('toggles loading of entry and sets specific value', () => {
+      mutations.TOGGLE_LOADING(localState, { entry });
+
+      expect(entry.loading).toBeTruthy();
+
+      mutations.TOGGLE_LOADING(localState, { entry, forceValue: true });
+
+      expect(entry.loading).toBeTruthy();
+    });
+  });
+
+  describe('SET_LEFT_PANEL_COLLAPSED', () => {
+    it('sets left panel collapsed', () => {
+      mutations.SET_LEFT_PANEL_COLLAPSED(localState, true);
+
+      expect(localState.leftPanelCollapsed).toBeTruthy();
+
+      mutations.SET_LEFT_PANEL_COLLAPSED(localState, false);
+
+      expect(localState.leftPanelCollapsed).toBeFalsy();
+    });
+  });
+
+  describe('SET_RIGHT_PANEL_COLLAPSED', () => {
+    it('sets right panel collapsed', () => {
+      mutations.SET_RIGHT_PANEL_COLLAPSED(localState, true);
+
+      expect(localState.rightPanelCollapsed).toBeTruthy();
+
+      mutations.SET_RIGHT_PANEL_COLLAPSED(localState, false);
+
+      expect(localState.rightPanelCollapsed).toBeFalsy();
+    });
+  });
+
+  describe('CLEAR_STAGED_CHANGES', () => {
+    it('clears stagedFiles array', () => {
+      localState.stagedFiles.push('a');
+
+      mutations.CLEAR_STAGED_CHANGES(localState);
+
+      expect(localState.stagedFiles.length).toBe(0);
+    });
+  });
+
+  describe('UPDATE_VIEWER', () => {
+    it('sets viewer state', () => {
+      mutations.UPDATE_VIEWER(localState, 'diff');
+
+      expect(localState.viewer).toBe('diff');
+    });
+  });
+});
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f38ac6dd82fbb18525f1142aacf53d2fe17e3282
--- /dev/null
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -0,0 +1,66 @@
+import * as utils from '~/ide/stores/utils';
+
+describe('Multi-file store utils', () => {
+  describe('setPageTitle', () => {
+    it('sets the document page title', () => {
+      utils.setPageTitle('test');
+
+      expect(document.title).toBe('test');
+    });
+  });
+
+  describe('findIndexOfFile', () => {
+    let localState;
+
+    beforeEach(() => {
+      localState = [
+        {
+          path: '1',
+        },
+        {
+          path: '2',
+        },
+      ];
+    });
+
+    it('finds in the index of an entry by path', () => {
+      const index = utils.findIndexOfFile(localState, {
+        path: '2',
+      });
+
+      expect(index).toBe(1);
+    });
+  });
+
+  describe('findEntry', () => {
+    let localState;
+
+    beforeEach(() => {
+      localState = {
+        tree: [
+          {
+            type: 'tree',
+            name: 'test',
+          },
+          {
+            type: 'blob',
+            name: 'file',
+          },
+        ],
+      };
+    });
+
+    it('returns an entry found by name', () => {
+      const foundEntry = utils.findEntry(localState.tree, 'tree', 'test');
+
+      expect(foundEntry.type).toBe('tree');
+      expect(foundEntry.name).toBe('test');
+    });
+
+    it('returns undefined when no entry found', () => {
+      const foundEntry = utils.findEntry(localState.tree, 'blob', 'test');
+
+      expect(foundEntry).toBeUndefined();
+    });
+  });
+});
diff --git a/spec/javascripts/importer_status_spec.js b/spec/javascripts/importer_status_spec.js
index 71a2cd51f6349ca8c81f5a6b44360abc30c74030..0575d02886da5581674f831cf992cb3ed693736a 100644
--- a/spec/javascripts/importer_status_spec.js
+++ b/spec/javascripts/importer_status_spec.js
@@ -29,7 +29,10 @@ describe('Importer Status', () => {
       `);
       spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {});
       spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {});
-      instance = new ImporterStatus('', importUrl);
+      instance = new ImporterStatus({
+        jobsUrl: '',
+        importUrl,
+      });
     });
 
     it('sets table row to active after post request', (done) => {
@@ -65,7 +68,9 @@ describe('Importer Status', () => {
 
       spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {});
       spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {});
-      instance = new ImporterStatus(jobsUrl);
+      instance = new ImporterStatus({
+        jobsUrl,
+      });
     });
 
     function setupMock(importStatus) {
@@ -86,17 +91,17 @@ describe('Importer Status', () => {
 
     it('sets the job status to done', (done) => {
       setupMock('finished');
-      expectJobStatus(done, 'done');
+      expectJobStatus(done, 'Done');
     });
 
     it('sets the job status to scheduled', (done) => {
       setupMock('scheduled');
-      expectJobStatus(done, 'scheduled');
+      expectJobStatus(done, 'Scheduled');
     });
 
     it('sets the job status to started', (done) => {
       setupMock('started');
-      expectJobStatus(done, 'started');
+      expectJobStatus(done, 'Started');
     });
 
     it('sets the job status to custom status', (done) => {
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index d0fba908e348c1c8dbc8964a4746a05cc4d0d66f..050b1f2074eb7d5f798ce607ba1d357c60eea192 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import MockAdaptor from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import IntegrationSettingsForm from '~/integrations/integration_settings_form';
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index d53ffecbd35694acb3e835f1eac06782e47f9dad..57bf746f080597db64c2ccb43ff29aca316d9564 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import MockAdaptor from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import IssuableIndex from '~/issuable_index';
diff --git a/spec/javascripts/issuable_time_tracker_spec.js b/spec/javascripts/issuable_time_tracker_spec.js
index 365e9fe6a4bafd2c7d8b78bbac0f4f36b61a749c..ba9040524b13ffcc80cec8f49253e9a9ac26946e 100644
--- a/spec/javascripts/issuable_time_tracker_spec.js
+++ b/spec/javascripts/issuable_time_tracker_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-unused-vars, space-before-function-paren, func-call-spacing, no-spaced-func, semi, max-len, quotes, space-infix-ops, padded-blocks */
 
+import $ from 'jquery';
 import Vue from 'vue';
 
 import timeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 584db6c6632c6dc902da6cc5b0dcbad2a86ceab7..d5a87b5ce20efe456d5840529b3e603b06786554 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -1,8 +1,7 @@
 import Vue from 'vue';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
-import '~/render_math';
-import '~/render_gfm';
+import '~/behaviors/markdown/render_gfm';
 import * as urlUtils from '~/lib/utils/url_utility';
 import issuableApp from '~/issue_show/components/app.vue';
 import eventHub from '~/issue_show/event_hub';
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index ff7f99eec14fd91d0e3c25cb7cc66ec9bf30846f..d96151a8a3a514a1e8d9f83cb1346e54257038d1 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import descriptionComponent from '~/issue_show/components/description.vue';
 import * as taskList from '~/task_list';
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 177962ecf8248bec93a58573f05a0169c83be81c..f37426a72d4c287e7f9934820fef03a002d205c5 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,4 +1,6 @@
 /* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
+
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import Issue from '~/issue';
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index b4599688c6d74b868e7f633384dde410cfe9f5d5..c6bbacf237ac55a8c479bb40aae082afe8ac78c9 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import { numberToHumanSize } from '~/lib/utils/number_utils';
diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js
index 0961605ce5c2b8741614883d1481cf25cdc8bc48..4f861c39d3f479614408109002c644a1af530d20 100644
--- a/spec/javascripts/jobs/header_spec.js
+++ b/spec/javascripts/jobs/header_spec.js
@@ -36,14 +36,28 @@ describe('Job details header', () => {
       },
       isLoading: false,
     };
-
-    vm = mountComponent(HeaderComponent, props);
   });
 
   afterEach(() => {
     vm.$destroy();
   });
 
+  describe('job reason', () => {
+    it('should not render the reason when reason is absent', () => {
+      vm = mountComponent(HeaderComponent, props);
+
+      expect(vm.shouldRenderReason).toBe(false);
+    });
+
+    it('should render the reason when reason is present', () => {
+      props.job.callout_message = 'There is an unknown failure, please try again';
+
+      vm = mountComponent(HeaderComponent, props);
+
+      expect(vm.shouldRenderReason).toBe(true);
+    });
+  });
+
   describe('triggered job', () => {
     beforeEach(() => {
       vm = mountComponent(HeaderComponent, props);
@@ -51,14 +65,17 @@ describe('Job details header', () => {
 
     it('should render provided job information', () => {
       expect(
-        vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+        vm.$el
+          .querySelector('.header-main-content')
+          .textContent.replace(/\s+/g, ' ')
+          .trim(),
       ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
     });
 
     it('should render new issue link', () => {
-      expect(
-        vm.$el.querySelector('.js-new-issue').getAttribute('href'),
-      ).toEqual(props.job.new_issue_path);
+      expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+        props.job.new_issue_path,
+      );
     });
   });
 
@@ -68,7 +85,10 @@ describe('Job details header', () => {
       vm = mountComponent(HeaderComponent, props);
 
       expect(
-        vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+        vm.$el
+          .querySelector('.header-main-content')
+          .textContent.replace(/\s+/g, ' ')
+          .trim(),
       ).toEqual('failed Job #123 created 3 weeks ago by Foo');
     });
   });
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 43589d54be44cf5afa5632469f0f3cdedb6874a9..25ca8eb6c0b429155a8d45ca35e7f820a1e5eb5a 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -115,6 +115,10 @@ export default {
       commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
     },
   },
+  metadata: {
+    timeout_human_readable: '1m 40s',
+    timeout_source: 'runner',
+  },
   merge_request: {
     iid: 2,
     path: '/root/ci-mock/merge_requests/2',
diff --git a/spec/javascripts/jobs/sidebar_detail_row_spec.js b/spec/javascripts/jobs/sidebar_detail_row_spec.js
index 3ac65709c4a4a76143adbc92623b8a51ff6b7f6c..e6bfb0c4adcabcec0c916c194e0f630ca7b3e45d 100644
--- a/spec/javascripts/jobs/sidebar_detail_row_spec.js
+++ b/spec/javascripts/jobs/sidebar_detail_row_spec.js
@@ -37,4 +37,25 @@ describe('Sidebar detail row', () => {
       vm.$el.textContent.replace(/\s+/g, ' ').trim(),
     ).toEqual('this is the title: this is the value');
   });
+
+  describe('when helpUrl not provided', () => {
+    it('should not render help', () => {
+      expect(vm.$el.querySelector('.help-button')).toBeNull();
+    });
+  });
+
+  describe('when helpUrl provided', () => {
+    beforeEach(() => {
+      vm = new SidebarDetailRow({
+        propsData: {
+          helpUrl: 'help url',
+          value: 'foo',
+        },
+      }).$mount();
+    });
+
+    it('should render help', () => {
+      expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url');
+    });
+  });
 });
diff --git a/spec/javascripts/jobs/sidebar_details_block_spec.js b/spec/javascripts/jobs/sidebar_details_block_spec.js
index 95532ef5382521663071e7f00a6259a14a0fa6c7..6b397c22fb914995ac498112afd9a23a29d8382d 100644
--- a/spec/javascripts/jobs/sidebar_details_block_spec.js
+++ b/spec/javascripts/jobs/sidebar_details_block_spec.js
@@ -31,10 +31,25 @@ describe('Sidebar details block', () => {
     });
   });
 
+  describe("when user can't retry", () => {
+    it('should not render a retry button', () => {
+      vm = new SidebarComponent({
+        propsData: {
+          job: {},
+          canUserRetry: false,
+          isLoading: true,
+        },
+      }).$mount();
+
+      expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
+    });
+  });
+
   beforeEach(() => {
     vm = new SidebarComponent({
       propsData: {
         job,
+        canUserRetry: true,
         isLoading: false,
       },
     }).$mount();
@@ -42,7 +57,9 @@ describe('Sidebar details block', () => {
 
   describe('actions', () => {
     it('should render link to new issue', () => {
-      expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(job.new_issue_path);
+      expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+        job.new_issue_path,
+      );
       expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue');
     });
 
@@ -57,55 +74,49 @@ describe('Sidebar details block', () => {
 
   describe('information', () => {
     it('should render merge request link', () => {
-      expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-mr')),
-      ).toEqual('Merge Request: !2');
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-mr'))).toEqual('Merge Request: !2');
 
-      expect(
-        vm.$el.querySelector('.js-job-mr a').getAttribute('href'),
-      ).toEqual(job.merge_request.path);
+      expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual(
+        job.merge_request.path,
+      );
     });
 
     it('should render job duration', () => {
-      expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-duration')),
-      ).toEqual('Duration: 6 seconds');
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-duration'))).toEqual(
+        'Duration: 6 seconds',
+      );
     });
 
     it('should render erased date', () => {
-      expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-erased')),
-      ).toEqual('Erased: 3 weeks ago');
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-erased'))).toEqual('Erased: 3 weeks ago');
     });
 
     it('should render finished date', () => {
-      expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-finished')),
-      ).toEqual('Finished: 3 weeks ago');
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-finished'))).toEqual(
+        'Finished: 3 weeks ago',
+      );
     });
 
     it('should render queued date', () => {
-      expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-queued')),
-      ).toEqual('Queued: 9 seconds');
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-queued'))).toEqual('Queued: 9 seconds');
     });
 
     it('should render runner ID', () => {
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual('Runner: #1');
+    });
+
+    it('should render timeout information', () => {
       expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-runner')),
-      ).toEqual('Runner: #1');
+        trimWhitespace(vm.$el.querySelector('.js-job-timeout')),
+      ).toEqual('Timeout: 1m 40s (from runner)');
     });
 
     it('should render coverage', () => {
-      expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-coverage')),
-      ).toEqual('Coverage: 20%');
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-coverage'))).toEqual('Coverage: 20%');
     });
 
     it('should render tags', () => {
-      expect(
-        trimWhitespace(vm.$el.querySelector('.js-job-tags')),
-      ).toEqual('Tags: tag');
+      expect(trimWhitespace(vm.$el.querySelector('.js-job-tags'))).toEqual('Tags: tag');
     });
   });
 });
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index 7d992f62f64c4019e8f47ec098b0451accd0b5d0..5aafb6ad8f08e8acf138c4d19f1f62fea614b9f8 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-new */
+
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import IssuableContext from '~/issuable_context';
diff --git a/spec/javascripts/labels_select_spec.js b/spec/javascripts/labels_select_spec.js
index b8f7b1dc85556cef19cc882290e687072631707e..a2b89c0aef5df2e0336a42411eea2a9fac1d8f7a 100644
--- a/spec/javascripts/labels_select_spec.js
+++ b/spec/javascripts/labels_select_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import LabelsSelect from '~/labels_select';
 
 const mockUrl = '/foo/bar/url';
diff --git a/spec/javascripts/lib/utils/text_markdown_spec.js b/spec/javascripts/lib/utils/text_markdown_spec.js
index a95a7e2a5be79f1b962210bd2d607915a139d464..ca0e7c395a0998b57e0ccef9db1eb8393a5a62f5 100644
--- a/spec/javascripts/lib/utils/text_markdown_spec.js
+++ b/spec/javascripts/lib/utils/text_markdown_spec.js
@@ -1,4 +1,4 @@
-import textUtils from '~/lib/utils/text_markdown';
+import { insertMarkdownText } from '~/lib/utils/text_markdown';
 
 describe('init markdown', () => {
   let textArea;
@@ -21,7 +21,7 @@ describe('init markdown', () => {
       textArea.selectionStart = 0;
       textArea.selectionEnd = 0;
 
-      textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+      insertMarkdownText(textArea, textArea.value, '*', null, '', false);
 
       expect(textArea.value).toEqual(`${initialValue}* `);
     });
@@ -32,7 +32,7 @@ describe('init markdown', () => {
       textArea.value = initialValue;
       textArea.setSelectionRange(initialValue.length, initialValue.length);
 
-      textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+      insertMarkdownText(textArea, textArea.value, '*', null, '', false);
 
       expect(textArea.value).toEqual(`${initialValue}\n* `);
     });
@@ -43,7 +43,7 @@ describe('init markdown', () => {
       textArea.value = initialValue;
       textArea.setSelectionRange(initialValue.length, initialValue.length);
 
-      textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+      insertMarkdownText(textArea, textArea.value, '*', null, '', false);
 
       expect(textArea.value).toEqual(`${initialValue}* `);
     });
@@ -54,7 +54,7 @@ describe('init markdown', () => {
       textArea.value = initialValue;
       textArea.setSelectionRange(initialValue.length, initialValue.length);
 
-      textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+      insertMarkdownText(textArea, textArea.value, '*', null, '', false);
 
       expect(textArea.value).toEqual(`${initialValue}* `);
     });
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index e57a55fa71ab1af4c18cd6696a57520168516981..ae00fb767149fe1edf6d77279c0d8d4f4f573689 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -65,11 +65,15 @@ describe('text_utility', () => {
 
   describe('stripHtml', () => {
     it('replaces html tag with the default replacement', () => {
-      expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.');
+      expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual(
+        'This is a text with html.',
+      );
     });
 
     it('replaces html tags with the provided replacement', () => {
-      expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with  html .');
+      expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual(
+        'This is a text with  html .',
+      );
     });
   });
 
@@ -78,4 +82,10 @@ describe('text_utility', () => {
       expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase');
     });
   });
+
+  describe('convertToSentenceCase', () => {
+    it('converts Sentence Case to Sentence case', () => {
+      expect(textUtils.convertToSentenceCase('Hello World')).toBe('Hello world');
+    });
+  });
 });
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index 89f4b85541d457d36d9f9b46aedb939821b6d8d0..d2bdc9e160c0854f1935eb0135e8b824f1e09d33 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */
 
+import $ from 'jquery';
 import LineHighlighter from '~/line_highlighter';
 
 (function() {
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
new file mode 100644
index 0000000000000000000000000000000000000000..7cc5e753c2222ef87fc4e8f5ee77c4ab727fd10e
--- /dev/null
+++ b/spec/javascripts/matchers.js
@@ -0,0 +1,35 @@
+export default {
+  toHaveSpriteIcon: () => ({
+    compare(element, iconName) {
+      if (!iconName) {
+        throw new Error('toHaveSpriteIcon is missing iconName argument!');
+      }
+
+      if (!(element instanceof HTMLElement)) {
+        throw new Error(`${element} is not a DOM element!`);
+      }
+
+      const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
+      const matchingIcon = iconReferences.find(reference => reference.getAttribute('xlink:href').endsWith(`#${iconName}`));
+      const result = {
+        pass: !!matchingIcon,
+      };
+
+      if (result.pass) {
+        result.message = `${element.outerHTML} contains the sprite icon "${iconName}"!`;
+      } else {
+        result.message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
+
+        const existingIcons = iconReferences.map((reference) => {
+          const iconUrl = reference.getAttribute('xlink:href');
+          return `"${iconUrl.replace(/^.+#/, '')}"`;
+        });
+        if (existingIcons.length > 0) {
+          result.message += ` (only found ${existingIcons.join(',')})`;
+        }
+      }
+
+      return result;
+    },
+  }),
+};
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
index 0d16b23302f6c30d4a2c8ccd0d4c2e64cbe767a6..dc9dc4d424911bb35159731e38adf206e080c684 100644
--- a/spec/javascripts/merge_request_notes_spec.js
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -1,9 +1,9 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import 'autosize';
 import '~/gl_form';
 import '~/lib/utils/text_utility';
-import '~/render_gfm';
-import '~/render_math';
+import '~/behaviors/markdown/render_gfm';
 import Notes from '~/notes';
 
 const upArrowKeyCode = 38;
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index bdfd16ac99573cf20c52b3f9d3b1b897aaf510f1..74ceff76d373b6efa9d7ddfee2a6a6b2a8635aef 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -1,4 +1,6 @@
 /* eslint-disable space-before-function-paren, no-return-assign */
+
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import MergeRequest from '~/merge_request';
@@ -27,7 +29,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
       });
 
       it('modifies the Markdown field', function() {
-        spyOn(jQuery, 'ajax').and.stub();
+        spyOn($, 'ajax').and.stub();
         const changeEvent = document.createEvent('HTMLEvents');
         changeEvent.initEvent('change', true, true);
         $('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
@@ -48,7 +50,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
 
     describe('class constructor', () => {
       beforeEach(() => {
-        spyOn(jQuery, 'ajax').and.stub();
+        spyOn($, 'ajax').and.stub();
       });
 
       it('calls .initCloseReopenReport', () => {
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index fda24db98b495d1fccb032ba9e62b2755ec3bf2b..79c8cf0ba32e5822e700a72cd5f717a703dfc560 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,4 +1,6 @@
 /* eslint-disable no-var, comma-dangle, object-shorthand */
+
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import * as urlUtils from '~/lib/utils/url_utility';
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index 6fa6f44f953206b0eb64ee64f0067f22d1ca7b0d..009b3fd75b7145dceeeb932e4dfe49325bdbf4a9 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-new */
 
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index eb8f6bbe50d286043ae6e623d5087d85e3ebf99d..eba6dcf47c5416dba940266fa09fe5b7b0d05d8c 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -5,24 +5,36 @@ import axios from '~/lib/utils/axios_utils';
 import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data';
 
 describe('Dashboard', () => {
-  const fixtureName = 'environments/metrics/metrics.html.raw';
   let DashboardComponent;
-  let component;
-  preloadFixtures(fixtureName);
+
+  const propsData = {
+    hasMetrics: false,
+    documentationPath: '/path/to/docs',
+    settingsPath: '/path/to/settings',
+    clustersPath: '/path/to/clusters',
+    tagsPath: '/path/to/tags',
+    projectPath: '/path/to/project',
+    metricsEndpoint: mockApiEndpoint,
+    deploymentEndpoint: null,
+    emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
+    emptyLoadingSvgPath: '/path/to/loading.svg',
+    emptyNoDataSvgPath: '/path/to/no-data.svg',
+    emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
+  };
 
   beforeEach(() => {
-    loadFixtures(fixtureName);
+    setFixtures('<div class="prometheus-graphs"></div>');
     DashboardComponent = Vue.extend(Dashboard);
   });
 
   describe('no metrics are available yet', () => {
     it('shows a getting started empty state when no metrics are present', () => {
-      component = new DashboardComponent({
-        el: document.querySelector('#prometheus-graphs'),
+      const component = new DashboardComponent({
+        el: document.querySelector('.prometheus-graphs'),
+        propsData,
       });
 
-      component.$mount();
-      expect(component.$el.querySelector('#prometheus-graphs')).toBe(null);
+      expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
       expect(component.state).toEqual('gettingStarted');
     });
   });
@@ -30,11 +42,8 @@ describe('Dashboard', () => {
   describe('requests information to the server', () => {
     let mock;
     beforeEach(() => {
-      document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true');
       mock = new MockAdapter(axios);
-      mock.onGet(mockApiEndpoint).reply(200, {
-        metricsGroupsAPIResponse,
-      });
+      mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
     });
 
     afterEach(() => {
@@ -42,14 +51,43 @@ describe('Dashboard', () => {
     });
 
     it('shows up a loading state', (done) => {
-      component = new DashboardComponent({
-        el: document.querySelector('#prometheus-graphs'),
+      const component = new DashboardComponent({
+        el: document.querySelector('.prometheus-graphs'),
+        propsData: { ...propsData, hasMetrics: true },
       });
-      component.$mount();
+
       Vue.nextTick(() => {
         expect(component.state).toEqual('loading');
         done();
       });
     });
+
+    it('hides the legend when showLegend is false', (done) => {
+      const component = new DashboardComponent({
+        el: document.querySelector('.prometheus-graphs'),
+        propsData: { ...propsData, hasMetrics: true, showLegend: false },
+      });
+
+      setTimeout(() => {
+        expect(component.showEmptyState).toEqual(false);
+        expect(component.$el.querySelector('.legend-group')).toEqual(null);
+        expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
+        done();
+      });
+    });
+
+    it('hides the group panels when showPanels is false', (done) => {
+      const component = new DashboardComponent({
+        el: document.querySelector('.prometheus-graphs'),
+        propsData: { ...propsData, hasMetrics: true, showPanels: false },
+      });
+
+      setTimeout(() => {
+        expect(component.showEmptyState).toEqual(false);
+        expect(component.$el.querySelector('.prometheus-panel')).toEqual(null);
+        expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
+        done();
+      });
+    });
   });
 });
diff --git a/spec/javascripts/monitoring/dashboard_state_spec.js b/spec/javascripts/monitoring/dashboard_state_spec.js
index df3198dd3e217c72c51715d056b972a6b0d217d6..b4c5f4baa78cc8d43ac34db4afccc00980189779 100644
--- a/spec/javascripts/monitoring/dashboard_state_spec.js
+++ b/spec/javascripts/monitoring/dashboard_state_spec.js
@@ -2,13 +2,22 @@ import Vue from 'vue';
 import EmptyState from '~/monitoring/components/empty_state.vue';
 import { statePaths } from './mock_data';
 
-const createComponent = (propsData) => {
+function createComponent(props) {
   const Component = Vue.extend(EmptyState);
 
   return new Component({
-    propsData,
+    propsData: {
+      ...props,
+      settingsPath: statePaths.settingsPath,
+      clustersPath: statePaths.clustersPath,
+      documentationPath: statePaths.documentationPath,
+      emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
+      emptyLoadingSvgPath: '/path/to/loading.svg',
+      emptyNoDataSvgPath: '/path/to/no-data.svg',
+      emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
+    },
   }).$mount();
-};
+}
 
 function getTextFromNode(component, selector) {
   return component.$el.querySelector(selector).firstChild.nodeValue.trim();
@@ -19,11 +28,6 @@ describe('EmptyState', () => {
     it('currentState', () => {
       const component = createComponent({
         selectedState: 'gettingStarted',
-        settingsPath: statePaths.settingsPath,
-        documentationPath: statePaths.documentationPath,
-        emptyGettingStartedSvgPath: 'foo',
-        emptyLoadingSvgPath: 'foo',
-        emptyUnableToConnectSvgPath: 'foo',
       });
 
       expect(component.currentState).toBe(component.states.gettingStarted);
@@ -32,11 +36,6 @@ describe('EmptyState', () => {
     it('showButtonDescription returns a description with a link for the unableToConnect state', () => {
       const component = createComponent({
         selectedState: 'unableToConnect',
-        settingsPath: statePaths.settingsPath,
-        documentationPath: statePaths.documentationPath,
-        emptyGettingStartedSvgPath: 'foo',
-        emptyLoadingSvgPath: 'foo',
-        emptyUnableToConnectSvgPath: 'foo',
       });
 
       expect(component.showButtonDescription).toEqual(true);
@@ -45,11 +44,6 @@ describe('EmptyState', () => {
     it('showButtonDescription returns the description without a link for any other state', () => {
       const component = createComponent({
         selectedState: 'loading',
-        settingsPath: statePaths.settingsPath,
-        documentationPath: statePaths.documentationPath,
-        emptyGettingStartedSvgPath: 'foo',
-        emptyLoadingSvgPath: 'foo',
-        emptyUnableToConnectSvgPath: 'foo',
       });
 
       expect(component.showButtonDescription).toEqual(false);
@@ -59,12 +53,6 @@ describe('EmptyState', () => {
   it('should show the gettingStarted state', () => {
     const component = createComponent({
       selectedState: 'gettingStarted',
-      settingsPath: statePaths.settingsPath,
-      clustersPath: statePaths.clustersPath,
-      documentationPath: statePaths.documentationPath,
-      emptyGettingStartedSvgPath: 'foo',
-      emptyLoadingSvgPath: 'foo',
-      emptyUnableToConnectSvgPath: 'foo',
     });
 
     expect(component.$el.querySelector('svg')).toBeDefined();
@@ -76,11 +64,6 @@ describe('EmptyState', () => {
   it('should show the loading state', () => {
     const component = createComponent({
       selectedState: 'loading',
-      settingsPath: statePaths.settingsPath,
-      documentationPath: statePaths.documentationPath,
-      emptyGettingStartedSvgPath: 'foo',
-      emptyLoadingSvgPath: 'foo',
-      emptyUnableToConnectSvgPath: 'foo',
     });
 
     expect(component.$el.querySelector('svg')).toBeDefined();
@@ -92,11 +75,6 @@ describe('EmptyState', () => {
   it('should show the unableToConnect state', () => {
     const component = createComponent({
       selectedState: 'unableToConnect',
-      settingsPath: statePaths.settingsPath,
-      documentationPath: statePaths.documentationPath,
-      emptyGettingStartedSvgPath: 'foo',
-      emptyLoadingSvgPath: 'foo',
-      emptyUnableToConnectSvgPath: 'foo',
     });
 
     expect(component.$el.querySelector('svg')).toBeDefined();
diff --git a/spec/javascripts/monitoring/graph/axis_spec.js b/spec/javascripts/monitoring/graph/axis_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7adba006370cc14b846db96d449b96b8b51e0fa
--- /dev/null
+++ b/spec/javascripts/monitoring/graph/axis_spec.js
@@ -0,0 +1,65 @@
+import Vue from 'vue';
+import GraphAxis from '~/monitoring/components/graph/axis.vue';
+import measurements from '~/monitoring/utils/measurements';
+
+const createComponent = propsData => {
+  const Component = Vue.extend(GraphAxis);
+
+  return new Component({
+    propsData,
+  }).$mount();
+};
+
+const defaultValuesComponent = {
+  graphWidth: 500,
+  graphHeight: 300,
+  graphHeightOffset: 120,
+  margin: measurements.large.margin,
+  measurements: measurements.large,
+  yAxisLabel: 'Values',
+  unitOfDisplay: 'MB',
+};
+
+function getTextFromNode(component, selector) {
+  return component.$el.querySelector(selector).firstChild.nodeValue.trim();
+}
+
+describe('Axis', () => {
+  describe('Computed props', () => {
+    it('textTransform', () => {
+      const component = createComponent(defaultValuesComponent);
+
+      expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
+    });
+
+    it('xPosition', () => {
+      const component = createComponent(defaultValuesComponent);
+
+      expect(component.xPosition).toEqual(180);
+    });
+
+    it('yPosition', () => {
+      const component = createComponent(defaultValuesComponent);
+
+      expect(component.yPosition).toEqual(240);
+    });
+
+    it('rectTransform', () => {
+      const component = createComponent(defaultValuesComponent);
+
+      expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
+    });
+  });
+
+  it('has 2 rect-axis-text rect svg elements', () => {
+    const component = createComponent(defaultValuesComponent);
+
+    expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
+  });
+
+  it('contains text to signal the usage, title and time with multiple time series', () => {
+    const component = createComponent(defaultValuesComponent);
+
+    expect(getTextFromNode(component, '.y-label-text')).toEqual('Values (MB)');
+  });
+});
diff --git a/spec/javascripts/monitoring/graph/legend_spec.js b/spec/javascripts/monitoring/graph/legend_spec.js
index 145c8db28d59d544f7c52e4f7a5919448297cc3f..abcc51aa0775c049efc21070d11a486cc202dc19 100644
--- a/spec/javascripts/monitoring/graph/legend_spec.js
+++ b/spec/javascripts/monitoring/graph/legend_spec.js
@@ -1,106 +1,44 @@
 import Vue from 'vue';
 import GraphLegend from '~/monitoring/components/graph/legend.vue';
-import measurements from '~/monitoring/utils/measurements';
 import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
 import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
 
-const createComponent = (propsData) => {
-  const Component = Vue.extend(GraphLegend);
-
-  return new Component({
-    propsData,
-  }).$mount();
-};
-
 const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
 
-const defaultValuesComponent = {
-  graphWidth: 500,
-  graphHeight: 300,
-  graphHeightOffset: 120,
-  margin: measurements.large.margin,
-  measurements: measurements.large,
-  areaColorRgb: '#f0f0f0',
-  legendTitle: 'Title',
-  yAxisLabel: 'Values',
-  metricUsage: 'Value',
-  unitOfDisplay: 'Req/Sec',
-  currentDataIndex: 0,
-};
+const defaultValuesComponent = {};
 
-const timeSeries = createTimeSeries(convertedMetrics[0].queries,
-  defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
-  defaultValuesComponent.graphHeightOffset);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
 
 defaultValuesComponent.timeSeries = timeSeries;
 
-function getTextFromNode(component, selector) {
-  return component.$el.querySelector(selector).firstChild.nodeValue.trim();
-}
-
-describe('GraphLegend', () => {
-  describe('Computed props', () => {
-    it('textTransform', () => {
-      const component = createComponent(defaultValuesComponent);
-
-      expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
-    });
-
-    it('xPosition', () => {
-      const component = createComponent(defaultValuesComponent);
-
-      expect(component.xPosition).toEqual(180);
-    });
-
-    it('yPosition', () => {
-      const component = createComponent(defaultValuesComponent);
-
-      expect(component.yPosition).toEqual(240);
-    });
-
-    it('rectTransform', () => {
-      const component = createComponent(defaultValuesComponent);
+describe('Legend Component', () => {
+  let vm;
+  let Legend;
 
-      expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
-    });
+  beforeEach(() => {
+    Legend = Vue.extend(GraphLegend);
   });
 
-  describe('methods', () => {
-    it('translateLegendGroup should only change Y direction', () => {
-      const component = createComponent(defaultValuesComponent);
-
-      const translatedCoordinate = component.translateLegendGroup(1);
-      expect(translatedCoordinate.indexOf('translate(0, ')).not.toEqual(-1);
+  describe('View', () => {
+    beforeEach(() => {
+      vm = mountComponent(Legend, {
+        legendTitle: 'legend',
+        timeSeries,
+        currentDataIndex: 0,
+        unitOfDisplay: 'Req/Sec',
+      });
     });
 
-    it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => {
-      const component = createComponent(defaultValuesComponent);
+    it('should render the usage, title and time with multiple time series', () => {
+      const titles = vm.$el.querySelectorAll('.legend-metric-title');
 
-      const formattedMetricUsage = component.formatMetricUsage(timeSeries[0]);
-      const valueFromSeries = timeSeries[0].values[component.currentDataIndex].value;
-      expect(formattedMetricUsage.indexOf(component.unitOfDisplay)).not.toEqual(-1);
-      expect(formattedMetricUsage.indexOf(valueFromSeries)).not.toEqual(-1);
+      expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1);
+      expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1);
     });
-  });
-
-  it('has 2 rect-axis-text rect svg elements', () => {
-    const component = createComponent(defaultValuesComponent);
-
-    expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
-  });
 
-  it('contains text to signal the usage, title and time with multiple time series', () => {
-    const component = createComponent(defaultValuesComponent);
-    const titles = component.$el.querySelectorAll('.legend-metric-title');
-
-    expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1);
-    expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1);
-    expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel);
-  });
-
-  it('should contain the same number of legend groups as the timeSeries length', () => {
-    const component = createComponent(defaultValuesComponent);
-
-    expect(component.$el.querySelectorAll('.legend-group').length).toEqual(component.timeSeries.length);
+    it('should container the same number of rows in the table as time series', () => {
+      expect(vm.$el.querySelectorAll('.prometheus-table tr').length).toEqual(vm.timeSeries.length);
+    });
   });
 });
diff --git a/spec/javascripts/monitoring/graph/track_info_spec.js b/spec/javascripts/monitoring/graph/track_info_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..d3121d553f944bbba6c01482aecf33d44edb4e4f
--- /dev/null
+++ b/spec/javascripts/monitoring/graph/track_info_spec.js
@@ -0,0 +1,44 @@
+import Vue from 'vue';
+import TrackInfo from '~/monitoring/components/graph/track_info.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
+
+describe('TrackInfo component', () => {
+  let vm;
+  let Component;
+
+  beforeEach(() => {
+    Component = Vue.extend(TrackInfo);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('Computed props', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, { track: timeSeries[0] });
+    });
+
+    it('summaryMetrics', () => {
+      expect(vm.summaryMetrics).toEqual('Avg: 0.000 路 Max: 0.000');
+    });
+  });
+
+  describe('Rendered output', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, { track: timeSeries[0] });
+    });
+
+    it('contains metric tag and the summary metrics', () => {
+      const metricTag = vm.$el.querySelector('strong');
+
+      expect(metricTag.textContent.trim()).toEqual(vm.track.metricTag);
+      expect(vm.$el.textContent).toContain('Avg: 0.000 路 Max: 0.000');
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/graph/track_line_spec.js b/spec/javascripts/monitoring/graph/track_line_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..45106830a67ca1a02c791ad28591d9860cd7c550
--- /dev/null
+++ b/spec/javascripts/monitoring/graph/track_line_spec.js
@@ -0,0 +1,52 @@
+import Vue from 'vue';
+import TrackLine from '~/monitoring/components/graph/track_line.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
+
+describe('TrackLine component', () => {
+  let vm;
+  let Component;
+
+  beforeEach(() => {
+    Component = Vue.extend(TrackLine);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('Computed props', () => {
+    it('stylizedLine for dashed lineStyles', () => {
+      vm = mountComponent(Component, { track: { ...timeSeries[0], lineStyle: 'dashed' } });
+
+      expect(vm.stylizedLine).toEqual('6, 3');
+    });
+
+    it('stylizedLine for dotted lineStyles', () => {
+      vm = mountComponent(Component, { track: { ...timeSeries[0], lineStyle: 'dotted' } });
+
+      expect(vm.stylizedLine).toEqual('3, 3');
+    });
+  });
+
+  describe('Rendered output', () => {
+    it('has an svg with a line', () => {
+      vm = mountComponent(Component, { track: { ...timeSeries[0] } });
+      const svgEl = vm.$el.querySelector('svg');
+      const lineEl = vm.$el.querySelector('svg line');
+
+      expect(svgEl.getAttribute('width')).toEqual('15');
+      expect(svgEl.getAttribute('height')).toEqual('6');
+
+      expect(lineEl.getAttribute('stroke-width')).toEqual('4');
+      expect(lineEl.getAttribute('x1')).toEqual('0');
+      expect(lineEl.getAttribute('x2')).toEqual('15');
+      expect(lineEl.getAttribute('y1')).toEqual('2');
+      expect(lineEl.getAttribute('y2')).toEqual('2');
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js
index b1d69752badb8c2bc37be7031fbb55791fe3a4da..1213c80ba3a4ca22722afc5b87fe5fdb30c827a8 100644
--- a/spec/javascripts/monitoring/graph_spec.js
+++ b/spec/javascripts/monitoring/graph_spec.js
@@ -2,11 +2,15 @@ import Vue from 'vue';
 import Graph from '~/monitoring/components/graph.vue';
 import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
 import eventHub from '~/monitoring/event_hub';
-import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data';
+import {
+  deploymentData,
+  convertDatesMultipleSeries,
+  singleRowMetricsMultipleSeries,
+} from './mock_data';
 
 const tagsPath = 'http://test.host/frontend-fixtures/environments-project/tags';
 const projectPath = 'http://test.host/frontend-fixtures/environments-project';
-const createComponent = (propsData) => {
+const createComponent = propsData => {
   const Component = Vue.extend(Graph);
 
   return new Component({
@@ -14,7 +18,9 @@ const createComponent = (propsData) => {
   }).$mount();
 };
 
-const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const convertedMetrics = convertDatesMultipleSeries(
+  singleRowMetricsMultipleSeries,
+);
 
 describe('Graph', () => {
   beforeEach(() => {
@@ -31,7 +37,9 @@ describe('Graph', () => {
       projectPath,
     });
 
-    expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title);
+    expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(
+      component.graphData.title,
+    );
   });
 
   describe('Computed props', () => {
@@ -46,8 +54,9 @@ describe('Graph', () => {
       });
 
       const transformedHeight = `${component.graphHeight - 100}`;
-      expect(component.axisTransform.indexOf(transformedHeight))
-        .not.toEqual(-1);
+      expect(component.axisTransform.indexOf(transformedHeight)).not.toEqual(
+        -1,
+      );
     });
 
     it('outerViewBox gets a width and height property based on the DOM size of the element', () => {
@@ -63,11 +72,11 @@ describe('Graph', () => {
       const viewBoxArray = component.outerViewBox.split(' ');
       expect(typeof component.outerViewBox).toEqual('string');
       expect(viewBoxArray[2]).toEqual(component.graphWidth.toString());
-      expect(viewBoxArray[3]).toEqual(component.graphHeight.toString());
+      expect(viewBoxArray[3]).toEqual((component.graphHeight - 50).toString());
     });
   });
 
-  it('sends an event to the eventhub when it has finished resizing', (done) => {
+  it('sends an event to the eventhub when it has finished resizing', done => {
     const component = createComponent({
       graphData: convertedMetrics[1],
       classType: 'col-md-6',
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index f30208b27b6e3f459a88c12ef95b7f649a381bf7..50da6da2e07f9f0a31426adaf7bab8bc89acfdd1 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -3,2426 +3,645 @@
 export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`;
 
 export const metricsGroupsAPIResponse = {
-  'success': true,
-  'data': [
+  success: true,
+  data: [
     {
-        'group': 'Kubernetes',
-        'priority': 1,
-        'metrics': [
-          {
-            'title': 'Memory usage',
-            'weight': 1,
-            'queries': [
+      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: [
                 {
-                  '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'
-                          ]
-                      ]
-                    }
-                  ]
-              }
-          ]
+                  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': [
+          title: 'CPU usage',
+          weight: 1,
+          queries: [
+            {
+              query_range:
+                'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+              result: [
                 {
-                    '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'
-                                ]
-                            ]
-                        }
-                    ]
-                }
-            ]
-        }
-      ]
-    }
+                  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'
+  last_update: '2017-05-25T13:18:34.949Z',
 };
 
 export default metricsGroupsAPIResponse;
@@ -2432,41 +651,44 @@ export const deploymentData = [
     id: 111,
     iid: 3,
     sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
-    commitUrl: 'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+    commitUrl:
+      'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
     ref: {
-      name: 'master'
+      name: 'master',
     },
     created_at: '2017-05-31T21:23:37.881Z',
     tag: false,
     tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
-    'last?': true
+    'last?': true,
   },
   {
     id: 110,
     iid: 2,
     sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
-    commitUrl: 'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+    commitUrl:
+      'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
     ref: {
-      name: 'master'
+      name: 'master',
     },
     created_at: '2017-05-30T20:08:04.629Z',
     tag: false,
-      tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
-    'last?': false
+    tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
+    'last?': false,
   },
   {
     id: 109,
     iid: 1,
     sha: '6511e58faafaa7ad2228990ec57f19d66f7db7c2',
-    commitUrl: 'http://test.host/frontend-fixtures/environments-project/commit/6511e58faafaa7ad2228990ec57f19d66f7db7c2',
+    commitUrl:
+      'http://test.host/frontend-fixtures/environments-project/commit/6511e58faafaa7ad2228990ec57f19d66f7db7c2',
     ref: {
-      name: 'update2-readme'
+      name: 'update2-readme',
     },
     created_at: '2017-05-30T17:42:38.409Z',
     tag: false,
     tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
-    'last?': false
-  }
+    'last?': false,
+  },
 ];
 
 export const statePaths = {
@@ -2476,5844 +698,5844 @@ export const statePaths = {
 };
 
 export const singleRowMetricsMultipleSeries = [
-    {
-        'title': 'Multiple Time Series',
-        'weight': 1,
-        'y_label': 'Request Rates',
-        'queries': [
-            {
-                'query_range': 'sum(rate(nginx_responses_total{environment="production"}[2m])) by (status_code)',
-                'label': 'Requests',
-                'unit': 'Req/sec',
-                'result': [
-                    {
-                        'metric': {
-                            'status_code': '1xx'
-                        },
-                        'values': [
-                            {
-                                'time': '2017-08-27T11:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T11:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T12:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T13:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T14:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T15:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T16:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T17:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:01:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:02:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:03:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:04:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:05:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:06:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:07:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:08:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:09:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:10:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:11:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:12:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:13:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:14:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:15:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:16:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:17:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:18:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:19:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:20:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:21:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:22:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:23:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:24:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:25:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:26:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:27:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:28:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:29:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:30:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:31:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:32:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:33:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:34:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:35:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:36:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:37:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:38:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:39:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:40:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:41:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:42:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:43:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:44:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:45:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:46:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:47:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:48:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:49:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:50:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:51:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:52:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:53:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:54:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:55:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:56:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:57:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:58:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T18:59:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T19:00:51.462Z',
-                                'value': '0'
-                            },
-                            {
-                                'time': '2017-08-27T19:01:51.462Z',
-                                'value': '0'
-                            }
-                        ]
-                    },
-                    {
-                        'metric': {
-                            'status_code': '2xx'
-                        },
-                        'values': [
-                            {
-                                'time': '2017-08-27T11:01:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:02:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T11:03:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:04:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:05:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:06:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:07:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:08:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:09:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:10:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:11:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:12:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:13:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:14:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:15:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:16:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:17:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:18:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:19:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:20:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:21:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:22:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:23:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:24:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:25:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:26:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:27:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:28:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:29:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:30:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:31:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:32:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:33:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:34:51.462Z',
-                                'value': '1.333320635041571'
-                            },
-                            {
-                                'time': '2017-08-27T11:35:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:36:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:37:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:38:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:39:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:40:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:41:51.462Z',
-                                'value': '1.3333587306424883'
-                            },
-                            {
-                                'time': '2017-08-27T11:42:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:43:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:44:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:45:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:46:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:47:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:48:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:49:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T11:50:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:51:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:52:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:53:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:54:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:55:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:56:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:57:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T11:58:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T11:59:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:00:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:01:51.462Z',
-                                'value': '1.3333460318669703'
-                            },
-                            {
-                                'time': '2017-08-27T12:02:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:03:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:04:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:05:51.462Z',
-                                'value': '1.31427319739812'
-                            },
-                            {
-                                'time': '2017-08-27T12:06:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:07:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:08:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:09:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:10:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:11:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:12:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:13:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:14:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:15:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:16:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:17:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:18:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:19:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:20:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:21:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:22:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:23:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:24:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:25:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:26:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:27:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:28:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:29:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:30:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:31:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:32:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:33:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:34:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:35:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:36:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:37:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:38:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:39:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:40:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:41:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:42:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:43:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:44:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:45:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:46:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:47:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:48:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:49:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:50:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:51:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:52:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:53:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:54:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T12:55:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:56:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:57:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T12:58:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T12:59:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:00:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:01:51.462Z',
-                                'value': '1.295225759754669'
-                            },
-                            {
-                                'time': '2017-08-27T13:02:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:03:51.462Z',
-                                'value': '1.2952627669098458'
-                            },
-                            {
-                                'time': '2017-08-27T13:04:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:05:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:06:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:07:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:08:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:09:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:10:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:11:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:12:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:13:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:14:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:15:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T13:16:51.462Z',
-                                'value': '1.3333587306424883'
-                            },
-                            {
-                                'time': '2017-08-27T13:17:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:18:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:19:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:20:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:21:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:22:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:23:51.462Z',
-                                'value': '1.276190476190476'
-                            },
-                            {
-                                'time': '2017-08-27T13:24:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T13:25:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:26:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:27:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:28:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:29:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:30:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:31:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:32:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:33:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:34:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:35:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:36:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:37:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:38:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:39:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:40:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:41:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:42:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:43:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:44:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:45:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:46:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T13:47:51.462Z',
-                                'value': '1.276190476190476'
-                            },
-                            {
-                                'time': '2017-08-27T13:48:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:49:51.462Z',
-                                'value': '1.295225759754669'
-                            },
-                            {
-                                'time': '2017-08-27T13:50:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:51:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:52:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:53:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:54:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:55:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:56:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T13:57:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T13:58:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T13:59:51.462Z',
-                                'value': '1.295225759754669'
-                            },
-                            {
-                                'time': '2017-08-27T14:00:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:01:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:02:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:03:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:04:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:05:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:06:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:07:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:08:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:09:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:10:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:11:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:12:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:13:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:14:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:15:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:16:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:17:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:18:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:19:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:20:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:21:51.462Z',
-                                'value': '1.3333079369916765'
-                            },
-                            {
-                                'time': '2017-08-27T14:22:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:23:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:24:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:25:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:26:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:27:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:28:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:29:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:30:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:31:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:32:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:33:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T14:34:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:35:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:36:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:37:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:38:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:39:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:40:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:41:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:42:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:43:51.462Z',
-                                'value': '1.276190476190476'
-                            },
-                            {
-                                'time': '2017-08-27T14:44:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T14:45:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:46:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:47:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:48:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:49:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:50:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:51:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:52:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:53:51.462Z',
-                                'value': '1.333320635041571'
-                            },
-                            {
-                                'time': '2017-08-27T14:54:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:55:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T14:56:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:57:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T14:58:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T14:59:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:00:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:01:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:02:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:03:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:04:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T15:05:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:06:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:07:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:08:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:09:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:10:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:11:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:12:51.462Z',
-                                'value': '1.31427319739812'
-                            },
-                            {
-                                'time': '2017-08-27T15:13:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:14:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:15:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:16:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:17:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:18:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:19:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:20:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:21:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:22:51.462Z',
-                                'value': '1.3333460318669703'
-                            },
-                            {
-                                'time': '2017-08-27T15:23:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:24:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:25:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:26:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:27:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:28:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:29:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:30:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:31:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:32:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:33:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:34:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:35:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:36:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:37:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:38:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:39:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:40:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:41:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:42:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:43:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:44:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:45:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:46:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:47:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:48:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:49:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T15:50:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:51:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:52:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:53:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:54:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:55:51.462Z',
-                                'value': '1.3333587306424883'
-                            },
-                            {
-                                'time': '2017-08-27T15:56:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T15:57:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:58:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T15:59:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:00:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:01:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:02:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:03:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:04:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:05:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:06:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:07:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:08:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:09:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:10:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:11:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:12:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:13:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:14:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:15:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:16:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:17:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:18:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:19:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:20:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:21:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:22:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:23:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:24:51.462Z',
-                                'value': '1.295225759754669'
-                            },
-                            {
-                                'time': '2017-08-27T16:25:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:26:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:27:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:28:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:29:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:30:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:31:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:32:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:33:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:34:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:35:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:36:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:37:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:38:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:39:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:40:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:41:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:42:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:43:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:44:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:45:51.462Z',
-                                'value': '1.3142982314117277'
-                            },
-                            {
-                                'time': '2017-08-27T16:46:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:47:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:48:51.462Z',
-                                'value': '1.333320635041571'
-                            },
-                            {
-                                'time': '2017-08-27T16:49:51.462Z',
-                                'value': '1.31427319739812'
-                            },
-                            {
-                                'time': '2017-08-27T16:50:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:51:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:52:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:53:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:54:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:55:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T16:56:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:57:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T16:58:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T16:59:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:00:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:01:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:02:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:03:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:04:51.462Z',
-                                'value': '1.2952504309564854'
-                            },
-                            {
-                                'time': '2017-08-27T17:05:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:06:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:07:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:08:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:09:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:10:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:11:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:12:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:13:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:14:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:15:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:16:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:17:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:18:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:19:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:20:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:21:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:22:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:23:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:24:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:25:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:26:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:27:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:28:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:29:51.462Z',
-                                'value': '1.295225759754669'
-                            },
-                            {
-                                'time': '2017-08-27T17:30:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:31:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:32:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:33:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:34:51.462Z',
-                                'value': '1.295225759754669'
-                            },
-                            {
-                                'time': '2017-08-27T17:35:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:36:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:37:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:38:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:39:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:40:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:41:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:42:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:43:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:44:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:45:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:46:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:47:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:48:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:49:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:50:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:51:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:52:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:53:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:54:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:55:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T17:56:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:57:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T17:58:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T17:59:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T18:00:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:01:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:02:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:03:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:04:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:05:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:06:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:07:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:08:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:09:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:10:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:11:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:12:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T18:13:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:14:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:15:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:16:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:17:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:18:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:19:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:20:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:21:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:22:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:23:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:24:51.462Z',
-                                'value': '1.2571428571428571'
-                            },
-                            {
-                                'time': '2017-08-27T18:25:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:26:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:27:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:28:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:29:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:30:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:31:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:32:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:33:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:34:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:35:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:36:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:37:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T18:38:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:39:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:40:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:41:51.462Z',
-                                'value': '1.580952380952381'
-                            },
-                            {
-                                'time': '2017-08-27T18:42:51.462Z',
-                                'value': '1.7333333333333334'
-                            },
-                            {
-                                'time': '2017-08-27T18:43:51.462Z',
-                                'value': '2.057142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:44:51.462Z',
-                                'value': '2.1904761904761902'
-                            },
-                            {
-                                'time': '2017-08-27T18:45:51.462Z',
-                                'value': '1.8285714285714287'
-                            },
-                            {
-                                'time': '2017-08-27T18:46:51.462Z',
-                                'value': '2.1142857142857143'
-                            },
-                            {
-                                'time': '2017-08-27T18:47:51.462Z',
-                                'value': '1.619047619047619'
-                            },
-                            {
-                                'time': '2017-08-27T18:48:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:49:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:50:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T18:51:51.462Z',
-                                'value': '1.2952504309564854'
-                            },
-                            {
-                                'time': '2017-08-27T18:52:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:53:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:54:51.462Z',
-                                'value': '1.3333333333333333'
-                            },
-                            {
-                                'time': '2017-08-27T18:55:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:56:51.462Z',
-                                'value': '1.314285714285714'
-                            },
-                            {
-                                'time': '2017-08-27T18:57:51.462Z',
-                                'value': '1.295238095238095'
-                            },
-                            {
-                                'time': '2017-08-27T18:58:51.462Z',
-                                'value': '1.7142857142857142'
-                            },
-                            {
-                                'time': '2017-08-27T18:59:51.462Z',
-                                'value': '1.7333333333333334'
-                            },
-                            {
-                                'time': '2017-08-27T19:00:51.462Z',
-                                'value': '1.3904761904761904'
-                            },
-                            {
-                                'time': '2017-08-27T19:01:51.462Z',
-                                'value': '1.5047619047619047'
-                            }
-                        ]
-                    },
-                ],
-                'when': [
-                  {
-                    'value': 'hundred(s)',
-                    'color': 'green',
-                  },
-                ],
-            }
-        ]
-    },
-    {
-        'title': 'Throughput',
-        'weight': 1,
-        'y_label': 'Requests / Sec',
-        'queries': [
-            {
-                'query_range': 'sum(rate(nginx_requests_total{server_zone!=\'*\', server_zone!=\'_\', container_name!=\'POD\',environment=\'production\'}[2m]))',
-                'label': 'Total',
-                'unit': 'req / sec',
-                'result': [
-                    {
-                        'metric': {
-
-                        },
-                        'values': [
-                            {
-                                'time': '2017-08-27T11:01:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:02:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T11:03:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:04:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:05:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:06:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:07:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:08:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:09:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:10:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:11:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:12:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:13:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:14:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:15:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:16:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:17:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:18:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:19:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:20:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:21:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:22:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:23:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:24:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:25:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:26:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:27:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:28:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:29:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:30:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:31:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:32:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:33:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:34:51.462Z',
-                                'value': '0.4952333787297264'
-                            },
-                            {
-                                'time': '2017-08-27T11:35:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:36:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:37:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:38:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:39:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:40:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:41:51.462Z',
-                                'value': '0.49524752852435283'
-                            },
-                            {
-                                'time': '2017-08-27T11:42:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:43:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:44:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:45:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:46:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:47:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:48:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:49:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T11:50:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:51:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:52:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:53:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:54:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:55:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:56:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:57:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T11:58:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T11:59:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:00:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:01:51.462Z',
-                                'value': '0.49524281183630325'
-                            },
-                            {
-                                'time': '2017-08-27T12:02:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:03:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:04:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:05:51.462Z',
-                                'value': '0.4857096599080009'
-                            },
-                            {
-                                'time': '2017-08-27T12:06:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:07:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:08:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:09:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:10:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:11:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:12:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:13:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:14:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:15:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:16:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:17:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:18:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:19:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:20:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:21:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:22:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:23:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:24:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:25:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:26:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:27:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:28:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:29:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:30:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:31:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:32:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:33:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:34:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:35:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:36:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:37:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:38:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:39:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:40:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:41:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:42:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:43:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:44:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:45:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:46:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:47:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:48:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:49:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:50:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:51:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:52:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:53:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:54:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T12:55:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:56:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:57:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T12:58:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T12:59:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:00:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:01:51.462Z',
-                                'value': '0.4761859410862754'
-                            },
-                            {
-                                'time': '2017-08-27T13:02:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:03:51.462Z',
-                                'value': '0.4761995466580315'
-                            },
-                            {
-                                'time': '2017-08-27T13:04:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:05:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:06:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:07:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:08:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:09:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:10:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:11:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:12:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:13:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:14:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:15:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T13:16:51.462Z',
-                                'value': '0.49524752852435283'
-                            },
-                            {
-                                'time': '2017-08-27T13:17:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:18:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:19:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:20:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:21:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:22:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:23:51.462Z',
-                                'value': '0.4666666666666667'
-                            },
-                            {
-                                'time': '2017-08-27T13:24:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T13:25:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:26:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:27:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:28:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:29:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:30:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:31:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:32:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:33:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:34:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:35:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:36:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:37:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:38:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:39:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:40:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:41:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:42:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:43:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:44:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:45:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:46:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T13:47:51.462Z',
-                                'value': '0.4666666666666667'
-                            },
-                            {
-                                'time': '2017-08-27T13:48:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:49:51.462Z',
-                                'value': '0.4761859410862754'
-                            },
-                            {
-                                'time': '2017-08-27T13:50:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:51:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:52:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:53:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:54:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:55:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:56:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T13:57:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T13:58:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T13:59:51.462Z',
-                                'value': '0.4761859410862754'
-                            },
-                            {
-                                'time': '2017-08-27T14:00:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:01:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:02:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:03:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:04:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:05:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:06:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:07:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:08:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:09:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:10:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:11:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:12:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:13:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:14:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:15:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:16:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:17:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:18:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:19:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:20:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:21:51.462Z',
-                                'value': '0.4952286623111941'
-                            },
-                            {
-                                'time': '2017-08-27T14:22:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:23:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:24:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:25:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:26:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:27:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:28:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:29:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:30:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:31:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:32:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:33:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T14:34:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:35:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:36:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:37:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:38:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:39:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:40:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:41:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:42:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:43:51.462Z',
-                                'value': '0.4666666666666667'
-                            },
-                            {
-                                'time': '2017-08-27T14:44:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T14:45:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:46:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:47:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:48:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:49:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:50:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:51:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:52:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:53:51.462Z',
-                                'value': '0.4952333787297264'
-                            },
-                            {
-                                'time': '2017-08-27T14:54:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:55:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T14:56:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:57:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T14:58:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T14:59:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:00:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:01:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:02:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:03:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:04:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T15:05:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:06:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:07:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:08:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:09:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:10:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:11:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:12:51.462Z',
-                                'value': '0.4857096599080009'
-                            },
-                            {
-                                'time': '2017-08-27T15:13:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:14:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:15:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:16:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:17:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:18:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:19:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:20:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:21:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:22:51.462Z',
-                                'value': '0.49524281183630325'
-                            },
-                            {
-                                'time': '2017-08-27T15:23:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:24:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:25:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:26:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:27:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:28:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:29:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:30:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:31:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:32:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:33:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:34:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:35:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:36:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:37:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:38:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:39:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:40:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:41:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:42:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:43:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:44:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:45:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:46:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:47:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:48:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:49:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T15:50:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:51:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:52:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:53:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:54:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:55:51.462Z',
-                                'value': '0.49524752852435283'
-                            },
-                            {
-                                'time': '2017-08-27T15:56:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T15:57:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:58:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T15:59:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:00:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:01:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:02:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:03:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:04:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:05:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:06:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:07:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:08:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:09:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:10:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:11:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:12:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:13:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:14:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:15:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:16:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:17:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:18:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:19:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:20:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:21:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:22:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:23:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:24:51.462Z',
-                                'value': '0.4761859410862754'
-                            },
-                            {
-                                'time': '2017-08-27T16:25:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:26:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:27:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:28:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:29:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:30:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:31:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:32:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:33:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:34:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:35:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:36:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:37:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:38:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:39:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:40:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:41:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:42:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:43:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:44:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:45:51.462Z',
-                                'value': '0.485718911608682'
-                            },
-                            {
-                                'time': '2017-08-27T16:46:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:47:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:48:51.462Z',
-                                'value': '0.4952333787297264'
-                            },
-                            {
-                                'time': '2017-08-27T16:49:51.462Z',
-                                'value': '0.4857096599080009'
-                            },
-                            {
-                                'time': '2017-08-27T16:50:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:51:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:52:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:53:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:54:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:55:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T16:56:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:57:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T16:58:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T16:59:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:00:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:01:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:02:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:03:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:04:51.462Z',
-                                'value': '0.47619501138106085'
-                            },
-                            {
-                                'time': '2017-08-27T17:05:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:06:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:07:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:08:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:09:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:10:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:11:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:12:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:13:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:14:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:15:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:16:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:17:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:18:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:19:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:20:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:21:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:22:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:23:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:24:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:25:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:26:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:27:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:28:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:29:51.462Z',
-                                'value': '0.4761859410862754'
-                            },
-                            {
-                                'time': '2017-08-27T17:30:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:31:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:32:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:33:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:34:51.462Z',
-                                'value': '0.4761859410862754'
-                            },
-                            {
-                                'time': '2017-08-27T17:35:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:36:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:37:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:38:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:39:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:40:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:41:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:42:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:43:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:44:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:45:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:46:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:47:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:48:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:49:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:50:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:51:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:52:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:53:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:54:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:55:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T17:56:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:57:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T17:58:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T17:59:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:00:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:01:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:02:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:03:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:04:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:05:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:06:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:07:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:08:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:09:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:10:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:11:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:12:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:13:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:14:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:15:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:16:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:17:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:18:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:19:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:20:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:21:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:22:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:23:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:24:51.462Z',
-                                'value': '0.45714285714285713'
-                            },
-                            {
-                                'time': '2017-08-27T18:25:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:26:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:27:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:28:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:29:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:30:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:31:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:32:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:33:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:34:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:35:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:36:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:37:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:38:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:39:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:40:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:41:51.462Z',
-                                'value': '0.6190476190476191'
-                            },
-                            {
-                                'time': '2017-08-27T18:42:51.462Z',
-                                'value': '0.6952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:43:51.462Z',
-                                'value': '0.857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:44:51.462Z',
-                                'value': '0.9238095238095239'
-                            },
-                            {
-                                'time': '2017-08-27T18:45:51.462Z',
-                                'value': '0.7428571428571429'
-                            },
-                            {
-                                'time': '2017-08-27T18:46:51.462Z',
-                                'value': '0.8857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:47:51.462Z',
-                                'value': '0.638095238095238'
-                            },
-                            {
-                                'time': '2017-08-27T18:48:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:49:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:50:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:51:51.462Z',
-                                'value': '0.47619501138106085'
-                            },
-                            {
-                                'time': '2017-08-27T18:52:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:53:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:54:51.462Z',
-                                'value': '0.4952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T18:55:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:56:51.462Z',
-                                'value': '0.4857142857142857'
-                            },
-                            {
-                                'time': '2017-08-27T18:57:51.462Z',
-                                'value': '0.47619047619047616'
-                            },
-                            {
-                                'time': '2017-08-27T18:58:51.462Z',
-                                'value': '0.6857142857142856'
-                            },
-                            {
-                                'time': '2017-08-27T18:59:51.462Z',
-                                'value': '0.6952380952380952'
-                            },
-                            {
-                                'time': '2017-08-27T19:00:51.462Z',
-                                'value': '0.5238095238095237'
-                            },
-                            {
-                                'time': '2017-08-27T19:01:51.462Z',
-                                'value': '0.5904761904761905'
-                            }
-                        ]
-                    }
-                ]
-            }
-        ]
-    }
+  {
+    title: 'Multiple Time Series',
+    weight: 1,
+    y_label: 'Request Rates',
+    queries: [
+      {
+        query_range:
+          'sum(rate(nginx_responses_total{environment="production"}[2m])) by (status_code)',
+        label: 'Requests',
+        unit: 'Req/sec',
+        result: [
+          {
+            metric: {
+              status_code: '1xx',
+            },
+            values: [
+              {
+                time: '2017-08-27T11:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T11:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T12:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T13:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T14:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T15:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T16:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T17:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:01:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:02:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:03:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:04:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:05:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:06:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:07:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:08:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:09:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:10:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:11:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:12:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:13:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:14:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:15:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:16:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:17:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:18:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:19:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:20:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:21:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:22:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:23:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:24:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:25:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:26:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:27:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:28:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:29:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:30:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:31:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:32:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:33:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:34:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:35:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:36:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:37:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:38:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:39:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:40:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:41:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:42:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:43:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:44:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:45:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:46:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:47:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:48:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:49:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:50:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:51:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:52:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:53:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:54:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:55:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:56:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:57:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:58:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T18:59:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T19:00:51.462Z',
+                value: '0',
+              },
+              {
+                time: '2017-08-27T19:01:51.462Z',
+                value: '0',
+              },
+            ],
+          },
+          {
+            metric: {
+              status_code: '2xx',
+            },
+            values: [
+              {
+                time: '2017-08-27T11:01:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:02:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T11:03:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:04:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:05:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:06:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:07:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:08:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:09:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:10:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:11:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:12:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:13:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:14:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:15:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:16:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:17:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:18:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:19:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:20:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:21:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:22:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:23:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:24:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:25:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:26:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:27:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:28:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:29:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:30:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:31:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:32:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:33:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:34:51.462Z',
+                value: '1.333320635041571',
+              },
+              {
+                time: '2017-08-27T11:35:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:36:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:37:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:38:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:39:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:40:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:41:51.462Z',
+                value: '1.3333587306424883',
+              },
+              {
+                time: '2017-08-27T11:42:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:43:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:44:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:45:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:46:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:47:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:48:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:49:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T11:50:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:51:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:52:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:53:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:54:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:55:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:56:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:57:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T11:58:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T11:59:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:00:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:01:51.462Z',
+                value: '1.3333460318669703',
+              },
+              {
+                time: '2017-08-27T12:02:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:03:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:04:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:05:51.462Z',
+                value: '1.31427319739812',
+              },
+              {
+                time: '2017-08-27T12:06:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:07:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:08:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:09:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:10:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:11:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:12:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:13:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:14:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:15:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:16:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:17:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:18:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:19:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:20:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:21:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:22:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:23:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:24:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:25:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:26:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:27:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:28:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:29:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:30:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:31:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:32:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:33:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:34:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:35:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:36:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:37:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:38:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:39:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:40:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:41:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:42:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:43:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:44:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:45:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:46:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:47:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:48:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:49:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:50:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:51:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:52:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:53:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:54:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T12:55:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:56:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:57:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T12:58:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T12:59:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:00:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:01:51.462Z',
+                value: '1.295225759754669',
+              },
+              {
+                time: '2017-08-27T13:02:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:03:51.462Z',
+                value: '1.2952627669098458',
+              },
+              {
+                time: '2017-08-27T13:04:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:05:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:06:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:07:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:08:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:09:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:10:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:11:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:12:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:13:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:14:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:15:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T13:16:51.462Z',
+                value: '1.3333587306424883',
+              },
+              {
+                time: '2017-08-27T13:17:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:18:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:19:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:20:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:21:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:22:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:23:51.462Z',
+                value: '1.276190476190476',
+              },
+              {
+                time: '2017-08-27T13:24:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T13:25:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:26:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:27:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:28:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:29:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:30:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:31:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:32:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:33:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:34:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:35:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:36:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:37:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:38:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:39:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:40:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:41:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:42:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:43:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:44:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:45:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:46:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T13:47:51.462Z',
+                value: '1.276190476190476',
+              },
+              {
+                time: '2017-08-27T13:48:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:49:51.462Z',
+                value: '1.295225759754669',
+              },
+              {
+                time: '2017-08-27T13:50:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:51:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:52:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:53:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:54:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:55:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:56:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T13:57:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T13:58:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T13:59:51.462Z',
+                value: '1.295225759754669',
+              },
+              {
+                time: '2017-08-27T14:00:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:01:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:02:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:03:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:04:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:05:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:06:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:07:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:08:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:09:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:10:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:11:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:12:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:13:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:14:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:15:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:16:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:17:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:18:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:19:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:20:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:21:51.462Z',
+                value: '1.3333079369916765',
+              },
+              {
+                time: '2017-08-27T14:22:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:23:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:24:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:25:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:26:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:27:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:28:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:29:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:30:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:31:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:32:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:33:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T14:34:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:35:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:36:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:37:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:38:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:39:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:40:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:41:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:42:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:43:51.462Z',
+                value: '1.276190476190476',
+              },
+              {
+                time: '2017-08-27T14:44:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T14:45:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:46:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:47:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:48:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:49:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:50:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:51:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:52:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:53:51.462Z',
+                value: '1.333320635041571',
+              },
+              {
+                time: '2017-08-27T14:54:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:55:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T14:56:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:57:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T14:58:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T14:59:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:00:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:01:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:02:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:03:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:04:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T15:05:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:06:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:07:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:08:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:09:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:10:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:11:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:12:51.462Z',
+                value: '1.31427319739812',
+              },
+              {
+                time: '2017-08-27T15:13:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:14:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:15:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:16:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:17:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:18:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:19:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:20:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:21:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:22:51.462Z',
+                value: '1.3333460318669703',
+              },
+              {
+                time: '2017-08-27T15:23:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:24:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:25:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:26:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:27:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:28:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:29:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:30:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:31:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:32:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:33:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:34:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:35:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:36:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:37:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:38:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:39:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:40:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:41:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:42:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:43:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:44:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:45:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:46:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:47:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:48:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:49:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T15:50:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:51:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:52:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:53:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:54:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:55:51.462Z',
+                value: '1.3333587306424883',
+              },
+              {
+                time: '2017-08-27T15:56:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T15:57:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:58:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T15:59:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:00:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:01:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:02:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:03:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:04:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:05:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:06:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:07:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:08:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:09:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:10:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:11:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:12:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:13:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:14:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:15:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:16:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:17:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:18:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:19:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:20:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:21:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:22:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:23:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:24:51.462Z',
+                value: '1.295225759754669',
+              },
+              {
+                time: '2017-08-27T16:25:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:26:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:27:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:28:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:29:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:30:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:31:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:32:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:33:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:34:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:35:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:36:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:37:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:38:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:39:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:40:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:41:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:42:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:43:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:44:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:45:51.462Z',
+                value: '1.3142982314117277',
+              },
+              {
+                time: '2017-08-27T16:46:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:47:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:48:51.462Z',
+                value: '1.333320635041571',
+              },
+              {
+                time: '2017-08-27T16:49:51.462Z',
+                value: '1.31427319739812',
+              },
+              {
+                time: '2017-08-27T16:50:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:51:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:52:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:53:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:54:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:55:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T16:56:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:57:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T16:58:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T16:59:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:00:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:01:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:02:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:03:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:04:51.462Z',
+                value: '1.2952504309564854',
+              },
+              {
+                time: '2017-08-27T17:05:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:06:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:07:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:08:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:09:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:10:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:11:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:12:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:13:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:14:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:15:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:16:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:17:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:18:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:19:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:20:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:21:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:22:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:23:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:24:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:25:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:26:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:27:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:28:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:29:51.462Z',
+                value: '1.295225759754669',
+              },
+              {
+                time: '2017-08-27T17:30:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:31:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:32:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:33:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:34:51.462Z',
+                value: '1.295225759754669',
+              },
+              {
+                time: '2017-08-27T17:35:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:36:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:37:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:38:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:39:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:40:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:41:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:42:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:43:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:44:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:45:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:46:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:47:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:48:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:49:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:50:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:51:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:52:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:53:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:54:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:55:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T17:56:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:57:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T17:58:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T17:59:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T18:00:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:01:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:02:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:03:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:04:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:05:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:06:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:07:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:08:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:09:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:10:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:11:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:12:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T18:13:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:14:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:15:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:16:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:17:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:18:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:19:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:20:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:21:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:22:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:23:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:24:51.462Z',
+                value: '1.2571428571428571',
+              },
+              {
+                time: '2017-08-27T18:25:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:26:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:27:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:28:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:29:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:30:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:31:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:32:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:33:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:34:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:35:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:36:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:37:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T18:38:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:39:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:40:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:41:51.462Z',
+                value: '1.580952380952381',
+              },
+              {
+                time: '2017-08-27T18:42:51.462Z',
+                value: '1.7333333333333334',
+              },
+              {
+                time: '2017-08-27T18:43:51.462Z',
+                value: '2.057142857142857',
+              },
+              {
+                time: '2017-08-27T18:44:51.462Z',
+                value: '2.1904761904761902',
+              },
+              {
+                time: '2017-08-27T18:45:51.462Z',
+                value: '1.8285714285714287',
+              },
+              {
+                time: '2017-08-27T18:46:51.462Z',
+                value: '2.1142857142857143',
+              },
+              {
+                time: '2017-08-27T18:47:51.462Z',
+                value: '1.619047619047619',
+              },
+              {
+                time: '2017-08-27T18:48:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:49:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:50:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T18:51:51.462Z',
+                value: '1.2952504309564854',
+              },
+              {
+                time: '2017-08-27T18:52:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:53:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:54:51.462Z',
+                value: '1.3333333333333333',
+              },
+              {
+                time: '2017-08-27T18:55:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:56:51.462Z',
+                value: '1.314285714285714',
+              },
+              {
+                time: '2017-08-27T18:57:51.462Z',
+                value: '1.295238095238095',
+              },
+              {
+                time: '2017-08-27T18:58:51.462Z',
+                value: '1.7142857142857142',
+              },
+              {
+                time: '2017-08-27T18:59:51.462Z',
+                value: '1.7333333333333334',
+              },
+              {
+                time: '2017-08-27T19:00:51.462Z',
+                value: '1.3904761904761904',
+              },
+              {
+                time: '2017-08-27T19:01:51.462Z',
+                value: '1.5047619047619047',
+              },
+            ],
+          },
+        ],
+        when: [
+          {
+            value: 'hundred(s)',
+            color: 'green',
+          },
+        ],
+      },
+    ],
+  },
+  {
+    title: 'Throughput',
+    weight: 1,
+    y_label: 'Requests / Sec',
+    queries: [
+      {
+        query_range:
+          "sum(rate(nginx_requests_total{server_zone!='*', server_zone!='_', container_name!='POD',environment='production'}[2m]))",
+        label: 'Total',
+        unit: 'req / sec',
+        result: [
+          {
+            metric: {},
+            values: [
+              {
+                time: '2017-08-27T11:01:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:02:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T11:03:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:04:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:05:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:06:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:07:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:08:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:09:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:10:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:11:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:12:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:13:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:14:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:15:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:16:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:17:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:18:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:19:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:20:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:21:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:22:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:23:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:24:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:25:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:26:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:27:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:28:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:29:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:30:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:31:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:32:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:33:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:34:51.462Z',
+                value: '0.4952333787297264',
+              },
+              {
+                time: '2017-08-27T11:35:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:36:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:37:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:38:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:39:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:40:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:41:51.462Z',
+                value: '0.49524752852435283',
+              },
+              {
+                time: '2017-08-27T11:42:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:43:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:44:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:45:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:46:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:47:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:48:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:49:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T11:50:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:51:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:52:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:53:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:54:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:55:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:56:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:57:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T11:58:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T11:59:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:00:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:01:51.462Z',
+                value: '0.49524281183630325',
+              },
+              {
+                time: '2017-08-27T12:02:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:03:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:04:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:05:51.462Z',
+                value: '0.4857096599080009',
+              },
+              {
+                time: '2017-08-27T12:06:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:07:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:08:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:09:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:10:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:11:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:12:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:13:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:14:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:15:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:16:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:17:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:18:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:19:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:20:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:21:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:22:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:23:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:24:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:25:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:26:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:27:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:28:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:29:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:30:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:31:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:32:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:33:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:34:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:35:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:36:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:37:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:38:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:39:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:40:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:41:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:42:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:43:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:44:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:45:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:46:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:47:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:48:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:49:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:50:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:51:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:52:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:53:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:54:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T12:55:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:56:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:57:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T12:58:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T12:59:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:00:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:01:51.462Z',
+                value: '0.4761859410862754',
+              },
+              {
+                time: '2017-08-27T13:02:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:03:51.462Z',
+                value: '0.4761995466580315',
+              },
+              {
+                time: '2017-08-27T13:04:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:05:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:06:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:07:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:08:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:09:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:10:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:11:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:12:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:13:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:14:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:15:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T13:16:51.462Z',
+                value: '0.49524752852435283',
+              },
+              {
+                time: '2017-08-27T13:17:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:18:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:19:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:20:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:21:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:22:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:23:51.462Z',
+                value: '0.4666666666666667',
+              },
+              {
+                time: '2017-08-27T13:24:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T13:25:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:26:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:27:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:28:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:29:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:30:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:31:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:32:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:33:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:34:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:35:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:36:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:37:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:38:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:39:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:40:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:41:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:42:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:43:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:44:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:45:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:46:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T13:47:51.462Z',
+                value: '0.4666666666666667',
+              },
+              {
+                time: '2017-08-27T13:48:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:49:51.462Z',
+                value: '0.4761859410862754',
+              },
+              {
+                time: '2017-08-27T13:50:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:51:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:52:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:53:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:54:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:55:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:56:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T13:57:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T13:58:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T13:59:51.462Z',
+                value: '0.4761859410862754',
+              },
+              {
+                time: '2017-08-27T14:00:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:01:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:02:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:03:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:04:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:05:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:06:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:07:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:08:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:09:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:10:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:11:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:12:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:13:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:14:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:15:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:16:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:17:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:18:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:19:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:20:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:21:51.462Z',
+                value: '0.4952286623111941',
+              },
+              {
+                time: '2017-08-27T14:22:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:23:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:24:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:25:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:26:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:27:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:28:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:29:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:30:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:31:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:32:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:33:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T14:34:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:35:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:36:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:37:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:38:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:39:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:40:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:41:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:42:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:43:51.462Z',
+                value: '0.4666666666666667',
+              },
+              {
+                time: '2017-08-27T14:44:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T14:45:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:46:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:47:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:48:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:49:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:50:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:51:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:52:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:53:51.462Z',
+                value: '0.4952333787297264',
+              },
+              {
+                time: '2017-08-27T14:54:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:55:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T14:56:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:57:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T14:58:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T14:59:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:00:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:01:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:02:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:03:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:04:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T15:05:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:06:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:07:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:08:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:09:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:10:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:11:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:12:51.462Z',
+                value: '0.4857096599080009',
+              },
+              {
+                time: '2017-08-27T15:13:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:14:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:15:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:16:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:17:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:18:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:19:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:20:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:21:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:22:51.462Z',
+                value: '0.49524281183630325',
+              },
+              {
+                time: '2017-08-27T15:23:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:24:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:25:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:26:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:27:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:28:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:29:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:30:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:31:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:32:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:33:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:34:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:35:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:36:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:37:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:38:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:39:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:40:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:41:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:42:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:43:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:44:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:45:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:46:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:47:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:48:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:49:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T15:50:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:51:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:52:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:53:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:54:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:55:51.462Z',
+                value: '0.49524752852435283',
+              },
+              {
+                time: '2017-08-27T15:56:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T15:57:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:58:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T15:59:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:00:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:01:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:02:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:03:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:04:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:05:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:06:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:07:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:08:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:09:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:10:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:11:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:12:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:13:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:14:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:15:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:16:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:17:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:18:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:19:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:20:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:21:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:22:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:23:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:24:51.462Z',
+                value: '0.4761859410862754',
+              },
+              {
+                time: '2017-08-27T16:25:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:26:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:27:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:28:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:29:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:30:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:31:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:32:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:33:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:34:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:35:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:36:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:37:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:38:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:39:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:40:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:41:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:42:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:43:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:44:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:45:51.462Z',
+                value: '0.485718911608682',
+              },
+              {
+                time: '2017-08-27T16:46:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:47:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:48:51.462Z',
+                value: '0.4952333787297264',
+              },
+              {
+                time: '2017-08-27T16:49:51.462Z',
+                value: '0.4857096599080009',
+              },
+              {
+                time: '2017-08-27T16:50:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:51:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:52:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:53:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:54:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:55:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T16:56:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:57:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T16:58:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T16:59:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:00:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:01:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:02:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:03:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:04:51.462Z',
+                value: '0.47619501138106085',
+              },
+              {
+                time: '2017-08-27T17:05:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:06:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:07:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:08:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:09:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:10:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:11:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:12:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:13:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:14:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:15:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:16:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:17:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:18:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:19:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:20:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:21:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:22:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:23:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:24:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:25:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:26:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:27:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:28:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:29:51.462Z',
+                value: '0.4761859410862754',
+              },
+              {
+                time: '2017-08-27T17:30:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:31:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:32:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:33:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:34:51.462Z',
+                value: '0.4761859410862754',
+              },
+              {
+                time: '2017-08-27T17:35:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:36:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:37:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:38:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:39:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:40:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:41:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:42:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:43:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:44:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:45:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:46:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:47:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:48:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:49:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:50:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:51:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:52:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:53:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:54:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:55:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T17:56:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:57:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T17:58:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T17:59:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T18:00:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:01:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:02:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:03:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:04:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:05:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:06:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:07:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:08:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:09:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:10:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:11:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:12:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T18:13:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:14:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:15:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:16:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:17:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:18:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:19:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:20:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:21:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:22:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:23:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:24:51.462Z',
+                value: '0.45714285714285713',
+              },
+              {
+                time: '2017-08-27T18:25:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:26:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:27:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:28:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:29:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:30:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:31:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:32:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:33:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:34:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:35:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:36:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:37:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T18:38:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:39:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:40:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:41:51.462Z',
+                value: '0.6190476190476191',
+              },
+              {
+                time: '2017-08-27T18:42:51.462Z',
+                value: '0.6952380952380952',
+              },
+              {
+                time: '2017-08-27T18:43:51.462Z',
+                value: '0.857142857142857',
+              },
+              {
+                time: '2017-08-27T18:44:51.462Z',
+                value: '0.9238095238095239',
+              },
+              {
+                time: '2017-08-27T18:45:51.462Z',
+                value: '0.7428571428571429',
+              },
+              {
+                time: '2017-08-27T18:46:51.462Z',
+                value: '0.8857142857142857',
+              },
+              {
+                time: '2017-08-27T18:47:51.462Z',
+                value: '0.638095238095238',
+              },
+              {
+                time: '2017-08-27T18:48:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:49:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:50:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T18:51:51.462Z',
+                value: '0.47619501138106085',
+              },
+              {
+                time: '2017-08-27T18:52:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:53:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:54:51.462Z',
+                value: '0.4952380952380952',
+              },
+              {
+                time: '2017-08-27T18:55:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:56:51.462Z',
+                value: '0.4857142857142857',
+              },
+              {
+                time: '2017-08-27T18:57:51.462Z',
+                value: '0.47619047619047616',
+              },
+              {
+                time: '2017-08-27T18:58:51.462Z',
+                value: '0.6857142857142856',
+              },
+              {
+                time: '2017-08-27T18:59:51.462Z',
+                value: '0.6952380952380952',
+              },
+              {
+                time: '2017-08-27T19:00:51.462Z',
+                value: '0.5238095238095237',
+              },
+              {
+                time: '2017-08-27T19:01:51.462Z',
+                value: '0.5904761904761905',
+              },
+            ],
+          },
+        ],
+      },
+    ],
+  },
 ];
 
 export function convertDatesMultipleSeries(multipleSeries) {
   const convertedMultiple = multipleSeries;
   multipleSeries.forEach((column, index) => {
     let convertedResult = [];
-    convertedResult = column.queries[0].result.map((resultObj) => {
+    convertedResult = column.queries[0].result.map(resultObj => {
       const convertedMetrics = {};
       convertedMetrics.values = resultObj.values.map(val => ({
-          time: new Date(val.time),
-          value: val.value,
+        time: new Date(val.time),
+        value: val.value,
       }));
       convertedMetrics.metric = resultObj.metric;
       return convertedMetrics;
diff --git a/spec/javascripts/namespace_select_spec.js b/spec/javascripts/namespace_select_spec.js
index 9d7625ca2698f4579333286e9628ee184648edc6..3b2641f7646e8009e9cf4b829b302158c1d556c1 100644
--- a/spec/javascripts/namespace_select_spec.js
+++ b/spec/javascripts/namespace_select_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import NamespaceSelect from '~/namespace_select';
 
 describe('NamespaceSelect', () => {
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 50a5e4ff056b551293603e37cc4154dc9a980aca..5e5d8f8f34f71c9a470ec3ae09f42a0117da94d2 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */
 
+import $ from 'jquery';
 import NewBranchForm from '~/new_branch_form';
 
 (function() {
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 6a7131528a32225dd047f400f3c68a111e827549..224debbeff6e8fb22b4cbccd26cd46c48cec1c9b 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import Autosize from 'autosize';
 import store from '~/notes/stores';
@@ -199,6 +200,20 @@ describe('issue_comment_form component', () => {
           done();
         });
       });
+
+      describe('when clicking close/reopen button', () => {
+        it('should disable button and show a loading spinner', (done) => {
+          const toggleStateButton = vm.$el.querySelector('.js-action-button');
+
+          toggleStateButton.click();
+          Vue.nextTick(() => {
+            expect(toggleStateButton.disabled).toEqual(true);
+            expect(toggleStateButton.querySelector('.js-loading-button-icon')).not.toBeNull();
+
+            done();
+          });
+        });
+      });
     });
 
     describe('issue is confidential', () => {
diff --git a/spec/javascripts/notes/components/diff_file_header_spec.js b/spec/javascripts/notes/components/diff_file_header_spec.js
index aed30a087a63f181f70e1918bd81fa798da39018..ef6d513444ada9fa0a4dd3c9f70097e31e2a9981 100644
--- a/spec/javascripts/notes/components/diff_file_header_spec.js
+++ b/spec/javascripts/notes/components/diff_file_header_spec.js
@@ -1,7 +1,7 @@
 import Vue from 'vue';
 import DiffFileHeader from '~/notes/components/diff_file_header.vue';
 import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import mountComponent from '../../helpers/vue_mount_component_helper';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
 
 const discussionFixture = 'merge_requests/diff_discussion.json';
 
diff --git a/spec/javascripts/notes/components/diff_with_note_spec.js b/spec/javascripts/notes/components/diff_with_note_spec.js
index 7f1f4bf0bcd8f7e1cde6cf14ad5625566837626c..f4ec7132dbd0b378f4058fecfd3e0631dca56414 100644
--- a/spec/javascripts/notes/components/diff_with_note_spec.js
+++ b/spec/javascripts/notes/components/diff_with_note_spec.js
@@ -1,7 +1,7 @@
 import Vue from 'vue';
 import DiffWithNote from '~/notes/components/diff_with_note.vue';
 import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import mountComponent from '../../helpers/vue_mount_component_helper';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
 
 const discussionFixture = 'merge_requests/diff_discussion.json';
 const imageDiscussionFixture = 'merge_requests/image_diff_discussion.json';
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index ab81aabb99280c800874dd69c9d3b58cfad976ea..1dfe890e05e9daf7f243cac2fd515b1a1d7b2976 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -3,7 +3,7 @@ import store from '~/notes/stores';
 import noteActions from '~/notes/components/note_actions.vue';
 import { userDataMock } from '../mock_data';
 
-describe('issse_note_actions component', () => {
+describe('issue_note_actions component', () => {
   let vm;
   let Component;
 
@@ -24,6 +24,7 @@ describe('issse_note_actions component', () => {
         authorId: 26,
         canDelete: true,
         canEdit: true,
+        canAwardEmoji: true,
         canReportAsAbuse: true,
         noteId: 539,
         reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
@@ -70,6 +71,7 @@ describe('issse_note_actions component', () => {
         authorId: 26,
         canDelete: false,
         canEdit: false,
+        canAwardEmoji: false,
         canReportAsAbuse: false,
         noteId: 539,
         reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index e1c612f510092608dd7eddb72642360af60ee8ef..0e792eee5e9aa8aea21cb07b677605b735b2c214 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -1,8 +1,9 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import Vue from 'vue';
 import notesApp from '~/notes/components/notes_app.vue';
 import service from '~/notes/services/notes_service';
-import '~/render_gfm';
+import '~/behaviors/markdown/render_gfm';
 import * as mockData from '../mock_data';
 
 const vueMatchers = {
diff --git a/spec/javascripts/notes/components/note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js
index 15995ec5a055393ae6dac4d41fd5287f92fbce67..1c30d8691b1a235772d0d9e42089ef4331aaabec 100644
--- a/spec/javascripts/notes/components/note_awards_list_spec.js
+++ b/spec/javascripts/notes/components/note_awards_list_spec.js
@@ -29,6 +29,7 @@ describe('note_awards_list component', () => {
         awards: awardsMock,
         noteAuthorId: 2,
         noteId: 545,
+        canAwardEmoji: true,
         toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
       },
     }).$mount();
@@ -43,14 +44,45 @@ describe('note_awards_list component', () => {
     expect(vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]')).toBeDefined();
   });
 
-  it('should be possible to remove awareded emoji', () => {
+  it('should be possible to remove awarded emoji', () => {
     spyOn(vm, 'handleAward').and.callThrough();
+    spyOn(vm, 'toggleAwardRequest').and.callThrough();
     vm.$el.querySelector('.js-awards-block button').click();
 
     expect(vm.handleAward).toHaveBeenCalledWith('flag_tz');
+    expect(vm.toggleAwardRequest).toHaveBeenCalled();
   });
 
   it('should be possible to add new emoji', () => {
     expect(vm.$el.querySelector('.js-add-award')).toBeDefined();
   });
+
+  describe('when the user cannot award emoji', () => {
+    beforeEach(() => {
+      const Component = Vue.extend(awardsNote);
+
+      vm = new Component({
+        store,
+        propsData: {
+          awards: awardsMock,
+          noteAuthorId: 2,
+          noteId: 545,
+          canAwardEmoji: false,
+          toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
+        },
+      }).$mount();
+    });
+
+    it('should not be possible to remove awarded emoji', () => {
+      spyOn(vm, 'toggleAwardRequest').and.callThrough();
+
+      vm.$el.querySelector('.js-awards-block button').click();
+
+      expect(vm.toggleAwardRequest).not.toHaveBeenCalled();
+    });
+
+    it('should not be possible to add new emoji', () => {
+      expect(vm.$el.querySelector('.js-add-award')).toBeNull();
+    });
+  });
 });
diff --git a/spec/javascripts/notes/components/note_body_spec.js b/spec/javascripts/notes/components/note_body_spec.js
index 0ff804f0e55ca4fc2dabae78fb0e94e2ffb0c162..4e551496ff02906f778b13782cb8f3ab12d50727 100644
--- a/spec/javascripts/notes/components/note_body_spec.js
+++ b/spec/javascripts/notes/components/note_body_spec.js
@@ -18,6 +18,7 @@ describe('issue_note_body component', () => {
       propsData: {
         note,
         canEdit: true,
+        canAwardEmoji: true,
       },
     }).$mount();
   });
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 19504e4f7c895172bb90e5ec50f6d5aad83a99a1..cda550760fe97d3b744ef4cff56c4251b9bcf232 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -25,26 +25,34 @@ describe('issue_discussion component', () => {
   });
 
   it('should render user avatar', () => {
-    expect(vm.$el.querySelector('.user-avatar-link')).toBeDefined();
+    expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
   });
 
   it('should render discussion header', () => {
-    expect(vm.$el.querySelector('.discussion-header')).toBeDefined();
+    expect(vm.$el.querySelector('.discussion-header')).not.toBeNull();
     expect(vm.$el.querySelector('.notes').children.length).toEqual(discussionMock.notes.length);
   });
 
   describe('actions', () => {
     it('should render reply button', () => {
-      expect(vm.$el.querySelector('.js-vue-discussion-reply').textContent.trim()).toEqual('Reply...');
+      expect(vm.$el.querySelector('.js-vue-discussion-reply').textContent.trim()).toEqual(
+        'Reply...',
+      );
     });
 
-    it('should toggle reply form', (done) => {
+    it('should toggle reply form', done => {
       vm.$el.querySelector('.js-vue-discussion-reply').click();
       Vue.nextTick(() => {
-        expect(vm.$refs.noteForm).toBeDefined();
+        expect(vm.$refs.noteForm).not.toBeNull();
         expect(vm.isReplying).toEqual(true);
         done();
       });
     });
+
+    it('does not render jump to discussion button', () => {
+      expect(
+        vm.$el.querySelector('*[data-original-title="Jump to next unresolved discussion"]'),
+      ).toBeNull();
+    });
   });
 });
diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js
index 88a7ffb0b9cdf7c84d9d396cc26ee1bbcfa14e5f..cfd037633e9164791810be86bc8f4cb110161c7a 100644
--- a/spec/javascripts/notes/components/noteable_note_spec.js
+++ b/spec/javascripts/notes/components/noteable_note_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import Vue from 'vue';
 import store from '~/notes/stores';
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index bf60cb12f5241739e8e5557399511cb7aec2778d..bfe3a65feee15e72413037612c9c5e202fc2a719 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1,7 +1,6 @@
-/* eslint-disable */
 export const notesDataMock = {
   discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
-  lastFetchedAt: '1501862675',
+  lastFetchedAt: 1501862675,
   markdownDocsPath: '/help/user/markdown',
   newSessionPath: '/users/sign_in?redirect_to_referer=yes',
   notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes',
@@ -10,6 +9,7 @@ export const notesDataMock = {
   totalNotes: 1,
   closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close',
   reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen',
+  canAwardEmoji: true,
 };
 
 export const userDataMock = {
@@ -31,6 +31,7 @@ export const noteableDataMock = {
   current_user: {
     can_create_note: true,
     can_update: true,
+    can_award_emoji: true,
   },
   description: '',
   due_date: null,
@@ -43,7 +44,8 @@ export const noteableDataMock = {
   milestone: null,
   milestone_id: null,
   moved_to_id: null,
-  preview_note_path: '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
+  preview_note_path:
+    '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
   project_id: 2,
   state: 'opened',
   time_estimate: 0,
@@ -52,6 +54,7 @@ export const noteableDataMock = {
   updated_at: '2017-08-04T09:53:01.226Z',
   updated_by_id: 1,
   web_url: '/gitlab-org/gitlab-ce/issues/26',
+  noteableType: 'issue',
 };
 
 export const lastFetchedAt = '1501862675';
@@ -60,465 +63,515 @@ export const individualNote = {
   expanded: true,
   id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
   individual_note: true,
-  notes: [{
-    id: 1390,
-    attachment: {
-      url: null,
-      filename: null,
-      image: false,
-    },
-    author: {
-      id: 1,
-      name: 'Root',
-      username: 'root',
-      state: 'active',
-      avatar_url: 'test',
-      path: '/root',
+  notes: [
+    {
+      id: 1390,
+      attachment: {
+        url: null,
+        filename: null,
+        image: false,
+      },
+      author: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: 'test',
+        path: '/root',
+      },
+      created_at: '2017-08-01T17: 09: 33.762Z',
+      updated_at: '2017-08-01T17: 09: 33.762Z',
+      system: false,
+      noteable_id: 98,
+      noteable_type: 'Issue',
+      type: null,
+      human_access: 'Owner',
+      note: 'sdfdsaf',
+      note_html: "<p dir='auto'>sdfdsaf</p>",
+      current_user: {
+        can_edit: true,
+        can_award_emoji: true,
+      },
+      discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+      emoji_awardable: true,
+      award_emoji: [
+        { name: 'baseball', user: { id: 1, name: 'Root', username: 'root' } },
+        { name: 'art', user: { id: 1, name: 'Root', username: 'root' } },
+      ],
+      toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
+      report_abuse_path:
+        '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390&user_id=1',
+      path: '/gitlab-org/gitlab-ce/notes/1390',
     },
-    created_at: '2017-08-01T17: 09: 33.762Z',
-    updated_at: '2017-08-01T17: 09: 33.762Z',
-    system: false,
-    noteable_id: 98,
-    noteable_type: 'Issue',
-    type: null,
-    human_access: 'Owner',
-    note: 'sdfdsaf',
-    note_html: '<p dir=\'auto\'>sdfdsaf</p>',
-    current_user: { can_edit: true },
-    discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
-    emoji_awardable: true,
-    award_emoji: [
-      { name: 'baseball', user: { id: 1, name: 'Root', username: 'root' } },
-      { name: 'art', user: { id: 1, name: 'Root', username: 'root' } },
-    ],
-    toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
-    report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390&user_id=1',
-    path: '/gitlab-org/gitlab-ce/notes/1390',
-  }],
+  ],
   reply_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
 };
 
 export const note = {
-  "id": 546,
-  "attachment": {
-    "url": null,
-    "filename": null,
-    "image": false
+  id: 546,
+  attachment: {
+    url: null,
+    filename: null,
+    image: false,
   },
-  "author": {
-    "id": 1,
-    "name": "Administrator",
-    "username": "root",
-    "state": "active",
-    "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "path": "/root"
+  author: {
+    id: 1,
+    name: 'Administrator',
+    username: 'root',
+    state: 'active',
+    avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+    path: '/root',
   },
-  "created_at": "2017-08-10T15:24:03.087Z",
-  "updated_at": "2017-08-10T15:24:03.087Z",
-  "system": false,
-  "noteable_id": 67,
-  "noteable_type": "Issue",
-  "noteable_iid": 7,
-  "type": null,
-  "human_access": "Owner",
-  "note": "Vel id placeat reprehenderit sit numquam.",
-  "note_html": "<p dir=\"auto\">Vel id placeat reprehenderit sit numquam.</p>",
-  "current_user": {
-    "can_edit": true
+  created_at: '2017-08-10T15:24:03.087Z',
+  updated_at: '2017-08-10T15:24:03.087Z',
+  system: false,
+  noteable_id: 67,
+  noteable_type: 'Issue',
+  noteable_iid: 7,
+  type: null,
+  human_access: 'Owner',
+  note: 'Vel id placeat reprehenderit sit numquam.',
+  note_html: '<p dir="auto">Vel id placeat reprehenderit sit numquam.</p>',
+  current_user: {
+    can_edit: true,
+    can_award_emoji: true,
   },
-  "discussion_id": "d3842a451b7f3d9a5dfce329515127b2d29a4cd0",
-  "emoji_awardable": true,
-  "award_emoji": [{
-    "name": "baseball",
-    "user": {
-      "id": 1,
-      "name": "Administrator",
-      "username": "root"
-    }
-  }, {
-    "name": "bath_tone3",
-    "user": {
-      "id": 1,
-      "name": "Administrator",
-      "username": "root"
-    }
-  }],
-  "toggle_award_path": "/gitlab-org/gitlab-ce/notes/546/toggle_award_emoji",
-  "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1",
-  "path": "/gitlab-org/gitlab-ce/notes/546"
-  }
+  discussion_id: 'd3842a451b7f3d9a5dfce329515127b2d29a4cd0',
+  emoji_awardable: true,
+  award_emoji: [
+    {
+      name: 'baseball',
+      user: {
+        id: 1,
+        name: 'Administrator',
+        username: 'root',
+      },
+    },
+    {
+      name: 'bath_tone3',
+      user: {
+        id: 1,
+        name: 'Administrator',
+        username: 'root',
+      },
+    },
+  ],
+  toggle_award_path: '/gitlab-org/gitlab-ce/notes/546/toggle_award_emoji',
+  report_abuse_path:
+    '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1',
+  path: '/gitlab-org/gitlab-ce/notes/546',
+};
 
 export const discussionMock = {
   id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
   reply_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
   expanded: true,
-  notes: [{
-    id: 1395,
-    attachment: {
-      url: null,
-      filename: null,
-      image: false,
-    },
-    author: {
-      id: 1,
-      name: 'Root',
-      username: 'root',
-      state: 'active',
-      avatar_url: null,
-      path: '/root',
-    },
-    created_at: '2017-08-02T10:51:58.559Z',
-    updated_at: '2017-08-02T10:51:58.559Z',
-    system: false,
-    noteable_id: 98,
-    noteable_type: 'Issue',
-    type: 'DiscussionNote',
-    human_access: 'Owner',
-    note: 'THIS IS A DICUSSSION!',
-    note_html: '<p dir=\'auto\'>THIS IS A DICUSSSION!</p>',
-    current_user: {
-      can_edit: true,
-    },
-    discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
-    emoji_awardable: true,
-    award_emoji: [],
-    toggle_award_path: '/gitlab-org/gitlab-ce/notes/1395/toggle_award_emoji',
-    report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1395&user_id=1',
-    path: '/gitlab-org/gitlab-ce/notes/1395',
-  }, {
-    id: 1396,
-    attachment: {
-      url: null,
-      filename: null,
-      image: false,
-    },
-    author: {
-      id: 1,
-      name: 'Root',
-      username: 'root',
-      state: 'active',
-      avatar_url: null,
-      path: '/root',
-    },
-    created_at: '2017-08-02T10:56:50.980Z',
-    updated_at: '2017-08-03T14:19:35.691Z',
-    system: false,
-    noteable_id: 98,
-    noteable_type: 'Issue',
-    type: 'DiscussionNote',
-    human_access: 'Owner',
-    note: 'sadfasdsdgdsf',
-    note_html: '<p dir=\'auto\'>sadfasdsdgdsf</p>',
-    last_edited_at: '2017-08-03T14:19:35.691Z',
-    last_edited_by: {
-      id: 1,
-      name: 'Root',
-      username: 'root',
-      state: 'active',
-      avatar_url: null,
-      path: '/root',
-    },
-    current_user: {
-      can_edit: true,
+  notes: [
+    {
+      id: 1395,
+      attachment: {
+        url: null,
+        filename: null,
+        image: false,
+      },
+      author: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: null,
+        path: '/root',
+      },
+      created_at: '2017-08-02T10:51:58.559Z',
+      updated_at: '2017-08-02T10:51:58.559Z',
+      system: false,
+      noteable_id: 98,
+      noteable_type: 'Issue',
+      type: 'DiscussionNote',
+      human_access: 'Owner',
+      note: 'THIS IS A DICUSSSION!',
+      note_html: "<p dir='auto'>THIS IS A DICUSSSION!</p>",
+      current_user: {
+        can_edit: true,
+        can_award_emoji: true,
+      },
+      discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
+      emoji_awardable: true,
+      award_emoji: [],
+      toggle_award_path: '/gitlab-org/gitlab-ce/notes/1395/toggle_award_emoji',
+      report_abuse_path:
+        '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1395&user_id=1',
+      path: '/gitlab-org/gitlab-ce/notes/1395',
     },
-    discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
-    emoji_awardable: true,
-    award_emoji: [],
-    toggle_award_path: '/gitlab-org/gitlab-ce/notes/1396/toggle_award_emoji',
-    report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1396&user_id=1',
-    path: '/gitlab-org/gitlab-ce/notes/1396',
-  }, {
-    id: 1437,
-    attachment: {
-      url: null,
-      filename: null,
-      image: false,
+    {
+      id: 1396,
+      attachment: {
+        url: null,
+        filename: null,
+        image: false,
+      },
+      author: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: null,
+        path: '/root',
+      },
+      created_at: '2017-08-02T10:56:50.980Z',
+      updated_at: '2017-08-03T14:19:35.691Z',
+      system: false,
+      noteable_id: 98,
+      noteable_type: 'Issue',
+      type: 'DiscussionNote',
+      human_access: 'Owner',
+      note: 'sadfasdsdgdsf',
+      note_html: "<p dir='auto'>sadfasdsdgdsf</p>",
+      last_edited_at: '2017-08-03T14:19:35.691Z',
+      last_edited_by: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: null,
+        path: '/root',
+      },
+      current_user: {
+        can_edit: true,
+        can_award_emoji: true,
+      },
+      discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
+      emoji_awardable: true,
+      award_emoji: [],
+      toggle_award_path: '/gitlab-org/gitlab-ce/notes/1396/toggle_award_emoji',
+      report_abuse_path:
+        '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1396&user_id=1',
+      path: '/gitlab-org/gitlab-ce/notes/1396',
     },
-    author: {
-      id: 1,
-      name: 'Root',
-      username: 'root',
-      state: 'active',
-      avatar_url: null,
-      path: '/root',
+    {
+      id: 1437,
+      attachment: {
+        url: null,
+        filename: null,
+        image: false,
+      },
+      author: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: null,
+        path: '/root',
+      },
+      created_at: '2017-08-03T18:11:18.780Z',
+      updated_at: '2017-08-04T09:52:31.062Z',
+      system: false,
+      noteable_id: 98,
+      noteable_type: 'Issue',
+      type: 'DiscussionNote',
+      human_access: 'Owner',
+      note: 'adsfasf Should disappear',
+      note_html: "<p dir='auto'>adsfasf Should disappear</p>",
+      last_edited_at: '2017-08-04T09:52:31.062Z',
+      last_edited_by: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: null,
+        path: '/root',
+      },
+      current_user: {
+        can_edit: true,
+        can_award_emoji: true,
+      },
+      discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
+      emoji_awardable: true,
+      award_emoji: [],
+      toggle_award_path: '/gitlab-org/gitlab-ce/notes/1437/toggle_award_emoji',
+      report_abuse_path:
+        '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1437&user_id=1',
+      path: '/gitlab-org/gitlab-ce/notes/1437',
     },
-    created_at: '2017-08-03T18:11:18.780Z',
-    updated_at: '2017-08-04T09:52:31.062Z',
-    system: false,
-    noteable_id: 98,
-    noteable_type: 'Issue',
-    type: 'DiscussionNote',
-    human_access: 'Owner',
-    note: 'adsfasf Should disappear',
-    note_html: '<p dir=\'auto\'>adsfasf Should disappear</p>',
-    last_edited_at: '2017-08-04T09:52:31.062Z',
-    last_edited_by: {
+  ],
+  individual_note: false,
+};
+
+export const loggedOutnoteableData = {
+  id: 98,
+  iid: 26,
+  author_id: 1,
+  description: '',
+  lock_version: 1,
+  milestone_id: null,
+  state: 'opened',
+  title: 'asdsa',
+  updated_by_id: 1,
+  created_at: '2017-02-07T10:11:18.395Z',
+  updated_at: '2017-08-08T10:22:51.564Z',
+  time_estimate: 0,
+  total_time_spent: 0,
+  human_time_estimate: null,
+  human_total_time_spent: null,
+  milestone: null,
+  labels: [],
+  branch_name: null,
+  confidential: false,
+  assignees: [
+    {
       id: 1,
       name: 'Root',
       username: 'root',
       state: 'active',
       avatar_url: null,
-      path: '/root',
+      web_url: 'http://localhost:3000/root',
     },
-    current_user: {
-      can_edit: true,
-    },
-    discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
-    emoji_awardable: true,
-    award_emoji: [],
-    toggle_award_path: '/gitlab-org/gitlab-ce/notes/1437/toggle_award_emoji',
-    report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1437&user_id=1',
-    path: '/gitlab-org/gitlab-ce/notes/1437',
-  }],
-  individual_note: false,
-};
-
-export const loggedOutnoteableData = {
-  "id": 98,
-  "iid": 26,
-  "author_id": 1,
-  "description": "",
-  "lock_version": 1,
-  "milestone_id": null,
-  "state": "opened",
-  "title": "asdsa",
-  "updated_by_id": 1,
-  "created_at": "2017-02-07T10:11:18.395Z",
-  "updated_at": "2017-08-08T10:22:51.564Z",
-  "time_estimate": 0,
-  "total_time_spent": 0,
-  "human_time_estimate": null,
-  "human_total_time_spent": null,
-  "milestone": null,
-  "labels": [],
-  "branch_name": null,
-  "confidential": false,
-  "assignees": [{
-    "id": 1,
-    "name": "Root",
-    "username": "root",
-    "state": "active",
-    "avatar_url": null,
-    "web_url": "http://localhost:3000/root"
-  }],
-  "due_date": null,
-  "moved_to_id": null,
-  "project_id": 2,
-  "web_url": "/gitlab-org/gitlab-ce/issues/26",
-  "current_user": {
-    "can_create_note": false,
-    "can_update": false
+  ],
+  due_date: null,
+  moved_to_id: null,
+  project_id: 2,
+  web_url: '/gitlab-org/gitlab-ce/issues/26',
+  current_user: {
+    can_create_note: false,
+    can_update: false,
   },
-  "create_note_path": "/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue",
-  "preview_note_path": "/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue"
-}
+  create_note_path: '/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue',
+  preview_note_path:
+    '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
+};
 
 export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
-  'GET': {
-    '/gitlab-org/gitlab-ce/issues/26/discussions.json': [{
-      "id": "0fb4e0e3f9276e55ff32eb4195add694aece4edd",
-      "reply_id": "0fb4e0e3f9276e55ff32eb4195add694aece4edd",
-      "expanded": true,
-      "notes": [{
-        "id": 1390,
-        "attachment": {
-          "url": null,
-          "filename": null,
-          "image": false
-        },
-        "author": {
-          "id": 1,
-          "name": "Root",
-          "username": "root",
-          "state": "active",
-          "avatar_url": null,
-          "path": "/root"
-        },
-        "created_at": "2017-08-01T17:09:33.762Z",
-        "updated_at": "2017-08-01T17:09:33.762Z",
-        "system": false,
-        "noteable_id": 98,
-        "noteable_type": "Issue",
-        "type": null,
-        "human_access": "Owner",
-        "note": "sdfdsaf",
-        "note_html": "\u003cp dir=\"auto\"\u003esdfdsaf\u003c/p\u003e",
-        "current_user": {
-          "can_edit": true
-        },
-        "discussion_id": "0fb4e0e3f9276e55ff32eb4195add694aece4edd",
-        "emoji_awardable": true,
-        "award_emoji": [{
-          "name": "baseball",
-          "user": {
-            "id": 1,
-            "name": "Root",
-            "username": "root"
-          }
-        }, {
-          "name": "art",
-          "user": {
-            "id": 1,
-            "name": "Root",
-            "username": "root"
-          }
-        }],
-        "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji",
-        "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390\u0026user_id=1",
-        "path": "/gitlab-org/gitlab-ce/notes/1390"
-      }],
-      "individual_note": true
-      }, {
-      "id": "70d5c92a4039a36c70100c6691c18c27e4b0a790",
-      "reply_id": "70d5c92a4039a36c70100c6691c18c27e4b0a790",
-      "expanded": true,
-      "notes": [{
-        "id": 1391,
-        "attachment": {
-          "url": null,
-          "filename": null,
-          "image": false
-        },
-        "author": {
-          "id": 1,
-          "name": "Root",
-          "username": "root",
-          "state": "active",
-          "avatar_url": null,
-          "path": "/root"
-        },
-        "created_at": "2017-08-02T10:51:38.685Z",
-        "updated_at": "2017-08-02T10:51:38.685Z",
-        "system": false,
-        "noteable_id": 98,
-        "noteable_type": "Issue",
-        "type": null,
-        "human_access": "Owner",
-        "note": "New note!",
-        "note_html": "\u003cp dir=\"auto\"\u003eNew note!\u003c/p\u003e",
-        "current_user": {
-          "can_edit": true
-        },
-        "discussion_id": "70d5c92a4039a36c70100c6691c18c27e4b0a790",
-        "emoji_awardable": true,
-        "award_emoji": [],
-        "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1391/toggle_award_emoji",
-        "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1391\u0026user_id=1",
-        "path": "/gitlab-org/gitlab-ce/notes/1391"
-      }],
-      "individual_note": true
-    }],
+  GET: {
+    '/gitlab-org/gitlab-ce/issues/26/discussions.json': [
+      {
+        id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+        reply_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+        expanded: true,
+        notes: [
+          {
+            id: 1390,
+            attachment: {
+              url: null,
+              filename: null,
+              image: false,
+            },
+            author: {
+              id: 1,
+              name: 'Root',
+              username: 'root',
+              state: 'active',
+              avatar_url: null,
+              path: '/root',
+            },
+            created_at: '2017-08-01T17:09:33.762Z',
+            updated_at: '2017-08-01T17:09:33.762Z',
+            system: false,
+            noteable_id: 98,
+            noteable_type: 'Issue',
+            type: null,
+            human_access: 'Owner',
+            note: 'sdfdsaf',
+            note_html: '\u003cp dir="auto"\u003esdfdsaf\u003c/p\u003e',
+            current_user: {
+              can_edit: true,
+              can_award_emoji: true,
+            },
+            discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+            emoji_awardable: true,
+            award_emoji: [
+              {
+                name: 'baseball',
+                user: {
+                  id: 1,
+                  name: 'Root',
+                  username: 'root',
+                },
+              },
+              {
+                name: 'art',
+                user: {
+                  id: 1,
+                  name: 'Root',
+                  username: 'root',
+                },
+              },
+            ],
+            toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
+            report_abuse_path:
+              '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390\u0026user_id=1',
+            path: '/gitlab-org/gitlab-ce/notes/1390',
+          },
+        ],
+        individual_note: true,
+      },
+      {
+        id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
+        reply_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
+        expanded: true,
+        notes: [
+          {
+            id: 1391,
+            attachment: {
+              url: null,
+              filename: null,
+              image: false,
+            },
+            author: {
+              id: 1,
+              name: 'Root',
+              username: 'root',
+              state: 'active',
+              avatar_url: null,
+              path: '/root',
+            },
+            created_at: '2017-08-02T10:51:38.685Z',
+            updated_at: '2017-08-02T10:51:38.685Z',
+            system: false,
+            noteable_id: 98,
+            noteable_type: 'Issue',
+            type: null,
+            human_access: 'Owner',
+            note: 'New note!',
+            note_html: '\u003cp dir="auto"\u003eNew note!\u003c/p\u003e',
+            current_user: {
+              can_edit: true,
+              can_award_emoji: true,
+            },
+            discussion_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
+            emoji_awardable: true,
+            award_emoji: [],
+            toggle_award_path: '/gitlab-org/gitlab-ce/notes/1391/toggle_award_emoji',
+            report_abuse_path:
+              '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1391\u0026user_id=1',
+            path: '/gitlab-org/gitlab-ce/notes/1391',
+          },
+        ],
+        individual_note: true,
+      },
+    ],
     '/gitlab-org/gitlab-ce/noteable/issue/98/notes': {
       last_fetched_at: 1512900838,
       notes: [],
     },
   },
-  'PUT': {
+  PUT: {
     '/gitlab-org/gitlab-ce/notes/1471': {
-      "commands_changes": null,
-      "valid": true,
-      "id": 1471,
-      "attachment": null,
-      "author": {
-        "id": 1,
-        "name": "Root",
-        "username": "root",
-        "state": "active",
-        "avatar_url": null,
-        "path": "/root"
+      commands_changes: null,
+      valid: true,
+      id: 1471,
+      attachment: null,
+      author: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: null,
+        path: '/root',
       },
-      "created_at": "2017-08-08T16:53:00.666Z",
-      "updated_at": "2017-12-10T11:03:21.876Z",
-      "system": false,
-      "noteable_id": 124,
-      "noteable_type": "Issue",
-      "noteable_iid": 29,
-      "type": "DiscussionNote",
-      "human_access": "Owner",
-      "note": "Adding a comment",
-      "note_html": "\u003cp dir=\"auto\"\u003eAdding a comment\u003c/p\u003e",
-      "last_edited_at": "2017-12-10T11:03:21.876Z",
-      "last_edited_by": {
-        "id": 1,
-        "name": 'Root',
-        "username": 'root',
-        "state": 'active',
-        "avatar_url": null,
-        "path": '/root',
+      created_at: '2017-08-08T16:53:00.666Z',
+      updated_at: '2017-12-10T11:03:21.876Z',
+      system: false,
+      noteable_id: 124,
+      noteable_type: 'Issue',
+      noteable_iid: 29,
+      type: 'DiscussionNote',
+      human_access: 'Owner',
+      note: 'Adding a comment',
+      note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e',
+      last_edited_at: '2017-12-10T11:03:21.876Z',
+      last_edited_by: {
+        id: 1,
+        name: 'Root',
+        username: 'root',
+        state: 'active',
+        avatar_url: null,
+        path: '/root',
       },
-      "current_user": {
-        "can_edit": true
+      current_user: {
+        can_edit: true,
+        can_award_emoji: true,
       },
-      "discussion_id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
-      "emoji_awardable": true,
-      "award_emoji": [],
-      "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji",
-      "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1",
-      "path": "/gitlab-org/gitlab-ce/notes/1471"
+      discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+      emoji_awardable: true,
+      award_emoji: [],
+      toggle_award_path: '/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji',
+      report_abuse_path:
+        '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1',
+      path: '/gitlab-org/gitlab-ce/notes/1471',
     },
-  }
+  },
 };
 
 export const DISCUSSION_NOTE_RESPONSE_MAP = {
   ...INDIVIDUAL_NOTE_RESPONSE_MAP,
-  'GET': {
+  GET: {
     ...INDIVIDUAL_NOTE_RESPONSE_MAP.GET,
-    '/gitlab-org/gitlab-ce/issues/26/discussions.json': [{
-      "id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
-      "reply_id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
-      "expanded": true,
-      "notes": [{
-        "id": 1471,
-        "attachment": {
-          "url": null,
-          "filename": null,
-          "image": false
-        },
-        "author": {
-          "id": 1,
-          "name": "Root",
-          "username": "root",
-          "state": "active",
-          "avatar_url": null,
-          "path": "/root"
-        },
-        "created_at": "2017-08-08T16:53:00.666Z",
-        "updated_at": "2017-08-08T16:53:00.666Z",
-        "system": false,
-        "noteable_id": 124,
-        "noteable_type": "Issue",
-        "noteable_iid": 29,
-        "type": "DiscussionNote",
-        "human_access": "Owner",
-        "note": "Adding a comment",
-        "note_html": "\u003cp dir=\"auto\"\u003eAdding a comment\u003c/p\u003e",
-        "current_user": {
-          "can_edit": true
-        },
-        "discussion_id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
-        "emoji_awardable": true,
-        "award_emoji": [],
-        "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji",
-        "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1",
-        "path": "/gitlab-org/gitlab-ce/notes/1471"
-      }],
-      "individual_note": false
-    }],
+    '/gitlab-org/gitlab-ce/issues/26/discussions.json': [
+      {
+        id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+        reply_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+        expanded: true,
+        notes: [
+          {
+            id: 1471,
+            attachment: {
+              url: null,
+              filename: null,
+              image: false,
+            },
+            author: {
+              id: 1,
+              name: 'Root',
+              username: 'root',
+              state: 'active',
+              avatar_url: null,
+              path: '/root',
+            },
+            created_at: '2017-08-08T16:53:00.666Z',
+            updated_at: '2017-08-08T16:53:00.666Z',
+            system: false,
+            noteable_id: 124,
+            noteable_type: 'Issue',
+            noteable_iid: 29,
+            type: 'DiscussionNote',
+            human_access: 'Owner',
+            note: 'Adding a comment',
+            note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e',
+            current_user: {
+              can_edit: true,
+              can_award_emoji: true,
+            },
+            discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+            emoji_awardable: true,
+            award_emoji: [],
+            toggle_award_path: '/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji',
+            report_abuse_path:
+              '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1',
+            path: '/gitlab-org/gitlab-ce/notes/1471',
+          },
+        ],
+        individual_note: false,
+      },
+    ],
   },
 };
 
 export function individualNoteInterceptor(request, next) {
   const body = INDIVIDUAL_NOTE_RESPONSE_MAP[request.method.toUpperCase()][request.url];
 
-  next(request.respondWith(JSON.stringify(body), {
-    status: 200,
-  }));
+  next(
+    request.respondWith(JSON.stringify(body), {
+      status: 200,
+    }),
+  );
 }
 
 export function discussionNoteInterceptor(request, next) {
   const body = DISCUSSION_NOTE_RESPONSE_MAP[request.method.toUpperCase()][request.url];
 
-  next(request.respondWith(JSON.stringify(body), {
-    status: 200,
-  }));
+  next(
+    request.respondWith(JSON.stringify(body), {
+      status: 200,
+    }),
+  );
 }
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index ab80ed7bbfb51802dd9659976bfca8cfde2503eb..520a25cc5c6b8b40406d032f689f0cf995fac9cf 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,10 +1,17 @@
 import Vue from 'vue';
 import _ from 'underscore';
+import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
 import * as actions from '~/notes/stores/actions';
 import store from '~/notes/stores';
 import testAction from '../../helpers/vuex_action_helper';
 import { resetStore } from '../helpers';
-import { discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
+import {
+  discussionMock,
+  notesDataMock,
+  userDataMock,
+  noteableDataMock,
+  individualNote,
+} from '../mock_data';
 
 describe('Actions Notes Store', () => {
   afterEach(() => {
@@ -12,66 +19,103 @@ describe('Actions Notes Store', () => {
   });
 
   describe('setNotesData', () => {
-    it('should set received notes data', (done) => {
-      testAction(actions.setNotesData, null, { notesData: {} }, [
-        { type: 'SET_NOTES_DATA', payload: notesDataMock },
-      ], done);
+    it('should set received notes data', done => {
+      testAction(
+        actions.setNotesData,
+        notesDataMock,
+        { notesData: {} },
+        [{ type: 'SET_NOTES_DATA', payload: notesDataMock }],
+        [],
+        done,
+      );
     });
   });
 
   describe('setNoteableData', () => {
-    it('should set received issue data', (done) => {
-      testAction(actions.setNoteableData, null, { noteableData: {} }, [
-        { type: 'SET_NOTEABLE_DATA', payload: noteableDataMock },
-      ], done);
+    it('should set received issue data', done => {
+      testAction(
+        actions.setNoteableData,
+        noteableDataMock,
+        { noteableData: {} },
+        [{ type: 'SET_NOTEABLE_DATA', payload: noteableDataMock }],
+        [],
+        done,
+      );
     });
   });
 
   describe('setUserData', () => {
-    it('should set received user data', (done) => {
-      testAction(actions.setUserData, null, { userData: {} }, [
-        { type: 'SET_USER_DATA', payload: userDataMock },
-      ], done);
+    it('should set received user data', done => {
+      testAction(
+        actions.setUserData,
+        userDataMock,
+        { userData: {} },
+        [{ type: 'SET_USER_DATA', payload: userDataMock }],
+        [],
+        done,
+      );
     });
   });
 
   describe('setLastFetchedAt', () => {
-    it('should set received timestamp', (done) => {
-      testAction(actions.setLastFetchedAt, null, { lastFetchedAt: {} }, [
-        { type: 'SET_LAST_FETCHED_AT', payload: 'timestamp' },
-      ], done);
+    it('should set received timestamp', done => {
+      testAction(
+        actions.setLastFetchedAt,
+        'timestamp',
+        { lastFetchedAt: {} },
+        [{ type: 'SET_LAST_FETCHED_AT', payload: 'timestamp' }],
+        [],
+        done,
+      );
     });
   });
 
   describe('setInitialNotes', () => {
-    it('should set initial notes', (done) => {
-      testAction(actions.setInitialNotes, null, { notes: [] }, [
-        { type: 'SET_INITIAL_NOTES', payload: [individualNote] },
-      ], done);
+    it('should set initial notes', done => {
+      testAction(
+        actions.setInitialNotes,
+        [individualNote],
+        { notes: [] },
+        [{ type: 'SET_INITIAL_NOTES', payload: [individualNote] }],
+        [],
+        done,
+      );
     });
   });
 
   describe('setTargetNoteHash', () => {
-    it('should set target note hash', (done) => {
-      testAction(actions.setTargetNoteHash, null, { notes: [] }, [
-        { type: 'SET_TARGET_NOTE_HASH', payload: 'hash' },
-      ], done);
+    it('should set target note hash', done => {
+      testAction(
+        actions.setTargetNoteHash,
+        'hash',
+        { notes: [] },
+        [{ type: 'SET_TARGET_NOTE_HASH', payload: 'hash' }],
+        [],
+        done,
+      );
     });
   });
 
   describe('toggleDiscussion', () => {
-    it('should toggle discussion', (done) => {
-      testAction(actions.toggleDiscussion, null, { notes: [discussionMock] }, [
-        { type: 'TOGGLE_DISCUSSION', payload: { discussionId: discussionMock.id } },
-      ], done);
+    it('should toggle discussion', done => {
+      testAction(
+        actions.toggleDiscussion,
+        { discussionId: discussionMock.id },
+        { notes: [discussionMock] },
+        [{ type: 'TOGGLE_DISCUSSION', payload: { discussionId: discussionMock.id } }],
+        [],
+        done,
+      );
     });
   });
 
   describe('async methods', () => {
     const interceptor = (request, next) => {
-      next(request.respondWith(JSON.stringify({}), {
-        status: 200,
-      }));
+      next(
+        request.respondWith(JSON.stringify({}), {
+          status: 200,
+        }),
+      );
     };
 
     beforeEach(() => {
@@ -83,10 +127,12 @@ describe('Actions Notes Store', () => {
     });
 
     describe('closeIssue', () => {
-      it('sets state as closed', (done) => {
-        store.dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
+      it('sets state as closed', done => {
+        store
+          .dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
           .then(() => {
             expect(store.state.noteableData.state).toEqual('closed');
+            expect(store.state.isToggleStateButtonLoading).toEqual(false);
             done();
           })
           .catch(done.fail);
@@ -94,10 +140,12 @@ describe('Actions Notes Store', () => {
     });
 
     describe('reopenIssue', () => {
-      it('sets state as reopened', (done) => {
-        store.dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
+      it('sets state as reopened', done => {
+        store
+          .dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
           .then(() => {
             expect(store.state.noteableData.state).toEqual('reopened');
+            expect(store.state.isToggleStateButtonLoading).toEqual(false);
             done();
           })
           .catch(done.fail);
@@ -107,7 +155,7 @@ describe('Actions Notes Store', () => {
 
   describe('emitStateChangedEvent', () => {
     it('emits an event on the document', () => {
-      document.addEventListener('issuable_vue_app:change', (event) => {
+      document.addEventListener('issuable_vue_app:change', event => {
         expect(event.detail.data).toEqual({ id: '1', state: 'closed' });
         expect(event.detail.isClosed).toEqual(false);
       });
@@ -116,17 +164,111 @@ describe('Actions Notes Store', () => {
     });
   });
 
+  describe('toggleStateButtonLoading', () => {
+    it('should set loading as true', done => {
+      testAction(
+        actions.toggleStateButtonLoading,
+        true,
+        {},
+        [{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: true }],
+        [],
+        done,
+      );
+    });
+
+    it('should set loading as false', done => {
+      testAction(
+        actions.toggleStateButtonLoading,
+        false,
+        {},
+        [{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: false }],
+        [],
+        done,
+      );
+    });
+  });
+
   describe('toggleIssueLocalState', () => {
-    it('sets issue state as closed', (done) => {
-      testAction(actions.toggleIssueLocalState, 'closed', {}, [
-        { type: 'CLOSE_ISSUE', payload: 'closed' },
-      ], done);
+    it('sets issue state as closed', done => {
+      testAction(actions.toggleIssueLocalState, 'closed', {}, [{ type: 'CLOSE_ISSUE' }], [], done);
     });
 
-    it('sets issue state as reopened', (done) => {
-      testAction(actions.toggleIssueLocalState, 'reopened', {}, [
-        { type: 'REOPEN_ISSUE', payload: 'reopened' },
-      ], done);
+    it('sets issue state as reopened', done => {
+      testAction(actions.toggleIssueLocalState, 'reopened', {}, [{ type: 'REOPEN_ISSUE' }], [], done);
+    });
+  });
+
+  describe('poll', () => {
+    beforeEach(done => {
+      jasmine.clock().install();
+
+      spyOn(Vue.http, 'get').and.callThrough();
+
+      store
+        .dispatch('setNotesData', notesDataMock)
+        .then(done)
+        .catch(done.fail);
+    });
+
+    afterEach(() => {
+      jasmine.clock().uninstall();
+    });
+
+    it('calls service with last fetched state', done => {
+      const interceptor = (request, next) => {
+        next(
+          request.respondWith(
+            JSON.stringify({
+              notes: [],
+              last_fetched_at: '123456',
+            }),
+            {
+              status: 200,
+              headers: {
+                'poll-interval': '1000',
+              },
+            },
+          ),
+        );
+      };
+
+      Vue.http.interceptors.push(interceptor);
+      Vue.http.interceptors.push(headersInterceptor);
+
+      store
+        .dispatch('poll')
+        .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
+        .then(() => {
+          expect(Vue.http.get).toHaveBeenCalledWith(jasmine.anything(), {
+            url: jasmine.anything(),
+            method: 'get',
+            headers: {
+              'X-Last-Fetched-At': undefined,
+            },
+          });
+          expect(store.state.lastFetchedAt).toBe('123456');
+
+          jasmine.clock().tick(1500);
+        })
+        .then(
+          () =>
+            new Promise(resolve => {
+              requestAnimationFrame(resolve);
+            }),
+        )
+        .then(() => {
+          expect(Vue.http.get.calls.count()).toBe(2);
+          expect(Vue.http.get.calls.mostRecent().args[1].headers).toEqual({
+            'X-Last-Fetched-At': '123456',
+          });
+        })
+        .then(() => store.dispatch('stopPolling'))
+        .then(() => {
+          Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+          Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
+        })
+        .then(done)
+        .catch(done.fail);
     });
   });
 });
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index e4baefc5bfc850c4eaccee30a3dfe52ae6769d29..98f101d6bc57abfd4f61d0ef0ec99b9b65998e16 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -101,10 +101,21 @@ describe('Notes Store mutations', () => {
       const state = {
         notes: [],
       };
+      const legacyNote = {
+        id: 2,
+        individual_note: true,
+        notes: [{
+          note: '1',
+        }, {
+          note: '2',
+        }],
+      };
 
-      mutations.SET_INITIAL_NOTES(state, [note]);
+      mutations.SET_INITIAL_NOTES(state, [note, legacyNote]);
       expect(state.notes[0].id).toEqual(note.id);
-      expect(state.notes.length).toEqual(1);
+      expect(state.notes[1].notes[0].note).toBe(legacyNote.notes[0].note);
+      expect(state.notes[2].notes[0].note).toBe(legacyNote.notes[1].note);
+      expect(state.notes.length).toEqual(3);
     });
   });
 
@@ -217,4 +228,70 @@ describe('Notes Store mutations', () => {
       expect(state.notes[0].notes[0].note).toEqual('Foo');
     });
   });
+
+  describe('CLOSE_ISSUE', () => {
+    it('should set issue as closed', () => {
+      const state = {
+        notes: [],
+        targetNoteHash: null,
+        lastFetchedAt: null,
+        isToggleStateButtonLoading: false,
+        notesData: {},
+        userData: {},
+        noteableData: {},
+      };
+
+      mutations.CLOSE_ISSUE(state);
+      expect(state.noteableData.state).toEqual('closed');
+    });
+  });
+
+  describe('REOPEN_ISSUE', () => {
+    it('should set issue as closed', () => {
+      const state = {
+        notes: [],
+        targetNoteHash: null,
+        lastFetchedAt: null,
+        isToggleStateButtonLoading: false,
+        notesData: {},
+        userData: {},
+        noteableData: {},
+      };
+
+      mutations.REOPEN_ISSUE(state);
+      expect(state.noteableData.state).toEqual('reopened');
+    });
+  });
+
+  describe('TOGGLE_STATE_BUTTON_LOADING', () => {
+    it('should set isToggleStateButtonLoading as true', () => {
+      const state = {
+        notes: [],
+        targetNoteHash: null,
+        lastFetchedAt: null,
+        isToggleStateButtonLoading: false,
+        notesData: {},
+        userData: {},
+        noteableData: {},
+      };
+
+      mutations.TOGGLE_STATE_BUTTON_LOADING(state, true);
+      expect(state.isToggleStateButtonLoading).toEqual(true);
+    });
+
+    it('should set isToggleStateButtonLoading as false', () => {
+      const state = {
+        notes: [],
+        targetNoteHash: null,
+        lastFetchedAt: null,
+        isToggleStateButtonLoading: true,
+        notesData: {},
+        userData: {},
+        noteableData: {},
+      };
+
+      mutations.TOGGLE_STATE_BUTTON_LOADING(state, false);
+      expect(state.isToggleStateButtonLoading).toEqual(false);
+    });
+  });
 });
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index d4a148e6ab10c410833627b7f3a4e8a5db7d2123..ec56ab0e2f05d241ba803823491a360802598eed 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,4 +1,5 @@
 /* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
+import $ from 'jquery';
 import _ from 'underscore';
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
@@ -6,7 +7,7 @@ import * as urlUtils from '~/lib/utils/url_utility';
 import 'autosize';
 import '~/gl_form';
 import '~/lib/utils/text_utility';
-import '~/render_gfm';
+import '~/behaviors/markdown/render_gfm';
 import Notes from '~/notes';
 import timeoutPromise from './helpers/set_timeout_promise_helper';
 
@@ -15,15 +16,15 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
   window.gl = window.gl || {};
   gl.utils = gl.utils || {};
 
-  const htmlEscape = (comment) => {
-    const escapedString = comment.replace(/["&'<>]/g, (a) => {
+  const htmlEscape = comment => {
+    const escapedString = comment.replace(/["&'<>]/g, a => {
       const escapedToken = {
         '&': '&amp;',
         '<': '&lt;',
         '>': '&gt;',
         '"': '&quot;',
         "'": '&#x27;',
-        '`': '&#x60;'
+        '`': '&#x60;',
       }[a];
 
       return escapedToken;
@@ -38,7 +39,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
     var commentsTemplate = 'merge_requests/merge_request_with_comment.html.raw';
     preloadFixtures(commentsTemplate);
 
-    beforeEach(function () {
+    beforeEach(function() {
       loadFixtures(commentsTemplate);
       gl.utils.disableButtonIfEmptyField = _.noop;
       window.project_uploads_path = 'http://test.host/uploads';
@@ -50,6 +51,17 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       $('body').removeAttr('data-page');
     });
 
+    describe('addBinding', () => {
+      it('calls postComment when comment button is clicked', () => {
+        spyOn(Notes.prototype, 'postComment');
+        this.notes = new Notes('', []);
+
+        $('.js-comment-button').click();
+
+        expect(Notes.prototype.postComment).toHaveBeenCalled();
+      });
+    });
+
     describe('task lists', function() {
       let mock;
 
@@ -57,7 +69,13 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         spyOn(axios, 'patch').and.callThrough();
         mock = new MockAdapter(axios);
 
-        mock.onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`).reply(200, {});
+        mock
+          .onPatch(
+            `${
+              gl.TEST_HOST
+            }/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
+          )
+          .reply(200, {});
 
         $('.js-comment-button').on('click', function(e) {
           e.preventDefault();
@@ -72,18 +90,27 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       it('modifies the Markdown field', function() {
         const changeEvent = document.createEvent('HTMLEvents');
         changeEvent.initEvent('change', true, true);
-        $('input[type=checkbox]').attr('checked', true)[1].dispatchEvent(changeEvent);
+        $('input[type=checkbox]')
+          .attr('checked', true)[1]
+          .dispatchEvent(changeEvent);
 
-        expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item');
+        expect($('.js-task-list-field.original-task-list').val()).toBe(
+          '- [x] Task List Item',
+        );
       });
 
       it('submits an ajax request on tasklist:changed', function(done) {
         $('.js-task-list-container').trigger('tasklist:changed');
 
         setTimeout(() => {
-          expect(axios.patch).toHaveBeenCalledWith(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`, {
-            note: { note: '' },
-          });
+          expect(axios.patch).toHaveBeenCalledWith(
+            `${
+              gl.TEST_HOST
+            }/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
+            {
+              note: { note: '' },
+            },
+          );
           done();
         });
       });
@@ -99,10 +126,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         spyOn(this.notes, 'renderNote').and.stub();
 
         $(textarea).data('autosave', {
-          reset: function() {}
+          reset: function() {},
         });
 
-        $('.js-comment-button').on('click', (e) => {
+        $('.js-comment-button').on('click', e => {
           const $form = $(this);
           e.preventDefault();
           this.notes.addNote($form);
@@ -148,7 +175,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
                   <div class="note-text">${sampleComment}</div>
                  </li>`,
           note: sampleComment,
-          valid: true
+          valid: true,
         };
         $form = $('form.js-main-target-form');
         $notesContainer = $('ul.main-notes-list');
@@ -162,7 +189,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         mock.restore();
       });
 
-      it('updates note and resets edit form', (done) => {
+      it('updates note and resets edit form', done => {
         spyOn(this.notes, 'revertNoteEditForm');
         spyOn(this.notes, 'setupNewNote');
 
@@ -174,7 +201,9 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
           updatedNote.note = 'bar';
           this.notes.updateNote(updatedNote, $targetNote);
 
-          expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
+          expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith(
+            $targetNote,
+          );
           expect(this.notes.setupNewNote).toHaveBeenCalled();
 
           done();
@@ -230,17 +259,14 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
           note: 'heya',
           html: '<div>heya</div>',
         };
-        $notesList = jasmine.createSpyObj('$notesList', [
-          'find',
-          'append',
-        ]);
+        $notesList = jasmine.createSpyObj('$notesList', ['find', 'append']);
 
         notes = jasmine.createSpyObj('notes', [
           'setupNewNote',
           'refresh',
           'collapseLongCommitList',
           'updateNotesCount',
-          'putConflictEditWarningInPlace'
+          'putConflictEditWarningInPlace',
         ]);
         notes.taskList = jasmine.createSpyObj('tasklist', ['init']);
         notes.note_ids = [];
@@ -257,7 +283,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
           Notes.isNewNote.and.returnValue(true);
           Notes.prototype.renderNote.call(notes, note, null, $notesList);
 
-          expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
+          expect(Notes.animateAppendNote).toHaveBeenCalledWith(
+            note.html,
+            $notesList,
+          );
         });
       });
 
@@ -272,7 +301,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
 
           Notes.prototype.renderNote.call(notes, note, null, $notesList);
 
-          expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note);
+          expect(Notes.animateUpdateNote).toHaveBeenCalledWith(
+            note.html,
+            $note,
+          );
           expect(notes.setupNewNote).toHaveBeenCalledWith($newNote);
         });
 
@@ -300,7 +332,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
             $notesList.find.and.returnValue($note);
             Notes.prototype.renderNote.call(notes, note, null, $notesList);
 
-            expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note);
+            expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(
+              note,
+              $note,
+            );
           });
         });
       });
@@ -310,11 +345,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       it('should consider same note text as the same', () => {
         const result = Notes.isUpdatedNote(
           {
-            note: 'initial'
+            note: 'initial',
           },
           $(`<div>
             <div class="original-note-content">initial</div>
-          </div>`)
+          </div>`),
         );
 
         expect(result).toEqual(false);
@@ -323,11 +358,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       it('should consider same note with trailing newline as the same', () => {
         const result = Notes.isUpdatedNote(
           {
-            note: 'initial\n'
+            note: 'initial\n',
           },
           $(`<div>
             <div class="original-note-content">initial\n</div>
-          </div>`)
+          </div>`),
         );
 
         expect(result).toEqual(false);
@@ -336,11 +371,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       it('should consider different notes as different', () => {
         const result = Notes.isUpdatedNote(
           {
-            note: 'foo'
+            note: 'foo',
           },
           $(`<div>
             <div class="original-note-content">bar</div>
-          </div>`)
+          </div>`),
         );
 
         expect(result).toEqual(true);
@@ -396,7 +431,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         it('should call Notes.animateAppendNote', () => {
           Notes.prototype.renderDiscussionNote.call(notes, note, $form);
 
-          expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.discussion_html, $('.main-notes-list'));
+          expect(Notes.animateAppendNote).toHaveBeenCalledWith(
+            note.discussion_html,
+            $('.main-notes-list'),
+          );
         });
 
         it('should append to row selected with line_code', () => {
@@ -427,7 +465,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         });
 
         it('should call Notes.animateAppendNote', () => {
-          expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer);
+          expect(Notes.animateAppendNote).toHaveBeenCalledWith(
+            note.html,
+            discussionContainer,
+          );
         });
       });
     });
@@ -460,9 +501,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
 
       beforeEach(() => {
         noteHTML = '<div></div>';
-        $note = jasmine.createSpyObj('$note', [
-          'replaceWith'
-        ]);
+        $note = jasmine.createSpyObj('$note', ['replaceWith']);
 
         $updatedNote = Notes.animateUpdateNote(noteHTML, $note);
       });
@@ -500,7 +539,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
                 <div class="note-text">${sampleComment}</div>
                </li>`,
         note: sampleComment,
-        valid: true
+        valid: true,
       };
       let $form;
       let $notesContainer;
@@ -533,10 +572,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         mockNotesPost();
 
         $('.js-comment-button').click();
-        expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true);
+        expect($notesContainer.find('.note.being-posted').length > 0).toEqual(
+          true,
+        );
       });
 
-      it('should remove placeholder note when new comment is done posting', (done) => {
+      it('should remove placeholder note when new comment is done posting', done => {
         mockNotesPost();
 
         $('.js-comment-button').click();
@@ -548,19 +589,44 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         });
       });
 
-      it('should show actual note element when new comment is done posting', (done) => {
+      describe('postComment', () => {
+        it('disables the submit button', done => {
+          const $submitButton = $form.find('.js-comment-submit-button');
+          expect($submitButton).not.toBeDisabled();
+          const dummyEvent = {
+            preventDefault() {},
+            target: $submitButton,
+          };
+          mock.onPost(NOTES_POST_PATH).replyOnce(() => {
+            expect($submitButton).toBeDisabled();
+            return [200, note];
+          });
+
+          this.notes
+            .postComment(dummyEvent)
+            .then(() => {
+              expect($submitButton).not.toBeDisabled();
+            })
+            .then(done)
+            .catch(done.fail);
+        });
+      });
+
+      it('should show actual note element when new comment is done posting', done => {
         mockNotesPost();
 
         $('.js-comment-button').click();
 
         setTimeout(() => {
-          expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
+          expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(
+            true,
+          );
 
           done();
         });
       });
 
-      it('should reset Form when new comment is done posting', (done) => {
+      it('should reset Form when new comment is done posting', done => {
         mockNotesPost();
 
         $('.js-comment-button').click();
@@ -572,19 +638,24 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         });
       });
 
-      it('should show flash error message when new comment failed to be posted', (done) => {
+      it('should show flash error message when new comment failed to be posted', done => {
         mockNotesPostError();
 
         $('.js-comment-button').click();
 
         setTimeout(() => {
-          expect($notesContainer.parent().find('.flash-container .flash-text').is(':visible')).toEqual(true);
+          expect(
+            $notesContainer
+              .parent()
+              .find('.flash-container .flash-text')
+              .is(':visible'),
+          ).toEqual(true);
 
           done();
         });
       });
 
-      it('should show flash error message when comment failed to be updated', (done) => {
+      it('should show flash error message when comment failed to be updated', done => {
         mockNotesPost();
 
         $('.js-comment-button').click();
@@ -605,7 +676,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
           .then(() => {
             const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
             expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
-            expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(sampleComment); // See if comment reverted back to original
+            expect(
+              $updatedNoteEl
+                .find('.note-text')
+                .text()
+                .trim(),
+            ).toEqual(sampleComment); // See if comment reverted back to original
             expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
 
             done();
@@ -619,12 +695,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       const note = {
         commands_changes: {
           assignee_id: 1,
-          emoji_award: '100'
+          emoji_award: '100',
         },
         errors: {
-          commands_only: ['Commands applied']
+          commands_only: ['Commands applied'],
         },
-        valid: false
+        valid: false,
       };
       let $form;
       let $notesContainer;
@@ -639,12 +715,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         window.gon.current_user_fullname = 'Administrator';
         gl.awardsHandler = {
           addAwardToEmojiBar: () => {},
-          scrollToAwards: () => {}
+          scrollToAwards: () => {},
         };
         gl.GfmAutoComplete = {
           dataSources: {
-            commands: '/root/test-project/autocomplete_sources/commands'
-          }
+            commands: '/root/test-project/autocomplete_sources/commands',
+          },
         };
         $form = $('form.js-main-target-form');
         $notesContainer = $('ul.main-notes-list');
@@ -655,14 +731,18 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         mock.restore();
       });
 
-      it('should remove slash command placeholder when comment with slash commands is done posting', (done) => {
+      it('should remove slash command placeholder when comment with slash commands is done posting', done => {
         spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
         $('.js-comment-button').click();
 
-        expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
+        expect(
+          $notesContainer.find('.system-note.being-posted').length,
+        ).toEqual(1); // Placeholder shown
 
         setTimeout(() => {
-          expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+          expect(
+            $notesContainer.find('.system-note.being-posted').length,
+          ).toEqual(0); // Placeholder removed
           done();
         });
       });
@@ -677,7 +757,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
                 <div class="note-text">${sampleComment}</div>
                </li>`,
         note: sampleComment,
-        valid: true
+        valid: true,
       };
       let $form;
       let $notesContainer;
@@ -699,7 +779,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         mock.restore();
       });
 
-      it('should not render a script tag', (done) => {
+      it('should not render a script tag', done => {
         $('.js-comment-button').click();
 
         setTimeout(() => {
@@ -708,8 +788,15 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
           $noteEl.find('textarea.js-note-text').html(updatedComment);
           $noteEl.find('.js-comment-save-button').click();
 
-          const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container');
-          expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
+          const $updatedNoteEl = $notesContainer
+            .find(`#note_${note.id}`)
+            .find('.js-task-list-container');
+          expect(
+            $updatedNoteEl
+              .find('.note-text')
+              .text()
+              .trim(),
+          ).toEqual('');
 
           done();
         });
@@ -729,7 +816,9 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
 
       it('should return form metadata object from form reference', () => {
         $form.find('textarea.js-note-text').val(sampleComment);
-        const { formData, formContent, formAction } = this.notes.getFormData($form);
+        const { formData, formContent, formAction } = this.notes.getFormData(
+          $form,
+        );
 
         expect(formData.indexOf(sampleComment) > -1).toBe(true);
         expect(formContent).toEqual(sampleComment);
@@ -745,7 +834,9 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         const { formContent } = this.notes.getFormData($form);
 
         expect(_.escape).toHaveBeenCalledWith(sampleComment);
-        expect(formContent).toEqual('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;');
+        expect(formContent).toEqual(
+          '&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;',
+        );
       });
     });
 
@@ -755,7 +846,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       });
 
       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 sampleComment =
+          '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
         const hasQuickActions = this.notes.hasQuickActions(sampleComment);
 
         expect(hasQuickActions).toBeTruthy();
@@ -779,7 +871,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
     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 sampleComment =
+          '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
         const stripedComment = this.notes.stripQuickActions(sampleComment);
 
         expect(stripedComment).toBe('');
@@ -787,7 +880,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
 
       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 sampleComment =
+          '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
         const stripedComment = this.notes.stripQuickActions(sampleComment);
 
         expect(stripedComment).toBe('Merging this');
@@ -795,7 +889,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
 
       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 sampleComment =
+          'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
         const stripedComment = this.notes.stripQuickActions(sampleComment);
 
         expect(stripedComment).toBe(sampleComment);
@@ -806,7 +901,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       const availableQuickActions = [
         { name: 'close', description: 'Close this issue', params: [] },
         { name: 'title', description: 'Change title', params: [{}] },
-        { name: 'estimate', description: 'Set time estimate', params: [{}] }
+        { name: 'estimate', description: 'Set time estimate', params: [{}] },
       ];
 
       beforeEach(() => {
@@ -815,17 +910,29 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
 
       it('should return executing quick action description when note has single quick action', () => {
         const sampleComment = '/close';
-        expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).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 quick action description when note has multiple quick actions', () => {
         const sampleComment = '/close\n/title [Duplicate] Issue foobar';
-        expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying multiple commands');
+        expect(
+          this.notes.getQuickActionDescription(
+            sampleComment,
+            availableQuickActions,
+          ),
+        ).toBe('Applying multiple commands');
       });
 
       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.getQuickActionDescription(sampleComment)).toBe('Applying command');
+        expect(this.notes.getQuickActionDescription(sampleComment)).toBe(
+          'Applying command',
+        );
       });
     });
 
@@ -855,14 +962,35 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         expect($tempNote.attr('id')).toEqual(uniqueId);
         expect($tempNote.hasClass('being-posted')).toBeTruthy();
         expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
-        $tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() {
-          expect($(this).attr('href')).toEqual(`/${currentUsername}`);
-        });
-        expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
-        expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy();
-        expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual(currentUserFullname);
-        expect($tempNoteHeader.find('.note-headline-light').text().trim()).toEqual(`@${currentUsername}`);
-        expect($tempNote.find('.note-body .note-text p').text().trim()).toEqual(sampleComment);
+        $tempNote
+          .find('.timeline-icon > a, .note-header-info > a')
+          .each(function() {
+            expect($(this).attr('href')).toEqual(`/${currentUsername}`);
+          });
+        expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(
+          currentUserAvatar,
+        );
+        expect(
+          $tempNote.find('.timeline-content').hasClass('discussion'),
+        ).toBeFalsy();
+        expect(
+          $tempNoteHeader
+            .find('.hidden-xs')
+            .text()
+            .trim(),
+        ).toEqual(currentUserFullname);
+        expect(
+          $tempNoteHeader
+            .find('.note-headline-light')
+            .text()
+            .trim(),
+        ).toEqual(`@${currentUsername}`);
+        expect(
+          $tempNote
+            .find('.note-body .note-text p')
+            .text()
+            .trim(),
+        ).toEqual(sampleComment);
       });
 
       it('should return constructed placeholder element for discussion note based on form contents', () => {
@@ -871,11 +999,13 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
           uniqueId,
           isDiscussionNote: true,
           currentUsername,
-          currentUserFullname
+          currentUserFullname,
         });
 
         expect($tempNote.prop('nodeName')).toEqual('LI');
-        expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy();
+        expect(
+          $tempNote.find('.timeline-content').hasClass('discussion'),
+        ).toBeTruthy();
       });
 
       it('should return a escaped user name', () => {
@@ -889,7 +1019,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
           currentUserAvatar,
         });
         const $tempNoteHeader = $tempNote.find('.note-header');
-        expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual('Foo &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
+        expect(
+          $tempNoteHeader
+            .find('.hidden-xs')
+            .text()
+            .trim(),
+        ).toEqual('Foo &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
       });
     });
 
@@ -912,7 +1047,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
         expect($tempNote.attr('id')).toEqual(uniqueId);
         expect($tempNote.hasClass('being-posted')).toBeTruthy();
         expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
-        expect($tempNote.find('.timeline-content i').text().trim()).toEqual(sampleCommandDescription);
+        expect(
+          $tempNote
+            .find('.timeline-content i')
+            .text()
+            .trim(),
+        ).toEqual(sampleCommandDescription);
       });
     });
 
@@ -922,7 +1062,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       });
 
       it('shows a flash message', () => {
-        this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
+        this.notes.addFlash(
+          'Error message',
+          FLASH_TYPE_ALERT,
+          this.notes.parentTimeline.get(0),
+        );
 
         expect($('.flash-alert').is(':visible')).toBeTruthy();
       });
@@ -935,7 +1079,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       });
 
       it('hides visible flash message', () => {
-        this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
+        this.notes.addFlash(
+          'Error message 1',
+          FLASH_TYPE_ALERT,
+          this.notes.parentTimeline.get(0),
+        );
 
         this.notes.clearFlash();
 
@@ -943,4 +1091,4 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
       });
     });
   });
-}).call(window);
+}.call(window));
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index b24563f738b057c5a2f8197bb44b7e21905fe172..8816fe6defba7f22b4ba97cb06a03091d24df443 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
 
 describe('OAuthRememberMe', () => {
diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
index 349549b9e1f80c759329d32f6e7aaf169167f8d2..b0dc6ccc3d4950d9bf9580782e8f2775b0e4c457 100644
--- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
+++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import '~/lib/utils/text_utility';
 import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
 
diff --git a/spec/javascripts/pages/labels/components/promote_label_modal_spec.js b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a24f8204fe11d69888f6fa6b493bfa378f374b07
--- /dev/null
+++ b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
@@ -0,0 +1,89 @@
+import Vue from 'vue';
+import promoteLabelModal from '~/pages/projects/labels/components/promote_label_modal.vue';
+import eventHub from '~/pages/projects/labels/event_hub';
+import axios from '~/lib/utils/axios_utils';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('Promote label modal', () => {
+  let vm;
+  const Component = Vue.extend(promoteLabelModal);
+  const labelMockData = {
+    labelTitle: 'Documentation',
+    labelColor: '#5cb85c',
+    labelTextColor: '#ffffff',
+    url: `${gl.TEST_HOST}/dummy/promote/labels`,
+    groupName: 'group',
+  };
+
+  describe('Modal title and description', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, labelMockData);
+    });
+
+    afterEach(() => {
+      vm.$destroy();
+    });
+
+    it('contains the proper description', () => {
+      expect(vm.text).toContain(`Promoting ${labelMockData.labelTitle} will make it available for all projects inside ${labelMockData.groupName}`);
+    });
+
+    it('contains a label span with the color', () => {
+      const labelFromTitle = vm.$el.querySelector('.modal-header .label.color-label');
+
+      expect(labelFromTitle.style.backgroundColor).not.toBe(null);
+      expect(labelFromTitle.textContent).toContain(vm.labelTitle);
+    });
+  });
+
+  describe('When requesting a label promotion', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, {
+        ...labelMockData,
+      });
+      spyOn(eventHub, '$emit');
+    });
+
+    afterEach(() => {
+      vm.$destroy();
+    });
+
+    it('redirects when a label is promoted', (done) => {
+      const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+      spyOn(axios, 'post').and.callFake((url) => {
+        expect(url).toBe(labelMockData.url);
+        expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestStarted', labelMockData.url);
+        return Promise.resolve({
+          request: {
+            responseURL,
+          },
+        });
+      });
+
+      vm.onSubmit()
+        .then(() => {
+          expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestFinished', { labelUrl: labelMockData.url, successful: true });
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('displays an error if promoting a label failed', (done) => {
+      const dummyError = new Error('promoting label failed');
+      dummyError.response = { status: 500 };
+      spyOn(axios, 'post').and.callFake((url) => {
+        expect(url).toBe(labelMockData.url);
+        expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestStarted', labelMockData.url);
+        return Promise.reject(dummyError);
+      });
+
+      vm.onSubmit()
+        .catch((error) => {
+          expect(error).toBe(dummyError);
+          expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestFinished', { labelUrl: labelMockData.url, successful: false });
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b220423637caf1a3412f5fe96b8fcca26efcf3b
--- /dev/null
+++ b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+import promoteMilestoneModal from '~/pages/milestones/shared/components/promote_milestone_modal.vue';
+import eventHub from '~/pages/milestones/shared/event_hub';
+import axios from '~/lib/utils/axios_utils';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('Promote milestone modal', () => {
+  let vm;
+  const Component = Vue.extend(promoteMilestoneModal);
+  const milestoneMockData = {
+    milestoneTitle: 'v1.0',
+    url: `${gl.TEST_HOST}/dummy/promote/milestones`,
+    groupName: 'group',
+  };
+
+  describe('Modal title and description', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, milestoneMockData);
+    });
+
+    afterEach(() => {
+      vm.$destroy();
+    });
+
+    it('contains the proper description', () => {
+      expect(vm.text).toContain(`Promoting ${milestoneMockData.milestoneTitle} will make it available for all projects inside ${milestoneMockData.groupName}.`);
+    });
+
+    it('contains the correct title', () => {
+      expect(vm.title).toEqual('Promote v1.0 to group milestone?');
+    });
+  });
+
+  describe('When requesting a milestone promotion', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, {
+        ...milestoneMockData,
+      });
+      spyOn(eventHub, '$emit');
+    });
+
+    afterEach(() => {
+      vm.$destroy();
+    });
+
+    it('redirects when a milestone is promoted', (done) => {
+      const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+      spyOn(axios, 'post').and.callFake((url) => {
+        expect(url).toBe(milestoneMockData.url);
+        expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestStarted', milestoneMockData.url);
+        return Promise.resolve({
+          request: {
+            responseURL,
+          },
+        });
+      });
+
+      vm.onSubmit()
+        .then(() => {
+          expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestFinished', { milestoneUrl: milestoneMockData.url, successful: true });
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+
+    it('displays an error if promoting a milestone failed', (done) => {
+      const dummyError = new Error('promoting milestone failed');
+      dummyError.response = { status: 500 };
+      spyOn(axios, 'post').and.callFake((url) => {
+        expect(url).toBe(milestoneMockData.url);
+        expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestStarted', milestoneMockData.url);
+        return Promise.reject(dummyError);
+      });
+
+      vm.onSubmit()
+        .catch((error) => {
+          expect(error).toBe(dummyError);
+          expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestFinished', { milestoneUrl: milestoneMockData.url, successful: false });
+        })
+        .then(done)
+        .catch(done.fail);
+    });
+  });
+});
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c4611dc7662cfe0b6a862c12b98b6e2988f394b8
--- /dev/null
+++ b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
@@ -0,0 +1,80 @@
+import Vue from 'vue';
+import detailedMetric from '~/performance_bar/components/detailed_metric.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('detailedMetric', () => {
+  let vm;
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('when the current request has no details', () => {
+    beforeEach(() => {
+      vm = mountComponent(Vue.extend(detailedMetric), {
+        currentRequest: {},
+        metric: 'gitaly',
+        header: 'Gitaly calls',
+        details: 'details',
+        keys: ['feature', 'request'],
+      });
+    });
+
+    it('does not render the element', () => {
+      expect(vm.$el.innerHTML).toEqual(undefined);
+    });
+  });
+
+  describe('when the current request has details', () => {
+    const requestDetails = [
+      { duration: '100', feature: 'find_commit', request: 'abcdef' },
+      { duration: '23', feature: 'rebase_in_progress', request: '' },
+    ];
+
+    beforeEach(() => {
+      vm = mountComponent(Vue.extend(detailedMetric), {
+        currentRequest: {
+          details: {
+            gitaly: {
+              duration: '123ms',
+              calls: '456',
+              details: requestDetails,
+            },
+          },
+        },
+        metric: 'gitaly',
+        header: 'Gitaly calls',
+        details: 'details',
+        keys: ['feature', 'request'],
+      });
+    });
+
+    it('diplays details', () => {
+      expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456');
+    });
+
+    it('adds a modal with a table of the details', () => {
+      vm.$el
+        .querySelectorAll('.performance-bar-modal td strong')
+        .forEach((duration, index) => {
+          expect(duration.innerText).toContain(requestDetails[index].duration);
+        });
+
+      vm.$el
+        .querySelectorAll('.performance-bar-modal td:nth-child(2)')
+        .forEach((feature, index) => {
+          expect(feature.innerText).toContain(requestDetails[index].feature);
+        });
+
+      vm.$el
+        .querySelectorAll('.performance-bar-modal td:nth-child(3)')
+        .forEach((request, index) => {
+          expect(request.innerText).toContain(requestDetails[index].request);
+        });
+    });
+
+    it('displays the metric name', () => {
+      expect(vm.$el.innerText).toContain('gitaly');
+    });
+  });
+});
diff --git a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9ab9ab1c9f432b865f842daacb8f860628e8c6f0
--- /dev/null
+++ b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js
@@ -0,0 +1,88 @@
+import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
+import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue';
+import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
+import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import MockAdapter from 'axios-mock-adapter';
+
+describe('performance bar', () => {
+  let mock;
+  let vm;
+
+  beforeEach(() => {
+    const store = new PerformanceBarStore();
+
+    mock = new MockAdapter(axios);
+
+    mock.onGet('/-/peek/results').reply(
+      200,
+      {
+        data: {
+          gc: {
+            invokes: 0,
+            invoke_time: '0.00',
+            use_size: 0,
+            total_size: 0,
+            total_object: 0,
+            gc_time: '0.00',
+          },
+          host: { hostname: 'web-01' },
+        },
+      },
+      {},
+    );
+
+    vm = mountComponent(Vue.extend(performanceBarApp), {
+      store,
+      env: 'development',
+      requestId: '123',
+      peekUrl: '/-/peek/results',
+      profileUrl: '?lineprofiler=true',
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+    mock.restore();
+  });
+
+  it('sets the class to match the environment', () => {
+    expect(vm.$el.getAttribute('class')).toContain('development');
+  });
+
+  describe('loadRequestDetails', () => {
+    beforeEach(() => {
+      spyOn(vm.store, 'addRequest').and.callThrough();
+    });
+
+    it('does nothing if the request cannot be tracked', () => {
+      spyOn(vm.store, 'canTrackRequest').and.callFake(() => false);
+
+      vm.loadRequestDetails('123', 'https://gitlab.com/');
+
+      expect(vm.store.addRequest).not.toHaveBeenCalled();
+    });
+
+    it('adds the request immediately', () => {
+      vm.loadRequestDetails('123', 'https://gitlab.com/');
+
+      expect(vm.store.addRequest).toHaveBeenCalledWith(
+        '123',
+        'https://gitlab.com/',
+      );
+    });
+
+    it('makes an HTTP request for the request details', () => {
+      spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough();
+
+      vm.loadRequestDetails('456', 'https://gitlab.com/');
+
+      expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith(
+        '/-/peek/results',
+        '456',
+      );
+    });
+  });
+});
diff --git a/spec/javascripts/performance_bar/components/request_selector_spec.js b/spec/javascripts/performance_bar/components/request_selector_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6108a29f8c401a1526a8c1377acfe6bc017a353c
--- /dev/null
+++ b/spec/javascripts/performance_bar/components/request_selector_spec.js
@@ -0,0 +1,47 @@
+import Vue from 'vue';
+import requestSelector from '~/performance_bar/components/request_selector.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('request selector', () => {
+  const requests = [
+    { id: '123', url: 'https://gitlab.com/' },
+    {
+      id: '456',
+      url: 'https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1',
+    },
+    {
+      id: '789',
+      url:
+        'https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1.json?serializer=widget',
+    },
+  ];
+
+  let vm;
+
+  beforeEach(() => {
+    vm = mountComponent(Vue.extend(requestSelector), {
+      requests,
+      currentRequest: requests[1],
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  function optionText(requestId) {
+    return vm.$el.querySelector(`[value='${requestId}']`).innerText.trim();
+  }
+
+  it('displays the last component of the path', () => {
+    expect(optionText(requests[2].id)).toEqual('1.json?serializer=widget');
+  });
+
+  it('keeps the last two components of the path when the last component is numeric', () => {
+    expect(optionText(requests[1].id)).toEqual('merge_requests/1');
+  });
+
+  it('ignores trailing slashes', () => {
+    expect(optionText(requests[0].id)).toEqual('gitlab.com');
+  });
+});
diff --git a/spec/javascripts/performance_bar/components/simple_metric_spec.js b/spec/javascripts/performance_bar/components/simple_metric_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..98b843e9711db168eac0f962e856fb369d1522e3
--- /dev/null
+++ b/spec/javascripts/performance_bar/components/simple_metric_spec.js
@@ -0,0 +1,47 @@
+import Vue from 'vue';
+import simpleMetric from '~/performance_bar/components/simple_metric.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('simpleMetric', () => {
+  let vm;
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('when the current request has no details', () => {
+    beforeEach(() => {
+      vm = mountComponent(Vue.extend(simpleMetric), {
+        currentRequest: {},
+        metric: 'gitaly',
+      });
+    });
+
+    it('does not display details', () => {
+      expect(vm.$el.innerText).not.toContain('/');
+    });
+
+    it('displays the metric name', () => {
+      expect(vm.$el.innerText).toContain('gitaly');
+    });
+  });
+
+  describe('when the current request has details', () => {
+    beforeEach(() => {
+      vm = mountComponent(Vue.extend(simpleMetric), {
+        currentRequest: {
+          details: { gitaly: { duration: '123ms', calls: '456' } },
+        },
+        metric: 'gitaly',
+      });
+    });
+
+    it('diplays details', () => {
+      expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456');
+    });
+
+    it('displays the metric name', () => {
+      expect(vm.$el.innerText).toContain('gitaly');
+    });
+  });
+});
diff --git a/spec/javascripts/pipelines/blank_state_spec.js b/spec/javascripts/pipelines/blank_state_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b7a9b60d85c44d1e57f778890cec88f458983080
--- /dev/null
+++ b/spec/javascripts/pipelines/blank_state_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import component from '~/pipelines/components/blank_state.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Pipelines Blank State', () => {
+  let vm;
+  let Component;
+
+  beforeEach(() => {
+    Component = Vue.extend(component);
+
+    vm = mountComponent(Component,
+      {
+        svgPath: 'foo',
+        message: 'Blank State',
+      },
+    );
+  });
+
+  it('should render svg', () => {
+    expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toEqual('foo');
+  });
+
+  it('should render message', () => {
+    expect(
+      vm.$el.querySelector('h4').textContent.trim(),
+    ).toEqual('Blank State');
+  });
+});
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js
index 97f04844b3a0291bf860119acd81000313a9286d..71f77e5f42efb389221840a229bbe5621d8218fb 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/javascripts/pipelines/empty_state_spec.js
@@ -1,5 +1,6 @@
 import Vue from 'vue';
 import emptyStateComp from '~/pipelines/components/empty_state.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
 
 describe('Pipelines Empty State', () => {
   let component;
@@ -8,12 +9,15 @@ describe('Pipelines Empty State', () => {
   beforeEach(() => {
     EmptyStateComponent = Vue.extend(emptyStateComp);
 
-    component = new EmptyStateComponent({
-      propsData: {
-        helpPagePath: 'foo',
-        emptyStateSvgPath: 'foo',
-      },
-    }).$mount();
+    component = mountComponent(EmptyStateComponent, {
+      helpPagePath: 'foo',
+      emptyStateSvgPath: 'foo',
+      canSetCi: true,
+    });
+  });
+
+  afterEach(() => {
+    component.$destroy();
   });
 
   it('should render empty state SVG', () => {
@@ -24,16 +28,16 @@ describe('Pipelines Empty State', () => {
     expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
 
     expect(
-      component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
-    ).toContain('Continous Integration can help catch bugs by running your tests automatically');
+      component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '),
+    ).toContain('Continous Integration can help catch bugs by running your tests automatically,');
 
     expect(
-      component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
-    ).toContain('Continuous Deployment can help you deliver code to your product environment');
+      component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '),
+    ).toContain('while Continuous Deployment can help you deliver code to your product environment');
   });
 
   it('should render a link with provided help path', () => {
-    expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual('foo');
-    expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines');
+    expect(component.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual('foo');
+    expect(component.$el.querySelector('.js-get-started-pipelines').textContent).toContain('Get started with Pipelines');
   });
 });
diff --git a/spec/javascripts/pipelines/error_state_spec.js b/spec/javascripts/pipelines/error_state_spec.js
deleted file mode 100644
index a402857a4d18afa72d8d393e9856bd541be12db2..0000000000000000000000000000000000000000
--- a/spec/javascripts/pipelines/error_state_spec.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import Vue from 'vue';
-import errorStateComp from '~/pipelines/components/error_state.vue';
-
-describe('Pipelines Error State', () => {
-  let component;
-  let ErrorStateComponent;
-
-  beforeEach(() => {
-    ErrorStateComponent = Vue.extend(errorStateComp);
-
-    component = new ErrorStateComponent({
-      propsData: {
-        errorStateSvgPath: 'foo',
-      },
-    }).$mount();
-  });
-
-  it('should render error state SVG', () => {
-    expect(component.$el.querySelector('.svg-content svg')).toBeDefined();
-  });
-
-  it('should render emtpy state information', () => {
-    expect(
-      component.$el.querySelector('h4').textContent,
-    ).toContain('The API failed to fetch the pipelines');
-  });
-});
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index e8fcd4b1a3658186b46fae562b8dde261cff9f78..581209f215ded26a01ae1bd1bddac536dfa48ec0 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -1,25 +1,30 @@
 import Vue from 'vue';
 import actionComponent from '~/pipelines/components/graph/action_component.vue';
+import eventHub from '~/pipelines/event_hub';
+import mountComponent from '../../helpers/vue_mount_component_helper';
 
 describe('pipeline graph action component', () => {
   let component;
 
   beforeEach((done) => {
     const ActionComponent = Vue.extend(actionComponent);
-    component = new ActionComponent({
-      propsData: {
-        tooltipText: 'bar',
-        link: 'foo',
-        actionMethod: 'post',
-        actionIcon: 'cancel',
-      },
-    }).$mount();
+    component = mountComponent(ActionComponent, {
+      tooltipText: 'bar',
+      link: 'foo',
+      actionIcon: 'cancel',
+    });
 
     Vue.nextTick(done);
   });
 
-  it('should render a link', () => {
-    expect(component.$el.getAttribute('href')).toEqual('foo');
+  afterEach(() => {
+    component.$destroy();
+  });
+
+  it('should emit an event with the provided link', () => {
+    eventHub.$on('graphAction', (link) => {
+      expect(link).toEqual('foo');
+    });
   });
 
   it('should render the provided title as a bootstrap tooltip', () => {
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index ce181a1e515e99ae3461e7d14376adf05f51c8fa..c9677ae209a69a10e2cdb745e1d4c9d86debac7f 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -13,6 +13,7 @@ describe('pipeline graph job component', () => {
       icon: 'icon_status_success',
       text: 'passed',
       label: 'passed',
+      tooltip: 'passed',
       group: 'success',
       details_path: '/root/ci-mock/builds/4256',
       has_details: true,
@@ -137,6 +138,7 @@ describe('pipeline graph job component', () => {
           status: {
             icon: 'icon_status_success',
             label: 'success',
+            tooltip: 'success',
           },
         },
       });
diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js
index b9494f86d745ba7ee63c14c379b44fe22bd9134a..70eba98e939bc12f53e48e7f2699aa8a7c46f711 100644
--- a/spec/javascripts/pipelines/graph/mock_data.js
+++ b/spec/javascripts/pipelines/graph/mock_data.js
@@ -1,232 +1,261 @@
-/* eslint-disable quote-props, quotes, comma-dangle */
 export default {
-  "id": 123,
-  "user": {
-    "name": "Root",
-    "username": "root",
-    "id": 1,
-    "state": "active",
-    "avatar_url": null,
-    "web_url": "http://localhost:3000/root"
+  id: 123,
+  user: {
+    name: 'Root',
+    username: 'root',
+    id: 1,
+    state: 'active',
+    avatar_url: null,
+    web_url: 'http://localhost:3000/root',
   },
-  "active": false,
-  "coverage": null,
-  "path": "/root/ci-mock/pipelines/123",
-  "details": {
-    "status": {
-      "icon": "icon_status_success",
-      "text": "passed",
-      "label": "passed",
-      "group": "success",
-      "has_details": true,
-      "details_path": "/root/ci-mock/pipelines/123",
-      "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico"
+  active: false,
+  coverage: null,
+  path: '/root/ci-mock/pipelines/123',
+  details: {
+    status: {
+      icon: 'icon_status_success',
+      text: 'passed',
+      label: 'passed',
+      group: 'success',
+      has_details: true,
+      details_path: '/root/ci-mock/pipelines/123',
+      favicon:
+        '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
     },
-    "duration": 9,
-    "finished_at": "2017-04-19T14:30:27.542Z",
-    "stages": [{
-      "name": "test",
-      "title": "test: passed",
-      "groups": [{
-        "name": "test",
-        "size": 1,
-        "status": {
-          "icon": "icon_status_success",
-          "text": "passed",
-          "label": "passed",
-          "group": "success",
-          "has_details": true,
-          "details_path": "/root/ci-mock/builds/4153",
-          "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
-          "action": {
-            "icon": "retry",
-            "title": "Retry",
-            "path": "/root/ci-mock/builds/4153/retry",
-            "method": "post"
-          }
+    duration: 9,
+    finished_at: '2017-04-19T14:30:27.542Z',
+    stages: [
+      {
+        name: 'test',
+        title: 'test: passed',
+        groups: [
+          {
+            name: 'test',
+            size: 1,
+            status: {
+              icon: 'icon_status_success',
+              text: 'passed',
+              label: 'passed',
+              group: 'success',
+              has_details: true,
+              details_path: '/root/ci-mock/builds/4153',
+              favicon:
+                '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+              action: {
+                icon: 'retry',
+                title: 'Retry',
+                path: '/root/ci-mock/builds/4153/retry',
+                method: 'post',
+              },
+            },
+            jobs: [
+              {
+                id: 4153,
+                name: 'test',
+                build_path: '/root/ci-mock/builds/4153',
+                retry_path: '/root/ci-mock/builds/4153/retry',
+                playable: false,
+                created_at: '2017-04-13T09:25:18.959Z',
+                updated_at: '2017-04-13T09:25:23.118Z',
+                status: {
+                  icon: 'icon_status_success',
+                  text: 'passed',
+                  label: 'passed',
+                  group: 'success',
+                  has_details: true,
+                  details_path: '/root/ci-mock/builds/4153',
+                  favicon:
+                    '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+                  action: {
+                    icon: 'retry',
+                    title: 'Retry',
+                    path: '/root/ci-mock/builds/4153/retry',
+                    method: 'post',
+                  },
+                },
+              },
+            ],
+          },
+        ],
+        status: {
+          icon: 'icon_status_success',
+          text: 'passed',
+          label: 'passed',
+          group: 'success',
+          has_details: true,
+          details_path: '/root/ci-mock/pipelines/123#test',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
         },
-        "jobs": [{
-          "id": 4153,
-          "name": "test",
-          "build_path": "/root/ci-mock/builds/4153",
-          "retry_path": "/root/ci-mock/builds/4153/retry",
-          "playable": false,
-          "created_at": "2017-04-13T09:25:18.959Z",
-          "updated_at": "2017-04-13T09:25:23.118Z",
-          "status": {
-            "icon": "icon_status_success",
-            "text": "passed",
-            "label": "passed",
-            "group": "success",
-            "has_details": true,
-            "details_path": "/root/ci-mock/builds/4153",
-            "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
-            "action": {
-              "icon": "retry",
-              "title": "Retry",
-              "path": "/root/ci-mock/builds/4153/retry",
-              "method": "post"
-            }
-          }
-        }]
-      }],
-      "status": {
-        "icon": "icon_status_success",
-        "text": "passed",
-        "label": "passed",
-        "group": "success",
-        "has_details": true,
-        "details_path": "/root/ci-mock/pipelines/123#test",
-        "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico"
+        path: '/root/ci-mock/pipelines/123#test',
+        dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test',
       },
-      "path": "/root/ci-mock/pipelines/123#test",
-      "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=test"
-    }, {
-      "name": "deploy",
-      "title": "deploy: passed",
-      "groups": [{
-        "name": "deploy to production",
-        "size": 1,
-        "status": {
-          "icon": "icon_status_success",
-          "text": "passed",
-          "label": "passed",
-          "group": "success",
-          "has_details": true,
-          "details_path": "/root/ci-mock/builds/4166",
-          "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
-          "action": {
-            "icon": "retry",
-            "title": "Retry",
-            "path": "/root/ci-mock/builds/4166/retry",
-            "method": "post"
-          }
+      {
+        name: 'deploy',
+        title: 'deploy: passed',
+        groups: [
+          {
+            name: 'deploy to production',
+            size: 1,
+            status: {
+              icon: 'icon_status_success',
+              text: 'passed',
+              label: 'passed',
+              group: 'success',
+              has_details: true,
+              details_path: '/root/ci-mock/builds/4166',
+              favicon:
+                '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+              action: {
+                icon: 'retry',
+                title: 'Retry',
+                path: '/root/ci-mock/builds/4166/retry',
+                method: 'post',
+              },
+            },
+            jobs: [
+              {
+                id: 4166,
+                name: 'deploy to production',
+                build_path: '/root/ci-mock/builds/4166',
+                retry_path: '/root/ci-mock/builds/4166/retry',
+                playable: false,
+                created_at: '2017-04-19T14:29:46.463Z',
+                updated_at: '2017-04-19T14:30:27.498Z',
+                status: {
+                  icon: 'icon_status_success',
+                  text: 'passed',
+                  label: 'passed',
+                  group: 'success',
+                  has_details: true,
+                  details_path: '/root/ci-mock/builds/4166',
+                  favicon:
+                    '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+                  action: {
+                    icon: 'retry',
+                    title: 'Retry',
+                    path: '/root/ci-mock/builds/4166/retry',
+                    method: 'post',
+                  },
+                },
+              },
+            ],
+          },
+          {
+            name: 'deploy to staging',
+            size: 1,
+            status: {
+              icon: 'icon_status_success',
+              text: 'passed',
+              label: 'passed',
+              group: 'success',
+              has_details: true,
+              details_path: '/root/ci-mock/builds/4159',
+              favicon:
+                '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+              action: {
+                icon: 'retry',
+                title: 'Retry',
+                path: '/root/ci-mock/builds/4159/retry',
+                method: 'post',
+              },
+            },
+            jobs: [
+              {
+                id: 4159,
+                name: 'deploy to staging',
+                build_path: '/root/ci-mock/builds/4159',
+                retry_path: '/root/ci-mock/builds/4159/retry',
+                playable: false,
+                created_at: '2017-04-18T16:32:08.420Z',
+                updated_at: '2017-04-18T16:32:12.631Z',
+                status: {
+                  icon: 'icon_status_success',
+                  text: 'passed',
+                  label: 'passed',
+                  group: 'success',
+                  has_details: true,
+                  details_path: '/root/ci-mock/builds/4159',
+                  favicon:
+                    '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+                  action: {
+                    icon: 'retry',
+                    title: 'Retry',
+                    path: '/root/ci-mock/builds/4159/retry',
+                    method: 'post',
+                  },
+                },
+              },
+            ],
+          },
+        ],
+        status: {
+          icon: 'icon_status_success',
+          text: 'passed',
+          label: 'passed',
+          group: 'success',
+          has_details: true,
+          details_path: '/root/ci-mock/pipelines/123#deploy',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
         },
-        "jobs": [{
-          "id": 4166,
-          "name": "deploy to production",
-          "build_path": "/root/ci-mock/builds/4166",
-          "retry_path": "/root/ci-mock/builds/4166/retry",
-          "playable": false,
-          "created_at": "2017-04-19T14:29:46.463Z",
-          "updated_at": "2017-04-19T14:30:27.498Z",
-          "status": {
-            "icon": "icon_status_success",
-            "text": "passed",
-            "label": "passed",
-            "group": "success",
-            "has_details": true,
-            "details_path": "/root/ci-mock/builds/4166",
-            "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
-            "action": {
-              "icon": "retry",
-              "title": "Retry",
-              "path": "/root/ci-mock/builds/4166/retry",
-              "method": "post"
-            }
-          }
-        }]
-      }, {
-        "name": "deploy to staging",
-        "size": 1,
-        "status": {
-          "icon": "icon_status_success",
-          "text": "passed",
-          "label": "passed",
-          "group": "success",
-          "has_details": true,
-          "details_path": "/root/ci-mock/builds/4159",
-          "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
-          "action": {
-            "icon": "retry",
-            "title": "Retry",
-            "path": "/root/ci-mock/builds/4159/retry",
-            "method": "post"
-          }
-        },
-        "jobs": [{
-          "id": 4159,
-          "name": "deploy to staging",
-          "build_path": "/root/ci-mock/builds/4159",
-          "retry_path": "/root/ci-mock/builds/4159/retry",
-          "playable": false,
-          "created_at": "2017-04-18T16:32:08.420Z",
-          "updated_at": "2017-04-18T16:32:12.631Z",
-          "status": {
-            "icon": "icon_status_success",
-            "text": "passed",
-            "label": "passed",
-            "group": "success",
-            "has_details": true,
-            "details_path": "/root/ci-mock/builds/4159",
-            "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
-            "action": {
-              "icon": "retry",
-              "title": "Retry",
-              "path": "/root/ci-mock/builds/4159/retry",
-              "method": "post"
-            }
-          }
-        }]
-      }],
-      "status": {
-        "icon": "icon_status_success",
-        "text": "passed",
-        "label": "passed",
-        "group": "success",
-        "has_details": true,
-        "details_path": "/root/ci-mock/pipelines/123#deploy",
-        "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico"
+        path: '/root/ci-mock/pipelines/123#deploy',
+        dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy',
+      },
+    ],
+    artifacts: [],
+    manual_actions: [
+      {
+        name: 'deploy to production',
+        path: '/root/ci-mock/builds/4166/play',
+        playable: false,
       },
-      "path": "/root/ci-mock/pipelines/123#deploy",
-      "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=deploy"
-    }],
-    "artifacts": [],
-    "manual_actions": [{
-      "name": "deploy to production",
-      "path": "/root/ci-mock/builds/4166/play",
-      "playable": false
-    }]
+    ],
   },
-  "flags": {
-    "latest": true,
-    "triggered": false,
-    "stuck": false,
-    "yaml_errors": false,
-    "retryable": false,
-    "cancelable": false
+  flags: {
+    latest: true,
+    triggered: false,
+    stuck: false,
+    yaml_errors: false,
+    retryable: false,
+    cancelable: false,
   },
-  "ref": {
-    "name": "master",
-    "path": "/root/ci-mock/tree/master",
-    "tag": false,
-    "branch": true
+  ref: {
+    name: 'master',
+    path: '/root/ci-mock/tree/master',
+    tag: false,
+    branch: true,
   },
-  "commit": {
-    "id": "798e5f902592192afaba73f4668ae30e56eae492",
-    "short_id": "798e5f90",
-    "title": "Merge branch 'new-branch' into 'master'\r",
-    "created_at": "2017-04-13T10:25:17.000+01:00",
-    "parent_ids": ["54d483b1ed156fbbf618886ddf7ab023e24f8738", "c8e2d38a6c538822e81c57022a6e3a0cfedebbcc"],
-    "message": "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1",
-    "author_name": "Root",
-    "author_email": "admin@example.com",
-    "authored_date": "2017-04-13T10:25:17.000+01:00",
-    "committer_name": "Root",
-    "committer_email": "admin@example.com",
-    "committed_date": "2017-04-13T10:25:17.000+01:00",
-    "author": {
-      "name": "Root",
-      "username": "root",
-      "id": 1,
-      "state": "active",
-      "avatar_url": null,
-      "web_url": "http://localhost:3000/root"
+  commit: {
+    id: '798e5f902592192afaba73f4668ae30e56eae492',
+    short_id: '798e5f90',
+    title: "Merge branch 'new-branch' into 'master'\r",
+    created_at: '2017-04-13T10:25:17.000+01:00',
+    parent_ids: [
+      '54d483b1ed156fbbf618886ddf7ab023e24f8738',
+      'c8e2d38a6c538822e81c57022a6e3a0cfedebbcc',
+    ],
+    message:
+      "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1",
+    author_name: 'Root',
+    author_email: 'admin@example.com',
+    authored_date: '2017-04-13T10:25:17.000+01:00',
+    committer_name: 'Root',
+    committer_email: 'admin@example.com',
+    committed_date: '2017-04-13T10:25:17.000+01:00',
+    author: {
+      name: 'Root',
+      username: 'root',
+      id: 1,
+      state: 'active',
+      avatar_url: null,
+      web_url: 'http://localhost:3000/root',
     },
-    "author_gravatar_url": null,
-    "commit_url": "http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492",
-    "commit_path": "/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492"
+    author_gravatar_url: null,
+    commit_url:
+      'http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
+    commit_path: '/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
   },
-  "created_at": "2017-04-13T09:25:18.881Z",
-  "updated_at": "2017-04-19T14:30:27.561Z"
+  created_at: '2017-04-13T09:25:18.881Z',
+  updated_at: '2017-04-19T14:30:27.561Z',
 };
diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..59092e0f041e2dc77dd5ed5cbe77934d0101124e
--- /dev/null
+++ b/spec/javascripts/pipelines/mock_data.js
@@ -0,0 +1,326 @@
+export const pipelineWithStages = {
+  id: 20333396,
+  user: {
+    id: 128633,
+    name: 'R茅my Coutable',
+    username: 'rymai',
+    state: 'active',
+    avatar_url:
+      'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
+    web_url: 'https://gitlab.com/rymai',
+    path: '/rymai',
+  },
+  active: true,
+  coverage: '58.24',
+  source: 'push',
+  created_at: '2018-04-11T14:04:53.881Z',
+  updated_at: '2018-04-11T14:05:00.792Z',
+  path: '/gitlab-org/gitlab-ee/pipelines/20333396',
+  flags: {
+    latest: true,
+    stuck: false,
+    auto_devops: false,
+    yaml_errors: false,
+    retryable: false,
+    cancelable: true,
+    failure_reason: false,
+  },
+  details: {
+    status: {
+      icon: 'status_running',
+      text: 'running',
+      label: 'running',
+      group: 'running',
+      has_details: true,
+      details_path: '/gitlab-org/gitlab-ee/pipelines/20333396',
+      favicon:
+        'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
+    },
+    duration: null,
+    finished_at: null,
+    stages: [
+      {
+        name: 'build',
+        title: 'build: skipped',
+        status: {
+          icon: 'status_skipped',
+          text: 'skipped',
+          label: 'skipped',
+          group: 'skipped',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#build',
+          favicon:
+            'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_skipped-a2eee568a5bffdb494050c7b62dde241de9189280836288ac8923d369f16222d.ico',
+        },
+        path: '/gitlab-org/gitlab-ee/pipelines/20333396#build',
+        dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=build',
+      },
+      {
+        name: 'prepare',
+        title: 'prepare: passed',
+        status: {
+          icon: 'status_success',
+          text: 'passed',
+          label: 'passed',
+          group: 'success',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#prepare',
+          favicon:
+            'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_success-26f59841becbef8c6fe414e9e74471d8bfd6a91b5855c19fe7f5923a40a7da47.ico',
+        },
+        path: '/gitlab-org/gitlab-ee/pipelines/20333396#prepare',
+        dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=prepare',
+      },
+      {
+        name: 'test',
+        title: 'test: running',
+        status: {
+          icon: 'status_running',
+          text: 'running',
+          label: 'running',
+          group: 'running',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#test',
+          favicon:
+            'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
+        },
+        path: '/gitlab-org/gitlab-ee/pipelines/20333396#test',
+        dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=test',
+      },
+      {
+        name: 'post-test',
+        title: 'post-test: created',
+        status: {
+          icon: 'status_created',
+          text: 'created',
+          label: 'created',
+          group: 'created',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-test',
+          favicon:
+            'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
+        },
+        path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-test',
+        dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=post-test',
+      },
+      {
+        name: 'pages',
+        title: 'pages: created',
+        status: {
+          icon: 'status_created',
+          text: 'created',
+          label: 'created',
+          group: 'created',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#pages',
+          favicon:
+            'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
+        },
+        path: '/gitlab-org/gitlab-ee/pipelines/20333396#pages',
+        dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=pages',
+      },
+      {
+        name: 'post-cleanup',
+        title: 'post-cleanup: created',
+        status: {
+          icon: 'status_created',
+          text: 'created',
+          label: 'created',
+          group: 'created',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-cleanup',
+          favicon:
+            'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
+        },
+        path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-cleanup',
+        dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=post-cleanup',
+      },
+    ],
+    artifacts: [
+      {
+        name: 'gitlab:assets:compile',
+        expired: false,
+        expire_at: '2018-05-12T14:22:54.730Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411438/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411438/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411438/artifacts/browse',
+      },
+      {
+        name: 'rspec-mysql 12 28',
+        expired: false,
+        expire_at: '2018-05-12T14:22:45.136Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411397/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411397/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411397/artifacts/browse',
+      },
+      {
+        name: 'rspec-mysql 6 28',
+        expired: false,
+        expire_at: '2018-05-12T14:22:41.523Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411391/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411391/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411391/artifacts/browse',
+      },
+      {
+        name: 'rspec-pg geo 0 1',
+        expired: false,
+        expire_at: '2018-05-12T14:22:13.287Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411353/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411353/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411353/artifacts/browse',
+      },
+      {
+        name: 'rspec-mysql 0 28',
+        expired: false,
+        expire_at: '2018-05-12T14:22:06.834Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411385/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411385/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411385/artifacts/browse',
+      },
+      {
+        name: 'spinach-mysql 0 2',
+        expired: false,
+        expire_at: '2018-05-12T14:21:51.409Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411423/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411423/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411423/artifacts/browse',
+      },
+      {
+        name: 'karma',
+        expired: false,
+        expire_at: '2018-05-12T14:21:20.934Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411440/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411440/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411440/artifacts/browse',
+      },
+      {
+        name: 'spinach-pg 0 2',
+        expired: false,
+        expire_at: '2018-05-12T14:20:01.028Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411419/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411419/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411419/artifacts/browse',
+      },
+      {
+        name: 'spinach-pg 1 2',
+        expired: false,
+        expire_at: '2018-05-12T14:19:04.336Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411421/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411421/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411421/artifacts/browse',
+      },
+      {
+        name: 'sast',
+        expired: null,
+        expire_at: null,
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/download',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/browse',
+      },
+      {
+        name: 'codequality',
+        expired: false,
+        expire_at: '2018-04-18T14:16:24.484Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/browse',
+      },
+      {
+        name: 'cache gems',
+        expired: null,
+        expire_at: null,
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411447/artifacts/download',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411447/artifacts/browse',
+      },
+      {
+        name: 'dependency_scanning',
+        expired: null,
+        expire_at: null,
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411443/artifacts/download',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411443/artifacts/browse',
+      },
+      {
+        name: 'compile-assets',
+        expired: false,
+        expire_at: '2018-04-18T14:12:07.638Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411334/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411334/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411334/artifacts/browse',
+      },
+      {
+        name: 'setup-test-env',
+        expired: false,
+        expire_at: '2018-04-18T14:10:27.024Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411336/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411336/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411336/artifacts/browse',
+      },
+      {
+        name: 'retrieve-tests-metadata',
+        expired: false,
+        expire_at: '2018-05-12T14:06:35.926Z',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411333/artifacts/download',
+        keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411333/artifacts/keep',
+        browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411333/artifacts/browse',
+      },
+    ],
+    manual_actions: [
+      {
+        name: 'package-and-qa',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411330/play',
+        playable: true,
+      },
+      {
+        name: 'review-docs-deploy',
+        path: '/gitlab-org/gitlab-ee/-/jobs/62411332/play',
+        playable: true,
+      },
+    ],
+  },
+  ref: {
+    name: 'master',
+    path: '/gitlab-org/gitlab-ee/commits/master',
+    tag: false,
+    branch: true,
+  },
+  commit: {
+    id: 'e6a2885c503825792cb8a84a8731295e361bd059',
+    short_id: 'e6a2885c',
+    title: "Merge branch 'ce-to-ee-2018-04-11' into 'master'",
+    created_at: '2018-04-11T14:04:39.000Z',
+    parent_ids: [
+      '5d9b5118f6055f72cff1a82b88133609912f2c1d',
+      '6fdc6ee76a8062fe41b1a33f7c503334a6ebdc02',
+    ],
+    message:
+      "Merge branch 'ce-to-ee-2018-04-11' into 'master'\n\nCE upstream - 2018-04-11 12:26 UTC\n\nSee merge request gitlab-org/gitlab-ee!5326",
+    author_name: 'R茅my Coutable',
+    author_email: 'remy@rymai.me',
+    authored_date: '2018-04-11T14:04:39.000Z',
+    committer_name: 'R茅my Coutable',
+    committer_email: 'remy@rymai.me',
+    committed_date: '2018-04-11T14:04:39.000Z',
+    author: {
+      id: 128633,
+      name: 'R茅my Coutable',
+      username: 'rymai',
+      state: 'active',
+      avatar_url:
+        'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
+      web_url: 'https://gitlab.com/rymai',
+      path: '/rymai',
+    },
+    author_gravatar_url:
+      'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
+    commit_url:
+      'https://gitlab.com/gitlab-org/gitlab-ee/commit/e6a2885c503825792cb8a84a8731295e361bd059',
+    commit_path: '/gitlab-org/gitlab-ee/commit/e6a2885c503825792cb8a84a8731295e361bd059',
+  },
+  cancel_path: '/gitlab-org/gitlab-ee/pipelines/20333396/cancel',
+  triggered_by: null,
+  triggered: [],
+};
+
+export const stageReply = {
+  html:
+    '\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="karma - failed \u0026lt;br\u0026gt; (script failure)" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62402048"\u003e\u003cspan class="ci-status-icon ci-status-icon-failed"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_failed"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003ekarma\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62402048/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="codequality - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398081"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003ecodequality\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398081/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:check-schema-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398066"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:check-schema-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398066/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:migrate:reset-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398065"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:migrate:reset-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398065/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:migrate:reset-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398064"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:migrate:reset-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398064/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:rollback-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398070"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:rollback-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398070/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:rollback-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398069"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:rollback-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398069/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="dependency_scanning - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398083"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edependency_scanning\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398083/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="docs lint - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398061"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edocs lint\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398061/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="downtime_check - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398062"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edowntime_check\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398062/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="ee_compat_check - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398063"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003eee_compat_check\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398063/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab:assets:compile - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398075"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab:assets:compile\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398075/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab:setup-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398073"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab:setup-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398073/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab:setup-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398071"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab:setup-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398071/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab_git_test - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398086"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab_git_test\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398086/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="migration:path-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398068"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003emigration:path-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398068/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="migration:path-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398067"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003emigration:path-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398067/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="qa:internal - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398084"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003eqa:internal\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398084/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="qa:selectors - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398085"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003eqa:selectors\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398085/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 0 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398020"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 0 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398020/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 1 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398022"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 1 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398022/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 10 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398033"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 10 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398033/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 11 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398034"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 11 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398034/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 12 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398035"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 12 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398035/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 13 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398036"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 13 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398036/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 14 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398037"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 14 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398037/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 15 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398038"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 15 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398038/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 16 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398039"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 16 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398039/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 17 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398040"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 17 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398040/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 18 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398041"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 18 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398041/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 19 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398042"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 19 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398042/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 2 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398024"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 2 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398024/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 20 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398043"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 20 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398043/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 21 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398044"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 21 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398044/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 22 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398046"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 22 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398046/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 23 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398047"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 23 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398047/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 24 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398048"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 24 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398048/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 25 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398049"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 25 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398049/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 26 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398050"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 26 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398050/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 27 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398051"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 27 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398051/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 3 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398025"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 3 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398025/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 4 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398027"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 4 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398027/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 5 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398028"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 5 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398028/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 6 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398029"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 6 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398029/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 7 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398030"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 7 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398030/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 8 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398031"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 8 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398031/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 9 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398032"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 9 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398032/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 0 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397981"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 0 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397981/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 1 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397985"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 1 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397985/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 10 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398000"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 10 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398000/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 11 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398001"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 11 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398001/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 12 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398002"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 12 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398002/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 13 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398003"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 13 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398003/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 14 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398004"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 14 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398004/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 15 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398006"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 15 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398006/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 16 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398007"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 16 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398007/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 17 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398008"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 17 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398008/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 18 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398009"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 18 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398009/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 19 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398010"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 19 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398010/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 2 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397986"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 2 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397986/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 20 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398012"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 20 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398012/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 21 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398013"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 21 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398013/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 22 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398014"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 22 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398014/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 23 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398015"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 23 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398015/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 24 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398016"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 24 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398016/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 25 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398017"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 25 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398017/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 26 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398018"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 26 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398018/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 27 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398019"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 27 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398019/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 3 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397988"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 3 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397988/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 4 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397989"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 4 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397989/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 5 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397991"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 5 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397991/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 6 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397993"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 6 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397993/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 7 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397994"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 7 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397994/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 8 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397995"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 8 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397995/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 9 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397996"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 9 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397996/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="sast - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398082"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003esast\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398082/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-mysql 0 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398058"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-mysql 0 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398058/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-mysql 1 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398059"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-mysql 1 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398059/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-pg 0 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398053"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-pg 0 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398053/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-pg 1 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398056"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-pg 1 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398056/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="static-analysis - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398060"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003estatic-analysis\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398060/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n',
+};
diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js
index 09a0c14d96ceb7db518dc5785d01971d1a335e6e..d6232f5c5671288ddd3ec9ff17a69e16ed972635 100644
--- a/spec/javascripts/pipelines/nav_controls_spec.js
+++ b/spec/javascripts/pipelines/nav_controls_spec.js
@@ -1,116 +1,79 @@
 import Vue from 'vue';
 import navControlsComp from '~/pipelines/components/nav_controls.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
 
 describe('Pipelines Nav Controls', () => {
   let NavControlsComponent;
+  let component;
 
   beforeEach(() => {
     NavControlsComponent = Vue.extend(navControlsComp);
   });
 
-  it('should render link to create a new pipeline', () => {
-    const mockData = {
-      newPipelinePath: 'foo',
-      hasCiEnabled: true,
-      helpPagePath: 'foo',
-      ciLintPath: 'foo',
-      resetCachePath: 'foo',
-      canCreatePipeline: true,
-    };
-
-    const component = new NavControlsComponent({
-      propsData: mockData,
-    }).$mount();
-
-    expect(component.$el.querySelector('.btn-create').textContent).toContain('Run Pipeline');
-    expect(component.$el.querySelector('.btn-create').getAttribute('href')).toEqual(mockData.newPipelinePath);
+  afterEach(() => {
+    component.$destroy();
   });
 
-  it('should not render link to create pipeline if no permission is provided', () => {
+  it('should render link to create a new pipeline', () => {
     const mockData = {
       newPipelinePath: 'foo',
-      hasCiEnabled: true,
-      helpPagePath: 'foo',
       ciLintPath: 'foo',
       resetCachePath: 'foo',
-      canCreatePipeline: false,
     };
 
-    const component = new NavControlsComponent({
-      propsData: mockData,
-    }).$mount();
+    component = mountComponent(NavControlsComponent, mockData);
 
-    expect(component.$el.querySelector('.btn-create')).toEqual(null);
+    expect(component.$el.querySelector('.js-run-pipeline').textContent).toContain('Run Pipeline');
+    expect(component.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(mockData.newPipelinePath);
   });
 
-  it('should render link for resetting runner caches', () => {
+  it('should not render link to create pipeline if no path is provided', () => {
     const mockData = {
-      newPipelinePath: 'foo',
-      hasCiEnabled: true,
       helpPagePath: 'foo',
       ciLintPath: 'foo',
       resetCachePath: 'foo',
-      canCreatePipeline: false,
     };
 
-    const component = new NavControlsComponent({
-      propsData: mockData,
-    }).$mount();
+    component = mountComponent(NavControlsComponent, mockData);
 
-    expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches');
-    expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath);
+    expect(component.$el.querySelector('.js-run-pipeline')).toEqual(null);
   });
 
   it('should render link for CI lint', () => {
     const mockData = {
       newPipelinePath: 'foo',
-      hasCiEnabled: true,
       helpPagePath: 'foo',
       ciLintPath: 'foo',
       resetCachePath: 'foo',
-      canCreatePipeline: true,
     };
 
-    const component = new NavControlsComponent({
-      propsData: mockData,
-    }).$mount();
+    component = mountComponent(NavControlsComponent, mockData);
 
-    expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint');
-    expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath);
+    expect(component.$el.querySelector('.js-ci-lint').textContent.trim()).toContain('CI Lint');
+    expect(component.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(mockData.ciLintPath);
   });
 
-  it('should render link to help page when CI is not enabled', () => {
-    const mockData = {
-      newPipelinePath: 'foo',
-      hasCiEnabled: false,
-      helpPagePath: 'foo',
-      ciLintPath: 'foo',
-      resetCachePath: 'foo',
-      canCreatePipeline: true,
-    };
+  describe('Reset Runners Cache', () => {
+    beforeEach(() => {
+      const mockData = {
+        newPipelinePath: 'foo',
+        ciLintPath: 'foo',
+        resetCachePath: 'foo',
+      };
 
-    const component = new NavControlsComponent({
-      propsData: mockData,
-    }).$mount();
+      component = mountComponent(NavControlsComponent, mockData);
+    });
 
-    expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines');
-    expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual(mockData.helpPagePath);
-  });
+    it('should render button for resetting runner caches', () => {
+      expect(component.$el.querySelector('.js-clear-cache').textContent.trim()).toContain('Clear Runner Caches');
+    });
 
-  it('should not render link to help page when CI is enabled', () => {
-    const mockData = {
-      newPipelinePath: 'foo',
-      hasCiEnabled: true,
-      helpPagePath: 'foo',
-      ciLintPath: 'foo',
-      resetCachePath: 'foo',
-      canCreatePipeline: true,
-    };
+    it('should emit postAction event when reset runner cache button is clicked', () => {
+      spyOn(component, '$emit');
 
-    const component = new NavControlsComponent({
-      propsData: mockData,
-    }).$mount();
+      component.$el.querySelector('.js-clear-cache').click();
 
-    expect(component.$el.querySelector('.btn-info')).toEqual(null);
+      expect(component.$emit).toHaveBeenCalledWith('resetRunnersCache', 'foo');
+    });
   });
 });
diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
index e58a8018ed5dcb3345d1bcf7395fc6c876836ee9..61ee2dc13caa127c72aae3e8144f0786783a21b7 100644
--- a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
+++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
@@ -1,42 +1,36 @@
-import _ from 'underscore';
-import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
 import PipelineMediator from '~/pipelines/pipeline_details_mediator';
 
 describe('PipelineMdediator', () => {
   let mediator;
+  let mock;
+
   beforeEach(() => {
-    mediator = new PipelineMediator({ endpoint: 'foo' });
+    mock = new MockAdapter(axios);
+    mediator = new PipelineMediator({ endpoint: 'foo.json' });
+  });
+
+  afterEach(() => {
+    mock.restore();
   });
 
   it('should set defaults', () => {
-    expect(mediator.options).toEqual({ endpoint: 'foo' });
+    expect(mediator.options).toEqual({ endpoint: 'foo.json' });
     expect(mediator.state.isLoading).toEqual(false);
     expect(mediator.store).toBeDefined();
     expect(mediator.service).toBeDefined();
   });
 
   describe('request and store data', () => {
-    const interceptor = (request, next) => {
-      next(request.respondWith(JSON.stringify({ foo: 'bar' }), {
-        status: 200,
-      }));
-    };
-
-    beforeEach(() => {
-      Vue.http.interceptors.push(interceptor);
-    });
-
-    afterEach(() => {
-      Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor);
-    });
-
-    it('should store received data', (done) => {
+    it('should store received data', done => {
+      mock.onGet('foo.json').reply(200, { id: '121123' });
       mediator.fetchPipeline();
 
       setTimeout(() => {
-        expect(mediator.store.state.pipeline).toEqual({ foo: 'bar' });
+        expect(mediator.store.state.pipeline).toEqual({ id: '121123' });
         done();
-      });
+      }, 0);
     });
   });
 });
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 54d5bfd51e662c98bf1cf1ac20b9326ced4b823b..ff17602da2bbeb67c934a9493d9805002358dcd0 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -1,56 +1,366 @@
-import _ from 'underscore';
 import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
 import pipelinesComp from '~/pipelines/components/pipelines.vue';
 import Store from '~/pipelines/stores/pipelines_store';
 import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { pipelineWithStages, stageReply } from './mock_data';
 
 describe('Pipelines', () => {
   const jsonFixtureName = 'pipelines/pipelines.json';
 
-  preloadFixtures('static/pipelines.html.raw');
   preloadFixtures(jsonFixtureName);
 
   let PipelinesComponent;
   let pipelines;
-  let component;
+  let vm;
+  let mock;
+
+  const paths = {
+    endpoint: 'twitter/flight/pipelines.json',
+    autoDevopsPath: '/help/topics/autodevops/index.md',
+    helpPagePath: '/help/ci/quick_start/README',
+    emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
+    errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
+    noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
+    ciLintPath: '/ci/lint',
+    resetCachePath: '/twitter/flight/settings/ci_cd/reset_cache',
+    newPipelinePath: '/twitter/flight/pipelines/new',
+  };
+
+  const noPermissions = {
+    endpoint: 'twitter/flight/pipelines.json',
+    autoDevopsPath: '/help/topics/autodevops/index.md',
+    helpPagePath: '/help/ci/quick_start/README',
+    emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
+    errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
+    noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
+  };
 
   beforeEach(() => {
-    loadFixtures('static/pipelines.html.raw');
+    mock = new MockAdapter(axios);
+
     pipelines = getJSONFixture(jsonFixtureName);
 
     PipelinesComponent = Vue.extend(pipelinesComp);
   });
 
   afterEach(() => {
-    component.$destroy();
+    vm.$destroy();
+    mock.restore();
   });
 
-  describe('successfull request', () => {
-    describe('with pipelines', () => {
-      const pipelinesInterceptor = (request, next) => {
-        next(request.respondWith(JSON.stringify(pipelines), {
-          status: 200,
-        }));
-      };
+  describe('With permission', () => {
+    describe('With pipelines in main tab', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
 
-      beforeEach(() => {
-        Vue.http.interceptors.push(pipelinesInterceptor);
-        component = mountComponent(PipelinesComponent, {
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: true,
+          canCreatePipeline: true,
+          ...paths,
+        });
+
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('renders tabs', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+      });
+
+      it('renders Run Pipeline link', () => {
+        expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
+      });
+
+      it('renders CI Lint link', () => {
+        expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
+      });
+
+      it('renders Clear Runner Cache button', () => {
+        expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
+      });
+
+      it('renders pipelines table', () => {
+        expect(
+          vm.$el.querySelectorAll('.gl-responsive-table-row').length,
+        ).toEqual(pipelines.pipelines.length + 1);
+      });
+    });
+
+    describe('Without pipelines on main tab with CI', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(200, {
+          pipelines: [],
+          count: {
+            all: 0,
+            pending: 0,
+            running: 0,
+            finished: 0,
+          },
+        });
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: true,
+          canCreatePipeline: true,
+          ...paths,
+        });
+
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('renders tabs', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+      });
+
+      it('renders Run Pipeline link', () => {
+        expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
+      });
+
+      it('renders CI Lint link', () => {
+        expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
+      });
+
+      it('renders Clear Runner Cache button', () => {
+        expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
+      });
+
+      it('renders tab empty state', () => {
+        expect(vm.$el.querySelector('.empty-state h4').textContent.trim()).toEqual('There are currently no pipelines.');
+      });
+    });
+
+    describe('Without pipelines nor CI', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(200, {
+          pipelines: [],
+          count: {
+            all: 0,
+            pending: 0,
+            running: 0,
+            finished: 0,
+          },
+        });
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: false,
+          canCreatePipeline: true,
+          ...paths,
+        });
+
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('renders empty state', () => {
+        expect(vm.$el.querySelector('.js-empty-state h4').textContent.trim()).toEqual('Build with confidence');
+        expect(vm.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual(paths.helpPagePath);
+      });
+
+      it('does not render tabs nor buttons', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBeNull();
+        expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+        expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+        expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+      });
+    });
+
+    describe('When API returns error', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(500, {});
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: false,
+          canCreatePipeline: true,
+          ...paths,
+        });
+
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('renders tabs', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+      });
+
+      it('renders buttons', () => {
+        expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
+        expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
+        expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
+      });
+
+      it('renders error state', () => {
+        expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error fetching the pipelines.');
+      });
+    });
+  });
+
+  describe('Without permission', () => {
+    describe('With pipelines in main tab', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+
+        vm = mountComponent(PipelinesComponent, {
           store: new Store(),
+          hasGitlabCi: false,
+          canCreatePipeline: false,
+          ...noPermissions,
+        });
+
+        setTimeout(() => {
+          done();
         });
       });
 
-      afterEach(() => {
-        Vue.http.interceptors = _.without(
-          Vue.http.interceptors, pipelinesInterceptor,
-        );
+      it('renders tabs', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+      });
+
+      it('does not render buttons', () => {
+        expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+        expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+        expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+      });
+
+      it('renders pipelines table', () => {
+        expect(
+          vm.$el.querySelectorAll('.gl-responsive-table-row').length,
+        ).toEqual(pipelines.pipelines.length + 1);
+      });
+    });
+
+    describe('Without pipelines on main tab with CI', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(200, {
+          pipelines: [],
+          count: {
+            all: 0,
+            pending: 0,
+            running: 0,
+            finished: 0,
+          },
+        });
+
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: true,
+          canCreatePipeline: false,
+          ...noPermissions,
+        });
+
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('renders tabs', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+      });
+
+      it('does not render buttons', () => {
+        expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+        expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+        expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+      });
+
+      it('renders tab empty state', () => {
+        expect(vm.$el.querySelector('.empty-state h4').textContent.trim()).toEqual('There are currently no pipelines.');
+      });
+    });
+
+    describe('Without pipelines nor CI', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(200, {
+          pipelines: [],
+          count: {
+            all: 0,
+            pending: 0,
+            running: 0,
+            finished: 0,
+          },
+        });
+
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: false,
+          canCreatePipeline: false,
+          ...noPermissions,
+        });
+
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('renders empty state without button to set CI', () => {
+        expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toEqual('This project is not currently set up to run pipelines.');
+        expect(vm.$el.querySelector('.js-get-started-pipelines')).toBeNull();
+      });
+
+      it('does not render tabs or buttons', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBeNull();
+        expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+        expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+        expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+      });
+    });
+
+    describe('When API returns error', () => {
+      beforeEach((done) => {
+        mock.onGet('twitter/flight/pipelines.json').reply(500, {});
+
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: false,
+          canCreatePipeline: true,
+          ...noPermissions,
+        });
+
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('renders tabs', () => {
+        expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+      });
+
+      it('does not renders buttons', () => {
+        expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+        expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+        expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+      });
+
+      it('renders error state', () => {
+        expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error fetching the pipelines.');
+      });
+    });
+  });
+
+  describe('successfull request', () => {
+    describe('with pipelines', () => {
+      beforeEach(() => {
+        mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: true,
+          canCreatePipeline: true,
+          ...paths,
+        });
       });
 
       it('should render table', (done) => {
         setTimeout(() => {
-          expect(component.$el.querySelector('.table-holder')).toBeDefined();
+          expect(vm.$el.querySelector('.table-holder')).toBeDefined();
           expect(
-            component.$el.querySelectorAll('.gl-responsive-table-row').length,
+            vm.$el.querySelectorAll('.gl-responsive-table-row').length,
           ).toEqual(pipelines.pipelines.length + 1);
           done();
         });
@@ -59,22 +369,22 @@ describe('Pipelines', () => {
       it('should render navigation tabs', (done) => {
         setTimeout(() => {
           expect(
-            component.$el.querySelector('.js-pipelines-tab-pending').textContent.trim(),
+            vm.$el.querySelector('.js-pipelines-tab-pending').textContent.trim(),
           ).toContain('Pending');
           expect(
-            component.$el.querySelector('.js-pipelines-tab-all').textContent.trim(),
+            vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim(),
           ).toContain('All');
           expect(
-            component.$el.querySelector('.js-pipelines-tab-running').textContent.trim(),
+            vm.$el.querySelector('.js-pipelines-tab-running').textContent.trim(),
           ).toContain('Running');
           expect(
-            component.$el.querySelector('.js-pipelines-tab-finished').textContent.trim(),
+            vm.$el.querySelector('.js-pipelines-tab-finished').textContent.trim(),
           ).toContain('Finished');
           expect(
-            component.$el.querySelector('.js-pipelines-tab-branches').textContent.trim(),
+            vm.$el.querySelector('.js-pipelines-tab-branches').textContent.trim(),
           ).toContain('Branches');
           expect(
-            component.$el.querySelector('.js-pipelines-tab-tags').textContent.trim(),
+            vm.$el.querySelector('.js-pipelines-tab-tags').textContent.trim(),
           ).toContain('Tags');
           done();
         });
@@ -82,10 +392,10 @@ describe('Pipelines', () => {
 
       it('should make an API request when using tabs', (done) => {
         setTimeout(() => {
-          spyOn(component, 'updateContent');
-          component.$el.querySelector('.js-pipelines-tab-finished').click();
+          spyOn(vm, 'updateContent');
+          vm.$el.querySelector('.js-pipelines-tab-finished').click();
 
-          expect(component.updateContent).toHaveBeenCalledWith({ scope: 'finished', page: '1' });
+          expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'finished', page: '1' });
           done();
         });
       });
@@ -93,9 +403,9 @@ describe('Pipelines', () => {
       describe('with pagination', () => {
         it('should make an API request when using pagination', (done) => {
           setTimeout(() => {
-            spyOn(component, 'updateContent');
+            spyOn(vm, 'updateContent');
             // Mock pagination
-            component.store.state.pageInfo = {
+            vm.store.state.pageInfo = {
               page: 1,
               total: 10,
               perPage: 2,
@@ -103,9 +413,9 @@ describe('Pipelines', () => {
               totalPages: 5,
             };
 
-            Vue.nextTick(() => {
-              component.$el.querySelector('.js-next-button a').click();
-              expect(component.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
+            vm.$nextTick(() => {
+              vm.$el.querySelector('.js-next-button a').click();
+              expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
 
               done();
             });
@@ -113,112 +423,324 @@ describe('Pipelines', () => {
         });
       });
     });
+  });
 
-    describe('without pipelines', () => {
-      const emptyInterceptor = (request, next) => {
-        next(request.respondWith(JSON.stringify([]), {
-          status: 200,
-        }));
-      };
+  describe('methods', () => {
+    beforeEach(() => {
+      spyOn(history, 'pushState').and.stub();
+    });
 
-      beforeEach(() => {
-        Vue.http.interceptors.push(emptyInterceptor);
-      });
+    describe('updateContent', () => {
+      it('should set given parameters', () => {
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: true,
+          canCreatePipeline: true,
+          ...paths,
+        });
+        vm.updateContent({ scope: 'finished', page: '4' });
 
-      afterEach(() => {
-        Vue.http.interceptors = _.without(
-          Vue.http.interceptors, emptyInterceptor,
-        );
+        expect(vm.page).toEqual('4');
+        expect(vm.scope).toEqual('finished');
+        expect(vm.requestData.scope).toEqual('finished');
+        expect(vm.requestData.page).toEqual('4');
       });
+    });
 
-      it('should render empty state', (done) => {
-        component = new PipelinesComponent({
-          propsData: {
-            store: new Store(),
-          },
-        }).$mount();
+    describe('onChangeTab', () => {
+      it('should set page to 1', () => {
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: true,
+          canCreatePipeline: true,
+          ...paths,
+        });
+        spyOn(vm, 'updateContent');
 
-        setTimeout(() => {
-          expect(component.$el.querySelector('.empty-state')).not.toBe(null);
-          done();
+        vm.onChangeTab('running');
+
+        expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' });
+      });
+    });
+
+    describe('onChangePage', () => {
+      it('should update page and keep scope', () => {
+        vm = mountComponent(PipelinesComponent, {
+          store: new Store(),
+          hasGitlabCi: true,
+          canCreatePipeline: true,
+          ...paths,
         });
+        spyOn(vm, 'updateContent');
+
+        vm.onChangePage(4);
+
+        expect(vm.updateContent).toHaveBeenCalledWith({ scope: vm.scope, page: '4' });
       });
     });
   });
 
-  describe('unsuccessfull request', () => {
-    const errorInterceptor = (request, next) => {
-      next(request.respondWith(JSON.stringify([]), {
-        status: 500,
-      }));
-    };
-
+  describe('computed properties', () => {
     beforeEach(() => {
-      Vue.http.interceptors.push(errorInterceptor);
+      vm = mountComponent(PipelinesComponent, {
+        store: new Store(),
+        hasGitlabCi: true,
+        canCreatePipeline: true,
+        ...paths,
+      });
     });
 
-    afterEach(() => {
-      Vue.http.interceptors = _.without(
-        Vue.http.interceptors, errorInterceptor,
-      );
+    describe('tabs', () => {
+      it('returns default tabs', () => {
+        expect(vm.tabs).toEqual([
+          { name: 'All', scope: 'all', count: undefined, isActive: true },
+          { name: 'Pending', scope: 'pending', count: undefined, isActive: false },
+          { name: 'Running', scope: 'running', count: undefined, isActive: false },
+          { name: 'Finished', scope: 'finished', count: undefined, isActive: false },
+          { name: 'Branches', scope: 'branches', isActive: false },
+          { name: 'Tags', scope: 'tags', isActive: false },
+        ]);
+      });
     });
 
-    it('should render error state', (done) => {
-      component = new PipelinesComponent({
-        propsData: {
-          store: new Store(),
-        },
-      }).$mount();
+    describe('emptyTabMessage', () => {
+      it('returns message with scope', (done) => {
+        vm.scope = 'pending';
 
-      setTimeout(() => {
-        expect(component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
-        done();
+        vm.$nextTick(() => {
+          expect(vm.emptyTabMessage).toEqual('There are currently no pending pipelines.');
+          done();
+        });
       });
-    });
-  });
 
-  describe('methods', () => {
-    beforeEach(() => {
-      spyOn(history, 'pushState').and.stub();
+      it('returns message without scope when scope is `all`', () => {
+        expect(vm.emptyTabMessage).toEqual('There are currently no pipelines.');
+      });
     });
 
-    describe('updateContent', () => {
-      it('should set given parameters', () => {
-        component = mountComponent(PipelinesComponent, {
-          store: new Store(),
+    describe('stateToRender', () => {
+      it('returns loading state when the app is loading', () => {
+        expect(vm.stateToRender).toEqual('loading');
+      });
+
+      it('returns error state when app has error', (done) => {
+        vm.hasError = true;
+        vm.isLoading = false;
+
+        vm.$nextTick(() => {
+          expect(vm.stateToRender).toEqual('error');
+          done();
+        });
+      });
+
+      it('returns table list when app has pipelines', (done) => {
+        vm.isLoading = false;
+        vm.hasError = false;
+        vm.state.pipelines = pipelines.pipelines;
+
+        vm.$nextTick(() => {
+          expect(vm.stateToRender).toEqual('tableList');
+
+          done();
+        });
+      });
+
+      it('returns empty tab when app does not have pipelines but project has pipelines', (done) => {
+        vm.state.count.all = 10;
+        vm.isLoading = false;
+
+        vm.$nextTick(() => {
+          expect(vm.stateToRender).toEqual('emptyTab');
+
+          done();
+        });
+      });
+
+      it('returns empty tab when project has CI', (done) => {
+        vm.isLoading = false;
+        vm.$nextTick(() => {
+          expect(vm.stateToRender).toEqual('emptyTab');
+
+          done();
         });
-        component.updateContent({ scope: 'finished', page: '4' });
+      });
+
+      it('returns empty state when project does not have pipelines nor CI', (done) => {
+        vm.isLoading = false;
+        vm.hasGitlabCi = false;
+        vm.$nextTick(() => {
+          expect(vm.stateToRender).toEqual('emptyState');
 
-        expect(component.page).toEqual('4');
-        expect(component.scope).toEqual('finished');
-        expect(component.requestData.scope).toEqual('finished');
-        expect(component.requestData.page).toEqual('4');
+          done();
+        });
       });
     });
 
-    describe('onChangeTab', () => {
-      it('should set page to 1', () => {
-        component = mountComponent(PipelinesComponent, {
-          store: new Store(),
+    describe('shouldRenderTabs', () => {
+      it('returns true when state is loading & has already made the first request', (done) => {
+        vm.isLoading = true;
+        vm.hasMadeRequest = true;
+
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderTabs).toEqual(true);
+
+          done();
         });
-        spyOn(component, 'updateContent');
+      });
 
-        component.onChangeTab('running');
+      it('returns true when state is tableList & has already made the first request', (done) => {
+        vm.isLoading = false;
+        vm.state.pipelines = pipelines.pipelines;
+        vm.hasMadeRequest = true;
 
-        expect(component.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' });
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderTabs).toEqual(true);
+
+          done();
+        });
+      });
+
+      it('returns true when state is error & has already made the first request', (done) => {
+        vm.isLoading = false;
+        vm.hasError = true;
+        vm.hasMadeRequest = true;
+
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderTabs).toEqual(true);
+
+          done();
+        });
+      });
+
+      it('returns true when state is empty tab & has already made the first request', (done) => {
+        vm.isLoading = false;
+        vm.state.count.all = 10;
+        vm.hasMadeRequest = true;
+
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderTabs).toEqual(true);
+
+          done();
+        });
+      });
+
+      it('returns false when has not made first request', (done) => {
+        vm.hasMadeRequest = false;
+
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderTabs).toEqual(false);
+
+          done();
+        });
+      });
+
+      it('returns false when state is emtpy state', (done) => {
+        vm.isLoading = false;
+        vm.hasMadeRequest = true;
+        vm.hasGitlabCi = false;
+
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderTabs).toEqual(false);
+
+          done();
+        });
       });
     });
 
-    describe('onChangePage', () => {
-      it('should update page and keep scope', () => {
-        component = mountComponent(PipelinesComponent, {
-          store: new Store(),
+    describe('shouldRenderButtons', () => {
+      it('returns true when it has paths & has made the first request', (done) => {
+        vm.hasMadeRequest = true;
+
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderButtons).toEqual(true);
+
+          done();
+        });
+      });
+
+      it('returns false when it has not made the first request', (done) => {
+        vm.hasMadeRequest = false;
+
+        vm.$nextTick(() => {
+          expect(vm.shouldRenderButtons).toEqual(false);
+
+          done();
         });
-        spyOn(component, 'updateContent');
+      });
+    });
+  });
 
-        component.onChangePage(4);
+  describe('updates results when a staged is clicked', () => {
+    beforeEach(() => {
+      const copyPipeline = Object.assign({}, pipelineWithStages);
+      copyPipeline.id += 1;
+      mock
+        .onGet('twitter/flight/pipelines.json').reply(200, {
+          pipelines: [pipelineWithStages],
+          count: {
+            all: 1,
+            finished: 1,
+            pending: 0,
+            running: 0,
+          },
+        }, {
+          'POLL-INTERVAL': 100,
+        })
+        .onGet(pipelineWithStages.details.stages[0].dropdown_path)
+          .reply(200, stageReply);
+
+      vm = mountComponent(PipelinesComponent, {
+        store: new Store(),
+        hasGitlabCi: true,
+        canCreatePipeline: true,
+        ...paths,
+      });
+    });
+
+    describe('when a request is being made', () => {
+      it('stops polling, cancels the request, fetches pipelines & restarts polling', (done) => {
+        spyOn(vm.poll, 'stop');
+        spyOn(vm.poll, 'restart');
+        spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
+        spyOn(vm.service.cancelationSource, 'cancel').and.callThrough();
 
-        expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
+        setTimeout(() => {
+          vm.isMakingRequest = true;
+          return vm.$nextTick()
+            .then(() => {
+              vm.$el.querySelector('.js-builds-dropdown-button').click();
+            })
+            .then(() => {
+              expect(vm.service.cancelationSource.cancel).toHaveBeenCalled();
+              expect(vm.poll.stop).toHaveBeenCalled();
+
+              setTimeout(() => {
+                expect(vm.getPipelines).toHaveBeenCalled();
+                expect(vm.poll.restart).toHaveBeenCalled();
+                done();
+              }, 0);
+            });
+        }, 0);
+      });
+    });
+
+    describe('when no request is being made', () => {
+      it('stops polling, fetches pipelines & restarts polling', (done) => {
+        spyOn(vm.poll, 'stop');
+        spyOn(vm.poll, 'restart');
+        spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
+
+        setTimeout(() => {
+          vm.$el.querySelector('.js-builds-dropdown-button').click();
+
+          expect(vm.poll.stop).toHaveBeenCalled();
+
+          setTimeout(() => {
+            expect(vm.getPipelines).toHaveBeenCalled();
+            expect(vm.poll.restart).toHaveBeenCalled();
+            done();
+          }, 0);
+        }, 0);
       });
     });
   });
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 61c2f783acca06eab1f370b262b28e30859a4b1c..be1632e72069d6a90bd859cfa3c834550b869c72 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -1,27 +1,36 @@
-import _ from 'underscore';
 import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
 import stage from '~/pipelines/components/stage.vue';
+import eventHub from '~/pipelines/event_hub';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
 
 describe('Pipelines stage component', () => {
   let StageComponent;
   let component;
+  let mock;
 
   beforeEach(() => {
+    mock = new MockAdapter(axios);
+
     StageComponent = Vue.extend(stage);
 
-    component = new StageComponent({
-      propsData: {
-        stage: {
-          status: {
-            group: 'success',
-            icon: 'icon_status_success',
-            title: 'success',
-          },
-          dropdown_path: 'foo',
+    component = mountComponent(StageComponent, {
+      stage: {
+        status: {
+          group: 'success',
+          icon: 'icon_status_success',
+          title: 'success',
         },
-        updateDropdown: false,
+        dropdown_path: 'path.json',
       },
-    }).$mount();
+      updateDropdown: false,
+    });
+  });
+
+  afterEach(() => {
+    component.$destroy();
+    mock.restore();
   });
 
   it('should render a dropdown with the status icon', () => {
@@ -31,49 +40,27 @@ describe('Pipelines stage component', () => {
   });
 
   describe('with successfull request', () => {
-    const interceptor = (request, next) => {
-      next(request.respondWith(JSON.stringify({ html: 'foo' }), {
-        status: 200,
-      }));
-    };
-
     beforeEach(() => {
-      Vue.http.interceptors.push(interceptor);
-    });
-
-    afterEach(() => {
-      Vue.http.interceptors = _.without(
-        Vue.http.interceptors, interceptor,
-      );
+      mock.onGet('path.json').reply(200, { html: 'foo' });
     });
 
-    it('should render the received data', (done) => {
+    it('should render the received data and emit `clickedDropdown` event', done => {
+      spyOn(eventHub, '$emit');
       component.$el.querySelector('button').click();
 
       setTimeout(() => {
         expect(
           component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
         ).toEqual('foo');
+        expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
         done();
       }, 0);
     });
   });
 
   describe('when request fails', () => {
-    const interceptor = (request, next) => {
-      next(request.respondWith(JSON.stringify({}), {
-        status: 500,
-      }));
-    };
-
     beforeEach(() => {
-      Vue.http.interceptors.push(interceptor);
-    });
-
-    afterEach(() => {
-      Vue.http.interceptors = _.without(
-        Vue.http.interceptors, interceptor,
-      );
+      mock.onGet('path.json').reply(500);
     });
 
     it('should close the dropdown', () => {
@@ -86,33 +73,18 @@ describe('Pipelines stage component', () => {
   });
 
   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,
-      );
+      mock.onGet('bar.json').reply(200, { html: 'this is the updated content' });
     });
 
-    it('should update the stage to request the new endpoint provided', (done) => {
+    it('should update the stage to request the new endpoint provided', done => {
       component.stage = {
         status: {
           group: 'running',
           icon: 'running',
           title: 'running',
         },
-        dropdown_path: 'bar',
+        dropdown_path: 'bar.json',
       };
 
       Vue.nextTick(() => {
@@ -121,7 +93,7 @@ describe('Pipelines stage component', () => {
         setTimeout(() => {
           expect(
             component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
-            ).toEqual('this is the updated content');
+          ).toEqual('this is the updated content');
           done();
         });
       });
diff --git a/spec/javascripts/profile/account/components/update_username_spec.js b/spec/javascripts/profile/account/components/update_username_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bac306edf5a4e6eeac99b3dbffcd725aecd61c7e
--- /dev/null
+++ b/spec/javascripts/profile/account/components/update_username_spec.js
@@ -0,0 +1,172 @@
+import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+
+import updateUsername from '~/profile/account/components/update_username.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('UpdateUsername component', () => {
+  const rootUrl = gl.TEST_HOST;
+  const actionUrl = `${gl.TEST_HOST}/update/username`;
+  const username = 'hasnoname';
+  const newUsername = 'new_username';
+  let Component;
+  let vm;
+  let axiosMock;
+
+  beforeEach(() => {
+    axiosMock = new MockAdapter(axios);
+    Component = Vue.extend(updateUsername);
+    vm = mountComponent(Component, {
+      actionUrl,
+      rootUrl,
+      initialUsername: username,
+    });
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+    axiosMock.restore();
+  });
+
+  const findElements = () => {
+    const modalSelector = `#${vm.$options.modalId}`;
+
+    return {
+      input: vm.$el.querySelector(`#${vm.$options.inputId}`),
+      openModalBtn: vm.$el.querySelector(`[data-target="${modalSelector}"]`),
+      modal: vm.$el.querySelector(modalSelector),
+      modalBody: vm.$el.querySelector(`${modalSelector} .modal-body`),
+      modalHeader: vm.$el.querySelector(`${modalSelector} .modal-title`),
+      confirmModalBtn: vm.$el.querySelector(`${modalSelector} .btn-warning`),
+    };
+  };
+
+  it('has a disabled button if the username was not changed', done => {
+    const { input, openModalBtn } = findElements();
+    input.dispatchEvent(new Event('input'));
+
+    Vue.nextTick()
+      .then(() => {
+        expect(vm.username).toBe(username);
+        expect(vm.newUsername).toBe(username);
+        expect(openModalBtn).toBeDisabled();
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('has an enabled button which if the username was changed', done => {
+    const { input, openModalBtn } = findElements();
+    input.value = newUsername;
+    input.dispatchEvent(new Event('input'));
+
+    Vue.nextTick()
+      .then(() => {
+        expect(vm.username).toBe(username);
+        expect(vm.newUsername).toBe(newUsername);
+        expect(openModalBtn).not.toBeDisabled();
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('confirmation modal contains proper header and body', done => {
+    const { modalBody, modalHeader } = findElements();
+
+    vm.newUsername = newUsername;
+
+    Vue.nextTick()
+      .then(() => {
+        expect(modalHeader.textContent).toContain('Change username?');
+        expect(modalBody.textContent).toContain(
+          `You are going to change the username ${username} to ${newUsername}`,
+        );
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('confirmation modal should escape usernames properly', done => {
+    const { modalBody } = findElements();
+
+    vm.username = vm.newUsername = '<i>Italic</i>';
+
+    Vue.nextTick()
+      .then(() => {
+        expect(modalBody.innerHTML).toContain('&lt;i&gt;Italic&lt;/i&gt;');
+        expect(modalBody.innerHTML).not.toContain(vm.username);
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('executes API call on confirmation button click', done => {
+    const { confirmModalBtn } = findElements();
+
+    axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]);
+    spyOn(axios, 'put').and.callThrough();
+
+    vm.newUsername = newUsername;
+
+    Vue.nextTick()
+      .then(() => {
+        confirmModalBtn.click();
+        expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } });
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('sets the username after a successful update', done => {
+    const { input, openModalBtn } = findElements();
+
+    axiosMock.onPut(actionUrl).replyOnce(() => {
+      expect(input).toBeDisabled();
+      expect(openModalBtn).toBeDisabled();
+
+      return [200, { message: 'Username changed' }];
+    });
+
+    vm.newUsername = newUsername;
+
+    vm
+      .onConfirm()
+      .then(() => {
+        expect(vm.username).toBe(newUsername);
+        expect(vm.newUsername).toBe(newUsername);
+        expect(input).not.toBeDisabled();
+        expect(input.value).toBe(newUsername);
+        expect(openModalBtn).toBeDisabled();
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+
+  it('does not set the username after a erroneous update', done => {
+    const { input, openModalBtn } = findElements();
+
+    axiosMock.onPut(actionUrl).replyOnce(() => {
+      expect(input).toBeDisabled();
+      expect(openModalBtn).toBeDisabled();
+
+      return [400, { message: 'Invalid username' }];
+    });
+
+    const invalidUsername = 'anything.git';
+    vm.newUsername = invalidUsername;
+
+    vm
+      .onConfirm()
+      .then(() => done.fail('Expected onConfirm to throw!'))
+      .catch(() => {
+        expect(vm.username).toBe(username);
+        expect(vm.newUsername).toBe(invalidUsername);
+        expect(input).not.toBeDisabled();
+        expect(input.value).toBe(invalidUsername);
+        expect(openModalBtn).not.toBeDisabled();
+      })
+      .then(done)
+      .catch(done.fail);
+  });
+});
diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js
index dda83645c924d834709339c0deeeeb6fa32e8cb7..1b65f767f96a5cb4aa037d6ff2a3d41a0febc11a 100644
--- a/spec/javascripts/project_select_combo_button_spec.js
+++ b/spec/javascripts/project_select_combo_button_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import ProjectSelectComboButton from '~/project_select_combo_button';
 
 const fixturePath = 'static/project_select_combo_button.html.raw';
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
index 8731ce35d81442440cade20ae59637bb1468710d..84515d2bf97d0a48e658f6102d5fe2fc534b76f1 100644
--- a/spec/javascripts/projects/project_new_spec.js
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import projectNew from '~/projects/project_new';
 
 describe('New Project', () => {
diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
index f6c0f51cf629a70df05803335bea6ea6b2592918..955ec6a531c2f1f970d733980f1546173bff84fd 100644
--- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
@@ -85,7 +85,7 @@ describe('PrometheusMetrics', () => {
       expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
       expect(prometheusMetrics.$monitoredMetricsList.hasClass('hidden')).toBeFalsy();
 
-      expect(prometheusMetrics.$monitoredMetricsCount.text()).toEqual('12');
+      expect(prometheusMetrics.$monitoredMetricsCount.text()).toEqual('3 exporters with 12 metrics were found');
       expect($metricsListLi.length).toEqual(metrics.length);
       expect($metricsListLi.first().find('.badge').text()).toEqual(`${metrics[0].active_metrics}`);
     });
diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js
index 3c9da4f107b27c8b0787e73c170190dd2a199fbe..bc4c444655a518d0bcd4380553d5ac23f619bd23 100644
--- a/spec/javascripts/registry/stores/actions_spec.js
+++ b/spec/javascripts/registry/stores/actions_spec.js
@@ -29,57 +29,96 @@ describe('Actions Registry Store', () => {
     describe('fetchRepos', () => {
       beforeEach(() => {
         interceptor = (request, next) => {
-          next(request.respondWith(JSON.stringify(reposServerResponse), {
-            status: 200,
-          }));
+          next(
+            request.respondWith(JSON.stringify(reposServerResponse), {
+              status: 200,
+            }),
+          );
         };
 
         Vue.http.interceptors.push(interceptor);
       });
 
-      it('should set receveived repos', (done) => {
-        testAction(actions.fetchRepos, null, mockedState, [
-          { type: types.TOGGLE_MAIN_LOADING },
-          { type: types.SET_REPOS_LIST, payload: reposServerResponse },
-        ], done);
+      it('should set receveived repos', done => {
+        testAction(
+          actions.fetchRepos,
+          null,
+          mockedState,
+          [
+            { type: types.TOGGLE_MAIN_LOADING },
+            { type: types.TOGGLE_MAIN_LOADING },
+            { type: types.SET_REPOS_LIST, payload: reposServerResponse },
+          ],
+          [],
+          done,
+        );
       });
     });
 
     describe('fetchList', () => {
       beforeEach(() => {
         interceptor = (request, next) => {
-          next(request.respondWith(JSON.stringify(registryServerResponse), {
-            status: 200,
-          }));
+          next(
+            request.respondWith(JSON.stringify(registryServerResponse), {
+              status: 200,
+            }),
+          );
         };
 
         Vue.http.interceptors.push(interceptor);
       });
 
-      it('should set received list', (done) => {
+      it('should set received list', done => {
         mockedState.repos = parsedReposServerResponse;
 
-        testAction(actions.fetchList, { repo: mockedState.repos[1] }, mockedState, [
-          { type: types.TOGGLE_REGISTRY_LIST_LOADING },
-          { type: types.SET_REGISTRY_LIST, payload: registryServerResponse },
-        ], done);
+        const repo = mockedState.repos[1];
+
+        testAction(
+          actions.fetchList,
+          { repo },
+          mockedState,
+          [
+            { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
+            { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
+            {
+              type: types.SET_REGISTRY_LIST,
+              payload: {
+                repo,
+                resp: registryServerResponse,
+                headers: jasmine.anything(),
+              },
+            },
+          ],
+          [],
+          done,
+        );
       });
     });
   });
 
   describe('setMainEndpoint', () => {
-    it('should commit set main endpoint', (done) => {
-      testAction(actions.setMainEndpoint, 'endpoint', mockedState, [
-        { type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' },
-      ], done);
+    it('should commit set main endpoint', done => {
+      testAction(
+        actions.setMainEndpoint,
+        'endpoint',
+        mockedState,
+        [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }],
+        [],
+        done,
+      );
     });
   });
 
   describe('toggleLoading', () => {
-    it('should commit toggle main loading', (done) => {
-      testAction(actions.toggleLoading, null, mockedState, [
-        { type: types.TOGGLE_MAIN_LOADING },
-      ], done);
+    it('should commit toggle main loading', done => {
+      testAction(
+        actions.toggleLoading,
+        null,
+        mockedState,
+        [{ type: types.TOGGLE_MAIN_LOADING }],
+        [],
+        done,
+      );
     });
   });
 });
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
deleted file mode 100644
index b509cedbe80d1cd0f113d11cfc64d92d6dbfcc1c..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file } from '../../helpers';
-
-describe('Multi-file editor commit sidebar list collapsed', () => {
-  let vm;
-
-  beforeEach(() => {
-    const Component = Vue.extend(listCollapsed);
-
-    vm = createComponentWithStore(Component, store);
-
-    vm.$store.state.openFiles.push(file('file1'), file('file2'));
-    vm.$store.state.openFiles[0].tempFile = true;
-    vm.$store.state.openFiles.forEach((f) => {
-      Object.assign(f, {
-        changed: true,
-      });
-    });
-
-    vm.$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-  });
-
-  it('renders added & modified files count', () => {
-    expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1');
-  });
-});
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
deleted file mode 100644
index 6f1a1d874d39d95112ca8f8cb364bf4aae493e61..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import Vue from 'vue';
-import listItem from '~/ide/components/commit_sidebar/list_item.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { file } from '../../helpers';
-
-describe('Multi-file editor commit sidebar list item', () => {
-  let vm;
-  let f;
-
-  beforeEach(() => {
-    const Component = Vue.extend(listItem);
-
-    f = file('test-file');
-
-    vm = mountComponent(Component, {
-      file: f,
-    });
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-  });
-
-  it('renders file path', () => {
-    expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
-  });
-
-  describe('computed', () => {
-    describe('iconName', () => {
-      it('returns modified when not a tempFile', () => {
-        expect(vm.iconName).toBe('file-modified');
-      });
-
-      it('returns addition when not a tempFile', () => {
-        f.tempFile = true;
-
-        expect(vm.iconName).toBe('file-addition');
-      });
-    });
-
-    describe('iconClass', () => {
-      it('returns modified when not a tempFile', () => {
-        expect(vm.iconClass).toContain('multi-file-modified');
-      });
-
-      it('returns addition when not a tempFile', () => {
-        f.tempFile = true;
-
-        expect(vm.iconClass).toContain('multi-file-addition');
-      });
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_spec.js
deleted file mode 100644
index aeb9de9ace456ff5e3185ef5c7d912829e6691b2..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/commit_sidebar/list_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file } from '../../helpers';
-
-describe('Multi-file editor commit sidebar list', () => {
-  let vm;
-
-  beforeEach(() => {
-    const Component = Vue.extend(commitSidebarList);
-
-    vm = createComponentWithStore(Component, store, {
-      title: 'Staged',
-      fileList: [],
-    });
-
-    vm.$store.state.rightPanelCollapsed = false;
-
-    vm.$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-  });
-
-  describe('empty file list', () => {
-    it('renders no changes text', () => {
-      expect(vm.$el.querySelector('.help-block').textContent.trim()).toBe('No changes');
-    });
-  });
-
-  describe('with a list of files', () => {
-    beforeEach((done) => {
-      const f = file('file name');
-      f.changed = true;
-      vm.fileList.push(f);
-
-      Vue.nextTick(done);
-    });
-
-    it('renders list', () => {
-      expect(vm.$el.querySelectorAll('li').length).toBe(1);
-    });
-  });
-
-  describe('collapsed', () => {
-    beforeEach((done) => {
-      vm.$store.state.rightPanelCollapsed = true;
-
-      Vue.nextTick(done);
-    });
-
-    it('hides list', () => {
-      expect(vm.$el.querySelector('.list-unstyled')).toBeNull();
-      expect(vm.$el.querySelector('.help-block')).toBeNull();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/ide_context_bar_spec.js b/spec/javascripts/repo/components/ide_context_bar_spec.js
deleted file mode 100644
index 935da259a990356fba47e330496fa004cf68465e..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/ide_context_bar_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ideContextBar from '~/ide/components/ide_context_bar.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-
-describe('Multi-file editor right context bar', () => {
-  let vm;
-
-  beforeEach(() => {
-    const Component = Vue.extend(ideContextBar);
-
-    vm = createComponentWithStore(Component, store);
-
-    vm.$store.state.rightPanelCollapsed = false;
-
-    vm.$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-  });
-
-  describe('collapsed', () => {
-    beforeEach((done) => {
-      vm.$store.state.rightPanelCollapsed = true;
-
-      Vue.nextTick(done);
-    });
-
-    it('adds collapsed class', () => {
-      expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
-    });
-
-    it('shows correct icon', () => {
-      expect(vm.currentIcon).toBe('angle-double-left');
-    });
-  });
-
-  it('clicking toggle collapse button collapses the bar', () => {
-    spyOn(vm, 'setPanelCollapsedStatus').and.returnValue(Promise.resolve());
-
-    vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
-
-    expect(vm.setPanelCollapsedStatus).toHaveBeenCalledWith({
-      side: 'right',
-      collapsed: true,
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/ide_repo_tree_spec.js b/spec/javascripts/repo/components/ide_repo_tree_spec.js
deleted file mode 100644
index e3bbda514da1c6c2164122dfdc094dbb3ac369d1..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/ide_repo_tree_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ideRepoTree from '~/ide/components/ide_repo_tree.vue';
-import { file, resetStore } from '../helpers';
-
-describe('IdeRepoTree', () => {
-  let vm;
-
-  beforeEach(() => {
-    const IdeRepoTree = Vue.extend(ideRepoTree);
-
-    vm = new IdeRepoTree({
-      store,
-      propsData: {
-        treeId: 'abcproject/mybranch',
-      },
-    });
-
-    vm.$store.state.currentBranch = 'master';
-    vm.$store.state.isRoot = true;
-    vm.$store.state.trees['abcproject/mybranch'] = {
-      tree: [file()],
-    };
-
-    vm.$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders a sidebar', () => {
-    const tbody = vm.$el.querySelector('tbody');
-
-    expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
-    expect(tbody.querySelector('.repo-file-options')).toBeFalsy();
-    expect(tbody.querySelector('.prev-directory')).toBeFalsy();
-    expect(tbody.querySelector('.loading-file')).toBeFalsy();
-    expect(tbody.querySelector('.file')).toBeTruthy();
-  });
-
-  it('renders 3 loading files if tree is loading', (done) => {
-    vm.treeId = '123';
-
-    Vue.nextTick(() => {
-      expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toEqual(3);
-
-      done();
-    });
-  });
-
-  it('renders a prev directory if is not root', (done) => {
-    vm.$store.state.isRoot = false;
-
-    Vue.nextTick(() => {
-      expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
-
-      done();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/ide_side_bar_spec.js b/spec/javascripts/repo/components/ide_side_bar_spec.js
deleted file mode 100644
index 79c3c8128e8a986eb7ba75b62f387c2a2246d73c..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/ide_side_bar_spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ideSidebar from '~/ide/components/ide_side_bar.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../helpers';
-
-describe('IdeSidebar', () => {
-  let vm;
-
-  beforeEach(() => {
-    const Component = Vue.extend(ideSidebar);
-
-    vm = createComponentWithStore(Component, store).$mount();
-
-    vm.$store.state.leftPanelCollapsed = false;
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders a sidebar', () => {
-    expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull();
-  });
-
-  describe('collapsed', () => {
-    beforeEach((done) => {
-      vm.$store.state.leftPanelCollapsed = true;
-
-      Vue.nextTick(done);
-    });
-
-    it('adds collapsed class', () => {
-      expect(vm.$el.classList).toContain('is-collapsed');
-    });
-
-    it('shows correct icon', () => {
-      expect(vm.currentIcon).toBe('angle-double-right');
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/ide_spec.js b/spec/javascripts/repo/components/ide_spec.js
deleted file mode 100644
index 18135177b5eba158842b5c1ddb1b3a4a196246ce..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/ide_spec.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ide from '~/ide/components/ide.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file, resetStore } from '../helpers';
-
-describe('ide component', () => {
-  let vm;
-
-  beforeEach(() => {
-    const Component = Vue.extend(ide);
-
-    vm = createComponentWithStore(Component, store, {
-      emptyStateSvgPath: 'svg',
-    }).$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('does not render panel right when no files open', () => {
-    expect(vm.$el.querySelector('.panel-right')).toBeNull();
-  });
-
-  it('renders panel right when files are open', (done) => {
-    vm.$store.state.trees['abcproject/mybranch'] = {
-      tree: [file()],
-    };
-
-    Vue.nextTick(() => {
-      expect(vm.$el.querySelector('.panel-right')).toBeNull();
-
-      done();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/new_branch_form_spec.js b/spec/javascripts/repo/components/new_branch_form_spec.js
deleted file mode 100644
index 82597fc75e8060542d1729c15b258d61444816d5..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/new_branch_form_spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import newBranchForm from '~/ide/components/new_branch_form.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../helpers';
-
-describe('Multi-file editor new branch form', () => {
-  let vm;
-
-  beforeEach(() => {
-    const Component = Vue.extend(newBranchForm);
-
-    vm = createComponentWithStore(Component, store);
-
-    vm.$store.state.currentBranch = 'master';
-
-    vm.$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  describe('template', () => {
-    it('renders submit as disabled', () => {
-      expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBe('disabled');
-    });
-
-    it('enables the submit button when branch is not empty', (done) => {
-      vm.branchName = 'testing';
-
-      Vue.nextTick(() => {
-        expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBeNull();
-
-        done();
-      });
-    });
-
-    it('displays current branch creating from', (done) => {
-      Vue.nextTick(() => {
-        expect(vm.$el.querySelector('p').textContent.replace(/\s+/g, ' ').trim()).toBe('Create from: master');
-
-        done();
-      });
-    });
-  });
-
-  describe('submitNewBranch', () => {
-    beforeEach(() => {
-      spyOn(vm, 'createNewBranch').and.returnValue(Promise.resolve());
-    });
-
-    it('sets to loading', () => {
-      vm.submitNewBranch();
-
-      expect(vm.loading).toBeTruthy();
-    });
-
-    it('hides current flash element', (done) => {
-      vm.$refs.flashContainer.innerHTML = '<div class="flash-alert"></div>';
-
-      vm.submitNewBranch();
-
-      Vue.nextTick(() => {
-        expect(vm.$el.querySelector('.flash-alert')).toBeNull();
-
-        done();
-      });
-    });
-
-    it('calls createdNewBranch with branchName', () => {
-      vm.branchName = 'testing';
-
-      vm.submitNewBranch();
-
-      expect(vm.createNewBranch).toHaveBeenCalledWith('testing');
-    });
-  });
-
-  describe('submitNewBranch with error', () => {
-    beforeEach(() => {
-      spyOn(vm, 'createNewBranch').and.returnValue(Promise.reject({
-        json: () => Promise.resolve({
-          message: 'error message',
-        }),
-      }));
-    });
-
-    it('sets loading to false', (done) => {
-      vm.loading = true;
-
-      vm.submitNewBranch();
-
-      setTimeout(() => {
-        expect(vm.loading).toBeFalsy();
-
-        done();
-      });
-    });
-
-    it('creates flash element', (done) => {
-      vm.submitNewBranch();
-
-      setTimeout(() => {
-        expect(vm.$el.querySelector('.flash-alert')).not.toBeNull();
-        expect(vm.$el.querySelector('.flash-alert').textContent.trim()).toBe('error message');
-
-        done();
-      });
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js
deleted file mode 100644
index 4a8e4445e2f477e3c7966e07947e9903594148c8..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/new_dropdown/index_spec.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import newDropdown from '~/ide/components/new_dropdown/index.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../../helpers';
-
-describe('new dropdown component', () => {
-  let vm;
-
-  beforeEach(() => {
-    const component = Vue.extend(newDropdown);
-
-    vm = createComponentWithStore(component, store, {
-      branch: 'master',
-      path: '',
-    });
-
-    vm.$store.state.currentProjectId = 'abcproject';
-    vm.$store.state.path = '';
-
-    vm.$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders new file, upload and new directory links', () => {
-    expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
-    expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file');
-    expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory');
-  });
-
-  describe('createNewItem', () => {
-    it('sets modalType to blob when new file is clicked', () => {
-      vm.$el.querySelectorAll('a')[0].click();
-
-      expect(vm.modalType).toBe('blob');
-    });
-
-    it('sets modalType to tree when new directory is clicked', () => {
-      vm.$el.querySelectorAll('a')[2].click();
-
-      expect(vm.modalType).toBe('tree');
-    });
-
-    it('opens modal when link is clicked', (done) => {
-      vm.$el.querySelectorAll('a')[0].click();
-
-      Vue.nextTick(() => {
-        expect(vm.$el.querySelector('.modal')).not.toBeNull();
-
-        done();
-      });
-    });
-  });
-
-  describe('hideModal', () => {
-    beforeAll((done) => {
-      vm.openModal = true;
-      Vue.nextTick(done);
-    });
-
-    it('closes modal after toggling', (done) => {
-      vm.hideModal();
-
-      Vue.nextTick()
-        .then(() => {
-          expect(vm.$el.querySelector('.modal')).toBeNull();
-        })
-        .then(done)
-        .catch(done.fail);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
deleted file mode 100644
index d6a1fdd115c673b1fc9a651dbd84c7fdaf2a4299..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/new_dropdown/modal_spec.js
+++ /dev/null
@@ -1,237 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import modal from '~/ide/components/new_dropdown/modal.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file, resetStore } from '../../helpers';
-
-describe('new file modal component', () => {
-  const Component = Vue.extend(modal);
-  let vm;
-  let projectTree;
-
-  beforeEach(() => {
-    spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
-      data: {
-        id: '123',
-      },
-    }));
-
-    spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
-      data: {
-        commit: {
-          id: '123branch',
-        },
-      },
-    }));
-
-    spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
-      headers: {
-        'page-title': 'test',
-      },
-      json: () => Promise.resolve({
-        last_commit_path: 'last_commit_path',
-        parent_tree_url: 'parent_tree_url',
-        path: '/',
-        trees: [{ name: 'tree' }],
-        blobs: [{ name: 'blob' }],
-        submodules: [{ name: 'submodule' }],
-      }),
-    }));
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  ['tree', 'blob'].forEach((type) => {
-    describe(type, () => {
-      beforeEach(() => {
-        store.state.projects.abcproject = {
-          web_url: '',
-        };
-        store.state.trees = [];
-        store.state.trees['abcproject/mybranch'] = {
-          tree: [],
-        };
-        projectTree = store.state.trees['abcproject/mybranch'];
-        store.state.currentProjectId = 'abcproject';
-
-        vm = createComponentWithStore(Component, store, {
-          type,
-          branchId: 'master',
-          path: '',
-          parent: projectTree,
-        });
-
-        vm.entryName = 'testing';
-
-        vm.$mount();
-      });
-
-      it(`sets modal title as ${type}`, () => {
-        const title = type === 'tree' ? 'directory' : 'file';
-
-        expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
-      });
-
-      it(`sets button label as ${type}`, () => {
-        const title = type === 'tree' ? 'directory' : 'file';
-
-        expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
-      });
-
-      it(`sets form label as ${type}`, () => {
-        const title = type === 'tree' ? 'Directory' : 'File';
-
-        expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`);
-      });
-
-      describe('createEntryInStore', () => {
-        it('calls createTempEntry', () => {
-          spyOn(vm, 'createTempEntry');
-
-          vm.createEntryInStore();
-
-          expect(vm.createTempEntry).toHaveBeenCalledWith({
-            projectId: 'abcproject',
-            branchId: 'master',
-            parent: projectTree,
-            name: 'testing',
-            type,
-          });
-        });
-
-        it('sets editMode to true', (done) => {
-          vm.createEntryInStore();
-
-          setTimeout(() => {
-            expect(vm.$store.state.editMode).toBeTruthy();
-
-            done();
-          });
-        });
-
-        it('toggles blob view', (done) => {
-          vm.createEntryInStore();
-
-          setTimeout(() => {
-            expect(vm.$store.state.currentBlobView).toBe('repo-editor');
-
-            done();
-          });
-        });
-
-        it('opens newly created file', (done) => {
-          if (type === 'blob') {
-            vm.createEntryInStore();
-
-            setTimeout(() => {
-              expect(vm.$store.state.openFiles.length).toBe(1);
-              expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep');
-
-              done();
-            });
-          } else {
-            done();
-          }
-        });
-
-        if (type === 'blob') {
-          it('creates new file', (done) => {
-            vm.createEntryInStore();
-
-            setTimeout(() => {
-              const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-              expect(baseTree.length).toBe(1);
-              expect(baseTree[0].name).toBe('testing');
-              expect(baseTree[0].type).toBe('blob');
-              expect(baseTree[0].tempFile).toBeTruthy();
-
-              done();
-            });
-          });
-
-          it('does not create temp file when file already exists', (done) => {
-            const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-            baseTree.push(file('testing', '1', type));
-
-            vm.createEntryInStore();
-
-            setTimeout(() => {
-              expect(baseTree.length).toBe(1);
-              expect(baseTree[0].name).toBe('testing');
-              expect(baseTree[0].type).toBe('blob');
-              expect(baseTree[0].tempFile).toBeFalsy();
-
-              done();
-            });
-          });
-        } else {
-          it('creates new tree', () => {
-            vm.createEntryInStore();
-
-            const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-            expect(baseTree.length).toBe(1);
-            expect(baseTree[0].name).toBe('testing');
-            expect(baseTree[0].type).toBe('tree');
-            expect(baseTree[0].tempFile).toBeTruthy();
-          });
-
-          it('creates multiple trees when entryName has slashes', () => {
-            vm.entryName = 'app/test';
-            vm.createEntryInStore();
-
-            const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-            expect(baseTree.length).toBe(1);
-            expect(baseTree[0].name).toBe('app');
-          });
-
-          it('creates tree in existing tree', () => {
-            const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-            baseTree.push(file('app', '1', 'tree'));
-
-            vm.entryName = 'app/test';
-            vm.createEntryInStore();
-
-            expect(baseTree.length).toBe(1);
-            expect(baseTree[0].name).toBe('app');
-            expect(baseTree[0].tempFile).toBeFalsy();
-            expect(baseTree[0].tree[0].tempFile).toBeTruthy();
-            expect(baseTree[0].tree[0].name).toBe('test');
-          });
-
-          it('does not create new tree when already exists', () => {
-            const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-            baseTree.push(file('app', '1', 'tree'));
-
-            vm.entryName = 'app';
-            vm.createEntryInStore();
-
-            expect(baseTree.length).toBe(1);
-            expect(baseTree[0].name).toBe('app');
-            expect(baseTree[0].tempFile).toBeFalsy();
-            expect(baseTree[0].tree.length).toBe(0);
-          });
-        }
-      });
-    });
-  });
-
-  it('focuses field on mount', () => {
-    document.body.innerHTML += '<div class="js-test"></div>';
-
-    vm = createComponentWithStore(Component, store, {
-      type: 'tree',
-      projectId: 'abcproject',
-      branchId: 'master',
-      path: '',
-    }).$mount('.js-test');
-
-    expect(document.activeElement).toBe(vm.$refs.fieldName);
-
-    vm.$el.remove();
-  });
-});
diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
deleted file mode 100644
index ee8aab3a2523184e73bae03d2db17bde51693e69..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/new_dropdown/upload_spec.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import Vue from 'vue';
-import upload from '~/ide/components/new_dropdown/upload.vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../../helpers';
-
-describe('new dropdown upload', () => {
-  let vm;
-  let projectTree;
-
-  beforeEach(() => {
-    spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
-      data: {
-        id: '123',
-      },
-    }));
-
-    spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
-      data: {
-        commit: {
-          id: '123branch',
-        },
-      },
-    }));
-
-    spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
-      headers: {
-        'page-title': 'test',
-      },
-      json: () => Promise.resolve({
-        last_commit_path: 'last_commit_path',
-        parent_tree_url: 'parent_tree_url',
-        path: '/',
-        trees: [{ name: 'tree' }],
-        blobs: [{ name: 'blob' }],
-        submodules: [{ name: 'submodule' }],
-      }),
-    }));
-
-    const Component = Vue.extend(upload);
-
-    store.state.projects.abcproject = {
-      web_url: '',
-    };
-    store.state.currentProjectId = 'abcproject';
-    store.state.trees = [];
-    store.state.trees['abcproject/mybranch'] = {
-      tree: [],
-    };
-    projectTree = store.state.trees['abcproject/mybranch'];
-
-    vm = createComponentWithStore(Component, store, {
-      branchId: 'master',
-      path: '',
-      parent: projectTree,
-    });
-
-    vm.entryName = 'testing';
-
-    vm.$mount();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  describe('readFile', () => {
-    beforeEach(() => {
-      spyOn(FileReader.prototype, 'readAsText');
-      spyOn(FileReader.prototype, 'readAsDataURL');
-    });
-
-    it('calls readAsText for text files', () => {
-      const file = {
-        type: 'text/html',
-      };
-
-      vm.readFile(file);
-
-      expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file);
-    });
-
-    it('calls readAsDataURL for non-text files', () => {
-      const file = {
-        type: 'images/png',
-      };
-
-      vm.readFile(file);
-
-      expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file);
-    });
-  });
-
-  describe('createFile', () => {
-    const target = {
-      result: 'content',
-    };
-    const binaryTarget = {
-      result: 'base64,base64content',
-    };
-    const file = {
-      name: 'file',
-    };
-
-    it('creates new file', (done) => {
-      vm.createFile(target, file, true);
-
-      vm.$nextTick(() => {
-        const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-        expect(baseTree.length).toBe(1);
-        expect(baseTree[0].name).toBe(file.name);
-        expect(baseTree[0].content).toBe(target.result);
-
-        done();
-      });
-    });
-
-    it('creates new file in path', (done) => {
-      const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-      const tree = {
-        type: 'tree',
-        name: 'testing',
-        path: 'testing',
-        tree: [],
-      };
-      baseTree.push(tree);
-
-      vm.parent = tree;
-      vm.createFile(target, file, true);
-
-      vm.$nextTick(() => {
-        expect(baseTree.length).toBe(1);
-        expect(baseTree[0].tree[0].name).toBe(file.name);
-        expect(baseTree[0].tree[0].content).toBe(target.result);
-        expect(baseTree[0].tree[0].path).toBe(`testing/${file.name}`);
-
-        done();
-      });
-    });
-
-    it('splits content on base64 if binary', (done) => {
-      vm.createFile(binaryTarget, file, false);
-
-      vm.$nextTick(() => {
-        const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
-        expect(baseTree.length).toBe(1);
-        expect(baseTree[0].name).toBe(file.name);
-        expect(baseTree[0].content).toBe(binaryTarget.result.split('base64,')[1]);
-        expect(baseTree[0].base64).toBe(true);
-
-        done();
-      });
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
deleted file mode 100644
index 934ada9dec2f20b6a7ba05000f3debe5533afc23..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import repoCommitSection from '~/ide/components/repo_commit_section.vue';
-import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
-import { file, resetStore } from '../helpers';
-
-describe('RepoCommitSection', () => {
-  let vm;
-
-  function createComponent() {
-    const RepoCommitSection = Vue.extend(repoCommitSection);
-
-    const comp = new RepoCommitSection({
-      store,
-    }).$mount();
-
-    comp.$store.state.currentProjectId = 'abcproject';
-    comp.$store.state.currentBranchId = 'master';
-    comp.$store.state.projects.abcproject = {
-      web_url: '',
-      branches: {
-        master: {
-          workingReference: '1',
-        },
-      },
-    };
-
-    comp.$store.state.rightPanelCollapsed = false;
-    comp.$store.state.currentBranch = 'master';
-    comp.$store.state.openFiles = [file('file1'), file('file2')];
-    comp.$store.state.openFiles.forEach(f => Object.assign(f, {
-      changed: true,
-      content: 'testing',
-    }));
-
-    return comp.$mount();
-  }
-
-  beforeEach((done) => {
-    vm = createComponent();
-
-    spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
-      headers: {
-        'page-title': 'test',
-      },
-      json: () => Promise.resolve({
-        last_commit_path: 'last_commit_path',
-        parent_tree_url: 'parent_tree_url',
-        path: '/',
-        trees: [{ name: 'tree' }],
-        blobs: [{ name: 'blob' }],
-        submodules: [{ name: 'submodule' }],
-      }),
-    }));
-
-    Vue.nextTick(done);
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders a commit section', () => {
-    const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
-    const submitCommit = vm.$el.querySelector('form .btn');
-
-    expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
-    expect(changedFileElements.length).toEqual(2);
-
-    changedFileElements.forEach((changedFile, i) => {
-      expect(changedFile.textContent.trim()).toEqual(vm.$store.getters.changedFiles[i].path);
-    });
-
-    expect(submitCommit.disabled).toBeTruthy();
-    expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
-  });
-
-  describe('when submitting', () => {
-    let changedFiles;
-
-    beforeEach(() => {
-      vm.commitMessage = 'testing';
-      changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles));
-
-      spyOn(service, 'commit').and.returnValue(Promise.resolve({
-        data: {
-          short_id: '1',
-          stats: {},
-        },
-      }));
-    });
-
-    it('allows you to submit', () => {
-      expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy();
-    });
-
-    it('submits commit', (done) => {
-      vm.makeCommit();
-
-      // Wait for the branch check to finish
-      getSetTimeoutPromise()
-        .then(() => Vue.nextTick())
-        .then(() => {
-          const args = service.commit.calls.allArgs()[0];
-          const { commit_message, actions, branch: payloadBranch } = args[1];
-
-          expect(commit_message).toBe('testing');
-          expect(actions.length).toEqual(2);
-          expect(payloadBranch).toEqual('master');
-          expect(actions[0].action).toEqual('update');
-          expect(actions[1].action).toEqual('update');
-          expect(actions[0].content).toEqual(changedFiles[0].content);
-          expect(actions[1].content).toEqual(changedFiles[1].content);
-          expect(actions[0].file_path).toEqual(changedFiles[0].path);
-          expect(actions[1].file_path).toEqual(changedFiles[1].path);
-        })
-        .then(done)
-        .catch(done.fail);
-    });
-
-    it('redirects to MR creation page if start new MR checkbox checked', (done) => {
-      spyOn(urlUtils, 'visitUrl');
-      vm.startNewMR = true;
-
-      vm.makeCommit();
-
-      getSetTimeoutPromise()
-        .then(() => Vue.nextTick())
-        .then(() => {
-          expect(urlUtils.visitUrl).toHaveBeenCalled();
-        })
-        .then(done)
-        .catch(done.fail);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js
deleted file mode 100644
index 2895b7945067cb15a8b2148e22a2f861d8b19153..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_edit_button_spec.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoEditButton from '~/ide/components/repo_edit_button.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoEditButton', () => {
-  let vm;
-
-  beforeEach(() => {
-    const f = file();
-    const RepoEditButton = Vue.extend(repoEditButton);
-
-    vm = new RepoEditButton({
-      store,
-    });
-
-    f.active = true;
-    vm.$store.dispatch('setInitialData', {
-      canCommit: true,
-      onTopOfBranch: true,
-    });
-    vm.$store.state.openFiles.push(f);
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders an edit button', () => {
-    vm.$mount();
-
-    expect(vm.$el.querySelector('.btn')).not.toBeNull();
-    expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
-  });
-
-  it('renders edit button with cancel text', () => {
-    vm.$store.state.editMode = true;
-
-    vm.$mount();
-
-    expect(vm.$el.querySelector('.btn')).not.toBeNull();
-    expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
-  });
-
-  it('toggles edit mode on click', (done) => {
-    vm.$mount();
-
-    vm.$el.querySelector('.btn').click();
-
-    vm.$nextTick(() => {
-      expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit');
-
-      done();
-    });
-  });
-
-  describe('discardPopupOpen', () => {
-    beforeEach(() => {
-      vm.$store.state.discardPopupOpen = true;
-      vm.$store.state.editMode = true;
-      vm.$store.state.openFiles[0].changed = true;
-
-      vm.$mount();
-    });
-
-    it('renders popup', () => {
-      expect(vm.$el.querySelector('.modal')).not.toBeNull();
-    });
-
-    it('removes all changed files', (done) => {
-      vm.$el.querySelector('.btn-warning').click();
-
-      vm.$nextTick(() => {
-        expect(vm.$store.getters.changedFiles.length).toBe(0);
-        expect(vm.$el.querySelector('.modal')).toBeNull();
-
-        done();
-      });
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
deleted file mode 100644
index e7b2ed08acdfb698c0ec92e6448a74bf449571a0..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoEditor from '~/ide/components/repo_editor.vue';
-import monacoLoader from '~/ide/monaco_loader';
-import { file, resetStore } from '../helpers';
-
-describe('RepoEditor', () => {
-  let vm;
-
-  beforeEach((done) => {
-    const f = file();
-    const RepoEditor = Vue.extend(repoEditor);
-
-    vm = new RepoEditor({
-      store,
-    });
-
-    f.active = true;
-    f.tempFile = true;
-    vm.$store.state.openFiles.push(f);
-    vm.$store.getters.activeFile.html = 'testing';
-    vm.monaco = true;
-
-    vm.$mount();
-
-    monacoLoader(['vs/editor/editor.main'], () => {
-      setTimeout(done, 0);
-    });
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders an ide container', (done) => {
-    Vue.nextTick(() => {
-      expect(vm.shouldHideEditor).toBeFalsy();
-
-      done();
-    });
-  });
-
-  describe('when open file is binary and not raw', () => {
-    beforeEach((done) => {
-      vm.$store.getters.activeFile.binary = true;
-
-      Vue.nextTick(done);
-    });
-
-    it('does not render the IDE', () => {
-      expect(vm.shouldHideEditor).toBeTruthy();
-    });
-
-    it('shows activeFile html', () => {
-      expect(vm.$el.textContent).toContain('testing');
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js
deleted file mode 100644
index 115569a911751d9dfa90ce9e630dca917e70798e..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_file_buttons_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoFileButtons from '~/ide/components/repo_file_buttons.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoFileButtons', () => {
-  const activeFile = file();
-  let vm;
-
-  function createComponent() {
-    const RepoFileButtons = Vue.extend(repoFileButtons);
-
-    activeFile.rawPath = 'test';
-    activeFile.blamePath = 'test';
-    activeFile.commitsPath = 'test';
-    activeFile.active = true;
-    store.state.openFiles.push(activeFile);
-
-    return new RepoFileButtons({
-      store,
-    }).$mount();
-  }
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders Raw, Blame, History, Permalink and Preview toggle', (done) => {
-    vm = createComponent();
-
-    vm.$nextTick(() => {
-      const raw = vm.$el.querySelector('.raw');
-      const blame = vm.$el.querySelector('.blame');
-      const history = vm.$el.querySelector('.history');
-
-      expect(raw.href).toMatch(`/${activeFile.rawPath}`);
-      expect(raw.textContent.trim()).toEqual('Raw');
-      expect(blame.href).toMatch(`/${activeFile.blamePath}`);
-      expect(blame.textContent.trim()).toEqual('Blame');
-      expect(history.href).toMatch(`/${activeFile.commitsPath}`);
-      expect(history.textContent.trim()).toEqual('History');
-      expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink');
-
-      done();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
deleted file mode 100644
index 27b55ed1f8711adf8f8ca5e7563fe48f877cf70a..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoFile from '~/ide/components/repo_file.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoFile', () => {
-  const updated = 'updated';
-  let vm;
-
-  function createComponent(propsData) {
-    const RepoFile = Vue.extend(repoFile);
-
-    return new RepoFile({
-      store,
-      propsData,
-    }).$mount();
-  }
-
-  afterEach(() => {
-    resetStore(vm.$store);
-  });
-
-  it('renders link, icon and name', () => {
-    const RepoFile = Vue.extend(repoFile);
-    vm = new RepoFile({
-      store,
-      propsData: {
-        file: file('t4'),
-      },
-    });
-    spyOn(vm, 'timeFormated').and.returnValue(updated);
-    vm.$mount();
-
-    const name = vm.$el.querySelector('.repo-file-name');
-
-    expect(name.href).toMatch('');
-    expect(name.textContent.trim()).toEqual(vm.file.name);
-  });
-
-  it('does render if hasFiles is true and is loading tree', () => {
-    vm = createComponent({
-      file: file('t1'),
-    });
-
-    expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
-  });
-
-  it('does not render commit message and datetime if mini', (done) => {
-    vm = createComponent({
-      file: file('t2'),
-    });
-    vm.$store.state.openFiles.push(vm.file);
-
-    vm.$nextTick(() => {
-      expect(vm.$el.querySelector('.commit-message')).toBeFalsy();
-      expect(vm.$el.querySelector('.commit-update')).toBeFalsy();
-
-      done();
-    });
-  });
-
-  it('fires clickFile when the link is clicked', () => {
-    vm = createComponent({
-      file: file('t3'),
-    });
-
-    spyOn(vm, 'clickFile');
-
-    vm.$el.click();
-
-    expect(vm.clickFile).toHaveBeenCalledWith(vm.file);
-  });
-
-  describe('submodule', () => {
-    let f;
-
-    beforeEach(() => {
-      f = file('submodule name', '123456789');
-      f.type = 'submodule';
-
-      vm = createComponent({
-        file: f,
-      });
-    });
-
-    afterEach(() => {
-      vm.$destroy();
-    });
-
-    it('renders submodule short ID', () => {
-      expect(vm.$el.querySelector('.commit-sha').textContent.trim()).toBe('12345678');
-    });
-
-    it('renders ID next to submodule name', () => {
-      expect(vm.$el.querySelector('td').textContent.replace(/\s+/g, ' ')).toContain('submodule name @ 12345678');
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js
deleted file mode 100644
index 18366fb89bceed48fd4bf80a10e8780def0abec7..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_loading_file_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoLoadingFile from '~/ide/components/repo_loading_file.vue';
-import { resetStore } from '../helpers';
-
-describe('RepoLoadingFile', () => {
-  let vm;
-
-  function createComponent() {
-    const RepoLoadingFile = Vue.extend(repoLoadingFile);
-
-    return new RepoLoadingFile({
-      store,
-    }).$mount();
-  }
-
-  function assertLines(lines) {
-    lines.forEach((line, n) => {
-      const index = n + 1;
-      expect(line.classList.contains(`skeleton-line-${index}`)).toBeTruthy();
-    });
-  }
-
-  function assertColumns(columns) {
-    columns.forEach((column) => {
-      const container = column.querySelector('.animation-container');
-      const lines = [...container.querySelectorAll(':scope > div')];
-
-      expect(container).toBeTruthy();
-      expect(lines.length).toEqual(6);
-      assertLines(lines);
-    });
-  }
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders 3 columns of animated LoC', () => {
-    vm = createComponent();
-    const columns = [...vm.$el.querySelectorAll('td')];
-
-    expect(columns.length).toEqual(3);
-    assertColumns(columns);
-  });
-
-  it('renders 1 column of animated LoC if isMini', (done) => {
-    vm = createComponent();
-    vm.$store.state.leftPanelCollapsed = true;
-    vm.$store.state.openFiles.push('test');
-
-    vm.$nextTick(() => {
-      const columns = [...vm.$el.querySelectorAll('td')];
-
-      expect(columns.length).toEqual(1);
-      assertColumns(columns);
-
-      done();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js
deleted file mode 100644
index ff26cab2262d6abb7478e4627b622d373fb9148d..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_prev_directory_spec.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoPrevDirectory from '~/ide/components/repo_prev_directory.vue';
-import { resetStore } from '../helpers';
-
-describe('RepoPrevDirectory', () => {
-  let vm;
-  const parentLink = 'parent';
-  function createComponent() {
-    const RepoPrevDirectory = Vue.extend(repoPrevDirectory);
-
-    const comp = new RepoPrevDirectory({
-      store,
-    });
-
-    comp.$store.state.parentTreeUrl = parentLink;
-
-    return comp.$mount();
-  }
-
-  beforeEach(() => {
-    vm = createComponent();
-  });
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders a prev dir link', () => {
-    const link = vm.$el.querySelector('a');
-
-    expect(link.href).toMatch(`/${parentLink}`);
-    expect(link.textContent).toEqual('...');
-  });
-
-  it('clicking row triggers getTreeData', () => {
-    spyOn(vm, 'getTreeData');
-
-    vm.$el.querySelector('td').click();
-
-    expect(vm.getTreeData).toHaveBeenCalledWith({ endpoint: parentLink });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js
deleted file mode 100644
index e90837e4cb2148f61545d7dec14d9dfd792bb447..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_preview_spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoPreview from '~/ide/components/repo_preview.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoPreview', () => {
-  let vm;
-
-  function createComponent() {
-    const f = file();
-    const RepoPreview = Vue.extend(repoPreview);
-
-    const comp = new RepoPreview({
-      store,
-    });
-
-    f.active = true;
-    f.html = 'test';
-
-    comp.$store.state.openFiles.push(f);
-
-    return comp.$mount();
-  }
-
-  afterEach(() => {
-    vm.$destroy();
-
-    resetStore(vm.$store);
-  });
-
-  it('renders a div with the activeFile html', () => {
-    vm = createComponent();
-
-    expect(vm.$el.tagName).toEqual('DIV');
-    expect(vm.$el.innerHTML).toContain('test');
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
deleted file mode 100644
index 933e8d3a06adf9bfb9d710a32e17433f2dbb14d5..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoTab from '~/ide/components/repo_tab.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoTab', () => {
-  let vm;
-
-  function createComponent(propsData) {
-    const RepoTab = Vue.extend(repoTab);
-
-    return new RepoTab({
-      store,
-      propsData,
-    }).$mount();
-  }
-
-  afterEach(() => {
-    resetStore(vm.$store);
-  });
-
-  it('renders a close link and a name link', () => {
-    vm = createComponent({
-      tab: file(),
-    });
-    vm.$store.state.openFiles.push(vm.tab);
-    const close = vm.$el.querySelector('.multi-file-tab-close');
-    const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`);
-
-    expect(close.querySelector('.fa-times')).toBeTruthy();
-    expect(name.textContent.trim()).toEqual(vm.tab.name);
-  });
-
-  it('fires clickFile when the link is clicked', () => {
-    vm = createComponent({
-      tab: file(),
-    });
-
-    spyOn(vm, 'clickFile');
-
-    vm.$el.click();
-
-    expect(vm.clickFile).toHaveBeenCalledWith(vm.tab);
-  });
-
-  it('calls closeFile when clicking close button', () => {
-    vm = createComponent({
-      tab: file(),
-    });
-
-    spyOn(vm, 'closeFile');
-
-    vm.$el.querySelector('.multi-file-tab-close').click();
-
-    expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab });
-  });
-
-  it('renders an fa-circle icon if tab is changed', () => {
-    const tab = file('changedFile');
-    tab.changed = true;
-    vm = createComponent({
-      tab,
-    });
-
-    expect(vm.$el.querySelector('.multi-file-tab-close .fa-circle')).not.toBeNull();
-  });
-
-  describe('methods', () => {
-    describe('closeTab', () => {
-      it('does not close tab if is changed', (done) => {
-        const tab = file('closeFile');
-        tab.changed = true;
-        tab.opened = true;
-        vm = createComponent({
-          tab,
-        });
-        vm.$store.state.openFiles.push(tab);
-        vm.$store.dispatch('setFileActive', tab);
-
-        vm.$el.querySelector('.multi-file-tab-close').click();
-
-        vm.$nextTick(() => {
-          expect(tab.opened).toBeTruthy();
-
-          done();
-        });
-      });
-
-      it('closes tab when clicking close btn', (done) => {
-        const tab = file('lose');
-        tab.opened = true;
-        vm = createComponent({
-          tab,
-        });
-        vm.$store.state.openFiles.push(tab);
-        vm.$store.dispatch('setFileActive', tab);
-
-        vm.$el.querySelector('.multi-file-tab-close').click();
-
-        vm.$nextTick(() => {
-          expect(tab.opened).toBeFalsy();
-
-          done();
-        });
-      });
-    });
-  });
-});
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
deleted file mode 100644
index 2c363364d70a3d08d896a5ade48d2beea3d2089f..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoTabs from '~/ide/components/repo_tabs.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoTabs', () => {
-  const openedFiles = [file('open1'), file('open2')];
-  let vm;
-
-  function createComponent() {
-    const RepoTabs = Vue.extend(repoTabs);
-
-    return new RepoTabs({
-      store,
-    }).$mount();
-  }
-
-  afterEach(() => {
-    resetStore(vm.$store);
-  });
-
-  it('renders a list of tabs', (done) => {
-    vm = createComponent();
-    openedFiles[0].active = true;
-    vm.$store.state.openFiles = openedFiles;
-
-    vm.$nextTick(() => {
-      const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
-
-      expect(tabs.length).toEqual(2);
-      expect(tabs[0].classList.contains('active')).toBeTruthy();
-      expect(tabs[1].classList.contains('active')).toBeFalsy();
-
-      done();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/helpers.js b/spec/javascripts/repo/helpers.js
deleted file mode 100644
index ac43d2211981b6d35f59bd3cb11e25ccca791715..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/helpers.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { decorateData } from '~/ide/stores/utils';
-import state from '~/ide/stores/state';
-
-export const resetStore = (store) => {
-  store.replaceState(state());
-};
-
-export const file = (name = 'name', id = name, type = '') => decorateData({
-  id,
-  type,
-  icon: 'icon',
-  url: 'url',
-  name,
-  path: name,
-  lastCommit: {},
-});
diff --git a/spec/javascripts/repo/lib/common/model_manager_spec.js b/spec/javascripts/repo/lib/common/model_manager_spec.js
deleted file mode 100644
index 563c2e338341650507580ee87bc906906afbbb3e..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/lib/common/model_manager_spec.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import ModelManager from '~/ide/lib/common/model_manager';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library model manager', () => {
-  let instance;
-
-  beforeEach((done) => {
-    monacoLoader(['vs/editor/editor.main'], () => {
-      instance = new ModelManager(monaco);
-
-      done();
-    });
-  });
-
-  afterEach(() => {
-    instance.dispose();
-  });
-
-  describe('addModel', () => {
-    it('caches model', () => {
-      instance.addModel(file());
-
-      expect(instance.models.size).toBe(1);
-    });
-
-    it('caches model by file path', () => {
-      instance.addModel(file('path-name'));
-
-      expect(instance.models.keys().next().value).toBe('path-name');
-    });
-
-    it('adds model into disposable', () => {
-      spyOn(instance.disposable, 'add').and.callThrough();
-
-      instance.addModel(file());
-
-      expect(instance.disposable.add).toHaveBeenCalled();
-    });
-
-    it('returns cached model', () => {
-      spyOn(instance.models, 'get').and.callThrough();
-
-      instance.addModel(file());
-      instance.addModel(file());
-
-      expect(instance.models.get).toHaveBeenCalled();
-    });
-  });
-
-  describe('hasCachedModel', () => {
-    it('returns false when no models exist', () => {
-      expect(instance.hasCachedModel('path')).toBeFalsy();
-    });
-
-    it('returns true when model exists', () => {
-      instance.addModel(file('path-name'));
-
-      expect(instance.hasCachedModel('path-name')).toBeTruthy();
-    });
-  });
-
-  describe('dispose', () => {
-    it('clears cached models', () => {
-      instance.addModel(file());
-
-      instance.dispose();
-
-      expect(instance.models.size).toBe(0);
-    });
-
-    it('calls disposable dispose', () => {
-      spyOn(instance.disposable, 'dispose').and.callThrough();
-
-      instance.dispose();
-
-      expect(instance.disposable.dispose).toHaveBeenCalled();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/lib/common/model_spec.js b/spec/javascripts/repo/lib/common/model_spec.js
deleted file mode 100644
index 878a4a3f3fe23aaa64a0457dceaaaa7fc0cb9243..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/lib/common/model_spec.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import Model from '~/ide/lib/common/model';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library model', () => {
-  let model;
-
-  beforeEach((done) => {
-    monacoLoader(['vs/editor/editor.main'], () => {
-      model = new Model(monaco, file('path'));
-
-      done();
-    });
-  });
-
-  afterEach(() => {
-    model.dispose();
-  });
-
-  it('creates original model & new model', () => {
-    expect(model.originalModel).not.toBeNull();
-    expect(model.model).not.toBeNull();
-  });
-
-  describe('path', () => {
-    it('returns file path', () => {
-      expect(model.path).toBe('path');
-    });
-  });
-
-  describe('getModel', () => {
-    it('returns model', () => {
-      expect(model.getModel()).toBe(model.model);
-    });
-  });
-
-  describe('getOriginalModel', () => {
-    it('returns original model', () => {
-      expect(model.getOriginalModel()).toBe(model.originalModel);
-    });
-  });
-
-  describe('onChange', () => {
-    it('caches event by path', () => {
-      model.onChange(() => {});
-
-      expect(model.events.size).toBe(1);
-      expect(model.events.keys().next().value).toBe('path');
-    });
-
-    it('calls callback on change', (done) => {
-      const spy = jasmine.createSpy();
-      model.onChange(spy);
-
-      model.getModel().setValue('123');
-
-      setTimeout(() => {
-        expect(spy).toHaveBeenCalledWith(model.getModel(), jasmine.anything());
-        done();
-      });
-    });
-  });
-
-  describe('dispose', () => {
-    it('calls disposable dispose', () => {
-      spyOn(model.disposable, 'dispose').and.callThrough();
-
-      model.dispose();
-
-      expect(model.disposable.dispose).toHaveBeenCalled();
-    });
-
-    it('clears events', () => {
-      model.onChange(() => {});
-
-      expect(model.events.size).toBe(1);
-
-      model.dispose();
-
-      expect(model.events.size).toBe(0);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/lib/decorations/controller_spec.js b/spec/javascripts/repo/lib/decorations/controller_spec.js
deleted file mode 100644
index fea12d74dcae1cd6c66a3b8dbfe2acdb448345a1..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/lib/decorations/controller_spec.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
-import DecorationsController from '~/ide/lib/decorations/controller';
-import Model from '~/ide/lib/common/model';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library decorations controller', () => {
-  let editorInstance;
-  let controller;
-  let model;
-
-  beforeEach((done) => {
-    monacoLoader(['vs/editor/editor.main'], () => {
-      editorInstance = editor.create(monaco);
-      editorInstance.createInstance(document.createElement('div'));
-
-      controller = new DecorationsController(editorInstance);
-      model = new Model(monaco, file('path'));
-
-      done();
-    });
-  });
-
-  afterEach(() => {
-    model.dispose();
-    editorInstance.dispose();
-    controller.dispose();
-  });
-
-  describe('getAllDecorationsForModel', () => {
-    it('returns empty array when no decorations exist for model', () => {
-      const decorations = controller.getAllDecorationsForModel(model);
-
-      expect(decorations).toEqual([]);
-    });
-
-    it('returns decorations by model URL', () => {
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
-      const decorations = controller.getAllDecorationsForModel(model);
-
-      expect(decorations[0]).toEqual({ decoration: 'decorationValue' });
-    });
-  });
-
-  describe('addDecorations', () => {
-    it('caches decorations in a new map', () => {
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
-      expect(controller.decorations.size).toBe(1);
-    });
-
-    it('does not create new cache model', () => {
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue2' }]);
-
-      expect(controller.decorations.size).toBe(1);
-    });
-
-    it('caches decorations by model URL', () => {
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
-      expect(controller.decorations.size).toBe(1);
-      expect(controller.decorations.keys().next().value).toBe('path');
-    });
-
-    it('calls decorate method', () => {
-      spyOn(controller, 'decorate');
-
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
-      expect(controller.decorate).toHaveBeenCalled();
-    });
-  });
-
-  describe('decorate', () => {
-    it('sets decorations on editor instance', () => {
-      spyOn(controller.editor.instance, 'deltaDecorations');
-
-      controller.decorate(model);
-
-      expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []);
-    });
-
-    it('caches decorations', () => {
-      spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
-
-      controller.decorate(model);
-
-      expect(controller.editorDecorations.size).toBe(1);
-    });
-
-    it('caches decorations by model URL', () => {
-      spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
-
-      controller.decorate(model);
-
-      expect(controller.editorDecorations.keys().next().value).toBe('path');
-    });
-  });
-
-  describe('dispose', () => {
-    it('clears cached decorations', () => {
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
-      controller.dispose();
-
-      expect(controller.decorations.size).toBe(0);
-    });
-
-    it('clears cached editorDecorations', () => {
-      controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
-      controller.dispose();
-
-      expect(controller.editorDecorations.size).toBe(0);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/lib/diff/controller_spec.js b/spec/javascripts/repo/lib/diff/controller_spec.js
deleted file mode 100644
index 1d55c165260f1097194379ca84afa3f8aef70233..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/lib/diff/controller_spec.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
-import ModelManager from '~/ide/lib/common/model_manager';
-import DecorationsController from '~/ide/lib/decorations/controller';
-import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
-import { computeDiff } from '~/ide/lib/diff/diff';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library dirty diff controller', () => {
-  let editorInstance;
-  let controller;
-  let modelManager;
-  let decorationsController;
-  let model;
-
-  beforeEach((done) => {
-    monacoLoader(['vs/editor/editor.main'], () => {
-      editorInstance = editor.create(monaco);
-      editorInstance.createInstance(document.createElement('div'));
-
-      modelManager = new ModelManager(monaco);
-      decorationsController = new DecorationsController(editorInstance);
-
-      model = modelManager.addModel(file());
-
-      controller = new DirtyDiffController(modelManager, decorationsController);
-
-      done();
-    });
-  });
-
-  afterEach(() => {
-    controller.dispose();
-    model.dispose();
-    decorationsController.dispose();
-    editorInstance.dispose();
-  });
-
-  describe('getDiffChangeType', () => {
-    ['added', 'removed', 'modified'].forEach((type) => {
-      it(`returns ${type}`, () => {
-        const change = {
-          [type]: true,
-        };
-
-        expect(getDiffChangeType(change)).toBe(type);
-      });
-    });
-  });
-
-  describe('getDecorator', () => {
-    ['added', 'removed', 'modified'].forEach((type) => {
-      it(`returns with linesDecorationsClassName for ${type}`, () => {
-        const change = {
-          [type]: true,
-        };
-
-        expect(
-          getDecorator(change).options.linesDecorationsClassName,
-        ).toBe(`dirty-diff dirty-diff-${type}`);
-      });
-
-      it('returns with line numbers', () => {
-        const change = {
-          lineNumber: 1,
-          endLineNumber: 2,
-          [type]: true,
-        };
-
-        const range = getDecorator(change).range;
-
-        expect(range.startLineNumber).toBe(1);
-        expect(range.endLineNumber).toBe(2);
-        expect(range.startColumn).toBe(1);
-        expect(range.endColumn).toBe(1);
-      });
-    });
-  });
-
-  describe('attachModel', () => {
-    it('adds change event callback', () => {
-      spyOn(model, 'onChange');
-
-      controller.attachModel(model);
-
-      expect(model.onChange).toHaveBeenCalled();
-    });
-
-    it('calls throttledComputeDiff on change', () => {
-      spyOn(controller, 'throttledComputeDiff');
-
-      controller.attachModel(model);
-
-      model.getModel().setValue('123');
-
-      expect(controller.throttledComputeDiff).toHaveBeenCalled();
-    });
-  });
-
-  describe('computeDiff', () => {
-    it('posts to worker', () => {
-      spyOn(controller.dirtyDiffWorker, 'postMessage');
-
-      controller.computeDiff(model);
-
-      expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({
-        path: model.path,
-        originalContent: '',
-        newContent: '',
-      });
-    });
-  });
-
-  describe('reDecorate', () => {
-    it('calls decorations controller decorate', () => {
-      spyOn(controller.decorationsController, 'decorate');
-
-      controller.reDecorate(model);
-
-      expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model);
-    });
-  });
-
-  describe('decorate', () => {
-    it('adds decorations into decorations controller', () => {
-      spyOn(controller.decorationsController, 'addDecorations');
-
-      controller.decorate({ data: { changes: [], path: 'path' } });
-
-      expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith('path', 'dirtyDiff', jasmine.anything());
-    });
-
-    it('adds decorations into editor', () => {
-      const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
-
-      controller.decorate({ data: { changes: computeDiff('123', '1234'), path: 'path' } });
-
-      expect(spy).toHaveBeenCalledWith([], [{
-        range: new monaco.Range(
-          1, 1, 1, 1,
-        ),
-        options: {
-          isWholeLine: true,
-          linesDecorationsClassName: 'dirty-diff dirty-diff-modified',
-        },
-      }]);
-    });
-  });
-
-  describe('dispose', () => {
-    it('calls disposable dispose', () => {
-      spyOn(controller.disposable, 'dispose').and.callThrough();
-
-      controller.dispose();
-
-      expect(controller.disposable.dispose).toHaveBeenCalled();
-    });
-
-    it('terminates worker', () => {
-      spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough();
-
-      controller.dispose();
-
-      expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled();
-    });
-
-    it('removes worker event listener', () => {
-      spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough();
-
-      controller.dispose();
-
-      expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith('message', jasmine.anything());
-    });
-  });
-});
diff --git a/spec/javascripts/repo/lib/editor_options_spec.js b/spec/javascripts/repo/lib/editor_options_spec.js
deleted file mode 100644
index edbf5450dceba917afe92285f2420beb6d442b42..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/lib/editor_options_spec.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import editorOptions from '~/ide/lib/editor_options';
-
-describe('Multi-file editor library editor options', () => {
-  it('returns an array', () => {
-    expect(editorOptions).toEqual(jasmine.any(Array));
-  });
-});
diff --git a/spec/javascripts/repo/lib/editor_spec.js b/spec/javascripts/repo/lib/editor_spec.js
deleted file mode 100644
index 8d51d48a7822dd096085fe9bd89c4e5570798d27..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/lib/editor_spec.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
-import { file } from '../helpers';
-
-describe('Multi-file editor library', () => {
-  let instance;
-
-  beforeEach((done) => {
-    monacoLoader(['vs/editor/editor.main'], () => {
-      instance = editor.create(monaco);
-
-      done();
-    });
-  });
-
-  afterEach(() => {
-    instance.dispose();
-  });
-
-  it('creates instance of editor', () => {
-    expect(editor.editorInstance).not.toBeNull();
-  });
-
-  describe('createInstance', () => {
-    let el;
-
-    beforeEach(() => {
-      el = document.createElement('div');
-    });
-
-    it('creates editor instance', () => {
-      spyOn(instance.monaco.editor, 'create').and.callThrough();
-
-      instance.createInstance(el);
-
-      expect(instance.monaco.editor.create).toHaveBeenCalled();
-    });
-
-    it('creates dirty diff controller', () => {
-      instance.createInstance(el);
-
-      expect(instance.dirtyDiffController).not.toBeNull();
-    });
-  });
-
-  describe('createModel', () => {
-    it('calls model manager addModel', () => {
-      spyOn(instance.modelManager, 'addModel');
-
-      instance.createModel('FILE');
-
-      expect(instance.modelManager.addModel).toHaveBeenCalledWith('FILE');
-    });
-  });
-
-  describe('attachModel', () => {
-    let model;
-
-    beforeEach(() => {
-      instance.createInstance(document.createElement('div'));
-
-      model = instance.createModel(file());
-    });
-
-    it('sets the current model on the instance', () => {
-      instance.attachModel(model);
-
-      expect(instance.currentModel).toBe(model);
-    });
-
-    it('attaches the model to the current instance', () => {
-      spyOn(instance.instance, 'setModel');
-
-      instance.attachModel(model);
-
-      expect(instance.instance.setModel).toHaveBeenCalledWith(model.getModel());
-    });
-
-    it('attaches the model to the dirty diff controller', () => {
-      spyOn(instance.dirtyDiffController, 'attachModel');
-
-      instance.attachModel(model);
-
-      expect(instance.dirtyDiffController.attachModel).toHaveBeenCalledWith(model);
-    });
-
-    it('re-decorates with the dirty diff controller', () => {
-      spyOn(instance.dirtyDiffController, 'reDecorate');
-
-      instance.attachModel(model);
-
-      expect(instance.dirtyDiffController.reDecorate).toHaveBeenCalledWith(model);
-    });
-  });
-
-  describe('clearEditor', () => {
-    it('resets the editor model', () => {
-      instance.createInstance(document.createElement('div'));
-
-      spyOn(instance.instance, 'setModel');
-
-      instance.clearEditor();
-
-      expect(instance.instance.setModel).toHaveBeenCalledWith(null);
-    });
-  });
-
-  describe('dispose', () => {
-    it('calls disposble dispose method', () => {
-      spyOn(instance.disposable, 'dispose').and.callThrough();
-
-      instance.dispose();
-
-      expect(instance.disposable.dispose).toHaveBeenCalled();
-    });
-
-    it('resets instance', () => {
-      instance.createInstance(document.createElement('div'));
-
-      expect(instance.instance).not.toBeNull();
-
-      instance.dispose();
-
-      expect(instance.instance).toBeNull();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/monaco_loader_spec.js b/spec/javascripts/repo/monaco_loader_spec.js
deleted file mode 100644
index b8ac36972aa56056d74c5269814668f53236cb4a..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/monaco_loader_spec.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import monacoContext from 'monaco-editor/dev/vs/loader';
-import monacoLoader from '~/ide/monaco_loader';
-
-describe('MonacoLoader', () => {
-  it('calls require.config and exports require', () => {
-    expect(monacoContext.require.getConfig()).toEqual(jasmine.objectContaining({
-      paths: {
-        vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
-      },
-    }));
-    expect(monacoLoader).toBe(monacoContext.require);
-  });
-});
diff --git a/spec/javascripts/repo/stores/actions/branch_spec.js b/spec/javascripts/repo/stores/actions/branch_spec.js
deleted file mode 100644
index 00d16fd790de52dc14d679d9af9487eff3a7caa2..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/actions/branch_spec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { resetStore } from '../../helpers';
-
-describe('Multi-file store branch actions', () => {
-  afterEach(() => {
-    resetStore(store);
-  });
-
-  describe('createNewBranch', () => {
-    beforeEach(() => {
-      spyOn(service, 'createBranch').and.returnValue(Promise.resolve({
-        json: () => ({
-          name: 'testing',
-        }),
-      }));
-      spyOn(history, 'pushState');
-
-      store.state.currentProjectId = 'abcproject';
-      store.state.currentBranchId = 'testing';
-      store.state.projects.abcproject = {
-        branches: {
-          master: {
-            workingReference: '1',
-          },
-        },
-      };
-    });
-
-    it('creates new branch', (done) => {
-      store.dispatch('createNewBranch', 'master')
-        .then(() => {
-          expect(store.state.currentBranchId).toBe('testing');
-          expect(service.createBranch).toHaveBeenCalledWith('abcproject', {
-            branch: 'master',
-            ref: 'testing',
-          });
-
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js
deleted file mode 100644
index e2d8f002e27dab7dce7580cfee8dd1176f31b6f2..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/actions/file_spec.js
+++ /dev/null
@@ -1,431 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { file, resetStore } from '../../helpers';
-
-describe('Multi-file store file actions', () => {
-  afterEach(() => {
-    resetStore(store);
-  });
-
-  describe('closeFile', () => {
-    let localFile;
-    let getLastCommitDataSpy;
-    let oldGetLastCommitData;
-
-    beforeEach(() => {
-      getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
-      oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
-      store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
-
-      localFile = file('testFile');
-      localFile.active = true;
-      localFile.opened = true;
-      localFile.parentTreeUrl = 'parentTreeUrl';
-
-      store.state.openFiles.push(localFile);
-    });
-
-    afterEach(() => {
-      store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
-    });
-
-    it('closes open files', (done) => {
-      store.dispatch('closeFile', { file: localFile })
-        .then(() => {
-          expect(localFile.opened).toBeFalsy();
-          expect(localFile.active).toBeFalsy();
-          expect(store.state.openFiles.length).toBe(0);
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('does not close file if has changed', (done) => {
-      localFile.changed = true;
-
-      store.dispatch('closeFile', { file: localFile })
-        .then(() => {
-          expect(localFile.opened).toBeTruthy();
-          expect(localFile.active).toBeTruthy();
-          expect(store.state.openFiles.length).toBe(1);
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('does not close file if temp file', (done) => {
-      localFile.tempFile = true;
-
-      store.dispatch('closeFile', { file: localFile })
-        .then(() => {
-          expect(localFile.opened).toBeTruthy();
-          expect(localFile.active).toBeTruthy();
-          expect(store.state.openFiles.length).toBe(1);
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('force closes a changed file', (done) => {
-      localFile.changed = true;
-
-      store.dispatch('closeFile', { file: localFile, force: true })
-        .then(() => {
-          expect(localFile.opened).toBeFalsy();
-          expect(localFile.active).toBeFalsy();
-          expect(store.state.openFiles.length).toBe(0);
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets next file as active', (done) => {
-      const f = file('otherfile');
-      store.state.openFiles.push(f);
-
-      expect(f.active).toBeFalsy();
-
-      store.dispatch('closeFile', { file: localFile })
-        .then(() => {
-          expect(f.active).toBeTruthy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('calls getLastCommitData', (done) => {
-      store.dispatch('closeFile', { file: localFile })
-        .then(() => {
-          expect(getLastCommitDataSpy).toHaveBeenCalled();
-
-          done();
-        }).catch(done.fail);
-    });
-  });
-
-  describe('setFileActive', () => {
-    let scrollToTabSpy;
-    let oldScrollToTab;
-
-    beforeEach(() => {
-      scrollToTabSpy = jasmine.createSpy('scrollToTab');
-      oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
-      store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
-    });
-
-    afterEach(() => {
-      store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
-    });
-
-    it('calls scrollToTab', (done) => {
-      store.dispatch('setFileActive', file('setThisActive'))
-        .then(() => {
-          expect(scrollToTabSpy).toHaveBeenCalled();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets the file active', (done) => {
-      const localFile = file('activeFile');
-
-      store.dispatch('setFileActive', localFile)
-        .then(() => {
-          expect(localFile.active).toBeTruthy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('returns early if file is already active', (done) => {
-      const localFile = file('earlyActive');
-      localFile.active = true;
-
-      store.dispatch('setFileActive', localFile)
-        .then(() => {
-          expect(scrollToTabSpy).not.toHaveBeenCalled();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets current active file to not active', (done) => {
-      const localFile = file('currentActive');
-      localFile.active = true;
-      store.state.openFiles.push(localFile);
-
-      store.dispatch('setFileActive', file('newActive'))
-        .then(() => {
-          expect(localFile.active).toBeFalsy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('resets location.hash for line highlighting', (done) => {
-      location.hash = 'test';
-
-      store.dispatch('setFileActive', file('otherActive'))
-        .then(() => {
-          expect(location.hash).not.toBe('test');
-
-          done();
-        }).catch(done.fail);
-    });
-  });
-
-  describe('getFileData', () => {
-    let localFile;
-
-    beforeEach(() => {
-      spyOn(service, 'getFileData').and.returnValue(Promise.resolve({
-        headers: {
-          'page-title': 'testing getFileData',
-        },
-        json: () => Promise.resolve({
-          blame_path: 'blame_path',
-          commits_path: 'commits_path',
-          permalink: 'permalink',
-          raw_path: 'raw_path',
-          binary: false,
-          html: '123',
-          render_error: '',
-        }),
-      }));
-
-      localFile = file('newCreate');
-      localFile.url = 'getFileDataURL';
-    });
-
-    afterEach(() => {
-      store.dispatch('closeFile', {
-        file: localFile,
-        force: true,
-      });
-    });
-
-    it('calls the service', (done) => {
-      store.dispatch('getFileData', localFile)
-        .then(() => {
-          expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets the file data', (done) => {
-      store.dispatch('getFileData', localFile)
-        .then(Vue.nextTick)
-        .then(() => {
-          expect(localFile.blamePath).toBe('blame_path');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets document title', (done) => {
-      store.dispatch('getFileData', localFile)
-        .then(() => {
-          expect(document.title).toBe('testing getFileData');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets the file as active', (done) => {
-      store.dispatch('getFileData', localFile)
-        .then(Vue.nextTick)
-        .then(() => {
-          expect(localFile.active).toBeTruthy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('adds the file to open files', (done) => {
-      store.dispatch('getFileData', localFile)
-        .then(Vue.nextTick)
-        .then(() => {
-          expect(store.state.openFiles.length).toBe(1);
-          expect(store.state.openFiles[0].name).toBe(localFile.name);
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('toggles the file loading', (done) => {
-      store.dispatch('getFileData', localFile)
-        .then(() => {
-          expect(localFile.loading).toBeTruthy();
-
-          return Vue.nextTick();
-        })
-        .then(() => {
-          expect(localFile.loading).toBeFalsy();
-
-          done();
-        }).catch(done.fail);
-    });
-  });
-
-  describe('getRawFileData', () => {
-    let tmpFile;
-
-    beforeEach(() => {
-      spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw'));
-
-      tmpFile = file('tmpFile');
-    });
-
-    it('calls getRawFileData service method', (done) => {
-      store.dispatch('getRawFileData', tmpFile)
-        .then(() => {
-          expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('updates file raw data', (done) => {
-      store.dispatch('getRawFileData', tmpFile)
-        .then(() => {
-          expect(tmpFile.raw).toBe('raw');
-
-          done();
-        }).catch(done.fail);
-    });
-  });
-
-  describe('changeFileContent', () => {
-    let tmpFile;
-
-    beforeEach(() => {
-      tmpFile = file('tmpFile');
-    });
-
-    it('updates file content', (done) => {
-      store.dispatch('changeFileContent', {
-        file: tmpFile,
-        content: 'content',
-      })
-      .then(() => {
-        expect(tmpFile.content).toBe('content');
-
-        done();
-      }).catch(done.fail);
-    });
-  });
-
-  describe('createTempFile', () => {
-    let projectTree;
-
-    beforeEach(() => {
-      document.body.innerHTML += '<div class="flash-container"></div>';
-
-      store.state.currentProjectId = 'abcproject';
-      store.state.currentBranchId = 'master';
-      store.state.projects.abcproject = {
-        branches: {
-          master: {
-            workingReference: '1',
-          },
-        },
-      };
-
-      store.state.trees['abcproject/mybranch'] = {
-        tree: [],
-      };
-
-      projectTree = store.state.trees['abcproject/mybranch'];
-    });
-
-    afterEach(() => {
-      document.querySelector('.flash-container').remove();
-    });
-
-    it('creates temp file', (done) => {
-      store.dispatch('createTempFile', {
-        name: 'test',
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-      }).then((f) => {
-        expect(f.tempFile).toBeTruthy();
-        expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1);
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('adds tmp file to open files', (done) => {
-      store.dispatch('createTempFile', {
-        name: 'test',
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-      }).then((f) => {
-        expect(store.state.openFiles.length).toBe(1);
-        expect(store.state.openFiles[0].name).toBe(f.name);
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('sets tmp file as active', (done) => {
-      store.dispatch('createTempFile', {
-        name: 'test',
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-      }).then((f) => {
-        expect(f.active).toBeTruthy();
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('enters edit mode if file is not base64', (done) => {
-      store.dispatch('createTempFile', {
-        name: 'test',
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-      }).then(() => {
-        expect(store.state.editMode).toBeTruthy();
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('creates flash message is file already exists', (done) => {
-      store.state.trees['abcproject/mybranch'].tree.push(file('test', '1', 'blob'));
-
-      store.dispatch('createTempFile', {
-        name: 'test',
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-      }).then(() => {
-        expect(document.querySelector('.flash-alert')).not.toBeNull();
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('increases level of file', (done) => {
-      store.state.trees['abcproject/mybranch'].level = 1;
-
-      store.dispatch('createTempFile', {
-        name: 'test',
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-      }).then((f) => {
-        expect(f.level).toBe(2);
-
-        done();
-      }).catch(done.fail);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/actions/tree_spec.js b/spec/javascripts/repo/stores/actions/tree_spec.js
deleted file mode 100644
index 65351dbb7d9334b1afc05de91de1630febd5bc98..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/actions/tree_spec.js
+++ /dev/null
@@ -1,350 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { file, resetStore } from '../../helpers';
-
-describe('Multi-file store tree actions', () => {
-  let projectTree;
-
-  const basicCallParameters = {
-    endpoint: 'rootEndpoint',
-    projectId: 'abcproject',
-    branch: 'master',
-  };
-
-  beforeEach(() => {
-    store.state.currentProjectId = 'abcproject';
-    store.state.currentBranchId = 'master';
-    store.state.projects.abcproject = {
-      web_url: '',
-      branches: {
-        master: {
-          workingReference: '1',
-        },
-      },
-    };
-  });
-
-  afterEach(() => {
-    resetStore(store);
-  });
-
-  describe('getTreeData', () => {
-    beforeEach(() => {
-      spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
-        headers: {
-          'page-title': 'test',
-        },
-        json: () => Promise.resolve({
-          last_commit_path: 'last_commit_path',
-          parent_tree_url: 'parent_tree_url',
-          path: '/',
-          trees: [{ name: 'tree' }],
-          blobs: [{ name: 'blob' }],
-          submodules: [{ name: 'submodule' }],
-        }),
-      }));
-    });
-
-    it('calls service getTreeData', (done) => {
-      store.dispatch('getTreeData', basicCallParameters)
-      .then(() => {
-        expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint');
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('adds data into tree', (done) => {
-      store.dispatch('getTreeData', basicCallParameters)
-        .then(() => {
-          projectTree = store.state.trees['abcproject/master'];
-          expect(projectTree.tree.length).toBe(3);
-          expect(projectTree.tree[0].type).toBe('tree');
-          expect(projectTree.tree[1].type).toBe('submodule');
-          expect(projectTree.tree[2].type).toBe('blob');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets parent tree URL', (done) => {
-      store.dispatch('getTreeData', basicCallParameters)
-        .then(() => {
-          expect(store.state.parentTreeUrl).toBe('parent_tree_url');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets last commit path', (done) => {
-      store.dispatch('getTreeData', basicCallParameters)
-        .then(() => {
-          expect(store.state.trees['abcproject/master'].lastCommitPath).toBe('last_commit_path');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets root if not currently at root', (done) => {
-      store.state.isInitialRoot = false;
-
-      store.dispatch('getTreeData', basicCallParameters)
-        .then(() => {
-          expect(store.state.isInitialRoot).toBeTruthy();
-          expect(store.state.isRoot).toBeTruthy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets page title', (done) => {
-      store.dispatch('getTreeData', basicCallParameters)
-        .then(() => {
-          expect(document.title).toBe('test');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('calls getLastCommitData if prevLastCommitPath is not null', (done) => {
-      const getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
-      const oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
-      store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
-      store.state.prevLastCommitPath = 'test';
-
-      store.dispatch('getTreeData', basicCallParameters)
-        .then(() => {
-          expect(getLastCommitDataSpy).toHaveBeenCalledWith(projectTree);
-
-          store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
-
-          done();
-        }).catch(done.fail);
-    });
-  });
-
-  describe('toggleTreeOpen', () => {
-    let oldGetTreeData;
-    let getTreeDataSpy;
-    let tree;
-
-    beforeEach(() => {
-      getTreeDataSpy = jasmine.createSpy('getTreeData');
-
-      oldGetTreeData = store._actions.getTreeData;  // eslint-disable-line
-      store._actions.getTreeData = [getTreeDataSpy]; // eslint-disable-line
-
-      tree = {
-        projectId: 'abcproject',
-        branchId: 'master',
-        opened: false,
-        tree: [],
-      };
-    });
-
-    afterEach(() => {
-      store._actions.getTreeData = oldGetTreeData; // eslint-disable-line
-    });
-
-    it('toggles the tree open', (done) => {
-      store.dispatch('toggleTreeOpen', {
-        endpoint: 'test',
-        tree,
-      }).then(() => {
-        expect(tree.opened).toBeTruthy();
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('calls getTreeData if tree is closed', (done) => {
-      store.dispatch('toggleTreeOpen', {
-        endpoint: 'test',
-        tree,
-      }).then(() => {
-        expect(getTreeDataSpy).toHaveBeenCalledWith({
-          projectId: 'abcproject',
-          branch: 'master',
-          endpoint: 'test',
-          tree,
-        });
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('resets entries tree', (done) => {
-      Object.assign(tree, {
-        opened: true,
-        tree: ['a'],
-      });
-
-      store.dispatch('toggleTreeOpen', {
-        endpoint: 'test',
-        tree,
-      }).then(() => {
-        expect(tree.tree.length).toBe(0);
-
-        done();
-      }).catch(done.fail);
-    });
-  });
-
-  describe('createTempTree', () => {
-    beforeEach(() => {
-      store.state.trees['abcproject/mybranch'] = {
-        tree: [],
-      };
-      projectTree = store.state.trees['abcproject/mybranch'];
-    });
-
-    it('creates temp tree', (done) => {
-      store.dispatch('createTempTree', {
-        projectId: store.state.currentProjectId,
-        branchId: store.state.currentBranchId,
-        name: 'test',
-        parent: projectTree,
-      })
-      .then(() => {
-        expect(projectTree.tree[0].name).toBe('test');
-        expect(projectTree.tree[0].type).toBe('tree');
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('creates new folder inside another tree', (done) => {
-      const tree = {
-        type: 'tree',
-        name: 'testing',
-        tree: [],
-      };
-
-      projectTree.tree.push(tree);
-
-      store.dispatch('createTempTree', {
-        projectId: store.state.currentProjectId,
-        branchId: store.state.currentBranchId,
-        name: 'testing/test',
-        parent: projectTree,
-      })
-      .then(() => {
-        expect(projectTree.tree[0].name).toBe('testing');
-        expect(projectTree.tree[0].tree[0].tempFile).toBeTruthy();
-        expect(projectTree.tree[0].tree[0].name).toBe('test');
-        expect(projectTree.tree[0].tree[0].type).toBe('tree');
-
-        done();
-      }).catch(done.fail);
-    });
-
-    it('does not create new tree if already exists', (done) => {
-      const tree = {
-        type: 'tree',
-        name: 'testing',
-        endpoint: 'test',
-        tree: [],
-      };
-
-      projectTree.tree.push(tree);
-
-      store.dispatch('createTempTree', {
-        projectId: store.state.currentProjectId,
-        branchId: store.state.currentBranchId,
-        name: 'testing/test',
-        parent: projectTree,
-      })
-      .then(() => {
-        expect(projectTree.tree[0].name).toBe('testing');
-        expect(projectTree.tree[0].tempFile).toBeUndefined();
-
-        done();
-      }).catch(done.fail);
-    });
-  });
-
-  describe('getLastCommitData', () => {
-    beforeEach(() => {
-      spyOn(service, 'getTreeLastCommit').and.returnValue(Promise.resolve({
-        headers: {
-          'more-logs-url': null,
-        },
-        json: () => Promise.resolve([{
-          type: 'tree',
-          file_name: 'testing',
-          commit: {
-            message: 'commit message',
-            authored_date: '123',
-          },
-        }]),
-      }));
-
-      store.state.trees['abcproject/mybranch'] = {
-        tree: [],
-      };
-
-      projectTree = store.state.trees['abcproject/mybranch'];
-      projectTree.tree.push(file('testing', '1', 'tree'));
-      projectTree.lastCommitPath = 'lastcommitpath';
-    });
-
-    it('calls service with lastCommitPath', (done) => {
-      store.dispatch('getLastCommitData', projectTree)
-        .then(() => {
-          expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('updates trees last commit data', (done) => {
-      store.dispatch('getLastCommitData', projectTree)
-      .then(Vue.nextTick)
-        .then(() => {
-          expect(projectTree.tree[0].lastCommit.message).toBe('commit message');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('does not update entry if not found', (done) => {
-      projectTree.tree[0].name = 'a';
-
-      store.dispatch('getLastCommitData', projectTree)
-        .then(Vue.nextTick)
-        .then(() => {
-          expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message');
-
-          done();
-        }).catch(done.fail);
-    });
-  });
-
-  describe('updateDirectoryData', () => {
-    it('adds data into tree', (done) => {
-      const tree = {
-        tree: [],
-      };
-      const data = {
-        trees: [{ name: 'tree' }],
-        submodules: [{ name: 'submodule' }],
-        blobs: [{ name: 'blob' }],
-      };
-
-      store.dispatch('updateDirectoryData', {
-        data,
-        tree,
-      }).then(() => {
-        expect(tree.tree[0].name).toBe('tree');
-        expect(tree.tree[0].type).toBe('tree');
-        expect(tree.tree[1].name).toBe('submodule');
-        expect(tree.tree[1].type).toBe('submodule');
-        expect(tree.tree[2].name).toBe('blob');
-        expect(tree.tree[2].type).toBe('blob');
-
-        done();
-      }).catch(done.fail);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
deleted file mode 100644
index f678967b092f63791ddf929a04054c68f9cd92ec..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ /dev/null
@@ -1,432 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { resetStore, file } from '../helpers';
-
-describe('Multi-file store actions', () => {
-  afterEach(() => {
-    resetStore(store);
-  });
-
-  describe('redirectToUrl', () => {
-    it('calls visitUrl', (done) => {
-      spyOn(urlUtils, 'visitUrl');
-
-      store.dispatch('redirectToUrl', 'test')
-        .then(() => {
-          expect(urlUtils.visitUrl).toHaveBeenCalledWith('test');
-
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
-  describe('setInitialData', () => {
-    it('commits initial data', (done) => {
-      store.dispatch('setInitialData', { canCommit: true })
-        .then(() => {
-          expect(store.state.canCommit).toBeTruthy();
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
-  describe('closeDiscardPopup', () => {
-    it('closes the discard popup', (done) => {
-      store.dispatch('closeDiscardPopup', false)
-        .then(() => {
-          expect(store.state.discardPopupOpen).toBeFalsy();
-
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
-  describe('discardAllChanges', () => {
-    beforeEach(() => {
-      store.state.openFiles.push(file('discardAll'));
-      store.state.openFiles[0].changed = true;
-    });
-  });
-
-  describe('closeAllFiles', () => {
-    beforeEach(() => {
-      store.state.openFiles.push(file('closeAll'));
-      store.state.openFiles[0].opened = true;
-    });
-
-    it('closes all open files', (done) => {
-      store.dispatch('closeAllFiles')
-        .then(() => {
-          expect(store.state.openFiles.length).toBe(0);
-
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
-  describe('toggleEditMode', () => {
-    it('toggles edit mode', (done) => {
-      store.state.editMode = true;
-
-      store.dispatch('toggleEditMode')
-        .then(() => {
-          expect(store.state.editMode).toBeFalsy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('sets preview mode', (done) => {
-      store.state.currentBlobView = 'repo-editor';
-      store.state.editMode = true;
-
-      store.dispatch('toggleEditMode')
-        .then(Vue.nextTick)
-        .then(() => {
-          expect(store.state.currentBlobView).toBe('repo-preview');
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('opens discard popup if there are changed files', (done) => {
-      store.state.editMode = true;
-      store.state.openFiles.push(file('discardChanges'));
-      store.state.openFiles[0].changed = true;
-
-      store.dispatch('toggleEditMode')
-        .then(() => {
-          expect(store.state.discardPopupOpen).toBeTruthy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('can force closed if there are changed files', (done) => {
-      store.state.editMode = true;
-
-      store.state.openFiles.push(file('forceClose'));
-      store.state.openFiles[0].changed = true;
-
-      store.dispatch('toggleEditMode', true)
-        .then(() => {
-          expect(store.state.discardPopupOpen).toBeFalsy();
-          expect(store.state.editMode).toBeFalsy();
-
-          done();
-        }).catch(done.fail);
-    });
-
-    it('discards file changes', (done) => {
-      const f = file('discard');
-      store.state.editMode = true;
-      store.state.openFiles.push(f);
-      f.changed = true;
-
-      store.dispatch('toggleEditMode', true)
-        .then(Vue.nextTick)
-        .then(() => {
-          expect(f.changed).toBeFalsy();
-
-          done();
-        }).catch(done.fail);
-    });
-  });
-
-  describe('toggleBlobView', () => {
-    it('sets edit mode view if in edit mode', (done) => {
-      store.dispatch('toggleBlobView')
-        .then(() => {
-          expect(store.state.currentBlobView).toBe('repo-editor');
-
-          done();
-        })
-        .catch(done.fail);
-    });
-
-    it('sets preview mode view if not in edit mode', (done) => {
-      store.state.editMode = false;
-
-      store.dispatch('toggleBlobView')
-      .then(() => {
-        expect(store.state.currentBlobView).toBe('repo-preview');
-
-        done();
-      })
-      .catch(done.fail);
-    });
-  });
-
-  describe('checkCommitStatus', () => {
-    beforeEach(() => {
-      store.state.currentProjectId = 'abcproject';
-      store.state.currentBranchId = 'master';
-      store.state.projects.abcproject = {
-        branches: {
-          master: {
-            workingReference: '1',
-          },
-        },
-      };
-    });
-
-    it('calls service', (done) => {
-      spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
-        data: {
-          commit: { id: '123' },
-        },
-      }));
-
-      store.dispatch('checkCommitStatus')
-        .then(() => {
-          expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
-
-          done();
-        })
-        .catch(done.fail);
-    });
-
-    it('returns true if current ref does not equal returned ID', (done) => {
-      spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
-        data: {
-          commit: { id: '123' },
-        },
-      }));
-
-      store.dispatch('checkCommitStatus')
-        .then((val) => {
-          expect(val).toBeTruthy();
-
-          done();
-        })
-        .catch(done.fail);
-    });
-
-    it('returns false if current ref equals returned ID', (done) => {
-      spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
-        data: {
-          commit: { id: '1' },
-        },
-      }));
-
-      store.dispatch('checkCommitStatus')
-        .then((val) => {
-          expect(val).toBeFalsy();
-
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
-  describe('commitChanges', () => {
-    let payload;
-
-    beforeEach(() => {
-      spyOn(window, 'scrollTo');
-
-      document.body.innerHTML += '<div class="flash-container"></div>';
-
-      store.state.currentProjectId = 'abcproject';
-      store.state.currentBranchId = 'master';
-      store.state.projects.abcproject = {
-        web_url: 'webUrl',
-        branches: {
-          master: {
-            workingReference: '1',
-          },
-        },
-      };
-
-      payload = {
-        branch: 'master',
-      };
-    });
-
-    afterEach(() => {
-      document.querySelector('.flash-container').remove();
-    });
-
-    describe('success', () => {
-      beforeEach(() => {
-        spyOn(service, 'commit').and.returnValue(Promise.resolve({
-          data: {
-            id: '123456',
-            short_id: '123',
-            message: 'test message',
-            committed_date: 'date',
-            stats: {
-              additions: '1',
-              deletions: '2',
-            },
-          },
-        }));
-      });
-
-      it('calls service', (done) => {
-        store.dispatch('commitChanges', { payload, newMr: false })
-          .then(() => {
-            expect(service.commit).toHaveBeenCalledWith('abcproject', payload);
-
-            done();
-          }).catch(done.fail);
-      });
-
-      it('shows flash notice', (done) => {
-        store.dispatch('commitChanges', { payload, newMr: false })
-          .then(() => {
-            const alert = document.querySelector('.flash-container');
-
-            expect(alert.querySelector('.flash-notice')).not.toBeNull();
-            expect(alert.textContent.trim()).toBe(
-              'Your changes have been committed. Commit 123 with 1 additions, 2 deletions.',
-            );
-
-            done();
-          }).catch(done.fail);
-      });
-
-      it('adds commit data to changed files', (done) => {
-        const changedFile = file('changed');
-        const f = file('newfile');
-        changedFile.changed = true;
-
-        store.state.openFiles.push(changedFile, f);
-
-        store.dispatch('commitChanges', { payload, newMr: false })
-          .then(() => {
-            expect(changedFile.lastCommit.message).toBe('test message');
-            expect(f.lastCommit.message).not.toBe('test message');
-
-            done();
-          }).catch(done.fail);
-      });
-
-      it('scrolls to top of page', (done) => {
-        store.dispatch('commitChanges', { payload, newMr: false })
-          .then(() => {
-            expect(window.scrollTo).toHaveBeenCalledWith(0, 0);
-
-            done();
-          }).catch(done.fail);
-      });
-
-      it('redirects to new merge request page', (done) => {
-        spyOn(urlUtils, 'visitUrl');
-
-        store.dispatch('commitChanges', { payload, newMr: true })
-          .then(() => {
-            expect(urlUtils.visitUrl).toHaveBeenCalledWith('webUrl/merge_requests/new?merge_request%5Bsource_branch%5D=master');
-
-            done();
-          }).catch(done.fail);
-      });
-    });
-
-    describe('failed', () => {
-      beforeEach(() => {
-        spyOn(service, 'commit').and.returnValue(Promise.resolve({
-          data: {
-            message: 'failed message',
-          },
-        }));
-      });
-
-      it('shows failed message', (done) => {
-        store.dispatch('commitChanges', { payload, newMr: false })
-          .then(() => {
-            const alert = document.querySelector('.flash-container');
-
-            expect(alert.textContent.trim()).toBe(
-              'failed message',
-            );
-
-            done();
-          }).catch(done.fail);
-      });
-    });
-  });
-
-  describe('createTempEntry', () => {
-    beforeEach(() => {
-      store.state.trees['abcproject/mybranch'] = {
-        tree: [],
-      };
-      store.state.projects.abcproject = {
-        web_url: '',
-      };
-    });
-
-    it('creates a temp tree', (done) => {
-      const projectTree = store.state.trees['abcproject/mybranch'];
-
-      store.dispatch('createTempEntry', {
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-        name: 'test',
-        type: 'tree',
-      })
-      .then(() => {
-        const baseTree = projectTree.tree;
-        expect(baseTree.length).toBe(1);
-        expect(baseTree[0].tempFile).toBeTruthy();
-        expect(baseTree[0].type).toBe('tree');
-
-        done();
-      })
-      .catch(done.fail);
-    });
-
-    it('creates temp file', (done) => {
-      const projectTree = store.state.trees['abcproject/mybranch'];
-
-      store.dispatch('createTempEntry', {
-        projectId: 'abcproject',
-        branchId: 'mybranch',
-        parent: projectTree,
-        name: 'test',
-        type: 'blob',
-      })
-      .then(() => {
-        const baseTree = projectTree.tree;
-        expect(baseTree.length).toBe(1);
-        expect(baseTree[0].tempFile).toBeTruthy();
-        expect(baseTree[0].type).toBe('blob');
-
-        done();
-      })
-      .catch(done.fail);
-    });
-  });
-
-  describe('popHistoryState', () => {
-
-  });
-
-  describe('scrollToTab', () => {
-    it('focuses the current active element', (done) => {
-      document.body.innerHTML += '<div id="tabs"><div class="active"><div class="repo-tab"></div></div></div>';
-      const el = document.querySelector('.repo-tab');
-      spyOn(el, 'focus');
-
-      store.dispatch('scrollToTab')
-        .then(() => {
-          setTimeout(() => {
-            expect(el.focus).toHaveBeenCalled();
-
-            document.getElementById('tabs').remove();
-
-            done();
-          });
-        })
-        .catch(done.fail);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/getters_spec.js b/spec/javascripts/repo/stores/getters_spec.js
deleted file mode 100644
index d0d5934f29a8d7159a590dd11411166976b965dd..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/getters_spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import * as getters from '~/ide/stores/getters';
-import state from '~/ide/stores/state';
-import { file } from '../helpers';
-
-describe('Multi-file store getters', () => {
-  let localState;
-
-  beforeEach(() => {
-    localState = state();
-  });
-
-  describe('changedFiles', () => {
-    it('returns a list of changed opened files', () => {
-      localState.openFiles.push(file());
-      localState.openFiles.push(file('changed'));
-      localState.openFiles[1].changed = true;
-
-      const changedFiles = getters.changedFiles(localState);
-
-      expect(changedFiles.length).toBe(1);
-      expect(changedFiles[0].name).toBe('changed');
-    });
-  });
-
-  describe('activeFile', () => {
-    it('returns the current active file', () => {
-      localState.openFiles.push(file());
-      localState.openFiles.push(file('active'));
-      localState.openFiles[1].active = true;
-
-      expect(getters.activeFile(localState).name).toBe('active');
-    });
-
-    it('returns undefined if no active files are found', () => {
-      localState.openFiles.push(file());
-      localState.openFiles.push(file('active'));
-
-      expect(getters.activeFile(localState)).toBeNull();
-    });
-  });
-
-  describe('activeFileExtension', () => {
-    it('returns the file extension for the current active file', () => {
-      localState.openFiles.push(file('active'));
-      localState.openFiles[0].active = true;
-      localState.openFiles[0].path = 'test.js';
-
-      expect(getters.activeFileExtension(localState)).toBe('.js');
-
-      localState.openFiles[0].path = 'test.es6.js';
-
-      expect(getters.activeFileExtension(localState)).toBe('.js');
-    });
-  });
-
-  describe('canEditFile', () => {
-    beforeEach(() => {
-      localState.onTopOfBranch = true;
-      localState.canCommit = true;
-
-      localState.openFiles.push(file());
-      localState.openFiles[0].active = true;
-    });
-
-    it('returns true if user can commit and has open files', () => {
-      expect(getters.canEditFile(localState)).toBeTruthy();
-    });
-
-    it('returns false if user can commit and has no open files', () => {
-      localState.openFiles = [];
-
-      expect(getters.canEditFile(localState)).toBeFalsy();
-    });
-
-    it('returns false if user can commit and active file is binary', () => {
-      localState.openFiles[0].binary = true;
-
-      expect(getters.canEditFile(localState)).toBeFalsy();
-    });
-
-    it('returns false if user cant commit', () => {
-      localState.canCommit = false;
-
-      expect(getters.canEditFile(localState)).toBeFalsy();
-    });
-  });
-
-  describe('modifiedFiles', () => {
-    it('returns a list of modified files', () => {
-      localState.openFiles.push(file());
-      localState.openFiles.push(file('changed'));
-      localState.openFiles[1].changed = true;
-
-      const modifiedFiles = getters.modifiedFiles(localState);
-
-      expect(modifiedFiles.length).toBe(1);
-      expect(modifiedFiles[0].name).toBe('changed');
-    });
-  });
-
-  describe('addedFiles', () => {
-    it('returns a list of added files', () => {
-      localState.openFiles.push(file());
-      localState.openFiles.push(file('added'));
-      localState.openFiles[1].changed = true;
-      localState.openFiles[1].tempFile = true;
-
-      const modifiedFiles = getters.addedFiles(localState);
-
-      expect(modifiedFiles.length).toBe(1);
-      expect(modifiedFiles[0].name).toBe('added');
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js
deleted file mode 100644
index 6e204ef0404e98104a791bb176c279fb31ad6013..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/mutations/file_spec.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import mutations from '~/ide/stores/mutations/file';
-import state from '~/ide/stores/state';
-import { file } from '../../helpers';
-
-describe('Multi-file store file mutations', () => {
-  let localState;
-  let localFile;
-
-  beforeEach(() => {
-    localState = state();
-    localFile = file();
-  });
-
-  describe('SET_FILE_ACTIVE', () => {
-    it('sets the file active', () => {
-      mutations.SET_FILE_ACTIVE(localState, {
-        file: localFile,
-        active: true,
-      });
-
-      expect(localFile.active).toBeTruthy();
-    });
-  });
-
-  describe('TOGGLE_FILE_OPEN', () => {
-    beforeEach(() => {
-      mutations.TOGGLE_FILE_OPEN(localState, localFile);
-    });
-
-    it('adds into opened files', () => {
-      expect(localFile.opened).toBeTruthy();
-      expect(localState.openFiles.length).toBe(1);
-    });
-
-    it('removes from opened files', () => {
-      mutations.TOGGLE_FILE_OPEN(localState, localFile);
-
-      expect(localFile.opened).toBeFalsy();
-      expect(localState.openFiles.length).toBe(0);
-    });
-  });
-
-  describe('SET_FILE_DATA', () => {
-    it('sets extra file data', () => {
-      mutations.SET_FILE_DATA(localState, {
-        data: {
-          blame_path: 'blame',
-          commits_path: 'commits',
-          permalink: 'permalink',
-          raw_path: 'raw',
-          binary: true,
-          html: 'html',
-          render_error: 'render_error',
-        },
-        file: localFile,
-      });
-
-      expect(localFile.blamePath).toBe('blame');
-      expect(localFile.commitsPath).toBe('commits');
-      expect(localFile.permalink).toBe('permalink');
-      expect(localFile.rawPath).toBe('raw');
-      expect(localFile.binary).toBeTruthy();
-      expect(localFile.html).toBe('html');
-      expect(localFile.renderError).toBe('render_error');
-    });
-  });
-
-  describe('SET_FILE_RAW_DATA', () => {
-    it('sets raw data', () => {
-      mutations.SET_FILE_RAW_DATA(localState, {
-        file: localFile,
-        raw: 'testing',
-      });
-
-      expect(localFile.raw).toBe('testing');
-    });
-  });
-
-  describe('UPDATE_FILE_CONTENT', () => {
-    beforeEach(() => {
-      localFile.raw = 'test';
-    });
-
-    it('sets content', () => {
-      mutations.UPDATE_FILE_CONTENT(localState, {
-        file: localFile,
-        content: 'test',
-      });
-
-      expect(localFile.content).toBe('test');
-    });
-
-    it('sets changed if content does not match raw', () => {
-      mutations.UPDATE_FILE_CONTENT(localState, {
-        file: localFile,
-        content: 'testing',
-      });
-
-      expect(localFile.content).toBe('testing');
-      expect(localFile.changed).toBeTruthy();
-    });
-  });
-
-  describe('DISCARD_FILE_CHANGES', () => {
-    beforeEach(() => {
-      localFile.content = 'test';
-      localFile.changed = true;
-    });
-
-    it('resets content and changed', () => {
-      mutations.DISCARD_FILE_CHANGES(localState, localFile);
-
-      expect(localFile.content).toBe('');
-      expect(localFile.changed).toBeFalsy();
-    });
-  });
-
-  describe('CREATE_TMP_FILE', () => {
-    it('adds file into parent tree', () => {
-      const f = file('tmpFile');
-
-      mutations.CREATE_TMP_FILE(localState, {
-        file: f,
-        parent: localFile,
-      });
-
-      expect(localFile.tree.length).toBe(1);
-      expect(localFile.tree[0].name).toBe(f.name);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js
deleted file mode 100644
index e6ca8ea139e3837f004c939b2834d53b87674655..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/mutations/tree_spec.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import mutations from '~/ide/stores/mutations/tree';
-import state from '~/ide/stores/state';
-import { file } from '../../helpers';
-
-describe('Multi-file store tree mutations', () => {
-  let localState;
-  let localTree;
-
-  beforeEach(() => {
-    localState = state();
-    localTree = file();
-  });
-
-  describe('TOGGLE_TREE_OPEN', () => {
-    it('toggles tree open', () => {
-      mutations.TOGGLE_TREE_OPEN(localState, localTree);
-
-      expect(localTree.opened).toBeTruthy();
-
-      mutations.TOGGLE_TREE_OPEN(localState, localTree);
-
-      expect(localTree.opened).toBeFalsy();
-    });
-  });
-
-  describe('SET_DIRECTORY_DATA', () => {
-    const data = [{
-      name: 'tree',
-    },
-    {
-      name: 'submodule',
-    },
-    {
-      name: 'blob',
-    }];
-
-    it('adds directory data', () => {
-      mutations.SET_DIRECTORY_DATA(localState, {
-        data,
-        tree: localState,
-      });
-
-      expect(localState.tree.length).toBe(3);
-      expect(localState.tree[0].name).toBe('tree');
-      expect(localState.tree[1].name).toBe('submodule');
-      expect(localState.tree[2].name).toBe('blob');
-    });
-  });
-
-  describe('SET_PARENT_TREE_URL', () => {
-    it('sets the parent tree url', () => {
-      mutations.SET_PARENT_TREE_URL(localState, 'test');
-
-      expect(localState.parentTreeUrl).toBe('test');
-    });
-  });
-
-  describe('CREATE_TMP_TREE', () => {
-    it('adds tree into parent tree', () => {
-      const tmpEntry = file('tmpTree');
-
-      mutations.CREATE_TMP_TREE(localState, {
-        tmpEntry,
-        parent: localTree,
-      });
-
-      expect(localTree.tree.length).toBe(1);
-      expect(localTree.tree[0].name).toBe(tmpEntry.name);
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/mutations_spec.js b/spec/javascripts/repo/stores/mutations_spec.js
deleted file mode 100644
index 5fd8ad949720b6e60467cd063db3519872acff87..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/mutations_spec.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import mutations from '~/ide/stores/mutations';
-import state from '~/ide/stores/state';
-import { file } from '../helpers';
-
-describe('Multi-file store mutations', () => {
-  let localState;
-  let entry;
-
-  beforeEach(() => {
-    localState = state();
-    entry = file();
-  });
-
-  describe('SET_INITIAL_DATA', () => {
-    it('sets all initial data', () => {
-      mutations.SET_INITIAL_DATA(localState, {
-        test: 'test',
-      });
-
-      expect(localState.test).toBe('test');
-    });
-  });
-
-  describe('SET_PREVIEW_MODE', () => {
-    it('sets currentBlobView to repo-preview', () => {
-      mutations.SET_PREVIEW_MODE(localState);
-
-      expect(localState.currentBlobView).toBe('repo-preview');
-
-      localState.currentBlobView = 'testing';
-
-      mutations.SET_PREVIEW_MODE(localState);
-
-      expect(localState.currentBlobView).toBe('repo-preview');
-    });
-  });
-
-  describe('SET_EDIT_MODE', () => {
-    it('sets currentBlobView to repo-editor', () => {
-      mutations.SET_EDIT_MODE(localState);
-
-      expect(localState.currentBlobView).toBe('repo-editor');
-
-      localState.currentBlobView = 'testing';
-
-      mutations.SET_EDIT_MODE(localState);
-
-      expect(localState.currentBlobView).toBe('repo-editor');
-    });
-  });
-
-  describe('TOGGLE_LOADING', () => {
-    it('toggles loading of entry', () => {
-      mutations.TOGGLE_LOADING(localState, entry);
-
-      expect(entry.loading).toBeTruthy();
-
-      mutations.TOGGLE_LOADING(localState, entry);
-
-      expect(entry.loading).toBeFalsy();
-    });
-  });
-
-  describe('TOGGLE_EDIT_MODE', () => {
-    it('toggles editMode', () => {
-      mutations.TOGGLE_EDIT_MODE(localState);
-
-      expect(localState.editMode).toBeFalsy();
-
-      mutations.TOGGLE_EDIT_MODE(localState);
-
-      expect(localState.editMode).toBeTruthy();
-    });
-  });
-
-  describe('TOGGLE_DISCARD_POPUP', () => {
-    it('sets discardPopupOpen', () => {
-      mutations.TOGGLE_DISCARD_POPUP(localState, true);
-
-      expect(localState.discardPopupOpen).toBeTruthy();
-
-      mutations.TOGGLE_DISCARD_POPUP(localState, false);
-
-      expect(localState.discardPopupOpen).toBeFalsy();
-    });
-  });
-
-  describe('SET_ROOT', () => {
-    it('sets isRoot & initialRoot', () => {
-      mutations.SET_ROOT(localState, true);
-
-      expect(localState.isRoot).toBeTruthy();
-      expect(localState.isInitialRoot).toBeTruthy();
-
-      mutations.SET_ROOT(localState, false);
-
-      expect(localState.isRoot).toBeFalsy();
-      expect(localState.isInitialRoot).toBeFalsy();
-    });
-  });
-
-  describe('SET_LEFT_PANEL_COLLAPSED', () => {
-    it('sets left panel collapsed', () => {
-      mutations.SET_LEFT_PANEL_COLLAPSED(localState, true);
-
-      expect(localState.leftPanelCollapsed).toBeTruthy();
-
-      mutations.SET_LEFT_PANEL_COLLAPSED(localState, false);
-
-      expect(localState.leftPanelCollapsed).toBeFalsy();
-    });
-  });
-
-  describe('SET_RIGHT_PANEL_COLLAPSED', () => {
-    it('sets right panel collapsed', () => {
-      mutations.SET_RIGHT_PANEL_COLLAPSED(localState, true);
-
-      expect(localState.rightPanelCollapsed).toBeTruthy();
-
-      mutations.SET_RIGHT_PANEL_COLLAPSED(localState, false);
-
-      expect(localState.rightPanelCollapsed).toBeFalsy();
-    });
-  });
-});
diff --git a/spec/javascripts/repo/stores/utils_spec.js b/spec/javascripts/repo/stores/utils_spec.js
deleted file mode 100644
index 89745a2029ef6b2f380a42ec81248af3fb5354a7..0000000000000000000000000000000000000000
--- a/spec/javascripts/repo/stores/utils_spec.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as utils from '~/ide/stores/utils';
-import state from '~/ide/stores/state';
-import { file } from '../helpers';
-
-describe('Multi-file store utils', () => {
-  describe('setPageTitle', () => {
-    it('sets the document page title', () => {
-      utils.setPageTitle('test');
-
-      expect(document.title).toBe('test');
-    });
-  });
-
-  describe('treeList', () => {
-    let localState;
-
-    beforeEach(() => {
-      localState = state();
-    });
-
-    it('returns flat tree list', () => {
-      localState.trees = [];
-      localState.trees['abcproject/mybranch'] = {
-        tree: [],
-      };
-      const baseTree = localState.trees['abcproject/mybranch'].tree;
-      baseTree.push(file('1'));
-      baseTree[0].tree.push(file('2'));
-      baseTree[0].tree[0].tree.push(file('3'));
-
-      const treeList = utils.treeList(localState, 'abcproject/mybranch');
-
-      expect(treeList.length).toBe(3);
-      expect(treeList[1].name).toBe(baseTree[0].tree[0].name);
-      expect(treeList[2].name).toBe(baseTree[0].tree[0].tree[0].name);
-    });
-  });
-
-  describe('createTemp', () => {
-    it('creates temp tree', () => {
-      const tmp = utils.createTemp({
-        name: 'test',
-        path: 'test',
-        type: 'tree',
-        level: 0,
-        changed: false,
-        content: '',
-        base64: '',
-      });
-
-      expect(tmp.tempFile).toBeTruthy();
-      expect(tmp.icon).toBe('fa-folder');
-    });
-
-    it('creates temp file', () => {
-      const tmp = utils.createTemp({
-        name: 'test',
-        path: 'test',
-        type: 'blob',
-        level: 0,
-        changed: false,
-        content: '',
-        base64: '',
-      });
-
-      expect(tmp.tempFile).toBeTruthy();
-      expect(tmp.icon).toBe('fa-file-text-o');
-    });
-  });
-
-  describe('findIndexOfFile', () => {
-    let localState;
-
-    beforeEach(() => {
-      localState = [{
-        path: '1',
-      }, {
-        path: '2',
-      }];
-    });
-
-    it('finds in the index of an entry by path', () => {
-      const index = utils.findIndexOfFile(localState, {
-        path: '2',
-      });
-
-      expect(index).toBe(1);
-    });
-  });
-
-  describe('findEntry', () => {
-    let localState;
-
-    beforeEach(() => {
-      localState = {
-        tree: [{
-          type: 'tree',
-          name: 'test',
-        }, {
-          type: 'blob',
-          name: 'file',
-        }],
-      };
-    });
-
-    it('returns an entry found by name', () => {
-      const foundEntry = utils.findEntry(localState.tree, 'tree', 'test');
-
-      expect(foundEntry.type).toBe('tree');
-      expect(foundEntry.name).toBe('test');
-    });
-
-    it('returns undefined when no entry found', () => {
-      const foundEntry = utils.findEntry(localState.tree, 'blob', 'test');
-
-      expect(foundEntry).toBeUndefined();
-    });
-  });
-});
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 35bb630bf5d3682929049d819354a48ce0e544fd..80770a6101129b7abb915bda5184b7a077f00aed 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */
 
+import $ from 'jquery';
 import MockAdapter from 'axios-mock-adapter';
 import '~/commons/bootstrap';
 import axios from '~/lib/utils/axios_utils';
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 206f95abc1a4d350d365df0a4d42cfc09466f15c..1a27955983dc08fabec168d6d2aae5e890c68506 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,12 +1,26 @@
 /* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, max-len */
 
+import $ from 'jquery';
 import '~/gl_dropdown';
 import SearchAutocomplete from '~/search_autocomplete';
 import '~/lib/utils/common_utils';
 import * as urlUtils from '~/lib/utils/url_utility';
 
-(function() {
-  var assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
+describe('Search autocomplete dropdown', () => {
+  var assertLinks,
+    dashboardIssuesPath,
+    dashboardMRsPath,
+    groupIssuesPath,
+    groupMRsPath,
+    groupName,
+    mockDashboardOptions,
+    mockGroupOptions,
+    mockProjectOptions,
+    projectIssuesPath,
+    projectMRsPath,
+    projectName,
+    userId,
+    widget;
   var userName = 'root';
 
   widget = null;
@@ -65,133 +79,126 @@ import * as urlUtils from '~/lib/utils/url_utility';
   // Mock `gl` object in window for dashboard specific page. App code will need it.
   mockDashboardOptions = function() {
     window.gl || (window.gl = {});
-    return window.gl.dashboardOptions = {
+    return (window.gl.dashboardOptions = {
       issuesPath: dashboardIssuesPath,
-      mrPath: dashboardMRsPath
-    };
+      mrPath: dashboardMRsPath,
+    });
   };
 
   // Mock `gl` object in window for project specific page. App code will need it.
   mockProjectOptions = function() {
     window.gl || (window.gl = {});
-    return window.gl.projectOptions = {
+    return (window.gl.projectOptions = {
       'gitlab-ce': {
         issuesPath: projectIssuesPath,
         mrPath: projectMRsPath,
-        projectName: projectName
-      }
-    };
+        projectName: projectName,
+      },
+    });
   };
 
   mockGroupOptions = function() {
     window.gl || (window.gl = {});
-    return window.gl.groupOptions = {
+    return (window.gl.groupOptions = {
       'gitlab-org': {
         issuesPath: groupIssuesPath,
         mrPath: groupMRsPath,
-        projectName: groupName
-      }
-    };
+        projectName: groupName,
+      },
+    });
   };
 
   assertLinks = function(list, issuesPath, mrsPath) {
-    var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
     if (issuesPath) {
-      issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName;
-      issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName;
-      a1 = "a[href='" + issuesAssignedToMeLink + "']";
-      a2 = "a[href='" + issuesIHaveCreatedLink + "']";
-      expect(list.find(a1).length).toBe(1);
-      expect(list.find(a1).text()).toBe('Issues assigned to me');
-      expect(list.find(a2).length).toBe(1);
-      expect(list.find(a2).text()).toBe("Issues I've created");
+      const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_id=${userId}"]`;
+      const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_id=${userId}"]`;
+      expect(list.find(issuesAssignedToMeLink).length).toBe(1);
+      expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me');
+      expect(list.find(issuesIHaveCreatedLink).length).toBe(1);
+      expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created");
     }
-    mrsAssignedToMeLink = mrsPath + "/?assignee_username=" + userName;
-    mrsIHaveCreatedLink = mrsPath + "/?author_username=" + userName;
-    a3 = "a[href='" + mrsAssignedToMeLink + "']";
-    a4 = "a[href='" + mrsIHaveCreatedLink + "']";
-    expect(list.find(a3).length).toBe(1);
-    expect(list.find(a3).text()).toBe('Merge requests assigned to me');
-    expect(list.find(a4).length).toBe(1);
-    return expect(list.find(a4).text()).toBe("Merge requests I've created");
+    const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_id=${userId}"]`;
+    const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_id=${userId}"]`;
+    expect(list.find(mrsAssignedToMeLink).length).toBe(1);
+    expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me');
+    expect(list.find(mrsIHaveCreatedLink).length).toBe(1);
+    expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
   };
 
-  describe('Search autocomplete dropdown', function() {
-    preloadFixtures('static/search_autocomplete.html.raw');
-    beforeEach(function() {
-      loadFixtures('static/search_autocomplete.html.raw');
+  preloadFixtures('static/search_autocomplete.html.raw');
+  beforeEach(function() {
+    loadFixtures('static/search_autocomplete.html.raw');
 
-      // Prevent turbolinks from triggering within gl_dropdown
-      spyOn(urlUtils, 'visitUrl').and.returnValue(true);
+    // Prevent turbolinks from triggering within gl_dropdown
+    spyOn(urlUtils, 'visitUrl').and.returnValue(true);
 
-      window.gon = {};
-      window.gon.current_user_id = userId;
-      window.gon.current_username = userName;
+    window.gon = {};
+    window.gon.current_user_id = userId;
+    window.gon.current_username = userName;
 
-      return widget = new SearchAutocomplete();
-    });
+    return (widget = new SearchAutocomplete());
+  });
 
-    afterEach(function() {
-      // Undo what we did to the shared <body>
-      removeBodyAttributes();
-      window.gon = {};
-    });
-    it('should show Dashboard specific dropdown menu', function() {
-      var list;
-      addBodyAttributes();
-      mockDashboardOptions();
-      widget.searchInput.triggerHandler('focus');
-      list = widget.wrap.find('.dropdown-menu').find('ul');
-      return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
-    });
-    it('should show Group specific dropdown menu', function() {
-      var list;
-      addBodyAttributes('group');
-      mockGroupOptions();
-      widget.searchInput.triggerHandler('focus');
-      list = widget.wrap.find('.dropdown-menu').find('ul');
-      return assertLinks(list, groupIssuesPath, groupMRsPath);
-    });
-    it('should show Project specific dropdown menu', function() {
-      var list;
-      addBodyAttributes('project');
-      mockProjectOptions();
-      widget.searchInput.triggerHandler('focus');
-      list = widget.wrap.find('.dropdown-menu').find('ul');
-      return assertLinks(list, projectIssuesPath, projectMRsPath);
-    });
-    it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() {
-      addBodyAttributes('project');
-      disableProjectIssues();
-      mockProjectOptions();
-      widget.searchInput.triggerHandler('focus');
-      const list = widget.wrap.find('.dropdown-menu').find('ul');
-      assertLinks(list, null, projectMRsPath);
-    });
-    it('should not show category related menu if there is text in the input', function() {
-      var link, list;
-      addBodyAttributes('project');
-      mockProjectOptions();
-      widget.searchInput.val('help');
-      widget.searchInput.triggerHandler('focus');
-      list = widget.wrap.find('.dropdown-menu').find('ul');
-      link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']";
-      return expect(list.find(link).length).toBe(0);
-    });
-    return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
-      var ENTER = 13;
-      var DOWN = 40;
-      addBodyAttributes();
-      mockDashboardOptions(true);
-      var submitSpy = spyOnEvent('form', 'submit');
-      widget.searchInput.triggerHandler('focus');
-      widget.wrap.trigger($.Event('keydown', { which: DOWN }));
-      var enterKeyEvent = $.Event('keydown', { which: ENTER });
-      widget.searchInput.trigger(enterKeyEvent);
-      // This does not currently catch failing behavior. For security reasons,
-      // browsers will not trigger default behavior (form submit, in this
-      // example) on JavaScript-created keypresses.
-      expect(submitSpy).not.toHaveBeenTriggered();
-    });
+  afterEach(function() {
+    // Undo what we did to the shared <body>
+    removeBodyAttributes();
+    window.gon = {};
+  });
+  it('should show Dashboard specific dropdown menu', function() {
+    var list;
+    addBodyAttributes();
+    mockDashboardOptions();
+    widget.searchInput.triggerHandler('focus');
+    list = widget.wrap.find('.dropdown-menu').find('ul');
+    return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
+  });
+  it('should show Group specific dropdown menu', function() {
+    var list;
+    addBodyAttributes('group');
+    mockGroupOptions();
+    widget.searchInput.triggerHandler('focus');
+    list = widget.wrap.find('.dropdown-menu').find('ul');
+    return assertLinks(list, groupIssuesPath, groupMRsPath);
+  });
+  it('should show Project specific dropdown menu', function() {
+    var list;
+    addBodyAttributes('project');
+    mockProjectOptions();
+    widget.searchInput.triggerHandler('focus');
+    list = widget.wrap.find('.dropdown-menu').find('ul');
+    return assertLinks(list, projectIssuesPath, projectMRsPath);
+  });
+  it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() {
+    addBodyAttributes('project');
+    disableProjectIssues();
+    mockProjectOptions();
+    widget.searchInput.triggerHandler('focus');
+    const list = widget.wrap.find('.dropdown-menu').find('ul');
+    assertLinks(list, null, projectMRsPath);
+  });
+  it('should not show category related menu if there is text in the input', function() {
+    var link, list;
+    addBodyAttributes('project');
+    mockProjectOptions();
+    widget.searchInput.val('help');
+    widget.searchInput.triggerHandler('focus');
+    list = widget.wrap.find('.dropdown-menu').find('ul');
+    link = "a[href='" + projectIssuesPath + '/?assignee_id=' + userId + "']";
+    return expect(list.find(link).length).toBe(0);
+  });
+  it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
+    var ENTER = 13;
+    var DOWN = 40;
+    addBodyAttributes();
+    mockDashboardOptions(true);
+    var submitSpy = spyOnEvent('form', 'submit');
+    widget.searchInput.triggerHandler('focus');
+    widget.wrap.trigger($.Event('keydown', { which: DOWN }));
+    var enterKeyEvent = $.Event('keydown', { which: ENTER });
+    widget.searchInput.trigger(enterKeyEvent);
+    // This does not currently catch failing behavior. For security reasons,
+    // browsers will not trigger default behavior (form submit, in this
+    // example) on JavaScript-created keypresses.
+    expect(submitSpy).not.toHaveBeenTriggered();
   });
-}).call(window);
+});
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
index 38e94d45e557899fc9b587cb8d4e7de9769d9445..522851c584b1b66108b3d76ca225f738ec760fb1 100644
--- a/spec/javascripts/search_spec.js
+++ b/spec/javascripts/search_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Api from '~/api';
 import Search from '~/pages/search/show/search';
 
diff --git a/spec/javascripts/shared/popover_spec.js b/spec/javascripts/shared/popover_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..1d574c9424ba9152c79354db1d5835876f09f039
--- /dev/null
+++ b/spec/javascripts/shared/popover_spec.js
@@ -0,0 +1,162 @@
+import $ from 'jquery';
+import {
+  togglePopover,
+  mouseleave,
+  mouseenter,
+} from '~/shared/popover';
+
+describe('popover', () => {
+  describe('togglePopover', () => {
+    describe('togglePopover(true)', () => {
+      it('returns true when popover is shown', () => {
+        const context = {
+          hasClass: () => false,
+          popover: () => {},
+          toggleClass: () => {},
+        };
+
+        expect(togglePopover.call(context, true)).toEqual(true);
+      });
+
+      it('returns false when popover is already shown', () => {
+        const context = {
+          hasClass: () => true,
+        };
+
+        expect(togglePopover.call(context, true)).toEqual(false);
+      });
+
+      it('shows popover', (done) => {
+        const context = {
+          hasClass: () => false,
+          popover: () => {},
+          toggleClass: () => {},
+        };
+
+        spyOn(context, 'popover').and.callFake((method) => {
+          expect(method).toEqual('show');
+          done();
+        });
+
+        togglePopover.call(context, true);
+      });
+
+      it('adds disable-animation and js-popover-show class', (done) => {
+        const context = {
+          hasClass: () => false,
+          popover: () => {},
+          toggleClass: () => {},
+        };
+
+        spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
+          expect(classNames).toEqual('disable-animation js-popover-show');
+          expect(show).toEqual(true);
+          done();
+        });
+
+        togglePopover.call(context, true);
+      });
+    });
+
+    describe('togglePopover(false)', () => {
+      it('returns true when popover is hidden', () => {
+        const context = {
+          hasClass: () => true,
+          popover: () => {},
+          toggleClass: () => {},
+        };
+
+        expect(togglePopover.call(context, false)).toEqual(true);
+      });
+
+      it('returns false when popover is already hidden', () => {
+        const context = {
+          hasClass: () => false,
+        };
+
+        expect(togglePopover.call(context, false)).toEqual(false);
+      });
+
+      it('hides popover', (done) => {
+        const context = {
+          hasClass: () => true,
+          popover: () => {},
+          toggleClass: () => {},
+        };
+
+        spyOn(context, 'popover').and.callFake((method) => {
+          expect(method).toEqual('hide');
+          done();
+        });
+
+        togglePopover.call(context, false);
+      });
+
+      it('removes disable-animation and js-popover-show class', (done) => {
+        const context = {
+          hasClass: () => true,
+          popover: () => {},
+          toggleClass: () => {},
+        };
+
+        spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
+          expect(classNames).toEqual('disable-animation js-popover-show');
+          expect(show).toEqual(false);
+          done();
+        });
+
+        togglePopover.call(context, false);
+      });
+    });
+  });
+
+  describe('mouseleave', () => {
+    it('calls hide popover if .popover:hover is false', () => {
+      const fakeJquery = {
+        length: 0,
+      };
+
+      spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
+      spyOn(togglePopover, 'call');
+      mouseleave();
+      expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), false);
+    });
+
+    it('does not call hide popover if .popover:hover is true', () => {
+      const fakeJquery = {
+        length: 1,
+      };
+
+      spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
+      spyOn(togglePopover, 'call');
+      mouseleave();
+      expect(togglePopover.call).not.toHaveBeenCalledWith(false);
+    });
+  });
+
+  describe('mouseenter', () => {
+    const context = {};
+
+    it('shows popover', () => {
+      spyOn(togglePopover, 'call').and.returnValue(false);
+      mouseenter.call(context);
+      expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), true);
+    });
+
+    it('registers mouseleave event if popover is showed', (done) => {
+      spyOn(togglePopover, 'call').and.returnValue(true);
+      spyOn($.fn, 'on').and.callFake((eventName) => {
+        expect(eventName).toEqual('mouseleave');
+        done();
+      });
+      mouseenter.call(context);
+    });
+
+    it('does not register mouseleave event if popover is not showed', () => {
+      spyOn(togglePopover, 'call').and.returnValue(false);
+      const spy = spyOn($.fn, 'on').and.callFake(() => {});
+      mouseenter.call(context);
+      expect(spy).not.toHaveBeenCalled();
+    });
+  });
+});
diff --git a/spec/javascripts/shortcuts_dashboard_navigation_spec.js b/spec/javascripts/shortcuts_dashboard_navigation_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..888b49004bf66a17e2c49a68c429adb6c8328ea6
--- /dev/null
+++ b/spec/javascripts/shortcuts_dashboard_navigation_spec.js
@@ -0,0 +1,24 @@
+import findAndFollowLink from '~/shortcuts_dashboard_navigation';
+import * as urlUtility from '~/lib/utils/url_utility';
+
+describe('findAndFollowLink', () => {
+  it('visits a link when the selector exists', () => {
+    const href = '/some/path';
+    const locationSpy = spyOn(urlUtility, 'visitUrl');
+
+    setFixtures(`<a class="my-shortcut" href="${href}">link</a>`);
+
+    findAndFollowLink('.my-shortcut');
+
+    expect(locationSpy).toHaveBeenCalledWith(href);
+  });
+
+  it('does not throw an exception when the selector does not exist', () => {
+    const locationSpy = spyOn(urlUtility, 'visitUrl');
+
+    // this should not throw an exception
+    findAndFollowLink('.this-selector-does-not-exist');
+
+    expect(locationSpy).not.toHaveBeenCalled();
+  });
+});
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 5d6a885d4cc6bf3bd7cceafcd7c93cfa2cd75186..b0d714cbefb79df40263a13248112e69bd60bcd4 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -1,4 +1,5 @@
-import initCopyAsGFM from '~/behaviors/copy_as_gfm';
+import $ from 'jquery';
+import initCopyAsGFM from '~/behaviors/markdown/copy_as_gfm';
 import ShortcutsIssuable from '~/shortcuts_issuable';
 
 initCopyAsGFM();
diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js
index a2a609edef9aabdb3f98469e7da18497b77e3dca..ee92295ef5ea72f08db9cf457718da1c9d9bc379 100644
--- a/spec/javascripts/shortcuts_spec.js
+++ b/spec/javascripts/shortcuts_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Shortcuts from '~/shortcuts';
 
 describe('Shortcuts', () => {
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index ac93f918ce436a56f31edf221e857c340ac9054a..509edba20367cdecaa2ab59e8b49aecfaa3a9549 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import AssigneeTitle from '~/sidebar/components/assignees/assignee_title';
+import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
 
 describe('AssigneeTitle component', () => {
   let component;
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index 88a33caf2e388b4534adde03fed9d0e93b194294..0c173062835d830b97336be99888c2e311b76864 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -62,4 +62,22 @@ describe('Confidential Issue Sidebar Block', () => {
       done();
     });
   });
+
+  it('displays the edit form when opened from collapsed state', (done) => {
+    expect(vm1.edit).toBe(false);
+
+    vm1.$el.querySelector('.sidebar-collapsed-icon').click();
+
+    expect(vm1.edit).toBe(true);
+
+    setTimeout(() => {
+      expect(
+        vm1.$el
+          .innerHTML
+          .includes('You are going to turn off the confidentiality.'),
+      ).toBe(true);
+
+      done();
+    });
+  });
 });
diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
index 696fca516bc11b188dd5239b9c4e3b3920d7bf5e..9abc3daf221bb56d21b3dffd03024abed5b2019a 100644
--- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
@@ -68,4 +68,22 @@ describe('LockIssueSidebar', () => {
       done();
     });
   });
+
+  it('displays the edit form when opened from collapsed state', (done) => {
+    expect(vm1.isLockDialogOpen).toBe(false);
+
+    vm1.$el.querySelector('.sidebar-collapsed-icon').click();
+
+    expect(vm1.isLockDialogOpen).toBe(true);
+
+    setTimeout(() => {
+      expect(
+        vm1.$el
+          .innerHTML
+          .includes('Unlock this issue?'),
+      ).toBe(true);
+
+      done();
+    });
+  });
 });
diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js
index d9e84e35f69805edf3088c9116c7bdfab8d3ac76..8b6e8b24f006af8d265b181bf8c58e3860abbbbf 100644
--- a/spec/javascripts/sidebar/mock_data.js
+++ b/spec/javascripts/sidebar/mock_data.js
@@ -1,7 +1,5 @@
-/* eslint-disable quote-props*/
-
 const RESPONSE_MAP = {
-  'GET': {
+  GET: {
     '/gitlab-org/gitlab-shell/issues/5.json': {
       id: 45,
       iid: 5,
@@ -27,7 +25,8 @@ const RESPONSE_MAP = {
           username: 'user0',
           id: 22,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
           web_url: 'http: //localhost:3001/user0',
         },
         {
@@ -35,7 +34,8 @@ const RESPONSE_MAP = {
           username: 'tajuana',
           id: 18,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
           web_url: 'http: //localhost:3001/tajuana',
         },
         {
@@ -43,7 +43,8 @@ const RESPONSE_MAP = {
           username: 'michaele.will',
           id: 16,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
           web_url: 'http: //localhost:3001/michaele.will',
         },
       ],
@@ -72,7 +73,8 @@ const RESPONSE_MAP = {
           username: 'user0',
           id: 22,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
           web_url: 'http://localhost:3001/user0',
         },
         {
@@ -80,7 +82,8 @@ const RESPONSE_MAP = {
           username: 'tajuana',
           id: 18,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
           web_url: 'http://localhost:3001/tajuana',
         },
         {
@@ -88,7 +91,8 @@ const RESPONSE_MAP = {
           username: 'michaele.will',
           id: 16,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
           web_url: 'http://localhost:3001/michaele.will',
         },
       ],
@@ -100,7 +104,8 @@ const RESPONSE_MAP = {
           username: 'user0',
           id: 22,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
           web_url: 'http://localhost:3001/user0',
         },
         {
@@ -108,7 +113,8 @@ const RESPONSE_MAP = {
           username: 'tajuana',
           id: 18,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
           web_url: 'http://localhost:3001/tajuana',
         },
         {
@@ -116,7 +122,8 @@ const RESPONSE_MAP = {
           username: 'michaele.will',
           id: 16,
           state: 'active',
-          avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+          avatar_url:
+            'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
           web_url: 'http://localhost:3001/michaele.will',
         },
       ],
@@ -126,20 +133,21 @@ const RESPONSE_MAP = {
     },
     '/autocomplete/projects?project_id=15': [
       {
-        'id': 0,
-        'name_with_namespace': 'No project',
-      }, {
-        'id': 20,
-        'name_with_namespace': 'foo / bar',
+        id: 0,
+        name_with_namespace: 'No project',
+      },
+      {
+        id: 20,
+        name_with_namespace: 'foo / bar',
       },
     ],
   },
-  'PUT': {
+  PUT: {
     '/gitlab-org/gitlab-shell/issues/5.json': {
       data: {},
     },
   },
-  'POST': {
+  POST: {
     '/gitlab-org/gitlab-shell/issues/5/move': {
       id: 123,
       iid: 5,
@@ -182,7 +190,8 @@ const mockData = {
       id: 1,
       name: 'Administrator',
       username: 'root',
-      avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+      avatar_url:
+        'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
     },
     rootPath: '/',
     fullPath: '/gitlab-org/gitlab-shell',
@@ -201,12 +210,14 @@ const mockData = {
   },
 };
 
-mockData.sidebarMockInterceptor = function (request, next) {
+mockData.sidebarMockInterceptor = function(request, next) {
   const body = this.responseMap[request.method.toUpperCase()][request.url];
 
-  next(request.respondWith(JSON.stringify(body), {
-    status: 200,
-  }));
+  next(
+    request.respondWith(JSON.stringify(body), {
+      status: 200,
+    }),
+  );
 }.bind(mockData);
 
 export default mockData;
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index 2fbb7268e0b2dfc46f6418bbd2263aee353769b5..ebaaa6e806b20cd35ae3aad39bd8e4798b54aec4 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -1,6 +1,6 @@
 import _ from 'underscore';
 import Vue from 'vue';
-import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees';
+import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees.vue';
 import SidebarMediator from '~/sidebar/sidebar_mediator';
 import SidebarService from '~/sidebar/services/sidebar_service';
 import SidebarStore from '~/sidebar/stores/sidebar_store';
diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
index 0da5d91e376d9f5d6bd05106ef83934c6a4c6476..d8e636cbdf01f54bc86152149dfd18981c25f99f 100644
--- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js
+++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import Vue from 'vue';
 import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index b1b03ef1e093252e298d0335a6cd2f55a5e4bc11..423432c9e5d8715c5a56b742275e1e21df672331 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -4,7 +4,7 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
 (() => {
   describe('SigninTabsMemoizer', () => {
     const fixtureTemplate = 'static/signin_tabs.html.raw';
-    const tabSelector = 'ul.nav-tabs';
+    const tabSelector = 'ul.new-session-tabs';
     const currentTabKey = 'current_signin_tab';
     let memo;
 
@@ -27,7 +27,7 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
     it('does nothing if no tab was previously selected', () => {
       createMemoizer();
 
-      expect(document.querySelector('li a.active').getAttribute('id')).toEqual('standard');
+      expect(document.querySelector(`${tabSelector} > li.active a`).getAttribute('href')).toEqual('#ldap');
     });
 
     it('shows last selected tab on boot', () => {
@@ -48,9 +48,9 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
     it('saves last selected tab on change', () => {
       createMemoizer();
 
-      document.getElementById('standard').click();
+      document.querySelector('a[href="#login-pane"]').click();
 
-      expect(memo.readData()).toEqual('#standard');
+      expect(memo.readData()).toEqual('#login-pane');
     });
 
     it('overrides last selected tab with hash tag when given', () => {
diff --git a/spec/javascripts/smart_interval_spec.js b/spec/javascripts/smart_interval_spec.js
index 7265e1b6cb56c00c30a381c1cfb0242246ed3253..a54219d58c223c21f46200066c61658cfe795087 100644
--- a/spec/javascripts/smart_interval_spec.js
+++ b/spec/javascripts/smart_interval_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'underscore';
 import SmartInterval from '~/smart_interval';
 
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index 763a15e710b5156ef0bca2e192d36039741d2d63..0d1fa680e001860e677b7535296dfaabc29aab5b 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -1,5 +1,6 @@
 /* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes */
 
+import $ from 'jquery';
 import syntaxHighlight from '~/syntax_highlight';
 
 describe('Syntax Highlighter', function() {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 1bcfdfe72b6043a1b962f137b683e73d4b868eca..14bff05e537a0b2c948ed107fdc2f17a220c320d 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -5,8 +5,12 @@ import '~/commons';
 
 import Vue from 'vue';
 import VueResource from 'vue-resource';
+import Translate from '~/vue_shared/translate';
 
 import { getDefaultAdapter } from '~/lib/utils/axios_utils';
+import { FIXTURES_PATH, TEST_HOST } from './test_constants';
+
+import customMatchers from './matchers';
 
 const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
 Vue.config.devtools = !isHeadlessChrome;
@@ -19,29 +23,33 @@ Vue.config.warnHandler = (msg, vm, trace) => {
 };
 
 let hasVueErrors = false;
-Vue.config.errorHandler = function (err) {
+Vue.config.errorHandler = function(err) {
   hasVueErrors = true;
   fail(err);
 };
 
 Vue.use(VueResource);
+Vue.use(Translate);
 
 // enable test fixtures
-jasmine.getFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
-jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
+jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
+jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
+
+beforeAll(() => jasmine.addMatchers(customMatchers));
 
 // globalize common libraries
 window.$ = window.jQuery = $;
 
 // stub expected globals
 window.gl = window.gl || {};
-window.gl.TEST_HOST = 'http://test.host';
+window.gl.TEST_HOST = TEST_HOST;
 window.gon = window.gon || {};
 window.gon.test_env = true;
+gon.relative_url_root = '';
 
 let hasUnhandledPromiseRejections = false;
 
-window.addEventListener('unhandledrejection', (event) => {
+window.addEventListener('unhandledrejection', event => {
   hasUnhandledPromiseRejections = true;
   console.error('Unhandled promise rejection:');
   console.error(event.reason.stack || event.reason);
@@ -64,15 +72,25 @@ beforeEach(() => {
 
 const axiosDefaultAdapter = getDefaultAdapter();
 
+let testFiles = process.env.TEST_FILES || [];
+if (testFiles.length > 0) {
+  testFiles = testFiles.map(path => path.replace(/^spec\/javascripts\//, '').replace(/\.js$/, ''));
+  console.log(`Running only tests matching: ${testFiles}`);
+} else {
+  console.log('Running all tests');
+}
+
 // render all of our tests
 const testsContext = require.context('.', true, /_spec$/);
-testsContext.keys().forEach(function (path) {
+testsContext.keys().forEach(function(path) {
   try {
-    testsContext(path);
+    if (testFiles.length === 0 || testFiles.some(p => path.includes(p))) {
+      testsContext(path);
+    }
   } catch (err) {
     console.error('[ERROR] Unable to load spec: ', path);
-    describe('Test bundle', function () {
-      it(`includes '${path}'`, function () {
+    describe('Test bundle', function() {
+      it(`includes '${path}'`, function() {
         expect(err).toBeNull();
       });
     });
@@ -80,7 +98,7 @@ testsContext.keys().forEach(function (path) {
 });
 
 describe('test errors', () => {
-  beforeAll((done) => {
+  beforeAll(done => {
     if (hasUnhandledPromiseRejections || hasVueWarnings || hasVueErrors) {
       setTimeout(done, 1000);
     } else {
@@ -144,18 +162,18 @@ if (process.env.BABEL_ENV === 'coverage') {
     './issue_show/index.js',
   ];
 
-  describe('Uncovered files', function () {
+  describe('Uncovered files', function() {
     const sourceFiles = require.context('~', true, /\.js$/);
 
     $.holdReady(true);
 
-    sourceFiles.keys().forEach(function (path) {
+    sourceFiles.keys().forEach(function(path) {
       // ignore if there is a matching spec file
       if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) {
         return;
       }
 
-      it(`includes '${path}'`, function () {
+      it(`includes '${path}'`, function() {
         try {
           sourceFiles(path);
         } catch (err) {
diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..df59195e9f63677db5f0698870d1dfdaf84cda81
--- /dev/null
+++ b/spec/javascripts/test_constants.js
@@ -0,0 +1,4 @@
+export const FIXTURES_PATH = '/base/spec/javascripts/fixtures';
+export const TEST_HOST = 'http://test.host';
+
+export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/one_white_pixel.png`;
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 35871dddf89bac1e1dcd55eafc462fa41f33f60d..898bbb3819b6bbddc642703fbf75756faf994de2 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import * as urlUtils from '~/lib/utils/url_utility';
 import Todos from '~/pages/dashboard/todos/index/todos';
 import '~/lib/utils/common_utils';
diff --git a/spec/javascripts/toggle_buttons_spec.js b/spec/javascripts/toggle_buttons_spec.js
index 205e396d68252e12632359c79254032bbc4e0b25..17d0b94ebe0e852b7f8dbe3f82677dd10892bcff 100644
--- a/spec/javascripts/toggle_buttons_spec.js
+++ b/spec/javascripts/toggle_buttons_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import setupToggleButtons from '~/toggle_buttons';
 import getSetTimeoutPromise from './helpers/set_timeout_promise_helper';
 
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index 4d15bcc4956ea231c5726e6a5ad4736887da54a0..39c47a5c06d768131d136feda2f616b023d5d05d 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import U2FAuthenticate from '~/u2f/authenticate';
 import 'vendor/u2f';
 import MockU2FDevice from './mock_u2f_device';
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index dbe89c2923c6a5092f661fde587e76ae76a0f24a..136b4cad737d1b50dc4a03451c4a668250352f8e 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import U2FRegister from '~/u2f/register';
 import 'vendor/u2f';
 import MockU2FDevice from './mock_u2f_device';
diff --git a/spec/javascripts/version_check_image_spec.js b/spec/javascripts/version_check_image_spec.js
index 9637bd0414a3d6098dbfd8760cd2aff5cba57080..5f963e8c11ea1fe5c19e15a221ba0b899af62a5e 100644
--- a/spec/javascripts/version_check_image_spec.js
+++ b/spec/javascripts/version_check_image_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import VersionCheckImage from '~/version_check_image';
 import ClassSpecHelper from './helpers/class_spec_helper';
 
diff --git a/spec/javascripts/visibility_select_spec.js b/spec/javascripts/visibility_select_spec.js
deleted file mode 100644
index 82714cb69bdb98906bd120e98a0328a5dda1fba2..0000000000000000000000000000000000000000
--- a/spec/javascripts/visibility_select_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import VisibilitySelect from '~/visibility_select';
-
-(() => {
-  describe('VisibilitySelect', function () {
-    const lockedElement = document.createElement('div');
-    lockedElement.dataset.helpBlock = 'lockedHelpBlock';
-
-    const checkedElement = document.createElement('div');
-    checkedElement.dataset.description = 'checkedDescription';
-
-    const mockElements = {
-      container: document.createElement('div'),
-      select: document.createElement('div'),
-      '.help-block': document.createElement('div'),
-      '.js-locked': lockedElement,
-      'option:checked': checkedElement,
-    };
-
-    beforeEach(function () {
-      spyOn(Element.prototype, 'querySelector').and.callFake(selector => mockElements[selector]);
-    });
-
-    describe('constructor', function () {
-      beforeEach(function () {
-        this.visibilitySelect = new VisibilitySelect(mockElements.container);
-      });
-
-      it('sets the container member', function () {
-        expect(this.visibilitySelect.container).toEqual(mockElements.container);
-      });
-
-      it('queries and sets the helpBlock member', function () {
-        expect(Element.prototype.querySelector).toHaveBeenCalledWith('.help-block');
-        expect(this.visibilitySelect.helpBlock).toEqual(mockElements['.help-block']);
-      });
-
-      it('queries and sets the select member', function () {
-        expect(Element.prototype.querySelector).toHaveBeenCalledWith('select');
-        expect(this.visibilitySelect.select).toEqual(mockElements.select);
-      });
-
-      describe('if there is no container element provided', function () {
-        it('throws an error', function () {
-          expect(() => new VisibilitySelect()).toThrowError('VisibilitySelect requires a container element as argument 1');
-        });
-      });
-    });
-
-    describe('init', function () {
-      describe('if there is a select', function () {
-        beforeEach(function () {
-          this.visibilitySelect = new VisibilitySelect(mockElements.container);
-        });
-
-        it('calls updateHelpText', function () {
-          spyOn(VisibilitySelect.prototype, 'updateHelpText');
-          this.visibilitySelect.init();
-          expect(this.visibilitySelect.updateHelpText).toHaveBeenCalled();
-        });
-
-        it('adds a change event listener', function () {
-          spyOn(this.visibilitySelect.select, 'addEventListener');
-          this.visibilitySelect.init();
-          expect(this.visibilitySelect.select.addEventListener.calls.argsFor(0)).toContain('change');
-        });
-      });
-
-      describe('if there is no select', function () {
-        beforeEach(function () {
-          mockElements.select = undefined;
-          this.visibilitySelect = new VisibilitySelect(mockElements.container);
-          this.visibilitySelect.init();
-        });
-
-        it('updates the helpBlock text to the locked `data-help-block` messaged', function () {
-          expect(this.visibilitySelect.helpBlock.textContent)
-            .toEqual(lockedElement.dataset.helpBlock);
-        });
-
-        afterEach(function () {
-          mockElements.select = document.createElement('div');
-        });
-      });
-    });
-
-    describe('updateHelpText', function () {
-      beforeEach(function () {
-        this.visibilitySelect = new VisibilitySelect(mockElements.container);
-        this.visibilitySelect.init();
-      });
-
-      it('updates the helpBlock text to the selected options `data-description`', function () {
-        expect(this.visibilitySelect.helpBlock.textContent)
-          .toEqual(checkedElement.dataset.description);
-      });
-    });
-  });
-})();
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ff8d54c029f3b9504dfd619466c9a163b53cd299
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -0,0 +1,172 @@
+import Vue from 'vue';
+import * as urlUtils from '~/lib/utils/url_utility';
+import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+
+const deploymentMockData = {
+  id: 15,
+  name: 'review/diplo',
+  url: '/root/acets-review-apps/environments/15',
+  stop_url: '/root/acets-review-apps/environments/15/stop',
+  metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+  metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+  external_url: 'http://diplo.',
+  external_url_formatted: 'diplo.',
+  deployed_at: '2017-03-22T22:44:42.258Z',
+  deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+};
+const createComponent = () => {
+  const Component = Vue.extend(deploymentComponent);
+
+  return new Component({
+    el: document.createElement('div'),
+    propsData: { deployment: { ...deploymentMockData } },
+  });
+};
+
+describe('Deployment component', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('computed', () => {
+    describe('deployTimeago', () => {
+      it('return formatted date', () => {
+        const readable = getTimeago().format(deploymentMockData.deployed_at);
+        expect(vm.deployTimeago).toEqual(readable);
+      });
+    });
+
+    describe('hasExternalUrls', () => {
+      it('should return true', () => {
+        expect(vm.hasExternalUrls).toEqual(true);
+      });
+
+      it('should return false when deployment has no external_url_formatted', () => {
+        vm.deployment.external_url_formatted = null;
+
+        expect(vm.hasExternalUrls).toEqual(false);
+      });
+
+      it('should return false when deployment has no external_url', () => {
+        vm.deployment.external_url = null;
+
+        expect(vm.hasExternalUrls).toEqual(false);
+      });
+    });
+
+    describe('hasDeploymentTime', () => {
+      it('should return true', () => {
+        expect(vm.hasDeploymentTime).toEqual(true);
+      });
+
+      it('should return false when deployment has no deployed_at', () => {
+        vm.deployment.deployed_at = null;
+
+        expect(vm.hasDeploymentTime).toEqual(false);
+      });
+
+      it('should return false when deployment has no deployed_at_formatted', () => {
+        vm.deployment.deployed_at_formatted = null;
+
+        expect(vm.hasDeploymentTime).toEqual(false);
+      });
+    });
+
+    describe('hasDeploymentMeta', () => {
+      it('should return true', () => {
+        expect(vm.hasDeploymentMeta).toEqual(true);
+      });
+
+      it('should return false when deployment has no url', () => {
+        vm.deployment.url = null;
+
+        expect(vm.hasDeploymentMeta).toEqual(false);
+      });
+
+      it('should return false when deployment has no name', () => {
+        vm.deployment.name = null;
+
+        expect(vm.hasDeploymentMeta).toEqual(false);
+      });
+    });
+  });
+
+  describe('methods', () => {
+    describe('stopEnvironment', () => {
+      const url = '/foo/bar';
+      const returnPromise = () => new Promise((resolve) => {
+        resolve({
+          data: {
+            redirect_url: url,
+          },
+        });
+      });
+      const mockStopEnvironment = () => {
+        vm.stopEnvironment(deploymentMockData);
+        return vm;
+      };
+
+      it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
+        spyOn(window, 'confirm').and.returnValue(true);
+        spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
+        spyOn(urlUtils, 'visitUrl').and.returnValue(true);
+        vm = mockStopEnvironment();
+
+        expect(window.confirm).toHaveBeenCalled();
+        expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
+        setTimeout(() => {
+          expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
+          done();
+        }, 333);
+      });
+
+      it('should show a confirm dialog but should not work if the dialog is rejected', () => {
+        spyOn(window, 'confirm').and.returnValue(false);
+        spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
+        vm = mockStopEnvironment();
+
+        expect(window.confirm).toHaveBeenCalled();
+        expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
+      });
+    });
+  });
+
+  describe('template', () => {
+    let el;
+
+    beforeEach(() => {
+      vm = createComponent(deploymentMockData);
+      el = vm.$el;
+    });
+
+    it('renders deployment name', () => {
+      expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deploymentMockData.url);
+      expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
+    });
+
+    it('renders external URL', () => {
+      expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deploymentMockData.external_url);
+      expect(el.querySelector('.js-deploy-url').innerText).toContain(deploymentMockData.external_url_formatted);
+    });
+
+    it('renders stop button', () => {
+      expect(el.querySelector('.btn')).not.toBeNull();
+    });
+
+    it('renders deployment time', () => {
+      expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
+    });
+
+    it('renders metrics component', () => {
+      expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
deleted file mode 100644
index 6a59dc3c87ec3c083ab0c66a910dd25175aea6d2..0000000000000000000000000000000000000000
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import { getTimeago } from '~/lib/utils/datetime_utility';
-
-const deploymentMockData = [
-  {
-    id: 15,
-    name: 'review/diplo',
-    url: '/root/acets-review-apps/environments/15',
-    stop_url: '/root/acets-review-apps/environments/15/stop',
-    metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
-    metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
-    external_url: 'http://diplo.',
-    external_url_formatted: 'diplo.',
-    deployed_at: '2017-03-22T22:44:42.258Z',
-    deployed_at_formatted: 'Mar 22, 2017 10:44pm',
-  },
-];
-const createComponent = () => {
-  const Component = Vue.extend(deploymentComponent);
-  const mr = {
-    deployments: deploymentMockData,
-  };
-  const service = {};
-
-  return new Component({
-    el: document.createElement('div'),
-    propsData: { mr, service },
-  });
-};
-
-describe('MRWidgetDeployment', () => {
-  describe('props', () => {
-    it('should have props', () => {
-      const { mr, service } = deploymentComponent.props;
-
-      expect(mr.type instanceof Object).toBeTruthy();
-      expect(mr.required).toBeTruthy();
-
-      expect(service.type instanceof Object).toBeTruthy();
-      expect(service.required).toBeTruthy();
-    });
-  });
-
-  describe('methods', () => {
-    let vm = createComponent();
-    const deployment = deploymentMockData[0];
-
-    describe('formatDate', () => {
-      it('should work', () => {
-        const readable = getTimeago().format(deployment.deployed_at);
-        expect(vm.formatDate(deployment.deployed_at)).toEqual(readable);
-      });
-    });
-
-    describe('hasExternalUrls', () => {
-      it('should return true', () => {
-        expect(vm.hasExternalUrls(deployment)).toBeTruthy();
-      });
-
-      it('should return false when there is not enough information', () => {
-        expect(vm.hasExternalUrls()).toBeFalsy();
-        expect(vm.hasExternalUrls({ external_url: 'Diplo' })).toBeFalsy();
-        expect(vm.hasExternalUrls({ external_url_formatted: 'Diplo' })).toBeFalsy();
-      });
-    });
-
-    describe('hasDeploymentTime', () => {
-      it('should return true', () => {
-        expect(vm.hasDeploymentTime(deployment)).toBeTruthy();
-      });
-
-      it('should return false when there is not enough information', () => {
-        expect(vm.hasDeploymentTime()).toBeFalsy();
-        expect(vm.hasDeploymentTime({ deployed_at: 'Diplo' })).toBeFalsy();
-        expect(vm.hasDeploymentTime({ deployed_at_formatted: 'Diplo' })).toBeFalsy();
-      });
-    });
-
-    describe('hasDeploymentMeta', () => {
-      it('should return true', () => {
-        expect(vm.hasDeploymentMeta(deployment)).toBeTruthy();
-      });
-
-      it('should return false when there is not enough information', () => {
-        expect(vm.hasDeploymentMeta()).toBeFalsy();
-        expect(vm.hasDeploymentMeta({ url: 'Diplo' })).toBeFalsy();
-        expect(vm.hasDeploymentMeta({ name: 'Diplo' })).toBeFalsy();
-      });
-    });
-
-    describe('stopEnvironment', () => {
-      const url = '/foo/bar';
-      const returnPromise = () => new Promise((resolve) => {
-        resolve({
-          data: {
-            redirect_url: url,
-          },
-        });
-      });
-      const mockStopEnvironment = () => {
-        vm.stopEnvironment(deploymentMockData);
-        return vm;
-      };
-
-      it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
-        spyOn(window, 'confirm').and.returnValue(true);
-        spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
-        spyOn(urlUtils, 'visitUrl').and.returnValue(true);
-        vm = mockStopEnvironment();
-
-        expect(window.confirm).toHaveBeenCalled();
-        expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
-        setTimeout(() => {
-          expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
-          done();
-        }, 333);
-      });
-
-      it('should show a confirm dialog but should not work if the dialog is rejected', () => {
-        spyOn(window, 'confirm').and.returnValue(false);
-        spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
-        vm = mockStopEnvironment();
-
-        expect(window.confirm).toHaveBeenCalled();
-        expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
-      });
-    });
-  });
-
-  describe('template', () => {
-    let vm;
-    let el;
-    const [deployment] = deploymentMockData;
-
-    beforeEach(() => {
-      vm = createComponent(deploymentMockData);
-      el = vm.$el;
-    });
-
-    it('should render template elements correctly', () => {
-      expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
-      expect(el.querySelector('.js-icon-link')).toBeDefined();
-      expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deployment.url);
-      expect(el.querySelector('.js-deploy-meta').innerText).toContain(deployment.name);
-      expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deployment.external_url);
-      expect(el.querySelector('.js-deploy-url').innerText).toContain(deployment.external_url_formatted);
-      expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.formatDate(deployment.deployed_at));
-      expect(el.querySelector('.js-mr-memory-usage')).toBeDefined();
-      expect(el.querySelector('button')).toBeDefined();
-    });
-
-    it('should list multiple deployments', (done) => {
-      vm.mr.deployments.push(deployment);
-      vm.mr.deployments.push(deployment);
-
-      Vue.nextTick(() => {
-        expect(el.querySelectorAll('.ci-widget').length).toEqual(3);
-        expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(3);
-        done();
-      });
-    });
-
-    it('should not have some elements when there is not enough data', (done) => {
-      vm.mr.deployments = [{}];
-
-      Vue.nextTick(() => {
-        expect(el.querySelectorAll('.js-deploy-meta').length).toEqual(0);
-        expect(el.querySelectorAll('.js-deploy-url').length).toEqual(0);
-        expect(el.querySelectorAll('.js-deploy-time').length).toEqual(0);
-        expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(0);
-        expect(el.querySelectorAll('.button').length).toEqual(0);
-        done();
-      });
-    });
-  });
-});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 235c33fac0d5ec452c4ab033b68b41512328f7c6..9b9c9656979c6ebe22fa53490a82033fea774eec 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -17,46 +17,58 @@ describe('MRWidgetHeader', () => {
   describe('computed', () => {
     describe('shouldShowCommitsBehindText', () => {
       it('return true when there are divergedCommitsCount', () => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 12,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
-          targetBranch: 'master',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 12,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+            targetBranch: 'master',
+            statusPath: 'abc',
+          },
+        });
 
         expect(vm.shouldShowCommitsBehindText).toEqual(true);
       });
 
       it('returns false where there are no divergedComits count', () => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 0,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
-          targetBranch: 'master',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 0,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+            targetBranch: 'master',
+            statusPath: 'abc',
+          },
+        });
         expect(vm.shouldShowCommitsBehindText).toEqual(false);
       });
     });
 
     describe('commitsText', () => {
       it('returns singular when there is one commit', () => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 1,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
-          targetBranch: 'master',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 1,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+            targetBranch: 'master',
+            statusPath: 'abc',
+          },
+        });
 
         expect(vm.commitsText).toEqual('1 commit behind');
       });
 
       it('returns plural when there is more than one commit', () => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 2,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
-          targetBranch: 'master',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 2,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+            targetBranch: 'master',
+            statusPath: 'abc',
+          },
+        });
 
         expect(vm.commitsText).toEqual('2 commits behind');
       });
@@ -66,24 +78,27 @@ describe('MRWidgetHeader', () => {
   describe('template', () => {
     describe('common elements', () => {
       beforeEach(() => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 12,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
-          sourceBranchRemoved: false,
-          targetBranchPath: 'foo/bar/commits-path',
-          targetBranchTreePath: 'foo/bar/tree/path',
-          targetBranch: 'master',
-          isOpen: true,
-          emailPatchesPath: '/mr/email-patches',
-          plainDiffPath: '/mr/plainDiffPath',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 12,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+            sourceBranchRemoved: false,
+            targetBranchPath: 'foo/bar/commits-path',
+            targetBranchTreePath: 'foo/bar/tree/path',
+            targetBranch: 'master',
+            isOpen: true,
+            emailPatchesPath: '/mr/email-patches',
+            plainDiffPath: '/mr/plainDiffPath',
+            statusPath: 'abc',
+          },
+        });
       });
 
       it('renders source branch link', () => {
-        expect(
-          vm.$el.querySelector('.js-source-branch').innerHTML,
-        ).toEqual('<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>');
+        expect(vm.$el.querySelector('.js-source-branch').innerHTML).toEqual(
+          '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+        );
       });
 
       it('renders clipboard button', () => {
@@ -101,18 +116,21 @@ describe('MRWidgetHeader', () => {
       });
 
       beforeEach(() => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 12,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
-          sourceBranchRemoved: false,
-          targetBranchPath: 'foo/bar/commits-path',
-          targetBranchTreePath: 'foo/bar/tree/path',
-          targetBranch: 'master',
-          isOpen: true,
-          emailPatchesPath: '/mr/email-patches',
-          plainDiffPath: '/mr/plainDiffPath',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 12,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+            sourceBranchRemoved: false,
+            targetBranchPath: 'foo/bar/commits-path',
+            targetBranchTreePath: 'foo/bar/tree/path',
+            targetBranch: 'master',
+            isOpen: true,
+            emailPatchesPath: '/mr/email-patches',
+            plainDiffPath: '/mr/plainDiffPath',
+            statusPath: 'abc',
+          },
+        });
       });
 
       it('renders checkout branch button with modal trigger', () => {
@@ -123,39 +141,49 @@ describe('MRWidgetHeader', () => {
         expect(button.getAttribute('data-toggle')).toEqual('modal');
       });
 
+      it('renders web ide button', () => {
+        const button = vm.$el.querySelector('.js-web-ide');
+
+        expect(button.textContent.trim()).toEqual('Web IDE');
+        expect(button.getAttribute('href')).toEqual('undefined/-/ide/projectabc');
+      });
+
       it('renders download dropdown with links', () => {
-        expect(
-          vm.$el.querySelector('.js-download-email-patches').textContent.trim(),
-        ).toEqual('Email patches');
+        expect(vm.$el.querySelector('.js-download-email-patches').textContent.trim()).toEqual(
+          'Email patches',
+        );
 
-        expect(
-          vm.$el.querySelector('.js-download-email-patches').getAttribute('href'),
-        ).toEqual('/mr/email-patches');
+        expect(vm.$el.querySelector('.js-download-email-patches').getAttribute('href')).toEqual(
+          '/mr/email-patches',
+        );
 
-        expect(
-          vm.$el.querySelector('.js-download-plain-diff').textContent.trim(),
-        ).toEqual('Plain diff');
+        expect(vm.$el.querySelector('.js-download-plain-diff').textContent.trim()).toEqual(
+          'Plain diff',
+        );
 
-        expect(
-          vm.$el.querySelector('.js-download-plain-diff').getAttribute('href'),
-        ).toEqual('/mr/plainDiffPath');
+        expect(vm.$el.querySelector('.js-download-plain-diff').getAttribute('href')).toEqual(
+          '/mr/plainDiffPath',
+        );
       });
     });
 
     describe('with a closed merge request', () => {
       beforeEach(() => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 12,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
-          sourceBranchRemoved: false,
-          targetBranchPath: 'foo/bar/commits-path',
-          targetBranchTreePath: 'foo/bar/tree/path',
-          targetBranch: 'master',
-          isOpen: false,
-          emailPatchesPath: '/mr/email-patches',
-          plainDiffPath: '/mr/plainDiffPath',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 12,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+            sourceBranchRemoved: false,
+            targetBranchPath: 'foo/bar/commits-path',
+            targetBranchTreePath: 'foo/bar/tree/path',
+            targetBranch: 'master',
+            isOpen: false,
+            emailPatchesPath: '/mr/email-patches',
+            plainDiffPath: '/mr/plainDiffPath',
+            statusPath: 'abc',
+          },
+        });
       });
 
       it('does not render checkout branch button with modal trigger', () => {
@@ -165,30 +193,29 @@ describe('MRWidgetHeader', () => {
       });
 
       it('does not render download dropdown with links', () => {
-        expect(
-          vm.$el.querySelector('.js-download-email-patches'),
-        ).toEqual(null);
+        expect(vm.$el.querySelector('.js-download-email-patches')).toEqual(null);
 
-        expect(
-          vm.$el.querySelector('.js-download-plain-diff'),
-        ).toEqual(null);
+        expect(vm.$el.querySelector('.js-download-plain-diff')).toEqual(null);
       });
     });
 
     describe('without diverged commits', () => {
       beforeEach(() => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 0,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
-          sourceBranchRemoved: false,
-          targetBranchPath: 'foo/bar/commits-path',
-          targetBranchTreePath: 'foo/bar/tree/path',
-          targetBranch: 'master',
-          isOpen: true,
-          emailPatchesPath: '/mr/email-patches',
-          plainDiffPath: '/mr/plainDiffPath',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 0,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+            sourceBranchRemoved: false,
+            targetBranchPath: 'foo/bar/commits-path',
+            targetBranchTreePath: 'foo/bar/tree/path',
+            targetBranch: 'master',
+            isOpen: true,
+            emailPatchesPath: '/mr/email-patches',
+            plainDiffPath: '/mr/plainDiffPath',
+            statusPath: 'abc',
+          },
+        });
       });
 
       it('does not render diverged commits info', () => {
@@ -198,22 +225,27 @@ describe('MRWidgetHeader', () => {
 
     describe('with diverged commits', () => {
       beforeEach(() => {
-        vm = mountComponent(Component, { mr: {
-          divergedCommitsCount: 12,
-          sourceBranch: 'mr-widget-refactor',
-          sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
-          sourceBranchRemoved: false,
-          targetBranchPath: 'foo/bar/commits-path',
-          targetBranchTreePath: 'foo/bar/tree/path',
-          targetBranch: 'master',
-          isOpen: true,
-          emailPatchesPath: '/mr/email-patches',
-          plainDiffPath: '/mr/plainDiffPath',
-        } });
+        vm = mountComponent(Component, {
+          mr: {
+            divergedCommitsCount: 12,
+            sourceBranch: 'mr-widget-refactor',
+            sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+            sourceBranchRemoved: false,
+            targetBranchPath: 'foo/bar/commits-path',
+            targetBranchTreePath: 'foo/bar/tree/path',
+            targetBranch: 'master',
+            isOpen: true,
+            emailPatchesPath: '/mr/email-patches',
+            plainDiffPath: '/mr/plainDiffPath',
+            statusPath: 'abc',
+          },
+        });
       });
 
       it('renders diverged commits info', () => {
-        expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual('(12 commits behind)');
+        expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual(
+          '(12 commits behind)',
+        );
       });
     });
   });
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..cee22d5342a2f422f07078c11d1ad172cc87b052
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js
@@ -0,0 +1,40 @@
+import Vue from 'vue';
+import maintainerEditComponent from '~/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('RWidgetMaintainerEdit', () => {
+  let Component;
+  let vm;
+
+  beforeEach(() => {
+    Component = Vue.extend(maintainerEditComponent);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('when a maintainer is allowed to edit', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, {
+        maintainerEditAllowed: true,
+      });
+    });
+
+    it('it renders the message', () => {
+      expect(vm.$el.textContent.trim()).toEqual('Allows edits from maintainers');
+    });
+  });
+
+  describe('when a maintainer is not allowed to edit', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, {
+        maintainerEditAllowed: false,
+      });
+    });
+
+    it('hides the message', () => {
+      expect(vm.$el.textContent.trim()).toEqual('');
+    });
+  });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 07ed7f7f53209053af052d0bc363dd639d442712..91e81a0675a7121f6b42743fb6450836ceced5f2 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import memoryUsageComponent from '~/vue_merge_request_widget/components/mr_widget_memory_usage';
+import MemoryUsage from '~/vue_merge_request_widget/components/memory_usage.vue';
 import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
 
 const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
@@ -23,9 +23,7 @@ const metricsMockData = {
     memory_values: [
       {
         metric: {},
-        values: [
-          [1493716685, '4.30859375'],
-        ],
+        values: [[1493716685, '4.30859375']],
       },
     ],
   },
@@ -34,7 +32,7 @@ const metricsMockData = {
 };
 
 const createComponent = () => {
-  const Component = Vue.extend(memoryUsageComponent);
+  const Component = Vue.extend(MemoryUsage);
 
   return new Component({
     el: document.createElement('div'),
@@ -53,7 +51,7 @@ const createComponent = () => {
 
 const messages = {
   loadingMetrics: 'Loading deployment statistics',
-  hasMetrics: 'Memory usage unchanged from 0MB to 0MB',
+  hasMetrics: 'Memory  usage is  unchanged  at 0MB',
   loadFailed: 'Failed to load deployment statistics',
   metricsUnavailable: 'Deployment statistics are not available currently',
 };
@@ -67,21 +65,9 @@ describe('MemoryUsage', () => {
     el = vm.$el;
   });
 
-  describe('props', () => {
-    it('should have props with defaults', () => {
-      const { metricsUrl } = memoryUsageComponent.props;
-      const MetricsUrlTypeClass = metricsUrl.type;
-
-      Vue.nextTick(() => {
-        expect(new MetricsUrlTypeClass() instanceof String).toBeTruthy();
-        expect(metricsUrl.required).toBeTruthy();
-      });
-    });
-  });
-
   describe('data', () => {
     it('should have default data', () => {
-      const data = memoryUsageComponent.data();
+      const data = MemoryUsage.data();
 
       expect(Array.isArray(data.memoryMetrics)).toBeTruthy();
       expect(data.memoryMetrics.length).toBe(0);
@@ -104,26 +90,26 @@ describe('MemoryUsage', () => {
   });
 
   describe('computed', () => {
-    describe('memoryChangeType', () => {
-      it('should return "increased" if memoryFrom value is less than memoryTo value', () => {
+    describe('memoryChangeMessage', () => {
+      it('should contain "increased" if memoryFrom value is less than memoryTo value', () => {
         vm.memoryFrom = 4.28;
         vm.memoryTo = 9.13;
 
-        expect(vm.memoryChangeType).toEqual('increased');
+        expect(vm.memoryChangeMessage.indexOf('increased')).not.toEqual('-1');
       });
 
-      it('should return "decreased" if memoryFrom value is less than memoryTo value', () => {
+      it('should contain "decreased" if memoryFrom value is less than memoryTo value', () => {
         vm.memoryFrom = 9.13;
         vm.memoryTo = 4.28;
 
-        expect(vm.memoryChangeType).toEqual('decreased');
+        expect(vm.memoryChangeMessage.indexOf('decreased')).not.toEqual('-1');
       });
 
-      it('should return "unchanged" if memoryFrom value equal to memoryTo value', () => {
+      it('should contain "unchanged" if memoryFrom value equal to memoryTo value', () => {
         vm.memoryFrom = 1;
         vm.memoryTo = 1;
 
-        expect(vm.memoryChangeType).toEqual('unchanged');
+        expect(vm.memoryChangeMessage.indexOf('unchanged')).not.toEqual('-1');
       });
     });
   });
@@ -142,7 +128,13 @@ describe('MemoryUsage', () => {
     describe('computeGraphData', () => {
       it('should populate sparkline graph', () => {
         vm.computeGraphData(metrics, deployment_time);
-        const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm;
+        const {
+          hasMetrics,
+          memoryMetrics,
+          deploymentTime,
+          memoryFrom,
+          memoryTo,
+        } = vm;
 
         expect(hasMetrics).toBeTruthy();
         expect(memoryMetrics.length > 0).toBeTruthy();
@@ -153,20 +145,26 @@ describe('MemoryUsage', () => {
     });
 
     describe('loadMetrics', () => {
-      const returnServicePromise = () => new Promise((resolve) => {
-        resolve({
-          data: metricsMockData,
+      const returnServicePromise = () =>
+        new Promise(resolve => {
+          resolve({
+            data: metricsMockData,
+          });
         });
-      });
 
-      it('should load metrics data using MRWidgetService', (done) => {
-        spyOn(MRWidgetService, 'fetchMetrics').and.returnValue(returnServicePromise(true));
+      it('should load metrics data using MRWidgetService', done => {
+        spyOn(MRWidgetService, 'fetchMetrics').and.returnValue(
+          returnServicePromise(true),
+        );
         spyOn(vm, 'computeGraphData');
 
         vm.loadMetrics();
         setTimeout(() => {
           expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url);
-          expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time);
+          expect(vm.computeGraphData).toHaveBeenCalledWith(
+            metrics,
+            deployment_time,
+          );
           done();
         }, 333);
       });
@@ -179,51 +177,67 @@ describe('MemoryUsage', () => {
       expect(el.querySelector('.js-usage-info')).toBeDefined();
     });
 
-    it('should show loading metrics message while metrics are being loaded', (done) => {
+    it('should show loading metrics message while metrics are being loaded', done => {
       vm.loadingMetrics = true;
       vm.hasMetrics = false;
       vm.loadFailed = false;
 
       Vue.nextTick(() => {
-        expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined();
-        expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined();
-        expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadingMetrics);
+        expect(
+          el.querySelector('.js-usage-info.usage-info-loading'),
+        ).toBeDefined();
+        expect(
+          el.querySelector('.js-usage-info .usage-info-load-spinner'),
+        ).toBeDefined();
+        expect(el.querySelector('.js-usage-info').innerText).toContain(
+          messages.loadingMetrics,
+        );
         done();
       });
     });
 
-    it('should show deployment memory usage when metrics are loaded', (done) => {
+    it('should show deployment memory usage when metrics are loaded', done => {
       vm.loadingMetrics = false;
       vm.hasMetrics = true;
       vm.loadFailed = false;
 
       Vue.nextTick(() => {
         expect(el.querySelector('.memory-graph-container')).toBeDefined();
-        expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics);
+        expect(el.querySelector('.js-usage-info').innerText).toContain(
+          messages.hasMetrics,
+        );
         done();
       });
     });
 
-    it('should show failure message when metrics loading failed', (done) => {
+    it('should show failure message when metrics loading failed', done => {
       vm.loadingMetrics = false;
       vm.hasMetrics = false;
       vm.loadFailed = true;
 
       Vue.nextTick(() => {
-        expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined();
-        expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed);
+        expect(
+          el.querySelector('.js-usage-info.usage-info-failed'),
+        ).toBeDefined();
+        expect(el.querySelector('.js-usage-info').innerText).toContain(
+          messages.loadFailed,
+        );
         done();
       });
     });
 
-    it('should show metrics unavailable message when metrics loading failed', (done) => {
+    it('should show metrics unavailable message when metrics loading failed', done => {
       vm.loadingMetrics = false;
       vm.hasMetrics = false;
       vm.loadFailed = false;
 
       Vue.nextTick(() => {
-        expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined();
-        expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable);
+        expect(
+          el.querySelector('.js-usage-info.usage-info-unavailable'),
+        ).toBeDefined();
+        expect(el.querySelector('.js-usage-info').innerText).toContain(
+          messages.metricsUnavailable,
+        );
         done();
       });
     });
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 431cb7f39136449cc1d3c15e89a73dfd88308113..ea8007d2029f7779d0a6042de0a8e5a9ead6c767 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
@@ -113,6 +113,46 @@ describe('MRWidgetPipeline', () => {
       });
     });
 
+    describe('without commit path', () => {
+      beforeEach(() => {
+        const mockCopy = Object.assign({}, mockData);
+        delete mockCopy.pipeline.commit;
+
+        vm = mountComponent(Component, {
+          pipeline: mockCopy.pipeline,
+          hasCi: true,
+          ciStatus: 'success',
+        });
+      });
+
+      it('should render pipeline ID', () => {
+        expect(
+          vm.$el.querySelector('.pipeline-id').textContent.trim(),
+        ).toEqual(`#${mockData.pipeline.id}`);
+      });
+
+      it('should render pipeline status', () => {
+        expect(
+          vm.$el.querySelector('.media-body').textContent.trim(),
+        ).toContain(mockData.pipeline.details.status.label);
+
+        expect(
+          vm.$el.querySelector('.js-commit-link'),
+        ).toBeNull();
+      });
+
+      it('should render pipeline graph', () => {
+        expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
+        expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(mockData.pipeline.details.stages.length);
+      });
+
+      it('should render coverage information', () => {
+        expect(
+          vm.$el.querySelector('.media-body').textContent,
+        ).toContain(`Coverage ${mockData.pipeline.coverage}`);
+      });
+    });
+
     describe('without coverage', () => {
       it('should not render a coverage', () => {
         const mockCopy = Object.assign({}, mockData);
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index 5323523abc0e4d0ec43fed32282b62fc141c0c49..3d05dbfa305673e6e723e47f739c929320f7fc0b 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,6 +1,7 @@
 import Vue from 'vue';
 import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
 import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { removeBreakLine } from 'spec/helpers/vue_component_helper';
 
 describe('MRWidgetConflicts', () => {
   let Component;
@@ -78,8 +79,9 @@ describe('MRWidgetConflicts', () => {
     });
 
     it('should tell you to rebase locally', () => {
-      expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('Fast-forward merge is not possible.');
-      expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('To merge this request, first rebase locally');
+      expect(
+        removeBreakLine(vm.$el.textContent).trim(),
+      ).toContain('Fast-forward merge is not possible. To merge this request, first rebase locally.');
     });
   });
 });
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
index dd907ad90153a178c59769de997a58b540df62d8..d47815a5b5a6c37f1ce70d0537eda06b32d994c6 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
@@ -1,5 +1,6 @@
 import Vue from 'vue';
 import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
 import eventHub from '~/vue_merge_request_widget/event_hub';
 import mountComponent from 'spec/helpers/vue_mount_component_helper';
 
@@ -25,12 +26,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
         targetBranchPath,
         targetBranch,
       },
-      service: {
-        cancelAutomaticMerge() {},
-        mergeResource: {
-          save() {},
-        },
-      },
+      service: new MRWidgetService({}),
     });
   });
 
@@ -90,18 +86,16 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
 
     describe('removeSourceBranch', () => {
       it('should set flag and call service then request main component to update the widget', (done) => {
-        spyOn(vm.service.mergeResource, 'save').and.returnValue(new Promise((resolve) => {
-          resolve({
-            data: {
-              status: 'merge_when_pipeline_succeeds',
-            },
-          });
+        spyOn(vm.service, 'merge').and.returnValue(Promise.resolve({
+          data: {
+            status: 'merge_when_pipeline_succeeds',
+          },
         }));
 
         vm.removeSourceBranch();
         setTimeout(() => {
           expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
-          expect(vm.service.mergeResource.save).toHaveBeenCalledWith({
+          expect(vm.service.merge).toHaveBeenCalledWith({
             sha,
             merge_when_pipeline_succeeds: true,
             should_remove_source_branch: true,
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
index a8a02fa6b66a061c32bf8ffd1acad44e65921bd6..2a762c9336e52f59f83662535fc759c011eecec5 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -1,9 +1,9 @@
 import Vue from 'vue';
-import nothingToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge';
+import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
 
-describe('MRWidgetNothingToMerge', () => {
+describe('NothingToMerge', () => {
   describe('template', () => {
-    const Component = Vue.extend(nothingToMergeComponent);
+    const Component = Vue.extend(NothingToMerge);
     const newBlobPath = '/foo';
     const vm = new Component({
       el: document.createElement('div'),
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
index baacbc03fb167244bd30262bdf174b1bcd92cc18..ab096a56918378c78e2b309b4a64ef1176941999 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
@@ -1,6 +1,7 @@
 import Vue from 'vue';
 import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
 import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { removeBreakLine } from 'spec/helpers/vue_component_helper';
 
 describe('MRWidgetPipelineBlocked', () => {
   let vm;
@@ -18,6 +19,8 @@ describe('MRWidgetPipelineBlocked', () => {
   });
 
   it('renders information text', () => {
-    expect(vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ')).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed');
+    expect(
+      removeBreakLine(vm.$el.textContent).trim(),
+    ).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed');
   });
 });
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
index 78bac1c61a5f6c272a9454eb2ddade457ec4ab32..5573d7c5c936bfdbad280746187dfb86606ee9f6 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
@@ -1,16 +1,19 @@
 import Vue from 'vue';
-import pipelineFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_failed';
+import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
+import { removeBreakLine } from 'spec/helpers/vue_component_helper';
 
-describe('MRWidgetPipelineFailed', () => {
+describe('PipelineFailed', () => {
   describe('template', () => {
-    const Component = Vue.extend(pipelineFailedComponent);
+    const Component = Vue.extend(PipelineFailed);
     const vm = new Component({
       el: document.createElement('div'),
     });
     it('should have correct elements', () => {
       expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
       expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
-      expect(vm.$el.innerText).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure');
+      expect(
+        removeBreakLine(vm.$el.innerText).trim(),
+      ).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure');
     });
   });
 });
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 073f26cc78f4ead1944bd120fb293087f5bfcf4f..300b7882d03545574d0ba9ca8b5bbdcb30739b94 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -1,12 +1,12 @@
 import Vue from 'vue';
-import readyToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_ready_to_merge';
+import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
 import eventHub from '~/vue_merge_request_widget/event_hub';
 import * as simplePoll from '~/lib/utils/simple_poll';
 
 const commitMessage = 'This is the commit message';
 const commitMessageWithDescription = 'This is the commit message description';
 const createComponent = (customConfig = {}) => {
-  const Component = Vue.extend(readyToMergeComponent);
+  const Component = Vue.extend(ReadyToMerge);
   const mr = {
     isPipelineActive: false,
     pipeline: null,
@@ -36,7 +36,7 @@ const createComponent = (customConfig = {}) => {
   });
 };
 
-describe('MRWidgetReadyToMerge', () => {
+describe('ReadyToMerge', () => {
   let vm;
 
   beforeEach(() => {
@@ -49,7 +49,7 @@ describe('MRWidgetReadyToMerge', () => {
 
   describe('props', () => {
     it('should have props', () => {
-      const { mr, service } = readyToMergeComponent.props;
+      const { mr, service } = ReadyToMerge.props;
 
       expect(mr.type instanceof Object).toBeTruthy();
       expect(mr.required).toBeTruthy();
@@ -517,13 +517,9 @@ describe('MRWidgetReadyToMerge', () => {
 
   describe('Remove source branch checkbox', () => {
     describe('when user can merge but cannot delete branch', () => {
-      it('isRemoveSourceBranchButtonDisabled should be true', () => {
-        expect(vm.isRemoveSourceBranchButtonDisabled).toBe(true);
-      });
-
       it('should be disabled in the rendered output', () => {
         const checkboxElement = vm.$el.querySelector('#remove-source-branch-input');
-        expect(checkboxElement.getAttribute('disabled')).toBe('disabled');
+        expect(checkboxElement).toBeNull();
       });
     });
 
@@ -540,7 +536,7 @@ describe('MRWidgetReadyToMerge', () => {
 
       it('should be enabled in rendered output', () => {
         const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input');
-        expect(checkboxElement.getAttribute('disabled')).toBeNull();
+        expect(checkboxElement).not.toBeNull();
       });
     });
   });
@@ -549,12 +545,12 @@ describe('MRWidgetReadyToMerge', () => {
     describe('when allowed to merge', () => {
       beforeEach(() => {
         vm = createComponent({
-          mr: { isMergeAllowed: true },
+          mr: { isMergeAllowed: true, canRemoveSourceBranch: true },
         });
       });
 
       it('shows remove source branch checkbox', () => {
-        expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeDefined();
+        expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).not.toBeNull();
       });
 
       it('shows modify commit message button', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
index 4c67504b642af7bac340d25c58215dbbe8e1f072..abf642c166aef6b8c2fcf7ac97bfab262228fcb3 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
@@ -1,16 +1,25 @@
 import Vue from 'vue';
-import shaMismatchComponent from '~/vue_merge_request_widget/components/states/mr_widget_sha_mismatch';
+import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { removeBreakLine } from 'spec/helpers/vue_component_helper';
 
-describe('MRWidgetSHAMismatch', () => {
-  describe('template', () => {
-    const Component = Vue.extend(shaMismatchComponent);
-    const vm = new Component({
-      el: document.createElement('div'),
-    });
-    it('should have correct elements', () => {
-      expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
-      expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
-      expect(vm.$el.innerText).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging');
-    });
+describe('ShaMismatch', () => {
+  let vm;
+
+  beforeEach(() => {
+    const Component = Vue.extend(ShaMismatch);
+    vm = mountComponent(Component);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('should render information message', () => {
+    expect(vm.$el.querySelector('button').disabled).toEqual(true);
+
+    expect(
+      removeBreakLine(vm.$el.textContent).trim(),
+    ).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging');
   });
 });
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
index fe87f1103544ecdf72e0d60c8526aebfcb92a2de..d797f1266df5be020386711f862817defa738821 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -1,47 +1,37 @@
 import Vue from 'vue';
-import unresolvedDiscussionsComponent from '~/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions';
+import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
 
-describe('MRWidgetUnresolvedDiscussions', () => {
-  describe('props', () => {
-    it('should have props', () => {
-      const { mr } = unresolvedDiscussionsComponent.props;
+describe('UnresolvedDiscussions', () => {
+  const Component = Vue.extend(UnresolvedDiscussions);
+  let vm;
 
-      expect(mr.type instanceof Object).toBeTruthy();
-      expect(mr.required).toBeTruthy();
-    });
+  afterEach(() => {
+    vm.$destroy();
   });
 
-  describe('template', () => {
-    let el;
-    let vm;
-    const path = 'foo/bar';
-
+  describe('with discussions path', () => {
     beforeEach(() => {
-      const Component = Vue.extend(unresolvedDiscussionsComponent);
-      const mr = {
-        createIssueToResolveDiscussionsPath: path,
-      };
-      vm = new Component({
-        el: document.createElement('div'),
-        propsData: { mr },
-      });
-      el = vm.$el;
+      vm = mountComponent(Component, { mr: {
+        createIssueToResolveDiscussionsPath: gl.TEST_HOST,
+      } });
     });
 
     it('should have correct elements', () => {
-      expect(el.classList.contains('mr-widget-body')).toBeTruthy();
-      expect(el.innerText).toContain('There are unresolved discussions. Please resolve these discussions');
-      expect(el.innerText).toContain('Create an issue to resolve them later');
-      expect(el.querySelector('.js-create-issue').getAttribute('href')).toEqual(path);
+      expect(vm.$el.innerText).toContain('There are unresolved discussions. Please resolve these discussions');
+      expect(vm.$el.innerText).toContain('Create an issue to resolve them later');
+      expect(vm.$el.querySelector('.js-create-issue').getAttribute('href')).toEqual(gl.TEST_HOST);
     });
+  });
 
-    it('should not show create issue button if user cannot create issue', (done) => {
-      vm.mr.createIssueToResolveDiscussionsPath = '';
+  describe('without discussions path', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, { mr: {} });
+    });
 
-      Vue.nextTick(() => {
-        expect(el.querySelector('.js-create-issue')).toEqual(null);
-        done();
-      });
+    it('should not show create issue link if user cannot create issue', () => {
+      expect(vm.$el.innerText).toContain('There are unresolved discussions. Please resolve these discussions');
+      expect(vm.$el.querySelector('.js-create-issue')).toEqual(null);
     });
   });
 });
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 3dd7530748429d189789e4af924865cdf1b6140e..3fc7663b9c2f0d36d2d2ad613d9db15c36e98500 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -1,213 +1,218 @@
-/* eslint-disable */
-
 export default {
-  "id": 132,
-  "iid": 22,
-  "assignee_id": null,
-  "author_id": 1,
-  "description": "",
-  "lock_version": null,
-  "milestone_id": null,
-  "position": 0,
-  "state": "merged",
-  "title": "Update README.md",
-  "updated_by_id": null,
-  "created_at": "2017-04-07T12:27:26.718Z",
-  "updated_at": "2017-04-07T15:39:25.852Z",
-  "time_estimate": 0,
-  "total_time_spent": 0,
-  "human_time_estimate": null,
-  "human_total_time_spent": null,
-  "in_progress_merge_commit_sha": null,
-  "merge_commit_sha": "53027d060246c8f47e4a9310fb332aa52f221775",
-  "merge_error": null,
-  "merge_params": {
-    "force_remove_source_branch": null
+  id: 132,
+  iid: 22,
+  assignee_id: null,
+  author_id: 1,
+  description: '',
+  lock_version: null,
+  milestone_id: null,
+  position: 0,
+  state: 'merged',
+  title: 'Update README.md',
+  updated_by_id: null,
+  created_at: '2017-04-07T12:27:26.718Z',
+  updated_at: '2017-04-07T15:39:25.852Z',
+  time_estimate: 0,
+  total_time_spent: 0,
+  human_time_estimate: null,
+  human_total_time_spent: null,
+  in_progress_merge_commit_sha: null,
+  merge_commit_sha: '53027d060246c8f47e4a9310fb332aa52f221775',
+  merge_error: null,
+  merge_params: {
+    force_remove_source_branch: null,
   },
-  "merge_status": "can_be_merged",
-  "merge_user_id": null,
-  "merge_when_pipeline_succeeds": false,
-  "source_branch": "daaaa",
-  "source_branch_link": "daaaa",
-  "source_project_id": 19,
-  "target_branch": "master",
-  "target_project_id": 19,
-  "metrics": {
-    "merged_by": {
-      "name": "Administrator",
-      "username": "root",
-      "id": 1,
-      "state": "active",
-      "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "http://localhost:3000/root"
+  merge_status: 'can_be_merged',
+  merge_user_id: null,
+  merge_when_pipeline_succeeds: false,
+  source_branch: 'daaaa',
+  source_branch_link: 'daaaa',
+  source_project_id: 19,
+  target_branch: 'master',
+  target_project_id: 19,
+  metrics: {
+    merged_by: {
+      name: 'Administrator',
+      username: 'root',
+      id: 1,
+      state: 'active',
+      avatar_url:
+        'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+      web_url: 'http://localhost:3000/root',
     },
-    "merged_at": "2017-04-07T15:39:25.696Z",
-    "closed_by": null,
-    "closed_at": null
+    merged_at: '2017-04-07T15:39:25.696Z',
+    closed_by: null,
+    closed_at: null,
   },
-  "author": {
-    "name": "Administrator",
-    "username": "root",
-    "id": 1,
-    "state": "active",
-    "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://localhost:3000/root"
+  author: {
+    name: 'Administrator',
+    username: 'root',
+    id: 1,
+    state: 'active',
+    avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+    web_url: 'http://localhost:3000/root',
   },
-  "merge_user": null,
-  "diff_head_sha": "104096c51715e12e7ae41f9333e9fa35b73f385d",
-  "diff_head_commit_short_id": "104096c5",
-  "merge_commit_message": "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
-  "pipeline": {
-    "id": 172,
-    "user": {
-      "name": "Administrator",
-      "username": "root",
-      "id": 1,
-      "state": "active",
-      "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "http://localhost:3000/root"
+  merge_user: null,
+  diff_head_sha: '104096c51715e12e7ae41f9333e9fa35b73f385d',
+  diff_head_commit_short_id: '104096c5',
+  merge_commit_message:
+    "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+  pipeline: {
+    id: 172,
+    user: {
+      name: 'Administrator',
+      username: 'root',
+      id: 1,
+      state: 'active',
+      avatar_url:
+        'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+      web_url: 'http://localhost:3000/root',
     },
-    "active": false,
-    "coverage": "92.16",
-    "path": "/root/acets-app/pipelines/172",
-    "details": {
-      "status": {
-        "icon": "icon_status_success",
-        "favicon": "favicon_status_success",
-        "text": "passed",
-        "label": "passed",
-        "group": "success",
-        "has_details": true,
-        "details_path": "/root/acets-app/pipelines/172"
+    active: false,
+    coverage: '92.16',
+    path: '/root/acets-app/pipelines/172',
+    details: {
+      status: {
+        icon: 'icon_status_success',
+        favicon: 'favicon_status_success',
+        text: 'passed',
+        label: 'passed',
+        group: 'success',
+        has_details: true,
+        details_path: '/root/acets-app/pipelines/172',
       },
-      "duration": null,
-      "finished_at": "2017-04-07T14:00:14.256Z",
-      "stages": [
+      duration: null,
+      finished_at: '2017-04-07T14:00:14.256Z',
+      stages: [
         {
-          "name": "build",
-          "title": "build: failed",
-          "status": {
-            "icon": "icon_status_failed",
-            "favicon": "favicon_status_failed",
-            "text": "failed",
-            "label": "failed",
-            "group": "failed",
-            "has_details": true,
-            "details_path": "/root/acets-app/pipelines/172#build"
+          name: 'build',
+          title: 'build: failed',
+          status: {
+            icon: 'icon_status_failed',
+            favicon: 'favicon_status_failed',
+            text: 'failed',
+            label: 'failed',
+            group: 'failed',
+            has_details: true,
+            details_path: '/root/acets-app/pipelines/172#build',
           },
-          "path": "/root/acets-app/pipelines/172#build",
-          "dropdown_path": "/root/acets-app/pipelines/172/stage.json?stage=build"
+          path: '/root/acets-app/pipelines/172#build',
+          dropdown_path: '/root/acets-app/pipelines/172/stage.json?stage=build',
         },
         {
-          "name": "review",
-          "title": "review: skipped",
-          "status": {
-            "icon": "icon_status_skipped",
-            "favicon": "favicon_status_skipped",
-            "text": "skipped",
-            "label": "skipped",
-            "group": "skipped",
-            "has_details": true,
-            "details_path": "/root/acets-app/pipelines/172#review"
+          name: 'review',
+          title: 'review: skipped',
+          status: {
+            icon: 'icon_status_skipped',
+            favicon: 'favicon_status_skipped',
+            text: 'skipped',
+            label: 'skipped',
+            group: 'skipped',
+            has_details: true,
+            details_path: '/root/acets-app/pipelines/172#review',
           },
-          "path": "/root/acets-app/pipelines/172#review",
-          "dropdown_path": "/root/acets-app/pipelines/172/stage.json?stage=review"
-        }
-      ],
-      "artifacts": [
-
+          path: '/root/acets-app/pipelines/172#review',
+          dropdown_path: '/root/acets-app/pipelines/172/stage.json?stage=review',
+        },
       ],
-      "manual_actions": [
+      artifacts: [],
+      manual_actions: [
         {
-          "name": "stop_review",
-          "path": "/root/acets-app/builds/1427/play",
-          "playable": false
-        }
-      ]
+          name: 'stop_review',
+          path: '/root/acets-app/builds/1427/play',
+          playable: false,
+        },
+      ],
     },
-    "flags": {
-      "latest": false,
-      "triggered": false,
-      "stuck": false,
-      "yaml_errors": false,
-      "retryable": true,
-      "cancelable": false
+    flags: {
+      latest: false,
+      triggered: false,
+      stuck: false,
+      yaml_errors: false,
+      retryable: true,
+      cancelable: false,
     },
-    "ref": {
-      "name": "daaaa",
-      "path": "/root/acets-app/tree/daaaa",
-      "tag": false,
-      "branch": true
+    ref: {
+      name: 'daaaa',
+      path: '/root/acets-app/tree/daaaa',
+      tag: false,
+      branch: true,
     },
-    "commit": {
-      "id": "104096c51715e12e7ae41f9333e9fa35b73f385d",
-      "short_id": "104096c5",
-      "title": "Update README.md",
-      "created_at": "2017-04-07T15:27:18.000+03:00",
-      "parent_ids": [
-        "2396536178668d8930c29d904e53bd4d06228b32"
-      ],
-      "message": "Update README.md",
-      "author_name": "Administrator",
-      "author_email": "admin@example.com",
-      "authored_date": "2017-04-07T15:27:18.000+03:00",
-      "committer_name": "Administrator",
-      "committer_email": "admin@example.com",
-      "committed_date": "2017-04-07T15:27:18.000+03:00",
-      "author": {
-        "name": "Administrator",
-        "username": "root",
-        "id": 1,
-        "state": "active",
-        "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-        "web_url": "http://localhost:3000/root"
+    commit: {
+      id: '104096c51715e12e7ae41f9333e9fa35b73f385d',
+      short_id: '104096c5',
+      title: 'Update README.md',
+      created_at: '2017-04-07T15:27:18.000+03:00',
+      parent_ids: ['2396536178668d8930c29d904e53bd4d06228b32'],
+      message: 'Update README.md',
+      author_name: 'Administrator',
+      author_email: 'admin@example.com',
+      authored_date: '2017-04-07T15:27:18.000+03:00',
+      committer_name: 'Administrator',
+      committer_email: 'admin@example.com',
+      committed_date: '2017-04-07T15:27:18.000+03:00',
+      author: {
+        name: 'Administrator',
+        username: 'root',
+        id: 1,
+        state: 'active',
+        avatar_url:
+          'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+        web_url: 'http://localhost:3000/root',
       },
-      "author_gravatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "commit_url": "http://localhost:3000/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d",
-      "commit_path": "/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d"
+      author_gravatar_url:
+        'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+      commit_url:
+        'http://localhost:3000/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
+      commit_path: '/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
     },
-    "retry_path": "/root/acets-app/pipelines/172/retry",
-    "created_at": "2017-04-07T12:27:19.520Z",
-    "updated_at": "2017-04-07T15:28:44.800Z"
+    retry_path: '/root/acets-app/pipelines/172/retry',
+    created_at: '2017-04-07T12:27:19.520Z',
+    updated_at: '2017-04-07T15:28:44.800Z',
   },
-  "work_in_progress": false,
-  "source_branch_exists": false,
-  "mergeable_discussions_state": true,
-  "conflicts_can_be_resolved_in_ui": false,
-  "branch_missing": true,
-  "commits_count": 1,
-  "has_conflicts": false,
-  "can_be_merged": true,
-  "has_ci": true,
-  "ci_status": "success",
-  "pipeline_status_path": "/root/acets-app/merge_requests/22/pipeline_status",
-  "issues_links": {
-    "closing": "",
-    "mentioned_but_not_closing": ""
+  work_in_progress: false,
+  source_branch_exists: false,
+  mergeable_discussions_state: true,
+  conflicts_can_be_resolved_in_ui: false,
+  branch_missing: true,
+  commits_count: 1,
+  has_conflicts: false,
+  can_be_merged: true,
+  has_ci: true,
+  ci_status: 'success',
+  pipeline_status_path: '/root/acets-app/merge_requests/22/pipeline_status',
+  issues_links: {
+    closing: '',
+    mentioned_but_not_closing: '',
   },
-  "current_user": {
-    "can_resolve_conflicts": true,
-    "can_remove_source_branch": false,
-    "can_revert_on_current_merge_request": true,
-    "can_cherry_pick_on_current_merge_request": true
+  current_user: {
+    can_resolve_conflicts: true,
+    can_remove_source_branch: false,
+    can_revert_on_current_merge_request: true,
+    can_cherry_pick_on_current_merge_request: true,
   },
-  "target_branch_path": "/root/acets-app/branches/master",
-  "source_branch_path": "/root/acets-app/branches/daaaa",
-  "conflict_resolution_ui_path": "/root/acets-app/merge_requests/22/conflicts",
-  "remove_wip_path": "/root/acets-app/merge_requests/22/remove_wip",
-  "cancel_merge_when_pipeline_succeeds_path": "/root/acets-app/merge_requests/22/cancel_merge_when_pipeline_succeeds",
-  "create_issue_to_resolve_discussions_path": "/root/acets-app/issues/new?merge_request_to_resolve_discussions_of=22",
-  "merge_path": "/root/acets-app/merge_requests/22/merge",
-  "cherry_pick_in_fork_path": "/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+revert+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1",
-  "revert_in_fork_path": "/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1",
-  "email_patches_path": "/root/acets-app/merge_requests/22.patch",
-  "plain_diff_path": "/root/acets-app/merge_requests/22.diff",
-  "status_path": "/root/acets-app/merge_requests/22.json",
-  "merge_check_path": "/root/acets-app/merge_requests/22/merge_check",
-  "ci_environments_status_url": "/root/acets-app/merge_requests/22/ci_environments_status",
-  "project_archived": false,
-  "merge_commit_message_with_description": "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
-  "diverged_commits_count": 0,
-  "only_allow_merge_if_pipeline_succeeds": false,
-  "commit_change_content_path": "/root/acets-app/merge_requests/22/commit_change_content"
-}
+  target_branch_path: '/root/acets-app/branches/master',
+  source_branch_path: '/root/acets-app/branches/daaaa',
+  conflict_resolution_ui_path: '/root/acets-app/merge_requests/22/conflicts',
+  remove_wip_path: '/root/acets-app/merge_requests/22/remove_wip',
+  cancel_merge_when_pipeline_succeeds_path:
+    '/root/acets-app/merge_requests/22/cancel_merge_when_pipeline_succeeds',
+  create_issue_to_resolve_discussions_path:
+    '/root/acets-app/issues/new?merge_request_to_resolve_discussions_of=22',
+  merge_path: '/root/acets-app/merge_requests/22/merge',
+  cherry_pick_in_fork_path:
+    '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+revert+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
+  revert_in_fork_path:
+    '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
+  email_patches_path: '/root/acets-app/merge_requests/22.patch',
+  plain_diff_path: '/root/acets-app/merge_requests/22.diff',
+  status_path: '/root/acets-app/merge_requests/22.json',
+  merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
+  ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
+  project_archived: false,
+  merge_commit_message_with_description:
+    "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+  diverged_commits_count: 0,
+  only_allow_merge_if_pipeline_succeeds: false,
+  commit_change_content_path: '/root/acets-app/merge_requests/22/commit_change_content',
+};
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 18ba34b55a5df3361239fa83a9e618cc1e89717a..e55c7649d4057d488f666f1e619a3c72d052b0f5 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -81,14 +81,46 @@ describe('mrWidgetOptions', () => {
       });
     });
 
-    describe('shouldRenderDeployments', () => {
-      it('should return false for the initial data', () => {
-        expect(vm.shouldRenderDeployments).toBeFalsy();
+    describe('shouldRenderSourceBranchRemovalStatus', () => {
+      beforeEach(() => {
+        vm.mr.state = 'readyToMerge';
+      });
+
+      it('should return true when cannot remove source branch and branch will be removed', () => {
+        vm.mr.canRemoveSourceBranch = false;
+        vm.mr.shouldRemoveSourceBranch = true;
+
+        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(true);
+      });
+
+      it('should return false when can remove source branch and branch will be removed', () => {
+        vm.mr.canRemoveSourceBranch = true;
+        vm.mr.shouldRemoveSourceBranch = true;
+
+        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+      });
+
+      it('should return false when cannot remove source branch and branch will not be removed', () => {
+        vm.mr.canRemoveSourceBranch = false;
+        vm.mr.shouldRemoveSourceBranch = false;
+
+        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
       });
 
-      it('should return true if there is deployments', () => {
-        vm.mr.deployments.push({}, {});
-        expect(vm.shouldRenderDeployments).toBeTruthy();
+      it('should return false when in merged state', () => {
+        vm.mr.canRemoveSourceBranch = false;
+        vm.mr.shouldRemoveSourceBranch = true;
+        vm.mr.state = 'merged';
+
+        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+      });
+
+      it('should return false when in nothing to merge state', () => {
+        vm.mr.canRemoveSourceBranch = false;
+        vm.mr.shouldRemoveSourceBranch = true;
+        vm.mr.state = 'nothingToMerge';
+
+        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
       });
     });
   });
@@ -146,16 +178,16 @@ describe('mrWidgetOptions', () => {
 
     describe('fetchDeployments', () => {
       it('should fetch deployments', (done) => {
-        spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ deployment: 1 }]));
+        spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
 
         vm.fetchDeployments();
 
         setTimeout(() => {
           expect(vm.service.fetchDeployments).toHaveBeenCalled();
           expect(vm.mr.deployments.length).toEqual(1);
-          expect(vm.mr.deployments[0].deployment).toEqual(1);
+          expect(vm.mr.deployments[0].id).toBe(1);
           done();
-        }, 333);
+        });
       });
     });
 
@@ -325,33 +357,6 @@ describe('mrWidgetOptions', () => {
     });
   });
 
-  describe('components', () => {
-    it('should register all components', () => {
-      const comps = mrWidgetOptions.components;
-      expect(comps['mr-widget-header']).toBeDefined();
-      expect(comps['mr-widget-merge-help']).toBeDefined();
-      expect(comps['mr-widget-pipeline']).toBeDefined();
-      expect(comps['mr-widget-deployment']).toBeDefined();
-      expect(comps['mr-widget-related-links']).toBeDefined();
-      expect(comps['mr-widget-merged']).toBeDefined();
-      expect(comps['mr-widget-closed']).toBeDefined();
-      expect(comps['mr-widget-merging']).toBeDefined();
-      expect(comps['mr-widget-failed-to-merge']).toBeDefined();
-      expect(comps['mr-widget-wip']).toBeDefined();
-      expect(comps['mr-widget-archived']).toBeDefined();
-      expect(comps['mr-widget-conflicts']).toBeDefined();
-      expect(comps['mr-widget-nothing-to-merge']).toBeDefined();
-      expect(comps['mr-widget-not-allowed']).toBeDefined();
-      expect(comps['mr-widget-missing-branch']).toBeDefined();
-      expect(comps['mr-widget-ready-to-merge']).toBeDefined();
-      expect(comps['mr-widget-checking']).toBeDefined();
-      expect(comps['mr-widget-unresolved-discussions']).toBeDefined();
-      expect(comps['mr-widget-pipeline-blocked']).toBeDefined();
-      expect(comps['mr-widget-pipeline-failed']).toBeDefined();
-      expect(comps['mr-widget-merge-when-pipeline-succeeds']).toBeDefined();
-    });
-  });
-
   describe('rendering relatedLinks', () => {
     beforeEach((done) => {
       vm.mr.relatedLinks = {
@@ -378,4 +383,66 @@ describe('mrWidgetOptions', () => {
       });
     });
   });
+
+  describe('rendering source branch removal status', () => {
+    it('renders when user cannot remove branch and branch should be removed', (done) => {
+      vm.mr.canRemoveSourceBranch = false;
+      vm.mr.shouldRemoveSourceBranch = true;
+      vm.mr.state = 'readyToMerge';
+
+      vm.$nextTick(() => {
+        const tooltip = vm.$el.querySelector('.fa-question-circle');
+
+        expect(vm.$el.textContent).toContain('Removes source branch');
+        expect(tooltip.getAttribute('data-original-title')).toBe(
+          'A user with write access to the source branch selected this option',
+        );
+
+        done();
+      });
+    });
+
+    it('does not render in merged state', (done) => {
+      vm.mr.canRemoveSourceBranch = false;
+      vm.mr.shouldRemoveSourceBranch = true;
+      vm.mr.state = 'merged';
+
+      vm.$nextTick(() => {
+        expect(vm.$el.textContent).toContain('The source branch has been removed');
+        expect(vm.$el.textContent).not.toContain('Removes source branch');
+
+        done();
+      });
+    });
+  });
+
+  describe('rendering deployments', () => {
+    const deploymentMockData = {
+      id: 15,
+      name: 'review/diplo',
+      url: '/root/acets-review-apps/environments/15',
+      stop_url: '/root/acets-review-apps/environments/15/stop',
+      metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+      metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+      external_url: 'http://diplo.',
+      external_url_formatted: 'diplo.',
+      deployed_at: '2017-03-22T22:44:42.258Z',
+      deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+    };
+
+    beforeEach((done) => {
+      vm.mr.deployments.push({
+        ...deploymentMockData,
+      }, {
+        ...deploymentMockData,
+        id: deploymentMockData.id + 1,
+      });
+
+      vm.$nextTick(done);
+    });
+
+    it('renders multiple deployments', () => {
+      expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
+    });
+  });
 });
diff --git a/spec/javascripts/vue_shared/components/callout_spec.js b/spec/javascripts/vue_shared/components/callout_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e62bd86f4ca324b469381878ff69251708ecbf64
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/callout_spec.js
@@ -0,0 +1,45 @@
+import Vue from 'vue';
+import callout from '~/vue_shared/components/callout.vue';
+import createComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('Callout Component', () => {
+  let CalloutComponent;
+  let vm;
+  const exampleMessage = 'This is a callout message!';
+
+  beforeEach(() => {
+    CalloutComponent = Vue.extend(callout);
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  it('should render the appropriate variant of callout', () => {
+    vm = createComponent(CalloutComponent, {
+      category: 'info',
+      message: exampleMessage,
+    });
+
+    expect(vm.$el.getAttribute('class')).toEqual('bs-callout bs-callout-info');
+
+    expect(vm.$el.tagName).toEqual('DIV');
+  });
+
+  it('should render accessibility attributes', () => {
+    vm = createComponent(CalloutComponent, {
+      message: exampleMessage,
+    });
+
+    expect(vm.$el.getAttribute('role')).toEqual('alert');
+    expect(vm.$el.getAttribute('aria-live')).toEqual('assertive');
+  });
+
+  it('should render the provided message', () => {
+    vm = createComponent(CalloutComponent, {
+      message: exampleMessage,
+    });
+
+    expect(vm.$el.innerHTML.trim()).toEqual(exampleMessage);
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
index d0fc10d69ea3af43b7e76e88a859cbb689bae0e1..f598b1afa745d1401ad9e7fa1e0f6b992f21c85c 100644
--- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js
+++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
@@ -10,6 +10,7 @@ describe('clipboard button', () => {
     vm = mountComponent(Component, {
       text: 'copy me',
       title: 'Copy this value into Clipboard!',
+      cssClass: 'btn-danger',
     });
   });
 
@@ -28,4 +29,8 @@ describe('clipboard button', () => {
     expect(vm.$el.getAttribute('data-placement')).toEqual('top');
     expect(vm.$el.getAttribute('data-container')).toEqual(null);
   });
+
+  it('should render provided classname', () => {
+    expect(vm.$el.classList).toContain('btn-danger');
+  });
 });
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index fdead87420928c97cfe4d351ff80840168c3c226..ed66361bfc3e4e20a11de973b099377eacb52ff6 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -1,5 +1,6 @@
 import Vue from 'vue';
 import commitComp from '~/vue_shared/components/commit.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
 
 describe('Commit component', () => {
   let props;
@@ -10,25 +11,28 @@ describe('Commit component', () => {
     CommitComponent = Vue.extend(commitComp);
   });
 
+  afterEach(() => {
+    component.$destroy();
+  });
+
   it('should render a fork icon if it does not represent a tag', () => {
-    component = new CommitComponent({
-      propsData: {
-        tag: false,
-        commitRef: {
-          name: 'master',
-          ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
-        },
-        commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
-        shortSha: 'b7836edd',
-        title: 'Commit message',
-        author: {
-          avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png',
-          web_url: 'https://gitlab.com/jschatz1',
-          path: '/jschatz1',
-          username: 'jschatz1',
-        },
+    component = mountComponent(CommitComponent, {
+      tag: false,
+      commitRef: {
+        name: 'master',
+        ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
       },
-    }).$mount();
+      commitUrl:
+        'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+      shortSha: 'b7836edd',
+      title: 'Commit message',
+      author: {
+        avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png',
+        web_url: 'https://gitlab.com/jschatz1',
+        path: '/jschatz1',
+        username: 'jschatz1',
+      },
+    });
 
     expect(component.$el.querySelector('.icon-container').children).toContain('svg');
   });
@@ -41,7 +45,8 @@ describe('Commit component', () => {
           name: 'master',
           ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
         },
-        commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+        commitUrl:
+          'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
         shortSha: 'b7836edd',
         title: 'Commit message',
         author: {
@@ -53,9 +58,7 @@ describe('Commit component', () => {
         commitIconSvg: '<svg></svg>',
       };
 
-      component = new CommitComponent({
-        propsData: props,
-      }).$mount();
+      component = mountComponent(CommitComponent, props);
     });
 
     it('should render a tag icon if it represents a tag', () => {
@@ -63,7 +66,9 @@ describe('Commit component', () => {
     });
 
     it('should render a link to the ref url', () => {
-      expect(component.$el.querySelector('.ref-name').getAttribute('href')).toEqual(props.commitRef.ref_url);
+      expect(component.$el.querySelector('.ref-name').getAttribute('href')).toEqual(
+        props.commitRef.ref_url,
+      );
     });
 
     it('should render the ref name', () => {
@@ -71,7 +76,9 @@ describe('Commit component', () => {
     });
 
     it('should render the commit short sha with a link to the commit url', () => {
-      expect(component.$el.querySelector('.commit-sha').getAttribute('href')).toEqual(props.commitUrl);
+      expect(component.$el.querySelector('.commit-sha').getAttribute('href')).toEqual(
+        props.commitUrl,
+      );
       expect(component.$el.querySelector('.commit-sha').textContent).toContain(props.shortSha);
     });
 
@@ -88,21 +95,25 @@ describe('Commit component', () => {
 
       it('Should render the author avatar with title and alt attributes', () => {
         expect(
-          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('data-original-title'),
+          component.$el
+            .querySelector('.commit-title .avatar-image-container img')
+            .getAttribute('data-original-title'),
         ).toContain(props.author.username);
         expect(
-          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'),
+          component.$el
+            .querySelector('.commit-title .avatar-image-container img')
+            .getAttribute('alt'),
         ).toContain(`${props.author.username}'s avatar`);
       });
     });
 
     it('should render the commit title', () => {
-      expect(
-        component.$el.querySelector('a.commit-row-message').getAttribute('href'),
-      ).toEqual(props.commitUrl);
-      expect(
-        component.$el.querySelector('a.commit-row-message').textContent,
-      ).toContain(props.title);
+      expect(component.$el.querySelector('a.commit-row-message').getAttribute('href')).toEqual(
+        props.commitUrl,
+      );
+      expect(component.$el.querySelector('a.commit-row-message').textContent).toContain(
+        props.title,
+      );
     });
   });
 
@@ -114,19 +125,18 @@ describe('Commit component', () => {
           name: 'master',
           ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
         },
-        commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+        commitUrl:
+          'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
         shortSha: 'b7836edd',
         title: null,
         author: {},
       };
 
-      component = new CommitComponent({
-        propsData: props,
-      }).$mount();
+      component = mountComponent(CommitComponent, props);
 
-      expect(
-        component.$el.querySelector('.commit-title span').textContent,
-      ).toContain('Cant find HEAD commit for this branch');
+      expect(component.$el.querySelector('.commit-title span').textContent).toContain(
+        "Can't find HEAD commit for this branch",
+      );
     });
   });
 });
diff --git a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..383f0cd29ea4fc981a4a85350923e5bc306b9560
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
@@ -0,0 +1,70 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import contentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('ContentViewer', () => {
+  let vm;
+  let mock;
+
+  function createComponent(props) {
+    const ContentViewer = Vue.extend(contentViewer);
+    vm = mountComponent(ContentViewer, props);
+  }
+
+  afterEach(() => {
+    vm.$destroy();
+    if (mock) mock.restore();
+  });
+
+  it('markdown preview renders + loads rendered markdown from server', done => {
+    mock = new MockAdapter(axios);
+    mock.onPost(`${gon.relative_url_root}/testproject/preview_markdown`).reply(200, {
+      body: '<b>testing</b>',
+    });
+
+    createComponent({
+      path: 'test.md',
+      content: '*  Test',
+      projectPath: 'testproject',
+    });
+
+    const previewContainer = vm.$el.querySelector('.md-previewer');
+
+    setTimeout(() => {
+      expect(previewContainer.textContent).toContain('testing');
+
+      done();
+    });
+  });
+
+  it('renders image preview', done => {
+    createComponent({
+      path: 'test.jpg',
+      fileSize: 1024,
+    });
+
+    setTimeout(() => {
+      expect(vm.$el.querySelector('.image_file img').getAttribute('src')).toBe('test.jpg');
+
+      done();
+    });
+  });
+
+  it('renders fallback download control', done => {
+    createComponent({
+      path: 'test.abc',
+      fileSize: 1024,
+    });
+
+    setTimeout(() => {
+      expect(vm.$el.querySelector('.file-info').textContent.trim()).toContain(
+        'test.abc (1.00 KiB)',
+      );
+      expect(vm.$el.querySelector('.btn.btn-default').textContent.trim()).toContain('Download');
+
+      done();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/deprecated_modal_spec.js b/spec/javascripts/vue_shared/components/deprecated_modal_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..59d4e549a910d2dbccd6135a937ff33e24ef68e9
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/deprecated_modal_spec.js
@@ -0,0 +1,67 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const modalComponent = Vue.extend(DeprecatedModal);
+
+describe('DeprecatedModal', () => {
+  let vm;
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('props', () => {
+    describe('without primaryButtonLabel', () => {
+      beforeEach(() => {
+        vm = mountComponent(modalComponent, {
+          primaryButtonLabel: null,
+        });
+      });
+
+      it('does not render a primary button', () => {
+        expect(vm.$el.querySelector('.js-primary-button')).toBeNull();
+      });
+    });
+
+    describe('with id', () => {
+      describe('does not render a primary button', () => {
+        beforeEach(() => {
+          vm = mountComponent(modalComponent, {
+            id: 'my-modal',
+          });
+        });
+
+        it('assigns the id to the modal', () => {
+          expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull();
+        });
+
+        it('does not show the modal immediately', () => {
+          expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show');
+        });
+
+        it('does not show a backdrop', () => {
+          expect(vm.$el.querySelector('modal-backdrop')).toBeNull();
+        });
+      });
+    });
+
+    it('works with data-toggle="modal"', (done) => {
+      setFixtures(`
+        <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
+        <div id="modal-container"></div>
+      `);
+
+      const modalContainer = document.getElementById('modal-container');
+      const modalButton = document.getElementById('modal-button');
+      vm = mountComponent(modalComponent, {
+        id: 'my-modal',
+      }, modalContainer);
+      const modalElement = vm.$el.querySelector('#my-modal');
+      $(modalElement).on('shown.bs.modal', () => done());
+
+      modalButton.click();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/gl_modal_spec.js
index 2805d9a7003609337b69122cc80d3217ac446dee..85cb1b90fc6eb5eb9cf42cebe59e6bde9e3ff02c 100644
--- a/spec/javascripts/vue_shared/components/gl_modal_spec.js
+++ b/spec/javascripts/vue_shared/components/gl_modal_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import GlModal from '~/vue_shared/components/gl_modal.vue';
 import mountComponent from 'spec/helpers/vue_mount_component_helper';
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 5f980bbf36c5aa5702728942249856cea2ea6849..690349754222d584cd9626581d4dfbc7fb13faf8 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import fieldComponent from '~/vue_shared/components/markdown/field.vue';
 
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
index edebd822295420d3ef13626fcf1ed3719b7498c7..02117638b63d5b9bf75e7fee9dfa646b7e70b609 100644
--- a/spec/javascripts/vue_shared/components/markdown/header_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js
@@ -1,10 +1,11 @@
 import Vue from 'vue';
+import $ from 'jquery';
 import headerComponent from '~/vue_shared/components/markdown/header.vue';
 
 describe('Markdown field header component', () => {
   let vm;
 
-  beforeEach((done) => {
+  beforeEach(done => {
     const Component = Vue.extend(headerComponent);
 
     vm = new Component({
@@ -17,24 +18,18 @@ describe('Markdown field header component', () => {
   });
 
   it('renders markdown buttons', () => {
-    expect(
-      vm.$el.querySelectorAll('.js-md').length,
-    ).toBe(7);
+    expect(vm.$el.querySelectorAll('.js-md').length).toBe(7);
   });
 
   it('renders `write` link as active when previewMarkdown is false', () => {
-    expect(
-      vm.$el.querySelector('li:nth-child(1)').classList.contains('active'),
-    ).toBeTruthy();
+    expect(vm.$el.querySelector('li:nth-child(1)').classList.contains('active')).toBeTruthy();
   });
 
-  it('renders `preview` link as active when previewMarkdown is true', (done) => {
+  it('renders `preview` link as active when previewMarkdown is true', done => {
     vm.previewMarkdown = true;
 
     Vue.nextTick(() => {
-      expect(
-        vm.$el.querySelector('li:nth-child(2)').classList.contains('active'),
-      ).toBeTruthy();
+      expect(vm.$el.querySelector('li:nth-child(2)').classList.contains('active')).toBeTruthy();
 
       done();
     });
@@ -52,16 +47,24 @@ describe('Markdown field header component', () => {
     expect(vm.$emit).toHaveBeenCalledWith('write-markdown');
   });
 
-  it('blurs preview link after click', (done) => {
+  it('does not emit toggle markdown event when triggered from another form', () => {
+    spyOn(vm, '$emit');
+
+    $(document).triggerHandler('markdown-preview:show', [
+      $('<form><textarea class="markdown-area"></textarea></textarea></form>'),
+    ]);
+
+    expect(vm.$emit).not.toHaveBeenCalled();
+  });
+
+  it('blurs preview link after click', done => {
     const link = vm.$el.querySelector('li:nth-child(2) a');
     spyOn(HTMLElement.prototype, 'blur');
 
     link.click();
 
     setTimeout(() => {
-      expect(
-        link.blur,
-      ).toHaveBeenCalled();
+      expect(link.blur).toHaveBeenCalled();
 
       done();
     });
diff --git a/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js b/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js
index 818ef0af3c29c99413d44d5dd43c4dc8b4955c51..3e708f865c8ff2fc2bf60b7b183861115f40900d 100644
--- a/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js
@@ -1,6 +1,6 @@
 import Vue from 'vue';
 import toolbar from '~/vue_shared/components/markdown/toolbar.vue';
-import mountComponent from '../../../helpers/vue_mount_component_helper';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
 
 describe('toolbar', () => {
   let vm;
diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js
index d46a3f2328e2f50c5629517524cb0d5f5e6ed19e..73a69df019e79d0945d386827610f2582542eb72 100644
--- a/spec/javascripts/vue_shared/components/memory_graph_spec.js
+++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js
@@ -1,12 +1,12 @@
 import Vue from 'vue';
-import memoryGraphComponent from '~/vue_shared/components/memory_graph';
+import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
 import { mockMetrics, mockMedian, mockMedianIndex } from './mock_data';
 
 const defaultHeight = '25';
 const defaultWidth = '100';
 
 const createComponent = () => {
-  const Component = Vue.extend(memoryGraphComponent);
+  const Component = Vue.extend(MemoryGraph);
 
   return new Component({
     el: document.createElement('div'),
@@ -32,29 +32,9 @@ describe('MemoryGraph', () => {
     el = vm.$el;
   });
 
-  describe('props', () => {
-    it('should have props with defaults', (done) => {
-      const { metrics, deploymentTime, width, height } = memoryGraphComponent.props;
-
-      Vue.nextTick(() => {
-        const typeClassMatcher = (propItem, expectedType) => {
-          const PropItemTypeClass = propItem.type;
-          expect(new PropItemTypeClass() instanceof expectedType).toBeTruthy();
-          expect(propItem.required).toBeTruthy();
-        };
-
-        typeClassMatcher(metrics, Array);
-        typeClassMatcher(deploymentTime, Number);
-        typeClassMatcher(width, String);
-        typeClassMatcher(height, String);
-        done();
-      });
-    });
-  });
-
   describe('data', () => {
     it('should have default data', () => {
-      const data = memoryGraphComponent.data();
+      const data = MemoryGraph.data();
       const dataValidator = (dataItem, expectedType, defaultVal) => {
         expect(typeof dataItem).toBe(expectedType);
         expect(dataItem).toBe(defaultVal);
diff --git a/spec/javascripts/vue_shared/components/mock_data.js b/spec/javascripts/vue_shared/components/mock_data.js
index 0d781bdca74751bd2ca90e7c3e0f589009822aab..15b56c58c332db73203250ee2ae6467924f4b87c 100644
--- a/spec/javascripts/vue_shared/components/mock_data.js
+++ b/spec/javascripts/vue_shared/components/mock_data.js
@@ -1,5 +1,3 @@
-/* eslint-disable */
-
 export const mockMetrics = [
   [1493716685, '4.30859375'],
   [1493716745, '4.30859375'],
diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js
deleted file mode 100644
index 8412df74f98e4004527f42175e6a302d8c332ddb..0000000000000000000000000000000000000000
--- a/spec/javascripts/vue_shared/components/modal_spec.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import Vue from 'vue';
-import modal from '~/vue_shared/components/modal.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const modalComponent = Vue.extend(modal);
-
-describe('Modal', () => {
-  let vm;
-
-  afterEach(() => {
-    vm.$destroy();
-  });
-
-  describe('props', () => {
-    describe('without primaryButtonLabel', () => {
-      beforeEach(() => {
-        vm = mountComponent(modalComponent, {
-          primaryButtonLabel: null,
-        });
-      });
-
-      it('does not render a primary button', () => {
-        expect(vm.$el.querySelector('.js-primary-button')).toBeNull();
-      });
-    });
-
-    describe('with id', () => {
-      describe('does not render a primary button', () => {
-        beforeEach(() => {
-          vm = mountComponent(modalComponent, {
-            id: 'my-modal',
-          });
-        });
-
-        it('assigns the id to the modal', () => {
-          expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull();
-        });
-
-        it('does not show the modal immediately', () => {
-          expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show');
-        });
-
-        it('does not show a backdrop', () => {
-          expect(vm.$el.querySelector('modal-backdrop')).toBeNull();
-        });
-      });
-    });
-
-    it('works with data-toggle="modal"', (done) => {
-      setFixtures(`
-        <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
-        <div id="modal-container"></div>
-      `);
-
-      const modalContainer = document.getElementById('modal-container');
-      const modalButton = document.getElementById('modal-button');
-      vm = mountComponent(modalComponent, {
-        id: 'my-modal',
-      }, modalContainer);
-      const modalElement = vm.$el.querySelector('#my-modal');
-      $(modalElement).on('shown.bs.modal', () => done());
-
-      modalButton.click();
-    });
-  });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6fe9515320447beeed8cdb5369366dace3935e32
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
@@ -0,0 +1,107 @@
+import Vue from 'vue';
+
+import LabelsSelect from '~/labels_select';
+import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+import { mockConfig, mockLabels } from './mock_data';
+
+const createComponent = (config = mockConfig) => {
+  const Component = Vue.extend(baseComponent);
+
+  return mountComponent(Component, config);
+};
+
+describe('BaseComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('computed', () => {
+    describe('hiddenInputName', () => {
+      it('returns correct string when showCreate prop is `true`', () => {
+        expect(vm.hiddenInputName).toBe('issue[label_names][]');
+      });
+
+      it('returns correct string when showCreate prop is `false`', () => {
+        const mockConfigNonEditable = Object.assign({}, mockConfig, { showCreate: false });
+        const vmNonEditable = createComponent(mockConfigNonEditable);
+        expect(vmNonEditable.hiddenInputName).toBe('label_id[]');
+        vmNonEditable.$destroy();
+      });
+    });
+
+    describe('createLabelTitle', () => {
+      it('returns `Create project label` when `isProject` prop is true', () => {
+        expect(vm.createLabelTitle).toBe('Create project label');
+      });
+
+      it('return `Create group label` when `isProject` prop is false', () => {
+        const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false });
+        const vmGroup = createComponent(mockConfigGroup);
+        expect(vmGroup.createLabelTitle).toBe('Create group label');
+        vmGroup.$destroy();
+      });
+    });
+
+    describe('manageLabelsTitle', () => {
+      it('returns `Manage project labels` when `isProject` prop is true', () => {
+        expect(vm.manageLabelsTitle).toBe('Manage project labels');
+      });
+
+      it('return `Manage group labels` when `isProject` prop is false', () => {
+        const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false });
+        const vmGroup = createComponent(mockConfigGroup);
+        expect(vmGroup.manageLabelsTitle).toBe('Manage group labels');
+        vmGroup.$destroy();
+      });
+    });
+  });
+
+  describe('methods', () => {
+    describe('handleClick', () => {
+      it('emits onLabelClick event with label and list of labels as params', () => {
+        spyOn(vm, '$emit');
+        vm.handleClick(mockLabels[0]);
+        expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]);
+      });
+    });
+  });
+
+  describe('mounted', () => {
+    it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => {
+      expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true);
+    });
+  });
+
+  describe('template', () => {
+    it('renders component container element with classes `block labels`', () => {
+      expect(vm.$el.classList.contains('block')).toBe(true);
+      expect(vm.$el.classList.contains('labels')).toBe(true);
+    });
+
+    it('renders `.selectbox` element', () => {
+      expect(vm.$el.querySelector('.selectbox')).not.toBeNull();
+      expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;');
+    });
+
+    it('renders `.dropdown` element', () => {
+      expect(vm.$el.querySelector('.dropdown')).not.toBeNull();
+    });
+
+    it('renders `.dropdown-menu` element', () => {
+      const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu');
+      expect(dropdownMenuEl).not.toBeNull();
+      expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull();
+      expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull();
+      expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f25c70db125c066bdb7232a4618d3ee7caf69bdc
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
@@ -0,0 +1,82 @@
+import Vue from 'vue';
+
+import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+import { mockConfig, mockLabels } from './mock_data';
+
+const componentConfig = Object.assign({}, mockConfig, {
+  fieldName: 'label_id[]',
+  labels: mockLabels,
+  showExtraOptions: false,
+});
+
+const createComponent = (config = componentConfig) => {
+  const Component = Vue.extend(dropdownButtonComponent);
+
+  return mountComponent(Component, config);
+};
+
+describe('DropdownButtonComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('computed', () => {
+    describe('dropdownToggleText', () => {
+      it('returns text as `Label` when `labels` prop is empty array', () => {
+        const mockEmptyLabels = Object.assign({}, componentConfig, { labels: [] });
+        const vmEmptyLabels = createComponent(mockEmptyLabels);
+        expect(vmEmptyLabels.dropdownToggleText).toBe('Label');
+        vmEmptyLabels.$destroy();
+      });
+
+      it('returns first label name with remaining label count when `labels` prop has more than one item', () => {
+        const mockMoreLabels = Object.assign({}, componentConfig, {
+          labels: mockLabels.concat(mockLabels),
+        });
+        const vmMoreLabels = createComponent(mockMoreLabels);
+        expect(vmMoreLabels.dropdownToggleText).toBe('Foo Label +1 more');
+        vmMoreLabels.$destroy();
+      });
+
+      it('returns first label name when `labels` prop has only one item present', () => {
+        expect(vm.dropdownToggleText).toBe('Foo Label');
+      });
+    });
+  });
+
+  describe('template', () => {
+    it('renders component container element of type `button`', () => {
+      expect(vm.$el.nodeName).toBe('BUTTON');
+    });
+
+    it('renders component container element with required data attributes', () => {
+      expect(vm.$el.dataset.abilityName).toBe(vm.abilityName);
+      expect(vm.$el.dataset.fieldName).toBe(vm.fieldName);
+      expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath);
+      expect(vm.$el.dataset.labels).toBe(vm.labelsPath);
+      expect(vm.$el.dataset.namespacePath).toBe(vm.namespace);
+      expect(vm.$el.dataset.showAny).not.toBeDefined();
+    });
+
+    it('renders dropdown toggle text element', () => {
+      const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text');
+      expect(dropdownToggleTextEl).not.toBeNull();
+      expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label');
+    });
+
+    it('renders dropdown button icon', () => {
+      const dropdownIconEl = vm.$el.querySelector('i.fa');
+      expect(dropdownIconEl).not.toBeNull();
+      expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce559fe0335234fc53b08f1cf1e9772eb6b99c02
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
@@ -0,0 +1,94 @@
+import Vue from 'vue';
+
+import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+import { mockSuggestedColors } from './mock_data';
+
+const createComponent = (headerTitle) => {
+  const Component = Vue.extend(dropdownCreateLabelComponent);
+
+  return mountComponent(Component, {
+    headerTitle,
+  });
+};
+
+describe('DropdownCreateLabelComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    gon.suggested_label_colors = mockSuggestedColors;
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('created', () => {
+    it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => {
+      expect(vm.suggestedColors.length).toBe(mockSuggestedColors.length);
+    });
+  });
+
+  describe('template', () => {
+    it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => {
+      expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true);
+    });
+
+    it('renders `Go back` button on component header', () => {
+      const backButtonEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-back');
+      expect(backButtonEl).not.toBe(null);
+      expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null);
+    });
+
+    it('renders component header element as `Create new label` when `headerTitle` prop is not provided', () => {
+      const headerEl = vm.$el.querySelector('.dropdown-title');
+      expect(headerEl.innerText.trim()).toContain('Create new label');
+    });
+
+    it('renders component header element with value of `headerTitle` prop', () => {
+      const headerTitle = 'Create project label';
+      const vmWithHeaderTitle = createComponent(headerTitle);
+      const headerEl = vmWithHeaderTitle.$el.querySelector('.dropdown-title');
+      expect(headerEl.innerText.trim()).toContain(headerTitle);
+      vmWithHeaderTitle.$destroy();
+    });
+
+    it('renders `Close` button on component header', () => {
+      const closeButtonEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-close');
+      expect(closeButtonEl).not.toBe(null);
+      expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null);
+    });
+
+    it('renders `Name new label` input element', () => {
+      expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null);
+      expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null);
+    });
+
+    it('renders suggested colors list elements', () => {
+      const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown');
+      expect(colorsListContainerEl).not.toBe(null);
+      expect(colorsListContainerEl.querySelectorAll('a').length).toBe(mockSuggestedColors.length);
+
+      const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0];
+      expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0]);
+      expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);');
+    });
+
+    it('renders color input element', () => {
+      expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null);
+      expect(vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview')).not.toBe(null);
+      expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null);
+    });
+
+    it('renders component action buttons', () => {
+      const createBtnEl = vm.$el.querySelector('button.js-new-label-btn');
+      const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn');
+      expect(createBtnEl).not.toBe(null);
+      expect(createBtnEl.innerText.trim()).toBe('Create');
+      expect(cancelBtnEl.innerText.trim()).toBe('Cancel');
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..debeab25bd6dea565ded5f436713160924e583ab
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+
+import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+import { mockConfig } from './mock_data';
+
+const createComponent = (
+  labelsWebUrl = mockConfig.labelsWebUrl,
+  createLabelTitle,
+  manageLabelsTitle,
+) => {
+  const Component = Vue.extend(dropdownFooterComponent);
+
+  return mountComponent(Component, {
+    labelsWebUrl,
+    createLabelTitle,
+    manageLabelsTitle,
+  });
+};
+
+describe('DropdownFooterComponent', () => {
+  const createLabelTitle = 'Create project label';
+  const manageLabelsTitle = 'Manage project labels';
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('template', () => {
+    it('renders link element with `Create new label` when `createLabelTitle` prop is not provided', () => {
+      const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
+      expect(createLabelEl).not.toBeNull();
+      expect(createLabelEl.innerText.trim()).toBe('Create new label');
+    });
+
+    it('renders link element with value of `createLabelTitle` prop', () => {
+      const vmWithCreateLabelTitle = createComponent(mockConfig.labelsWebUrl, createLabelTitle);
+      const createLabelEl = vmWithCreateLabelTitle.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
+      expect(createLabelEl.innerText.trim()).toBe(createLabelTitle);
+      vmWithCreateLabelTitle.$destroy();
+    });
+
+    it('renders link element with `Manage labels` when `manageLabelsTitle` prop is not provided', () => {
+      const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
+      expect(manageLabelsEl).not.toBeNull();
+      expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl);
+      expect(manageLabelsEl.innerText.trim()).toBe('Manage labels');
+    });
+
+    it('renders link element with value of `manageLabelsTitle` prop', () => {
+      const vmWithManageLabelsTitle = createComponent(
+        mockConfig.labelsWebUrl,
+        createLabelTitle,
+        manageLabelsTitle,
+      );
+      const manageLabelsEl = vmWithManageLabelsTitle.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
+      expect(manageLabelsEl.innerText.trim()).toBe(manageLabelsTitle);
+      vmWithManageLabelsTitle.$destroy();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..cdf234bb0c424896ec08d55541681baa307e0155
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+
+import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+  const Component = Vue.extend(dropdownHeaderComponent);
+
+  return mountComponent(Component);
+};
+
+describe('DropdownHeaderComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('template', () => {
+    it('renders header text element', () => {
+      const headerEl = vm.$el.querySelector('.dropdown-title span');
+      expect(headerEl.innerText.trim()).toBe('Assign labels');
+    });
+
+    it('renders `Close` button element', () => {
+      const closeBtnEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-close');
+      expect(closeBtnEl).not.toBeNull();
+      expect(closeBtnEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..88733922a59be8083957f4f28335511c8aa77d86
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+
+import dropdownHiddenInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+import { mockLabels } from './mock_data';
+
+const createComponent = (name = 'label_id[]', label = mockLabels[0]) => {
+  const Component = Vue.extend(dropdownHiddenInputComponent);
+
+  return mountComponent(Component, {
+    name,
+    label,
+  });
+};
+
+describe('DropdownHiddenInputComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('template', () => {
+    it('renders input element of type `hidden`', () => {
+      expect(vm.$el.nodeName).toBe('INPUT');
+      expect(vm.$el.getAttribute('type')).toBe('hidden');
+      expect(vm.$el.getAttribute('name')).toBe(vm.name);
+      expect(vm.$el.getAttribute('value')).toBe(`${vm.label.id}`);
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..57608d957e7fb4f0667dfd7187474375b2b43e14
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+
+import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+  const Component = Vue.extend(dropdownSearchInputComponent);
+
+  return mountComponent(Component);
+};
+
+describe('DropdownSearchInputComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('template', () => {
+    it('renders input element with type `search`', () => {
+      const inputEl = vm.$el.querySelector('input.dropdown-input-field');
+      expect(inputEl).not.toBeNull();
+      expect(inputEl.getAttribute('type')).toBe('search');
+    });
+
+    it('renders search icon element', () => {
+      expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
+    });
+
+    it('renders clear search icon element', () => {
+      expect(vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear')).not.toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..7c3d2711f65ed5fb58bbc561ff0cebce430b60be
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
@@ -0,0 +1,42 @@
+import Vue from 'vue';
+
+import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const createComponent = (canEdit = true) => {
+  const Component = Vue.extend(dropdownTitleComponent);
+
+  return mountComponent(Component, {
+    canEdit,
+  });
+};
+
+describe('DropdownTitleComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('template', () => {
+    it('renders title text', () => {
+      expect(vm.$el.classList.contains('title', 'hide-collapsed')).toBe(true);
+      expect(vm.$el.innerText.trim()).toContain('Labels');
+    });
+
+    it('renders spinner icon element', () => {
+      expect(vm.$el.querySelector('.fa-spinner.fa-spin.block-loading')).not.toBeNull();
+    });
+
+    it('renders `Edit` button element', () => {
+      const editBtnEl = vm.$el.querySelector('button.edit-link.js-sidebar-dropdown-toggle');
+      expect(editBtnEl).not.toBeNull();
+      expect(editBtnEl.innerText.trim()).toBe('Edit');
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..39040670a878f8fc1afea45ad27278b33789fd91
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
@@ -0,0 +1,74 @@
+import Vue from 'vue';
+
+import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+import { mockLabels } from './mock_data';
+
+const createComponent = (labels = mockLabels) => {
+  const Component = Vue.extend(dropdownValueCollapsedComponent);
+
+  return mountComponent(Component, {
+    labels,
+  });
+};
+
+describe('DropdownValueCollapsedComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('computed', () => {
+    describe('labelsList', () => {
+      it('returns empty text when `labels` prop is empty array', () => {
+        const vmEmptyLabels = createComponent([]);
+        expect(vmEmptyLabels.labelsList).toBe('');
+        vmEmptyLabels.$destroy();
+      });
+
+      it('returns labels names separated by coma when `labels` prop has more than one item', () => {
+        const vmMoreLabels = createComponent(mockLabels.concat(mockLabels));
+        expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label');
+        vmMoreLabels.$destroy();
+      });
+
+      it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => {
+        const mockMoreLabels = Object.assign([], mockLabels);
+        for (let i = 0; i < 6; i += 1) {
+          mockMoreLabels.unshift(mockLabels[0]);
+        }
+
+        const vmMoreLabels = createComponent(mockMoreLabels);
+        expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more');
+        vmMoreLabels.$destroy();
+      });
+
+      it('returns first label name when `labels` prop has only one item present', () => {
+        expect(vm.labelsList).toBe('Foo Label');
+      });
+    });
+  });
+
+  describe('template', () => {
+    it('renders component container element with tooltip`', () => {
+      expect(vm.$el.dataset.placement).toBe('left');
+      expect(vm.$el.dataset.container).toBe('body');
+      expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList);
+    });
+
+    it('renders tags icon element', () => {
+      expect(vm.$el.querySelector('.fa-tags')).not.toBeNull();
+    });
+
+    it('renders labels count', () => {
+      expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`);
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..4397b00acfae5221cd28cf896ec7209ed6cf7a1a
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
@@ -0,0 +1,94 @@
+import Vue from 'vue';
+
+import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+import { mockConfig, mockLabels } from './mock_data';
+
+const createComponent = (
+  labels = mockLabels,
+  labelFilterBasePath = mockConfig.labelFilterBasePath,
+) => {
+  const Component = Vue.extend(dropdownValueComponent);
+
+  return mountComponent(Component, {
+    labels,
+    labelFilterBasePath,
+  });
+};
+
+describe('DropdownValueComponent', () => {
+  let vm;
+
+  beforeEach(() => {
+    vm = createComponent();
+  });
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('computed', () => {
+    describe('isEmpty', () => {
+      it('returns true if `labels` prop is empty', () => {
+        const vmEmptyLabels = createComponent([]);
+        expect(vmEmptyLabels.isEmpty).toBe(true);
+        vmEmptyLabels.$destroy();
+      });
+
+      it('returns false if `labels` prop is empty', () => {
+        expect(vm.isEmpty).toBe(false);
+      });
+    });
+  });
+
+  describe('methods', () => {
+    describe('labelFilterUrl', () => {
+      it('returns URL string starting with labelFilterBasePath and encoded label.title', () => {
+        expect(vm.labelFilterUrl({
+          title: 'Foo bar',
+        })).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20bar');
+      });
+    });
+
+    describe('labelStyle', () => {
+      it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => {
+        const label = {
+          textColor: '#FFFFFF',
+          color: '#BADA55',
+        };
+        const styleObj = vm.labelStyle(label);
+
+        expect(styleObj.color).toBe(label.textColor);
+        expect(styleObj.backgroundColor).toBe(label.color);
+      });
+    });
+  });
+
+  describe('template', () => {
+    it('renders component container element with classes `hide-collapsed value issuable-show-labels`', () => {
+      expect(vm.$el.classList.contains('hide-collapsed', 'value', 'issuable-show-labels')).toBe(true);
+    });
+
+    it('render slot content inside component when `labels` prop is empty', () => {
+      const vmEmptyLabels = createComponent([]);
+      expect(vmEmptyLabels.$el.querySelector('.text-secondary').innerText.trim()).toBe(mockConfig.emptyValueText);
+      vmEmptyLabels.$destroy();
+    });
+
+    it('renders label element with filter URL', () => {
+      expect(vm.$el.querySelector('a').getAttribute('href')).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20Label');
+    });
+
+    it('renders label element with tooltip and styles based on label details', () => {
+      const labelEl = vm.$el.querySelector('a span.label.color-label');
+      expect(labelEl).not.toBeNull();
+      expect(labelEl.dataset.placement).toBe('bottom');
+      expect(labelEl.dataset.container).toBe('body');
+      expect(labelEl.dataset.originalTitle).toBe(mockLabels[0].description);
+      expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);');
+      expect(labelEl.innerText.trim()).toBe(mockLabels[0].title);
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..3fcb91b6f5ed4b43e35082b51809d6e6c78f28ad
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
@@ -0,0 +1,50 @@
+export const mockLabels = [
+  {
+    id: 26,
+    title: 'Foo Label',
+    description: 'Foobar',
+    color: '#BADA55',
+    text_color: '#FFFFFF',
+  },
+];
+
+export const mockSuggestedColors = [
+  '#0033CC',
+  '#428BCA',
+  '#44AD8E',
+  '#A8D695',
+  '#5CB85C',
+  '#69D100',
+  '#004E00',
+  '#34495E',
+  '#7F8C8D',
+  '#A295D6',
+  '#5843AD',
+  '#8E44AD',
+  '#FFECDB',
+  '#AD4363',
+  '#D10069',
+  '#CC0033',
+  '#FF0000',
+  '#D9534F',
+  '#D1D100',
+  '#F0AD4E',
+  '#AD8D43',
+];
+
+export const mockConfig = {
+  showCreate: true,
+  isProject: true,
+  abilityName: 'issue',
+  context: {
+    labels: mockLabels,
+  },
+  namespace: 'gitlab-org',
+  updatePath: '/gitlab-org/my-project/issue/1',
+  labelsPath: '/gitlab-org/my-project/labels.json',
+  labelsWebUrl: '/gitlab-org/my-project/labels',
+  labelFilterBasePath: '/gitlab-org/my-project/issues',
+  canEdit: true,
+  suggestedColors: mockSuggestedColors,
+  emptyValueText: 'None',
+};
diff --git a/spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js b/spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js
index bbd508630691f4136614cf2bbff06e5f1c252d45..34487885cf0765de3a1ae7f08f45e3304db43b35 100644
--- a/spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js
+++ b/spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js
@@ -14,8 +14,8 @@ describe('Skeleton loading container', () => {
     vm.$destroy();
   });
 
-  it('renders 6 skeleton lines by default', () => {
-    expect(vm.$el.querySelector('.skeleton-line-6')).not.toBeNull();
+  it('renders 3 skeleton lines by default', () => {
+    expect(vm.$el.querySelector('.skeleton-line-3')).not.toBeNull();
   });
 
   it('renders in full mode by default', () => {
diff --git a/spec/javascripts/vue_shared/directives/tooltip_spec.js b/spec/javascripts/vue_shared/directives/tooltip_spec.js
index b1b3071527b2240268543df8c16c7e06b8b54da2..4a644913e44ff86650f4b477c086f9506911a646 100644
--- a/spec/javascripts/vue_shared/directives/tooltip_spec.js
+++ b/spec/javascripts/vue_shared/directives/tooltip_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Vue from 'vue';
 import tooltip from '~/vue_shared/directives/tooltip';
 
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 8edba1f47a3df3b9bce635baed85a80d1e45241c..7fe3bd920491865e6dea2dac51b50a82ad605489 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import Mousetrap from 'mousetrap';
 import Dropzone from 'dropzone';
 import ZenMode from '~/zen_mode';
diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b918301f1cbeb5c7e502ec421e0b1adee7a8ca25
--- /dev/null
+++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe API::Helpers::RelatedResourcesHelpers do
+  subject(:helpers) do
+    Class.new.include(described_class).new
+  end
+
+  describe '#expose_url' do
+    let(:path) { '/api/v4/awesome_endpoint' }
+    subject(:url) { helpers.expose_url(path) }
+
+    def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil)
+      expect(Gitlab::Application.routes).to receive(:default_url_options)
+        .and_return(protocol: protocol, host: host, port: port)
+    end
+
+    it 'respects the protocol if it is HTTP' do
+      stub_default_url_options(protocol: 'http')
+
+      is_expected.to start_with('http://')
+    end
+
+    it 'respects the protocol if it is HTTPS' do
+      stub_default_url_options(protocol: 'https')
+
+      is_expected.to start_with('https://')
+    end
+
+    it 'accepts port to be nil' do
+      stub_default_url_options(port: nil)
+
+      is_expected.to start_with('http://example.com/')
+    end
+
+    it 'includes port if provided' do
+      stub_default_url_options(port: 8080)
+
+      is_expected.to start_with('http://example.com:8080/')
+    end
+  end
+end
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 3c4deba4712e53cf290ed3e334a5eeab4430b818..58a49124ce6fcc6c1651a1a2d1037f7c5a11869d 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -3,6 +3,48 @@ require 'spec_helper'
 describe API::Helpers do
   subject { Class.new.include(described_class).new }
 
+  describe '#find_project' do
+    let(:project) { create(:project) }
+
+    shared_examples 'project finder' do
+      context 'when project exists' do
+        it 'returns requested project' do
+          expect(subject.find_project(existing_id)).to eq(project)
+        end
+
+        it 'returns nil' do
+          expect(subject.find_project(non_existing_id)).to be_nil
+        end
+      end
+    end
+
+    context 'when ID is used as an argument' do
+      let(:existing_id) { project.id }
+      let(:non_existing_id) { (Project.maximum(:id) || 0) + 1 }
+
+      it_behaves_like 'project finder'
+    end
+
+    context 'when PATH is used as an argument' do
+      let(:existing_id) { project.full_path }
+      let(:non_existing_id) { 'something/else' }
+
+      it_behaves_like 'project finder'
+
+      context 'with an invalid PATH' do
+        let(:non_existing_id) { 'undefined' } # path without slash
+
+        it_behaves_like 'project finder'
+
+        it 'does not hit the database' do
+          expect(Project).not_to receive(:find_by_full_path)
+
+          subject.find_project(non_existing_id)
+        end
+      end
+    end
+  end
+
   describe '#find_namespace' do
     let(:namespace) { create(:namespace) }
 
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..14d055cbcc109b38ba1a1b0af5b633dbde0553ef
--- /dev/null
+++ b/spec/lib/backup/files_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Backup::Files do
+  let(:progress) { StringIO.new }
+  let!(:project) { create(:project) }
+
+  before do
+    allow(progress).to receive(:puts)
+    allow(progress).to receive(:print)
+    allow(FileUtils).to receive(:mkdir_p).and_return(true)
+    allow(FileUtils).to receive(:mv).and_return(true)
+    allow(File).to receive(:exist?).and_return(true)
+    allow(File).to receive(:realpath).with("/var/gitlab-registry").and_return("/var/gitlab-registry")
+    allow(File).to receive(:realpath).with("/var/gitlab-registry/..").and_return("/var")
+
+    allow_any_instance_of(String).to receive(:color) do |string, _color|
+      string
+    end
+
+    allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
+  end
+
+  describe '#restore' do
+    subject { described_class.new('registry', '/var/gitlab-registry') }
+    let(:timestamp) { Time.utc(2017, 3, 22) }
+
+    around do |example|
+      Timecop.freeze(timestamp) { example.run }
+    end
+
+    describe 'folders with permission' do
+      before do
+        allow(subject).to receive(:run_pipeline!).and_return(true)
+        allow(subject).to receive(:backup_existing_files).and_return(true)
+        allow(Dir).to receive(:glob).with("/var/gitlab-registry/*", File::FNM_DOTMATCH).and_return(["/var/gitlab-registry/.", "/var/gitlab-registry/..", "/var/gitlab-registry/sample1"])
+      end
+
+      it 'moves all necessary files' do
+        allow(subject).to receive(:backup_existing_files).and_call_original
+        expect(FileUtils).to receive(:mv).with(["/var/gitlab-registry/sample1"], File.join(Gitlab.config.backup.path, "tmp", "registry.#{Time.now.to_i}"))
+        subject.restore
+      end
+
+      it 'raises no errors' do
+        expect { subject.restore }.not_to raise_error
+      end
+
+      it 'calls tar command with unlink' do
+        expect(subject).to receive(:run_pipeline!).with([%w(gzip -cd), %w(tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -)], any_args)
+        subject.restore
+      end
+    end
+
+    describe 'folders without permissions' do
+      before do
+        allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
+        allow(subject).to receive(:run_pipeline!).and_return(true)
+      end
+
+      it 'shows error message' do
+        expect(subject).to receive(:access_denied_error).with("/var/gitlab-registry")
+        subject.restore
+      end
+    end
+  end
+end
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index 5100f5737c2d9832dffc119171cd818c9a66872f..84688845fa54e1e080926776676352b02e9ddc9b 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -278,6 +278,10 @@ describe Backup::Manager do
       connection.directories.create(key: Gitlab.config.backup.upload.remote_directory)
     end
 
+    after do
+      Fog.unmock!
+    end
+
     context 'target path' do
       it 'uses the tar filename by default' do
         expect_any_instance_of(Fog::Collection).to receive(:create)
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index f7b1a61f4f8912ae053388f06365a48c058ae405..e4c1c9bafc0e802e09319801495024568b3f93ad 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -7,6 +7,8 @@ describe Backup::Repository do
   before do
     allow(progress).to receive(:puts)
     allow(progress).to receive(:print)
+    allow(FileUtils).to receive(:mkdir_p).and_return(true)
+    allow(FileUtils).to receive(:mv).and_return(true)
 
     allow_any_instance_of(String).to receive(:color) do |string, _color|
       string
@@ -28,6 +30,23 @@ describe Backup::Repository do
   end
 
   describe '#restore' do
+    subject { described_class.new }
+
+    let(:timestamp) { Time.utc(2017, 3, 22) }
+    let(:temp_dirs) do
+      Gitlab.config.repositories.storages.map do |name, storage|
+        File.join(storage.legacy_disk_path, '..', 'repositories.old.' + timestamp.to_i.to_s)
+      end
+    end
+
+    around do |example|
+      Timecop.freeze(timestamp) { example.run }
+    end
+
+    after do
+      temp_dirs.each { |path| FileUtils.rm_rf(path) }
+    end
+
     describe 'command failure' do
       before do
         allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
@@ -35,7 +54,7 @@ describe Backup::Repository do
 
       context 'hashed storage' do
         it 'shows the appropriate error' do
-          described_class.new.restore
+          subject.restore
 
           expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} (#{project.disk_path}) - error")
         end
@@ -45,12 +64,23 @@ describe Backup::Repository do
         let!(:project) { create(:project, :legacy_storage) }
 
         it 'shows the appropriate error' do
-          described_class.new.restore
+          subject.restore
 
           expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
         end
       end
     end
+
+    describe 'folders without permissions' do
+      before do
+        allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
+      end
+
+      it 'shows error message' do
+        expect(subject).to receive(:access_denied_error)
+        subject.restore
+      end
+    end
   end
 
   describe '#empty_repo?' do
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index e7ebb2a332f6926c6cb78b10253165484db4686a..1f53657c59c45e58f7f22fa66e8f588ee6b00d46 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -6,7 +6,10 @@ describe Banzai::CommitRenderer do
       user = build(:user)
       project = create(:project, :repository)
 
-      expect(Banzai::ObjectRenderer).to receive(:new).with(project, user).and_call_original
+      expect(Banzai::ObjectRenderer)
+        .to receive(:new)
+        .with(user: user, default_project: project)
+        .and_call_original
 
       described_class::ATTRIBUTES.each do |attr|
         expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index 68ca960caabb84699149d0180eb7740c88efee82..aadfe7637dd4501cea8b596d2c3ea9c45c95626f 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -14,6 +14,16 @@ describe Banzai::CrossProjectReference do
       end
     end
 
+    context 'when no project was referenced in group context' do
+      it 'returns the group from context' do
+        group = double
+
+        allow(self).to receive(:context).and_return({ group: group })
+
+        expect(parent_from_ref(nil)).to eq group
+      end
+    end
+
     context 'when referenced project does not exist' do
       it 'returns nil' do
         expect(parent_from_ref('invalid/reference')).to be_nil
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index b7c2ff03125a3bf1a9784d6f273acdf9ac298c62..a50329473addab11298956e0a839b6d97fd9edb5 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -4,6 +4,7 @@ describe Banzai::Filter::AutolinkFilter do
   include FilterSpecHelper
 
   let(:link) { 'http://about.gitlab.com/' }
+  let(:quotes) { ['"', "'"] }
 
   it 'does nothing when :autolink is false' do
     exp = act = link
@@ -15,17 +16,7 @@ describe Banzai::Filter::AutolinkFilter do
     expect(filter(act).to_html).to eq exp
   end
 
-  context 'when the input contains no links' do
-    it 'does not parse_html back the rinku returned value' do
-      act = HTML::Pipeline.parse('<p>This text contains no links to autolink</p>')
-
-      expect_any_instance_of(described_class).not_to receive(:parse_html)
-
-      filter(act).to_html
-    end
-  end
-
-  context 'Rinku schemes' do
+  context 'Various schemes' do
     it 'autolinks http' do
       doc = filter("See #{link}")
       expect(doc.at_css('a').text).to eq link
@@ -56,32 +47,26 @@ describe Banzai::Filter::AutolinkFilter do
       expect(doc.at_css('a')['href']).to eq link
     end
 
-    it 'accepts link_attr options' do
-      doc = filter("See #{link}", link_attr: { class: 'custom' })
+    it 'autolinks multiple URLs' do
+      link1 = 'http://localhost:3000/'
+      link2 = 'http://google.com/'
 
-      expect(doc.at_css('a')['class']).to eq 'custom'
-    end
+      doc = filter("See #{link1} and #{link2}")
 
-    described_class::IGNORE_PARENTS.each do |elem|
-      it "ignores valid links contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>See #{link}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
+      found_links = doc.css('a')
 
-    context 'when the input contains link' do
-      it 'does parse_html back the rinku returned value' do
-        act = HTML::Pipeline.parse("<p>See #{link}</p>")
+      expect(found_links.size).to eq(2)
+      expect(found_links[0].text).to eq(link1)
+      expect(found_links[0]['href']).to eq(link1)
+      expect(found_links[1].text).to eq(link2)
+      expect(found_links[1]['href']).to eq(link2)
+    end
 
-        expect_any_instance_of(described_class).to receive(:parse_html).at_least(:once).and_call_original
+    it 'accepts link_attr options' do
+      doc = filter("See #{link}", link_attr: { class: 'custom' })
 
-        filter(act).to_html
-      end
+      expect(doc.at_css('a')['class']).to eq 'custom'
     end
-  end
-
-  context 'other schemes' do
-    let(:link) { 'foo://bar.baz/' }
 
     it 'autolinks smb' do
       link = 'smb:///Volumes/shared/foo.pdf'
@@ -91,6 +76,21 @@ describe Banzai::Filter::AutolinkFilter do
       expect(doc.at_css('a')['href']).to eq link
     end
 
+    it 'autolinks multiple occurences of smb' do
+      link1 = 'smb:///Volumes/shared/foo.pdf'
+      link2 = 'smb:///Volumes/shared/bar.pdf'
+
+      doc = filter("See #{link1} and #{link2}")
+
+      found_links = doc.css('a')
+
+      expect(found_links.size).to eq(2)
+      expect(found_links[0].text).to eq(link1)
+      expect(found_links[0]['href']).to eq(link1)
+      expect(found_links[1].text).to eq(link2)
+      expect(found_links[1]['href']).to eq(link2)
+    end
+
     it 'autolinks irc' do
       link = 'irc://irc.freenode.net/git'
       doc = filter("See #{link}")
@@ -122,14 +122,58 @@ describe Banzai::Filter::AutolinkFilter do
     end
 
     it 'does not include trailing punctuation' do
-      doc = filter("See #{link}.")
-      expect(doc.at_css('a').text).to eq link
+      ['.', ', ok?', '...', '?', '!', ': is that ok?'].each do |trailing_punctuation|
+        doc = filter("See #{link}#{trailing_punctuation}")
+        expect(doc.at_css('a').text).to eq link
+      end
+    end
 
-      doc = filter("See #{link}, ok?")
-      expect(doc.at_css('a').text).to eq link
+    it 'includes trailing punctuation when part of a balanced pair' do
+      described_class::PUNCTUATION_PAIRS.each do |close, open|
+        next if open.in?(quotes)
 
-      doc = filter("See #{link}...")
-      expect(doc.at_css('a').text).to eq link
+        balanced_link = "#{link}#{open}abc#{close}"
+        balanced_actual = filter("See #{balanced_link}...")
+        unbalanced_link = "#{link}#{close}"
+        unbalanced_actual = filter("See #{unbalanced_link}...")
+
+        expect(balanced_actual.at_css('a').text).to eq(balanced_link)
+        expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}..."))
+        expect(unbalanced_actual.at_css('a').text).to eq(link)
+        expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}..."))
+      end
+    end
+
+    it 'removes trailing quotes' do
+      quotes.each do |quote|
+        balanced_link = "#{link}#{quote}abc#{quote}"
+        balanced_actual = filter("See #{balanced_link}...")
+        unbalanced_link = "#{link}#{quote}"
+        unbalanced_actual = filter("See #{unbalanced_link}...")
+
+        expect(balanced_actual.at_css('a').text).to eq(balanced_link[0...-1])
+        expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}..."))
+        expect(unbalanced_actual.at_css('a').text).to eq(link)
+        expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}..."))
+      end
+    end
+
+    it 'removes one closing punctuation mark when the punctuation in the link is unbalanced' do
+      complicated_link = "(#{link}(a'b[c'd]))'"
+      expected_complicated_link = %Q{(<a href="#{link}(a'b[c'd]))">#{link}(a'b[c'd]))</a>'}
+      actual = unescape(filter(complicated_link).to_html)
+
+      expect(actual).to eq(Rinku.auto_link(complicated_link))
+      expect(actual).to eq(expected_complicated_link)
+    end
+
+    it 'does not double-encode HTML entities' do
+      encoded_link = "#{link}?foo=bar&amp;baz=quux"
+      expected_encoded_link = %Q{<a href="#{encoded_link}">#{encoded_link}</a>}
+      actual = unescape(filter(encoded_link).to_html)
+
+      expect(actual).to eq(Rinku.auto_link(encoded_link))
+      expect(actual).to eq(expected_encoded_link)
     end
 
     it 'does not include trailing HTML entities' do
@@ -151,4 +195,29 @@ describe Banzai::Filter::AutolinkFilter do
       end
     end
   end
+
+  context 'when the link is inside a tag' do
+    %w[http rdar].each do |protocol|
+      it "renders text after the link correctly for #{protocol}" do
+        doc = filter(ERB::Util.html_escape_once("<#{protocol}://link><another>"))
+
+        expect(doc.children.last.text).to include('<another>')
+      end
+    end
+  end
+
+  # Rinku does not escape these characters in HTML attributes, but content_tag
+  # does. We don't care about that difference for these specs, though.
+  def unescape(html)
+    %w([ ] { }).each do |cgi_escape|
+      html.sub!(CGI.escape(cgi_escape), cgi_escape)
+    end
+
+    quotes.each do |html_escape|
+      html.sub!(CGI.escape_html(html_escape), html_escape)
+      html.sub!(CGI.escape(html_escape), CGI.escape_html(html_escape))
+    end
+
+    html
+  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 a41a28a56f18c5b9ca6d9e2296b8216724c104d4..e1af5a1537132e009c21cdfce71a9594d34c6e08 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -233,4 +233,20 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
       expect(reference_filter(act).to_html).to eq exp
     end
   end
+
+  context 'group context' do
+    let(:context) { { project: nil, group: create(:group) } }
+
+    it 'ignores internal references' do
+      exp = act = "See #{range.to_reference}"
+
+      expect(reference_filter(act, context).to_html).to eq exp
+    end
+
+    it 'links to a full-path reference' do
+      reference = "#{project.full_path}@#{commit1.short_id}...#{commit2.short_id}"
+
+      expect(reference_filter("See #{reference}", context).css('a').first.text).to eql(reference)
+    end
+  end
 end
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 35f8792ff35d20095eaf740774d638565c1af3ee..d6c9e9e4b193d54e2ad28f5321f794a9cc41ed47 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -207,4 +207,51 @@ describe Banzai::Filter::CommitReferenceFilter do
       expect(reference_filter(act).to_html).to match(%r{<a.+>#{Regexp.escape(invalidate_reference(reference))}</a>})
     end
   end
+
+  context 'URL reference for a commit patch' do
+    let(:namespace) { create(:namespace) }
+    let(:project2)  { create(:project, :public, :repository, namespace: namespace) }
+    let(:commit)    { project2.commit }
+    let(:link)      { urls.project_commit_url(project2, commit.id) }
+    let(:extension) { '.patch' }
+    let(:reference) { link + extension }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href'))
+        .to eq reference
+    end
+
+    it 'has valid text' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.text).to eq("See #{commit.reference_link_text(project)} (patch)")
+    end
+
+    it 'does not link to patch when extension match is after the path' do
+      invalidate_commit_reference = reference_filter("#{link}/builds.patch")
+
+      doc = reference_filter("See (#{invalidate_commit_reference})")
+
+      expect(doc.css('a').first.attr('href')).to eq "#{link}/builds"
+      expect(doc.text).to eq("See (#{commit.reference_link_text(project)} (builds).patch)")
+    end
+  end
+
+  context 'group context' do
+    let(:context) { { project: nil, group: create(:group) } }
+
+    it 'ignores internal references' do
+      exp = act = "See #{commit.id}"
+
+      expect(reference_filter(act, context).to_html).to eq exp
+    end
+
+    it 'links to a valid reference' do
+      act = "See #{project.full_path}@#{commit.id}"
+
+      expect(reference_filter(act, context).css('a').first.text).to eql("#{project.full_path}@#{commit.short_id}")
+    end
+  end
 end
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1fd145116df0c434d327d7ed6b757e4ed1e1a9dc
--- /dev/null
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -0,0 +1,171 @@
+require 'spec_helper'
+require 'ffaker'
+
+describe Banzai::Filter::CommitTrailersFilter do
+  include FilterSpecHelper
+  include CommitTrailersSpecHelper
+
+  let(:secondary_email)     { create(:email, :confirmed) }
+  let(:user)                { create(:user) }
+
+  let(:trailer)             { "#{FFaker::Lorem.word}-by:"}
+
+  let(:commit_message)      { trailer_line(trailer, user.name, user.email) }
+  let(:commit_message_html) { commit_html(commit_message) }
+
+  context 'detects' do
+    let(:email) { FFaker::Internet.email }
+
+    it 'trailers in the form of *-by and replace users with links' do
+      doc = filter(commit_message_html)
+
+      expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
+    end
+
+    it 'trailers prefixed with whitespaces' do
+      message_html = commit_html("\n\r  #{commit_message}")
+
+      doc = filter(message_html)
+
+      expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
+    end
+
+    it 'GitLab users via a secondary email' do
+      _, message_html = build_commit_message(
+        trailer: trailer,
+        name: secondary_email.user.name,
+        email: secondary_email.email
+      )
+
+      doc = filter(message_html)
+
+      expect_to_have_user_link_with_avatar(
+        doc,
+        user: secondary_email.user,
+        trailer: trailer,
+        email: secondary_email.email
+      )
+    end
+
+    it 'non GitLab users and replaces them with mailto links' do
+      _, message_html = build_commit_message(
+        trailer: trailer,
+        name: FFaker::Name.name,
+        email: email
+      )
+
+      doc = filter(message_html)
+
+      expect_to_have_mailto_link(doc, email: email, trailer: trailer)
+    end
+
+    it 'multiple trailers in the same message' do
+      different_trailer = "#{FFaker::Lorem.word}-by:"
+      message = commit_html %(
+        #{commit_message}
+        #{trailer_line(different_trailer, FFaker::Name.name, email)}
+      )
+
+      doc = filter(message)
+
+      expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
+      expect_to_have_mailto_link(doc, email: email, trailer: different_trailer)
+    end
+
+    context 'special names' do
+      where(:name) do
+        [
+          'John S. Doe',
+          'L33t H@x0r'
+        ]
+      end
+
+      with_them do
+        it do
+          message, message_html = build_commit_message(
+            trailer: trailer,
+            name: name,
+            email: email
+          )
+
+          doc = filter(message_html)
+
+          expect_to_have_mailto_link(doc, email: email, trailer: trailer)
+          expect(doc.text).to match Regexp.escape(message)
+        end
+      end
+    end
+  end
+
+  context "ignores" do
+    it 'commit messages without trailers' do
+      exp = message = commit_html(FFaker::Lorem.sentence)
+      doc = filter(message)
+
+      expect(doc.to_html).to match Regexp.escape(exp)
+    end
+
+    it 'trailers that are inline the commit message body' do
+      message = commit_html %(
+        #{FFaker::Lorem.sentence} #{commit_message} #{FFaker::Lorem.sentence}
+      )
+
+      doc = filter(message)
+
+      expect(doc.css('a').size).to eq 0
+    end
+  end
+
+  context "structure" do
+    it 'preserves the commit trailer structure' do
+      doc = filter(commit_message_html)
+
+      expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
+      expect(doc.text).to match Regexp.escape(commit_message)
+    end
+
+    it 'preserves the original name used in the commit message' do
+      message, message_html = build_commit_message(
+        trailer: trailer,
+        name: FFaker::Name.name,
+        email: user.email
+      )
+
+      doc = filter(message_html)
+
+      expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
+      expect(doc.text).to match Regexp.escape(message)
+    end
+
+    it 'preserves the original email used in the commit message' do
+      message, message_html = build_commit_message(
+        trailer: trailer,
+        name: secondary_email.user.name,
+        email: secondary_email.email
+      )
+
+      doc = filter(message_html)
+
+      expect_to_have_user_link_with_avatar(
+        doc,
+        user: secondary_email.user,
+        trailer: trailer,
+        email: secondary_email.email
+      )
+      expect(doc.text).to match Regexp.escape(message)
+    end
+
+    it 'only replaces trailer lines not the full commit message' do
+      commit_body = FFaker::Lorem.paragraph
+      message = commit_html %(
+        #{commit_body}
+        #{commit_message}
+      )
+
+      doc = filter(message)
+
+      expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
+      expect(doc.text).to include(commit_body)
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index 17347768a49ef80609a72b9661fce75872946d93..a5373517ac8466e0da5ca934fb0ee1e23a8a4830 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::Filter::IssuableStateFilter do
   let(:context) { { current_user: user, issuable_state_filter_enabled: true } }
   let(:closed_issue) { create_issue(:closed) }
   let(:project) { create(:project, :public) }
+  let(:group) { create(:group) }
   let(:other_project) { create(:project, :public) }
 
   def create_link(text, data)
@@ -77,6 +78,13 @@ describe Banzai::Filter::IssuableStateFilter do
     expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference(other_project)} (closed)")
   end
 
+  it 'handles references from group scopes' do
+    link = create_link(closed_issue.to_reference(other_project), issue: closed_issue.id, reference_type: 'issue')
+    doc = filter(link, context.merge(project: nil, group: group))
+
+    expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference(other_project)} (closed)")
+  end
+
   it 'skips cross project references if the user cannot read cross project' do
     expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
     link = create_link(closed_issue.to_reference(other_project), issue: closed_issue.id, reference_type: 'issue')
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 862b1fe3fd3d8e4d2f785927f7bc5b7c7ea93eda..392905076dc5e3c7da5f5a8347a62082736b6de4 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -381,11 +381,11 @@ describe Banzai::Filter::LabelReferenceFilter do
     end
 
     it 'has valid link text' do
-      expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name_with_namespace}"
+      expect(result.css('a').first.text).to eq "#{label.name} in #{project2.full_name}"
     end
 
     it 'has valid text' do
-      expect(result.text).to eq "See #{label.name} in #{project2.name_with_namespace}"
+      expect(result.text).to eq "See #{label.name} in #{project2.full_name}"
     end
 
     it 'ignores invalid IDs on the referenced label' do
@@ -481,12 +481,12 @@ describe Banzai::Filter::LabelReferenceFilter do
 
     it 'has valid link text' do
       expect(result.css('a').first.text)
-        .to eq "#{group_label.name} in #{another_project.name_with_namespace}"
+        .to eq "#{group_label.name} in #{another_project.full_name}"
     end
 
     it 'has valid text' do
       expect(result.text)
-        .to eq "See #{group_label.name} in #{another_project.name_with_namespace}"
+        .to eq "See #{group_label.name} in #{another_project.full_name}"
     end
 
     it 'ignores invalid IDs on the referenced label' do
@@ -596,6 +596,27 @@ describe Banzai::Filter::LabelReferenceFilter do
   end
 
   describe 'group context' do
+    it 'points to the page defined in label_url_method' do
+      group = create(:group)
+      label = create(:group_label, group: group)
+      reference = "~#{label.name}"
+
+      result = reference_filter("See #{reference}", { project: nil, group: group, label_url_method: :group_url } )
+
+      expect(result.css('a').first.attr('href')).to eq(urls.group_url(group, label_name: label.name))
+    end
+
+    it 'finds labels also in ancestor groups' do
+      group = create(:group)
+      label = create(:group_label, group: group)
+      subgroup = create(:group, parent: group)
+      reference = "~#{label.name}"
+
+      result = reference_filter("See #{reference}", { project: nil, group: subgroup, label_url_method: :group_url } )
+
+      expect(result.css('a').first.attr('href')).to eq(urls.group_url(subgroup, label_name: label.name))
+    end
+
     it 'points to referenced project issues page' do
       project = create(:project)
       label = create(:label, project: project)
@@ -604,6 +625,7 @@ describe Banzai::Filter::LabelReferenceFilter do
       result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )
 
       expect(result.css('a').first.attr('href')).to eq(urls.project_issues_url(project, label_name: label.name))
+      expect(result.css('a').first.text).to eq "#{label.name} in #{project.full_name}"
     end
   end
 end
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 eeb82822f686796059dd17f4f536563c83b0a488..a1dd72c498f52a554cf8aa61b394d2a31b834c47 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -196,6 +196,41 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
     end
   end
 
+  context 'URL reference for a commit' do
+    let(:mr) { create(:merge_request, :with_diffs) }
+    let(:reference) do
+      urls.project_merge_request_url(mr.project, mr) + "/diffs?commit_id=#{mr.diff_head_sha}"
+    end
+    let(:commit) { mr.commits.find { |commit| commit.sha == mr.diff_head_sha } }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href'))
+        .to eq reference
+    end
+
+    it 'has valid text' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.text).to eq("See #{mr.to_reference(full: true)} (#{commit.short_id})")
+    end
+
+    it 'has valid title attribute' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('title')).to eq(commit.title)
+    end
+
+    it 'ignores invalid commit short_ids on link text' do
+      invalidate_commit_reference =
+        urls.project_merge_request_url(mr.project, mr) + "/diffs?commit_id=12345678"
+      doc = reference_filter("See #{invalidate_commit_reference}")
+
+      expect(doc.text).to eq("See #{mr.to_reference(full: true)} (diffs)")
+    end
+  end
+
   context 'cross-project URL reference' do
     let(:namespace) { create(:namespace, name: 'cross-reference') }
     let(:project2)  { create(:project, :public, namespace: namespace) }
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 6a9087d2e59b33d324a735209a8dedfe42c0fec7..f8fa9b2d13dd1d5d34f862faf7b49461588b69b5 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -343,14 +343,22 @@ describe Banzai::Filter::MilestoneReferenceFilter do
   end
 
   context 'group context' do
+    let(:context) { { project: nil, group: create(:group) } }
+    let(:milestone) { create(:milestone, project: project) }
+
     it 'links to a valid reference' do
-      milestone = create(:milestone, project: project)
       reference = "#{project.full_path}%#{milestone.iid}"
 
-      result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )
+      result = reference_filter("See #{reference}", context)
 
       expect(result.css('a').first.attr('href')).to eq(urls.milestone_url(milestone))
     end
+
+    it 'ignores internal references' do
+      exp = act = "See %#{milestone.iid}"
+
+      expect(reference_filter(act, context).to_html).to eq exp
+    end
   end
 
   context 'when milestone is open' do
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 3ca4652f7cc2fce6192680e09cc23956c1de802b..ba8dc68ceda22839541b813c9e1cf5c7a963d036 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -217,6 +217,23 @@ describe Banzai::Filter::RelativeLinkFilter do
       end
     end
 
+    context 'when ref name contains special chars' do
+      let(:ref) {'mark#\'@],+;-._/#@!$&()+down'}
+
+      it 'correctly escapes the ref' do
+        # Adressable won't escape the '#', so we do this manually
+        ref_escaped = 'mark%23\'@%5D,+;-._/%23@!$&()+down'
+
+        # Stub this method so the branch doesn't actually need to be in the repo
+        allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
+
+        doc = filter(link('files/images/logo-black.png'))
+
+        expect(doc.at_css('a')['href'])
+          .to eq "/#{project_path}/raw/#{ref_escaped}/files/images/logo-black.png"
+      end
+    end
+
     context 'when requested path is a directory with space in the repo' do
       let(:ref) { 'master' }
       let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') }
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index e068e02d4fc9f3f72746f497325f54200c59cf3e..21cf092428d73365a607e4df5eabfe26d30f5fc5 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -210,5 +210,11 @@ describe Banzai::Filter::SnippetReferenceFilter do
 
       expect(result.css('a').first.attr('href')).to eq(urls.project_snippet_url(project, snippet))
     end
+
+    it 'ignores internal references' do
+      exp = act = "See $#{snippet.id}"
+
+      expect(reference_filter(act, project: nil, group: create(:group)).to_html).to eq exp
+    end
   end
 end
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index 69763476dacfd28a7ec6b07f46d770d798086aa8..f42951d9781979460350afb89f6ef6ae26fee84e 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Banzai::IssuableExtractor do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
-  let(:extractor) { described_class.new(project, user) }
+  let(:extractor) { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:issue) { create(:issue, project: project) }
   let(:merge_request) { create(:merge_request, source_project: project) }
   let(:issue_link) do
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 074d521a5c67e09221fc69d9947763a5ba93c813..209a547c3b38fd78ee5de03bf6e9b1e62be41a3e 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -3,8 +3,15 @@ require 'spec_helper'
 describe Banzai::ObjectRenderer do
   let(:project) { create(:project, :repository) }
   let(:user) { project.owner }
-  let(:renderer) { described_class.new(project, user, custom_value: 'value') }
-  let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) }
+  let(:renderer) do
+    described_class.new(
+      default_project: project,
+      user: user,
+      redaction_context: { custom_value: 'value' }
+    )
+  end
+
+  let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
 
   describe '#render' do
     context 'with cache' do
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb
index 441f37259858ca0ca7ef1add2e022cd7778b1c74..aaeec953e4b513eea4a2e45a8f369b015a12f4a4 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/redactor_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Banzai::Redactor do
   let(:user) { create(:user) }
   let(:project) { build(:project) }
-  let(:redactor) { described_class.new(project, user) }
+  let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
 
   describe '#redact' do
     context 'when reference not visible to user' do
@@ -54,7 +54,7 @@ describe Banzai::Redactor do
 
     context 'when project is in pending delete' do
       let!(:issue) { create(:issue, project: project) }
-      let(:redactor) { described_class.new(project, user) }
+      let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
 
       before do
         project.update(pending_delete: true)
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index 6175d4c4ca950c0ae25b8f7843cfd662d96572b7..4e6e8eca38abdb06891b5fd56d3ce943ce15b2fa 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -5,13 +5,14 @@ describe Banzai::ReferenceParser::BaseParser do
 
   let(:user) { create(:user) }
   let(:project) { create(:project, :public) }
+  let(:context) { Banzai::RenderContext.new(project, user) }
 
   subject do
     klass = Class.new(described_class) do
       self.reference_type = :foo
     end
 
-    klass.new(project, user)
+    klass.new(context)
   end
 
   describe '.reference_type=' do
@@ -23,6 +24,19 @@ describe Banzai::ReferenceParser::BaseParser do
     end
   end
 
+  describe '#project_for_node' do
+    it 'returns the Project for a node' do
+      document = instance_double('document', fragment?: false)
+      project = instance_double('project')
+      object = instance_double('object', project: project)
+      node = instance_double('node', document: document)
+
+      context.associate_document(document, object)
+
+      expect(subject.project_for_node(node)).to eq(project)
+    end
+  end
+
   describe '#nodes_visible_to_user' do
     let(:link) { empty_html_link }
 
@@ -164,7 +178,7 @@ describe Banzai::ReferenceParser::BaseParser do
         self.reference_type = :test
       end
 
-      instance = dummy.new(project, user)
+      instance = dummy.new(Banzai::RenderContext.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)
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 3505659c2c3f92875cb23912eb08b8fc1212e538..cca53a8b9b9989f77be0d1c99c234c1a8a8f732a 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitParser do
 
   let(:project) { create(:project, :public) }
   let(:user) { create(:user) }
-  subject { described_class.new(project, user) }
+  subject { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:link) { empty_html_link }
 
   describe '#nodes_visible_to_user' do
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 21813177debc72c43f7a8c5f01c4dabf26cbd737..ff3b82cc482e0ce8a48e075eeb7e8c871634038f 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do
 
   let(:project) { create(:project, :public) }
   let(:user) { create(:user) }
-  subject { described_class.new(project, user) }
+  subject { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:link) { empty_html_link }
 
   describe '#nodes_visible_to_user' do
@@ -107,12 +107,9 @@ describe Banzai::ReferenceParser::CommitRangeParser do
   describe '#find_object' do
     let(:range) { double(:range) }
 
-    before do
-      expect(CommitRange).to receive(:new).and_return(range)
-    end
-
     context 'when the range has valid commits' do
       it 'returns the commit range' do
+        expect(CommitRange).to receive(:new).and_return(range)
         expect(range).to receive(:valid_commits?).and_return(true)
 
         expect(subject.find_object(project, '123..456')).to eq(range)
@@ -121,10 +118,19 @@ describe Banzai::ReferenceParser::CommitRangeParser do
 
     context 'when the range does not have any valid commits' do
       it 'returns nil' do
+        expect(CommitRange).to receive(:new).and_return(range)
         expect(range).to receive(:valid_commits?).and_return(false)
 
         expect(subject.find_object(project, '123..456')).to be_nil
       end
     end
+
+    context 'group context' do
+      it 'returns nil' do
+        group = create(:group)
+
+        expect(subject.find_object(group, '123..456')).to be_nil
+      end
+    end
   end
 end
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 25969b65168c67a07f824ed9f6b60739ddb02ce1..1cb31e571143216f766f7d3d31d0e4fdcab25b47 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do
 
   let(:project) { create(:project, :public) }
   let(:user) { create(:user) }
-  subject { described_class.new(project, user) }
+  subject { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:link) { empty_html_link }
 
   describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 0a63567ee4090d4b682b9f0fdd892e5e901485c1..77c2064caba3e69bb486bc146b88f4b3c0ea6e05 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -7,7 +7,7 @@ describe Banzai::ReferenceParser::IssueParser do
   let(:user)    { create(:user) }
   let(:issue)   { create(:issue, project: project) }
   let(:link)    { empty_html_link }
-  subject       { described_class.new(project, user) }
+  subject       { described_class.new(Banzai::RenderContext.new(project, user)) }
 
   describe '#nodes_visible_to_user' do
     context 'when the link has a data-issue attribute' do
@@ -117,4 +117,27 @@ describe Banzai::ReferenceParser::IssueParser do
       expect(subject.records_for_nodes(nodes)).to eq({ link => issue })
     end
   end
+
+  context 'when checking multiple merge requests on another project' do
+    let(:other_project) { create(:project, :public) }
+    let(:other_issue) { create(:issue, project: other_project) }
+
+    let(:control_links) do
+      [issue_link(other_issue)]
+    end
+
+    let(:actual_links) do
+      control_links + [issue_link(create(:issue, project: other_project))]
+    end
+
+    def issue_link(issue)
+      Nokogiri::HTML.fragment(%Q{<a data-issue="#{issue.id}"></a>}).children[0]
+    end
+
+    before do
+      project.add_developer(user)
+    end
+
+    it_behaves_like 'no N+1 queries'
+  end
 end
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index b700161d6c2d0e213738dd0bcd32f6363bc7075c..e4df2533821b5ccb0aa0d49aecd3ba430cb6c17b 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::LabelParser do
   let(:project) { create(:project, :public) }
   let(:user) { create(:user) }
   let(:label) { create(:label, project: project) }
-  subject { described_class.new(project, user) }
+  subject { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:link) { empty_html_link }
 
   describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index 775749ae3a771621d7177995c8c9219610ecfb8a..5417b1f00bed136c114ce5b3d7c7da17a2df075c 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -4,14 +4,13 @@ describe Banzai::ReferenceParser::MergeRequestParser do
   include ReferenceParserHelpers
 
   let(:user) { create(:user) }
-  let(:merge_request) { create(:merge_request) }
-  subject { described_class.new(merge_request.target_project, user) }
+  let(:project) { create(:project, :public) }
+  let(:merge_request) { create(:merge_request, source_project: project) }
+  subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) }
   let(:link) { empty_html_link }
 
   describe '#nodes_visible_to_user' do
     context 'when the link has a data-issue attribute' do
-      let(:project) { merge_request.target_project }
-
       before do
         project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
         link['data-merge-request'] = merge_request.id.to_s
@@ -40,4 +39,27 @@ describe Banzai::ReferenceParser::MergeRequestParser do
       end
     end
   end
+
+  context 'when checking multiple merge requests on another project' do
+    let(:other_project) { create(:project, :public) }
+    let(:other_merge_request) { create(:merge_request, source_project: other_project) }
+
+    let(:control_links) do
+      [merge_request_link(other_merge_request)]
+    end
+
+    let(:actual_links) do
+      control_links + [merge_request_link(create(:merge_request, :conflict, source_project: other_project))]
+    end
+
+    def merge_request_link(merge_request)
+      Nokogiri::HTML.fragment(%Q{<a data-merge-request="#{merge_request.id}"></a>}).children[0]
+    end
+
+    before do
+      project.add_developer(user)
+    end
+
+    it_behaves_like 'no N+1 queries'
+  end
 end
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 7dacdf8d6292a0689ef7261043ccb774d2a0d3f9..751d042ffded0e18b9780197cd2f00adeba8c71d 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MilestoneParser do
   let(:project) { create(:project, :public) }
   let(:user) { create(:user) }
   let(:milestone) { create(:milestone, project: project) }
-  subject { described_class.new(project, user) }
+  subject { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:link) { empty_html_link }
 
   describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 69ec3f66aa83fa53e77d45c0befd478a844ddbda..d410bd4c164e752d02e2fe22514506d94a581b30 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -9,7 +9,7 @@ describe Banzai::ReferenceParser::SnippetParser do
   let(:external_user) { create(:user, :external) }
   let(:project_member) { create(:user) }
 
-  subject { described_class.new(project, user) }
+  subject { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:link) { empty_html_link }
 
   def visible_references(snippet_visibility, user = nil)
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index b079a3be0298a2fa8b467993fa0a36faa45bded3..112447f098e590c86eaf3bdd3a6e0b7397b5b4aa 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::UserParser do
   let(:group) { create(:group) }
   let(:user) { create(:user) }
   let(:project) { create(:project, :public, group: group, creator: user) }
-  subject { described_class.new(project, user) }
+  subject { described_class.new(Banzai::RenderContext.new(project, user)) }
   let(:link) { empty_html_link }
 
   describe '#referenced_by' do
diff --git a/spec/lib/banzai/render_context_spec.rb b/spec/lib/banzai/render_context_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ad17db11613a463504203102ecd1e03351dad33a
--- /dev/null
+++ b/spec/lib/banzai/render_context_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::RenderContext do
+  let(:document) { Nokogiri::HTML.fragment('<p>hello</p>') }
+
+  describe '#project_for_node' do
+    it 'returns the default project if no associated project was found' do
+      project = instance_double('project')
+      context = described_class.new(project)
+
+      expect(context.project_for_node(document)).to eq(project)
+    end
+
+    it 'returns the associated project if one was associated explicitly' do
+      project = instance_double('project')
+      obj = instance_double('object', project: project)
+      context = described_class.new
+
+      context.associate_document(document, obj)
+
+      expect(context.project_for_node(document)).to eq(project)
+    end
+
+    it 'returns the project associated with a DocumentFragment when using a node' do
+      project = instance_double('project')
+      obj = instance_double('object', project: project)
+      context = described_class.new
+      node = document.children.first
+
+      context.associate_document(document, obj)
+
+      expect(context.project_for_node(node)).to eq(project)
+    end
+  end
+end
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index 4dab58b26a05a1011880e23fe77545a61c14518a..ff295068ba97a1f4a1a2bab0ef9cee0fa3900214 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe GroupUrlConstrainer do
+describe Constraints::GroupUrlConstrainer do
   let!(:group) { create(:group, path: 'gitlab') }
 
   describe '#matches?' do
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index 92331eb2e5d8fa3cc4ea7057e56ff595b64164b2..c96e7ab8495d04d81d6f6e87b3f5e03adee8710a 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe ProjectUrlConstrainer do
+describe Constraints::ProjectUrlConstrainer do
   let!(:project) { create(:project) }
   let!(:namespace) { project.namespace }
 
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index cb3b4ff1391593f30dfd590be37dc179fe673358..e2c85bb27bb26176521920559050bdffd5119cf4 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe UserUrlConstrainer do
+describe Constraints::UserUrlConstrainer do
   let!(:user) { create(:user, username: 'dz') }
 
   describe '#matches?' do
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cf40c467c722ae6dea74ae1d76d42be15345a6dc
--- /dev/null
+++ b/spec/lib/forever_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Forever do
+  describe '.date' do
+    subject { described_class.date }
+
+    context 'when using PostgreSQL' do
+      it 'should return Postgresql future date' do
+        allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+        expect(subject).to eq(described_class::POSTGRESQL_DATE)
+      end
+    end
+
+    context 'when using MySQL' do
+      it 'should return MySQL future date' do
+        allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+        expect(subject).to eq(described_class::MYSQL_DATE)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 2a0e19ae7968e37f5532f457d134d550a0d60596..e1782cff81af3c6e314b560d897876ce3e66689f 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -48,7 +48,7 @@ module Gitlab
           },
           'images' => {
             input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]',
-            output: "<img src=\"https://localhost.com/image.png\" alt=\"Alt text\">"
+            output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt='Alt text\" onerror=\"alert(7)'></span></p>\n</div>"
           },
           'pre' => {
             input: '```mypre"><script>alert(3)</script>',
diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb
index 9b3916bf9e350f73e3eb46989eaf1c3d4d6d4152..6b251d824f7fe077ff37d07fd1007ef9ba3d6a21 100644
--- a/spec/lib/gitlab/auth/ldap/access_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/access_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe Gitlab::Auth::LDAP::Access do
+  include LdapHelpers
+
   let(:access) { described_class.new user }
   let(:user) { create(:omniauth_user) }
 
@@ -32,8 +34,10 @@ describe Gitlab::Auth::LDAP::Access do
     end
 
     context 'when the user is found' do
+      let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
+
       before do
-        allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user)
+        allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
       end
 
       context 'and the user is disabled via active directory' do
@@ -120,6 +124,22 @@ describe Gitlab::Auth::LDAP::Access do
         end
       end
     end
+
+    context 'when the connection fails' do
+      before do
+        raise_ldap_connection_error
+      end
+
+      it 'does not block the user' do
+        access.allowed?
+
+        expect(user.ldap_blocked?).to be_falsey
+      end
+
+      it 'denies access' do
+        expect(access.allowed?).to be_falsey
+      end
+    end
   end
 
   describe '#block_user' do
diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
index 10c60d792bd84556b25cc2d0aa29a4e06b8ab9e9..3eeaf3862f67c9e8636d70e19e16eed2102a4b20 100644
--- a/spec/lib/gitlab/auth/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
@@ -124,16 +124,36 @@ describe Gitlab::Auth::LDAP::Adapter do
 
     context "when the search raises an LDAP exception" do
       before do
+        allow(adapter).to receive(:renew_connection_adapter).and_return(ldap)
         allow(ldap).to receive(:search) { raise Net::LDAP::Error, "some error" }
         allow(Rails.logger).to receive(:warn)
       end
 
-      it { is_expected.to eq [] }
+      context 'retries the operation' do
+        before do
+          stub_const("#{described_class}::MAX_SEARCH_RETRIES", 3)
+        end
+
+        it 'as many times as MAX_SEARCH_RETRIES' do
+          expect(ldap).to receive(:search).exactly(3).times
+          expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError)
+        end
+
+        context 'when no more retries' do
+          before do
+            stub_const("#{described_class}::MAX_SEARCH_RETRIES", 1)
+          end
 
-      it 'logs the error' do
-        subject
-        expect(Rails.logger).to have_received(:warn).with(
-          "LDAP search raised exception Net::LDAP::Error: some error")
+          it 'raises the exception' do
+            expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError)
+          end
+
+          it 'logs the error' do
+            expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError)
+            expect(Rails.logger).to have_received(:warn).with(
+              "LDAP search raised exception Net::LDAP::Error: some error")
+          end
+        end
       end
     end
   end
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 0c71f1d8ca65e40361b3a1abf9b8a57acba2ed1e..64f3d09a25b12a575006ac02d2485ab5ab8b07ca 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe Gitlab::Auth::OAuth::User do
+  include LdapHelpers
+
   let(:oauth_user) { described_class.new(auth_hash) }
   let(:gl_user) { oauth_user.gl_user }
   let(:uid) { 'my-uid' }
@@ -38,10 +40,6 @@ describe Gitlab::Auth::OAuth::User do
   end
 
   describe '#save' do
-    def stub_ldap_config(messages)
-      allow(Gitlab::Auth::LDAP::Config).to receive_messages(messages)
-    end
-
     let(:provider) { 'twitter' }
 
     describe 'when account exists on server' do
@@ -269,20 +267,47 @@ describe Gitlab::Auth::OAuth::User do
             end
 
             context 'when an LDAP person is not found by uid' do
-              it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do
+              it 'tries to find an LDAP person by email and adds the omniauth identity to the user' do
                 allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil)
-                allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
+                allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(ldap_user)
+
+                oauth_user.save
+
+                identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+                expect(identities_as_hash).to match_array(result_identities(dn, uid))
+              end
+
+              context 'when also not found by email' do
+                it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do
+                  allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil)
+                  allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(nil)
+                  allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
+
+                  oauth_user.save
+
+                  identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+                  expect(identities_as_hash).to match_array(result_identities(dn, uid))
+                end
+              end
+            end
 
+            def result_identities(dn, uid)
+              [
+                { provider: 'ldapmain', extern_uid: dn },
+                { provider: 'twitter', extern_uid: uid }
+              ]
+            end
+
+            context 'when there is an LDAP connection error' do
+              before do
+                raise_ldap_connection_error
+              end
+
+              it 'does not save the identity' do
                 oauth_user.save
 
                 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
-                expect(identities_as_hash)
-                  .to match_array(
-                    [
-                      { provider: 'ldapmain', extern_uid: dn },
-                      { provider: 'twitter', extern_uid: uid }
-                    ]
-                  )
+                expect(identities_as_hash).to match_array([{ provider: 'twitter', extern_uid: uid }])
               end
             end
           end
@@ -739,4 +764,19 @@ describe Gitlab::Auth::OAuth::User do
       expect(oauth_user.find_user).to eql gl_user
     end
   end
+
+  describe '#find_ldap_person' do
+    context 'when LDAP connection fails' do
+      before do
+        raise_ldap_connection_error
+      end
+
+      it 'returns nil' do
+        adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain')
+        hash = OmniAuth::AuthHash.new(uid: 'whatever', provider: 'ldapmain')
+
+        expect(oauth_user.send(:find_ldap_person, hash, adapter)).to be_nil
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index f969f9e8e38c187baef4f4f15c69bf962bc5e960..9ccd0b206cc3daecd3495aa9876d56f811d2b25b 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Auth do
 
   describe 'constants' do
     it 'API_SCOPES contains all scopes for API access' do
-      expect(subject::API_SCOPES).to eq %i[api read_user sudo]
+      expect(subject::API_SCOPES).to eq %i[api read_user sudo read_repository]
     end
 
     it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
@@ -19,7 +19,7 @@ describe Gitlab::Auth do
     it 'optional_scopes contains all non-default scopes' do
       stub_container_registry_config(enabled: true)
 
-      expect(subject.optional_scopes).to eq %i[read_user sudo read_registry openid]
+      expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid]
     end
 
     context 'registry_scopes' do
@@ -231,7 +231,7 @@ describe Gitlab::Auth do
           .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
       end
 
-      it 'falls through oauth authentication when the username is oauth2' do
+      it 'fails through oauth authentication when the username is oauth2' do
         user = create(
           :user,
           username: 'oauth2',
@@ -255,6 +255,122 @@ describe Gitlab::Auth do
 
       expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
     end
+
+    context 'while using deploy tokens' do
+      let(:project) { create(:project) }
+      let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) }
+
+      context 'when the deploy token has read_repository as scope' do
+        let(:deploy_token) { create(:deploy_token, read_registry: false, projects: [project]) }
+        let(:login) { deploy_token.username }
+
+        it 'succeeds when login and token are valid' do
+          auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code])
+
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: login)
+          expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
+            .to eq(auth_success)
+        end
+
+        it 'fails when login is not valid' do
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login')
+          expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
+            .to eq(auth_failure)
+        end
+
+        it 'fails when token is not valid' do
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+          expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip'))
+            .to eq(auth_failure)
+        end
+
+        it 'fails if token is nil' do
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+          expect(gl_auth.find_for_git_client(login, nil, project: project, ip: 'ip'))
+            .to eq(auth_failure)
+        end
+
+        it 'fails if token is not related to project' do
+          another_deploy_token = create(:deploy_token)
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+          expect(gl_auth.find_for_git_client(login, another_deploy_token.token, project: project, ip: 'ip'))
+            .to eq(auth_failure)
+        end
+
+        it 'fails if token has been revoked' do
+          deploy_token.revoke!
+
+          expect(deploy_token.revoked?).to be_truthy
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token')
+          expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: project, ip: 'ip'))
+            .to eq(auth_failure)
+        end
+      end
+
+      context 'when the deploy token has read_registry as a scope' do
+        let(:deploy_token) { create(:deploy_token, read_repository: false, projects: [project]) }
+        let(:login) { deploy_token.username }
+
+        context 'when registry enabled' do
+          before do
+            stub_container_registry_config(enabled: true)
+          end
+
+          it 'succeeds when login and token are valid' do
+            auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:read_container_image])
+
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: login)
+            expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip'))
+              .to eq(auth_success)
+          end
+
+          it 'fails when login is not valid' do
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login')
+            expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
+              .to eq(auth_failure)
+          end
+
+          it 'fails when token is not valid' do
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+            expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip'))
+              .to eq(auth_failure)
+          end
+
+          it 'fails if token is nil' do
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+            expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip'))
+              .to eq(auth_failure)
+          end
+
+          it 'fails if token is not related to project' do
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+            expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip'))
+              .to eq(auth_failure)
+          end
+
+          it 'fails if token has been revoked' do
+            deploy_token.revoke!
+
+            expect(deploy_token.revoked?).to be_truthy
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token')
+            expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip'))
+              .to eq(auth_failure)
+          end
+        end
+
+        context 'when registry disabled' do
+          before do
+            stub_container_registry_config(enabled: false)
+          end
+
+          it 'fails when login and token are valid' do
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+            expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip'))
+              .to eq(auth_failure)
+          end
+        end
+      end
+    end
   end
 
   describe 'find_with_user_password' do
@@ -315,13 +431,19 @@ describe Gitlab::Auth do
       it "tries to autheticate with db before ldap" do
         expect(Gitlab::Auth::LDAP::Authentication).not_to receive(:login)
 
-        gl_auth.find_with_user_password(username, password)
+        expect(gl_auth.find_with_user_password(username, password)).to eq(user)
+      end
+
+      it "does not find user by using ldap as fallback to for authentication" do
+        expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)
+
+        expect(gl_auth.find_with_user_password('ldap_user', 'password')).to be_nil
       end
 
-      it "uses ldap as fallback to for authentication" do
-        expect(Gitlab::Auth::LDAP::Authentication).to receive(:login)
+      it "find new user by using ldap as fallback to for authentication" do
+        expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(user)
 
-        gl_auth.find_with_user_password('ldap_user', 'password')
+        expect(gl_auth.find_with_user_password('ldap_user', 'password')).to eq(user)
       end
     end
 
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
index 21a791f56957246c328e45afdfc4c6b5bce88692..c43ed72038eab9bc99566b8d00d9ef2755b9bee7 100644
--- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -37,6 +37,18 @@ describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migratio
       expect(diff.reload.commits_count).to eq(0)
     end
 
+    it 'skips diffs that have commits_count already set' do
+      timestamp = 2.days.ago
+      diff = merge_request_diffs_table.create!(
+        merge_request_id: merge_request.id,
+        commits_count: 0,
+        updated_at: timestamp)
+
+      subject.perform(diff.id, diff.id)
+
+      expect(diff.reload.updated_at).to be_within(1.second).of(timestamp)
+    end
+
     it 'migrates multiple diffs to the correct values' do
       diffs = Array.new(3).map.with_index { |_, i| create_diff!(i, commits: 3) }
 
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index e112e9e9e3d0b6cf53de3bd974683c61f965072b..5ce84c610423c69d3cf94bb8951e0faace78a154 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -51,4 +51,20 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201
     expect { described_class.new.perform(1, 6) }
       .to raise_error ActiveRecord::RecordNotUnique
   end
+
+  context 'when invalid class can be loaded due to single table inheritance' do
+    let(:commit_status) do
+      jobs.create!(id: 7, commit_id: 1, project_id: 123, stage_idx: 4,
+                   stage: 'post-deploy', status: :failed)
+    end
+
+    before do
+      commit_status.update_column(:type, 'SomeClass')
+    end
+
+    it 'does ignore single table inheritance type' do
+      expect { described_class.new.perform(1, 7) }.not_to raise_error
+      expect(jobs.find(7)).to have_attributes(stage_id: (a_value > 0))
+    end
+  end
 end
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6f3fb994f17e3e453a8ae362107c8e0bfb075c2b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices, :migration, schema: 20180122154930 do
+  let(:services) { table(:services) }
+
+  describe '#perform' do
+    it 'migrates services where note_events is true' do
+      service = services.create(confidential_note_events: nil, note_events: true)
+
+      subject.perform(service.id, service.id)
+
+      expect(service.reload.confidential_note_events).to eq(true)
+    end
+
+    it 'ignores services where note_events is false' do
+      service = services.create(confidential_note_events: nil, note_events: false)
+
+      subject.perform(service.id, service.id)
+
+      expect(service.reload.confidential_note_events).to eq(nil)
+    end
+
+    it 'ignores services where confidential_note_events has already been set' do
+      service = services.create(confidential_note_events: false, note_events: true)
+
+      subject.perform(service.id, service.id)
+
+      expect(service.reload.confidential_note_events).to eq(false)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..82b484b7d5ba552c32d5a6e317554ae7a48d7d0e
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks, :migration, schema: 20180104131052 do
+  let(:web_hooks) { table(:web_hooks) }
+
+  describe '#perform' do
+    it 'migrates hooks where note_events is true' do
+      hook = web_hooks.create(confidential_note_events: nil, note_events: true)
+
+      subject.perform(hook.id, hook.id)
+
+      expect(hook.reload.confidential_note_events).to eq(true)
+    end
+
+    it 'ignores hooks where note_events is false' do
+      hook = web_hooks.create(confidential_note_events: nil, note_events: false)
+
+      subject.perform(hook.id, hook.id)
+
+      expect(hook.reload.confidential_note_events).to eq(nil)
+    end
+
+    it 'ignores hooks where confidential_note_events has already been set' do
+      hook = web_hooks.create(confidential_note_events: false, note_events: true)
+
+      subject.perform(hook.id, hook.id)
+
+      expect(hook.reload.confidential_note_events).to eq(false)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index 9f42cf1dfca09ffee036b237aa844bc2207b9fe0..0dc3705825d1a36b59d2beba7f0bf4367801e9c9 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -54,14 +54,14 @@ describe ::Gitlab::BareRepositoryImport::Repository do
   context 'hashed storage' do
     let(:gitlab_shell) { Gitlab::Shell.new }
     let(:repository_storage) { 'default' }
-    let(:root_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
+    let(:root_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
     let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' }
     let(:hashed_path) { "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" }
     let(:repo_path) { File.join(root_path, "#{hashed_path}.git") }
     let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") }
 
     before do
-      gitlab_shell.add_repository(repository_storage, hashed_path)
+      gitlab_shell.create_repository(repository_storage, hashed_path)
       repository = Rugged::Repository.new(repo_path)
       repository.config['gitlab.fullpath'] = 'to/repo'
     end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index a6a1d9e619fb5856cb3d64d799edc0531e935858..c63120b0b292462b996f14d2fee14b141ecc692a 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -137,7 +137,7 @@ describe Gitlab::BitbucketImport::Importer do
       it 'imports to the project disk_path' do
         expect(project.wiki).to receive(:repository_exists?) { false }
         expect(importer.gitlab_shell).to receive(:import_repository).with(
-          project.repository_storage_path,
+          project.repository_storage,
           project.wiki.disk_path,
           project.import_url + '/wiki'
         )
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 16704ff5e77d184fd91715e11eaeaed763b97331..18658588a408d3238a02664473b3742e11ff60b8 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
   let!(:project) { create(:project, :repository) }
   let(:pipeline_status) { described_class.new(project) }
-  let(:cache_key) { "projects/#{project.id}/pipeline_status" }
+  let(:cache_key) { described_class.cache_key_for_project(project) }
 
   describe '.load_for_project' do
     it "loads the status" do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index b49ddbfc780982e2a77c73bfaad1caad6d852e94..48e9902027c77ea5661fb72172e8b40716f92bb8 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -30,9 +30,10 @@ describe Gitlab::Checks::ChangeAccess do
       end
     end
 
-    context 'when the user is not allowed to push code' do
+    context 'when the user is not allowed to push to the repo' do
       it 'raises an error' do
         expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
+        expect(user_access).to receive(:can_push_to_branch?).with('master').and_return(false)
 
         expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.')
       end
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index 17756621221d15470bc0670b00d589a7d692b636..7201e4f7bf636e63cf23d0dec0c0b56e328f3094 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -2,23 +2,25 @@ require 'spec_helper'
 
 describe Gitlab::Checks::LfsIntegrity do
   include ProjectForksHelper
+
   let(:project) { create(:project, :repository) }
-  let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
+  let(:repository) { project.repository }
+  let(:newrev) do
+    operations = BareRepoOperations.new(repository.path)
+
+    # Create a commit not pointed at by any ref to emulate being in the
+    # pre-receive hook so that `--not --all` returns some objects
+    operations.commit_tree('8856a329dd38ca86dfb9ce5aa58a16d88cc119bd', "New LFS objects")
+  end
 
   subject { described_class.new(project, newrev) }
 
   describe '#objects_missing?' do
-    let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
-
-    before do
-      allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects) do |&lazy_block|
-        lazy_block.call([blob_object.id])
-      end
-    end
+    let(:blob_object) { repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
 
     context 'with LFS not enabled' do
       it 'skips integrity check' do
-        expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects)
+        expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers)
 
         subject.objects_missing?
       end
@@ -33,7 +35,7 @@ describe Gitlab::Checks::LfsIntegrity do
         let(:newrev) { nil }
 
         it 'skips integrity check' do
-          expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects)
+          expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers)
 
           expect(subject.objects_missing?).to be_falsey
         end
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index e263d29656c140bc27ed0c10dc4bc8e8921bffd5..8e9386b1ba17f1bb25a03d6d69661a8e8a66d8ea 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -44,44 +44,17 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
   end
 
   describe '#message' do
-    context 'when the push is rejected' do
-      it 'returns a redirect message telling the user to try again' do
-        project_moved = described_class.new(project, user, 'http', 'foo/bar')
-        message = "Project 'foo/bar' was moved to '#{project.full_path}'." +
-          "\n\nPlease update your Git remote:" +
-          "\n\n  git remote set-url origin #{project.http_url_to_repo} and try again.\n"
+    it 'returns a redirect message' do
+      project_moved = described_class.new(project, user, 'http', 'foo/bar')
+      message = <<~MSG
+                Project 'foo/bar' was moved to '#{project.full_path}'.
 
-        expect(project_moved.message(rejected: true)).to eq(message)
-      end
-    end
+                Please update your Git remote:
 
-    context 'when the push is not rejected' do
-      it 'returns a redirect message' do
-        project_moved = described_class.new(project, user, 'http', 'foo/bar')
-        message = "Project 'foo/bar' was moved to '#{project.full_path}'." +
-          "\n\nPlease update your Git remote:" +
-          "\n\n  git remote set-url origin #{project.http_url_to_repo}\n"
+                  git remote set-url origin #{project.http_url_to_repo}
+                MSG
 
-        expect(project_moved.message).to eq(message)
-      end
-    end
-  end
-
-  describe '#permanent_redirect?' do
-    context 'with a permanent RedirectRoute' do
-      it 'returns true' do
-        project.route.create_redirect('foo/bar', permanent: true)
-        project_moved = described_class.new(project, user, 'http', 'foo/bar')
-        expect(project_moved.permanent_redirect?).to be_truthy
-      end
-    end
-
-    context 'without a permanent RedirectRoute' do
-      it 'returns false' do
-        project.route.create_redirect('foo/bar')
-        project_moved = described_class.new(project, user, 'http', 'foo/bar')
-        expect(project_moved.permanent_redirect?).to be_falsy
-      end
+      expect(project_moved.message).to eq(message)
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2ce858836e3975d1f641f8ccc23f97c6da39511d
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Policy::Variables do
+  set(:project) { create(:project) }
+
+  let(:pipeline) do
+    build(:ci_empty_pipeline, project: project, ref: 'master', source: :push)
+  end
+
+  let(:ci_build) do
+    build(:ci_build, pipeline: pipeline, project: project, ref: 'master')
+  end
+
+  let(:seed) { double('build seed', to_resource: ci_build) }
+
+  before do
+    pipeline.variables.build(key: 'CI_PROJECT_NAME', value: '')
+  end
+
+  describe '#satisfied_by?' do
+    it 'is satisfied by at least one matching statement' do
+      policy = described_class.new(['$CI_PROJECT_ID', '$UNDEFINED'])
+
+      expect(policy).to be_satisfied_by(pipeline, seed)
+    end
+
+    it 'is not satisfied by an overriden empty variable' do
+      policy = described_class.new(['$CI_PROJECT_NAME'])
+
+      expect(policy).not_to be_satisfied_by(pipeline, seed)
+    end
+
+    it 'is satisfied by a truthy pipeline expression' do
+      policy = described_class.new([%($CI_PIPELINE_SOURCE == "push")])
+
+      expect(policy).to be_satisfied_by(pipeline, seed)
+    end
+
+    it 'is not satisfied by a falsy pipeline expression' do
+      policy = described_class.new([%($CI_PIPELINE_SOURCE == "invalid source")])
+
+      expect(policy).not_to be_satisfied_by(pipeline, seed)
+    end
+
+    it 'is satisfied by a truthy expression using undefined variable' do
+      policy = described_class.new(['$UNDEFINED == null'])
+
+      expect(policy).to be_satisfied_by(pipeline, seed)
+    end
+
+    it 'is not satisfied by a falsy expression using undefined variable' do
+      policy = described_class.new(['$UNDEFINED'])
+
+      expect(policy).not_to be_satisfied_by(pipeline, seed)
+    end
+
+    it 'allows to evaluate regular secret variables' do
+      create(:ci_variable, project: project, key: 'SECRET', value: 'my secret')
+
+      policy = described_class.new(["$SECRET == 'my secret'"])
+
+      expect(policy).to be_satisfied_by(pipeline, seed)
+    end
+
+    it 'does not persist neither pipeline nor build' do
+      described_class.new('$VAR').satisfied_by?(pipeline, seed)
+
+      expect(pipeline).not_to be_persisted
+      expect(seed.to_resource).not_to be_persisted
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index 5a21282712a79e516b1739fc5a27c30b57fee68d..cce4efaa069852714d898325122b7e5277add302 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -5,10 +5,14 @@ describe Gitlab::Ci::Build::Step do
     shared_examples 'has correct script' do
       subject { described_class.from_commands(job) }
 
+      before do
+        job.run!
+      end
+
       it 'fabricates an object' do
         expect(subject.name).to eq(:script)
         expect(subject.script).to eq(script)
-        expect(subject.timeout).to eq(job.timeout)
+        expect(subject.timeout).to eq(job.metadata_timeout)
         expect(subject.when).to eq('on_success')
         expect(subject.allow_failure).to be_falsey
       end
@@ -47,6 +51,10 @@ describe Gitlab::Ci::Build::Step do
 
     subject { described_class.from_after_script(job) }
 
+    before do
+      job.run!
+    end
+
     context 'when after_script is empty' do
       it 'doesn not fabricate an object' do
         is_expected.to be_nil
@@ -59,7 +67,7 @@ describe Gitlab::Ci::Build::Step do
       it 'fabricates an object' do
         expect(subject.name).to eq(:after_script)
         expect(subject.script).to eq(['ls -la', 'date'])
-        expect(subject.timeout).to eq(job.timeout)
+        expect(subject.timeout).to eq(job.metadata_timeout)
         expect(subject.when).to eq('always')
         expect(subject.allow_failure).to be_truthy
       end
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index f8188675013c3b5d7bd0d1fffbb08ccda41c710b..1668d3bbaac3e1d44360f6c9e128c8aff5d20465 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -1,6 +1,51 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Charts do
+  context "yearchart" do
+    let(:project) { create(:project) }
+    let(:chart) { Gitlab::Ci::Charts::YearChart.new(project) }
+
+    subject { chart.to }
+
+    it 'goes until the end of the current month (including the whole last day of the month)' do
+      is_expected.to eq(Date.today.end_of_month.end_of_day)
+    end
+
+    it 'starts at the beginning of the current year' do
+      expect(chart.from).to eq(chart.to.years_ago(1).beginning_of_month.beginning_of_day)
+    end
+  end
+
+  context "monthchart" do
+    let(:project) { create(:project) }
+    let(:chart) { Gitlab::Ci::Charts::MonthChart.new(project) }
+
+    subject { chart.to }
+
+    it 'includes the whole current day' do
+      is_expected.to eq(Date.today.end_of_day)
+    end
+
+    it 'starts one month ago' do
+      expect(chart.from).to eq(1.month.ago.beginning_of_day)
+    end
+  end
+
+  context "weekchart" do
+    let(:project) { create(:project) }
+    let(:chart) { Gitlab::Ci::Charts::WeekChart.new(project) }
+
+    subject { chart.to }
+
+    it 'includes the whole current day' do
+      is_expected.to eq(Date.today.end_of_day)
+    end
+
+    it 'starts one week ago' do
+      expect(chart.from).to eq(1.week.ago.beginning_of_day)
+    end
+  end
+
   context "pipeline_times" do
     let(:project) { create(:project) }
     let(:chart) { Gitlab::Ci::Charts::PipelineTime.new(project) }
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 5e83abf645b87f1532aa0fdc5009876030ae4037..08718c382b9675054163f09bf0d95ffb2258ea7a 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -83,6 +83,39 @@ describe Gitlab::Ci::Config::Entry::Policy do
       end
     end
 
+    context 'when specifying valid variables expressions policy' do
+      let(:config) { { variables: ['$VAR == null'] } }
+
+      it 'is a correct configuraton' do
+        expect(entry).to be_valid
+        expect(entry.value).to eq(config)
+      end
+    end
+
+    context 'when specifying variables expressions in invalid format' do
+      let(:config) { { variables: '$MY_VAR' } }
+
+      it 'reports an error about invalid format' do
+        expect(entry.errors).to include /should be an array of strings/
+      end
+    end
+
+    context 'when specifying invalid variables expressions statement' do
+      let(:config) { { variables: ['$MY_VAR =='] } }
+
+      it 'reports an error about invalid statement' do
+        expect(entry.errors).to include /invalid expression syntax/
+      end
+    end
+
+    context 'when specifying invalid variables expressions token' do
+      let(:config) { { variables: ['$MY_VAR == 123'] } }
+
+      it 'reports an error about invalid statement' do
+        expect(entry.errors).to include /invalid expression syntax/
+      end
+    end
+
     context 'when specifying unknown policy' do
       let(:config) { { refs: ['master'], invalid: :something } }
 
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
index 1b03227d67beb87fec67dfa353fafadbe88b4983..dc12ba076bcdea0cf7228fd8edf281a9e9070ece 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -5,23 +5,23 @@ describe Gitlab::Ci::Pipeline::Chain::Create do
   set(:user) { create(:user) }
 
   let(:pipeline) do
-    build(:ci_pipeline_with_one_job, project: project,
-                                     ref: 'master')
+    build(:ci_empty_pipeline, project: project, ref: 'master')
   end
 
   let(:command) do
     Gitlab::Ci::Pipeline::Chain::Command.new(
-      project: project,
-      current_user: user, seeds_block: nil)
+      project: project, current_user: user)
   end
 
   let(:step) { described_class.new(pipeline, command) }
 
-  before do
-    step.perform!
-  end
-
   context 'when pipeline is ready to be saved' do
+    before do
+      pipeline.stages.build(name: 'test', project: project)
+
+      step.perform!
+    end
+
     it 'saves a pipeline' do
       expect(pipeline).to be_persisted
     end
@@ -32,6 +32,7 @@ describe Gitlab::Ci::Pipeline::Chain::Create do
 
     it 'creates stages' do
       expect(pipeline.reload.stages).to be_one
+      expect(pipeline.stages.first).to be_persisted
     end
   end
 
@@ -40,6 +41,10 @@ describe Gitlab::Ci::Pipeline::Chain::Create do
       build(:ci_pipeline, project: project, ref: nil)
     end
 
+    before do
+      step.perform!
+    end
+
     it 'breaks the chain' do
       expect(step.break?).to be true
     end
@@ -49,18 +54,4 @@ describe Gitlab::Ci::Pipeline::Chain::Create do
         .to include /Failed to persist the pipeline/
     end
   end
-
-  context 'when there is a seed block present' do
-    let(:seeds) { spy('pipeline seeds') }
-
-    let(:command) do
-      double('command', project: project,
-                        current_user: user,
-                        seeds_block: seeds)
-    end
-
-    it 'executes the block' do
-      expect(seeds).to have_received(:call).with(pipeline)
-    end
-  end
 end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8312fa47cfa5a23c9b5f4fc4837e60911d3d9ab0
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -0,0 +1,158 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Populate do
+  set(:project) { create(:project) }
+  set(:user) { create(:user) }
+
+  let(:pipeline) do
+    build(:ci_pipeline_with_one_job, project: project,
+                                     ref: 'master',
+                                     user: user)
+  end
+
+  let(:command) do
+    Gitlab::Ci::Pipeline::Chain::Command.new(
+      project: project,
+      current_user: user,
+      seeds_block: nil)
+  end
+
+  let(:step) { described_class.new(pipeline, command) }
+
+  context 'when pipeline doesn not have seeds block' do
+    before do
+      step.perform!
+    end
+
+    it 'does not persist the pipeline' do
+      expect(pipeline).not_to be_persisted
+    end
+
+    it 'does not break the chain' do
+      expect(step.break?).to be false
+    end
+
+    it 'populates pipeline with stages' do
+      expect(pipeline.stages).to be_one
+      expect(pipeline.stages.first).not_to be_persisted
+    end
+
+    it 'populates pipeline with builds' do
+      expect(pipeline.builds).to be_one
+      expect(pipeline.builds.first).not_to be_persisted
+      expect(pipeline.stages.first.builds).to be_one
+      expect(pipeline.stages.first.builds.first).not_to be_persisted
+    end
+
+    it 'correctly assigns user' do
+      expect(pipeline.builds).to all(have_attributes(user: user))
+    end
+  end
+
+  context 'when pipeline is empty' do
+    let(:config) do
+      { rspec: {
+          script: 'ls',
+          only: ['something']
+      } }
+    end
+
+    let(:pipeline) do
+      build(:ci_pipeline, project: project, config: config)
+    end
+
+    before do
+      step.perform!
+    end
+
+    it 'breaks the chain' do
+      expect(step.break?).to be true
+    end
+
+    it 'appends an error about missing stages' do
+      expect(pipeline.errors.to_a)
+        .to include 'No stages / jobs for this pipeline.'
+    end
+  end
+
+  context 'when pipeline has validation errors' do
+    let(:pipeline) do
+      build(:ci_pipeline, project: project, ref: nil)
+    end
+
+    before do
+      step.perform!
+    end
+
+    it 'breaks the chain' do
+      expect(step.break?).to be true
+    end
+
+    it 'appends validation error' do
+      expect(pipeline.errors.to_a)
+        .to include 'Failed to build the pipeline!'
+    end
+  end
+
+  context 'when there is a seed blocks present' do
+    let(:command) do
+      Gitlab::Ci::Pipeline::Chain::Command.new(
+        project: project,
+        current_user: user,
+        seeds_block: seeds_block)
+    end
+
+    context 'when seeds block builds some resources' do
+      let(:seeds_block) do
+        ->(pipeline) { pipeline.variables.build(key: 'VAR', value: '123') }
+      end
+
+      it 'populates pipeline with resources described in the seeds block' do
+        step.perform!
+
+        expect(pipeline).not_to be_persisted
+        expect(pipeline.variables).not_to be_empty
+        expect(pipeline.variables.first).not_to be_persisted
+        expect(pipeline.variables.first.key).to eq 'VAR'
+        expect(pipeline.variables.first.value).to eq '123'
+      end
+    end
+
+    context 'when seeds block tries to persist some resources' do
+      let(:seeds_block) do
+        ->(pipeline) { pipeline.variables.create!(key: 'VAR', value: '123') }
+      end
+
+      it 'raises exception' do
+        expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved)
+      end
+    end
+  end
+
+  context 'when pipeline gets persisted during the process' do
+    let(:pipeline) { create(:ci_pipeline, project: project) }
+
+    it 'raises error' do
+      expect { step.perform! }.to raise_error(described_class::PopulateError)
+    end
+  end
+
+  context 'when using only/except build policies' do
+    let(:config) do
+      { rspec: { script: 'rspec', stage: 'test', only: ['master'] },
+        prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } }
+    end
+
+    let(:pipeline) do
+      build(:ci_pipeline, ref: 'master', config: config)
+    end
+
+    it 'populates pipeline according to used policies' do
+      step.perform!
+
+      expect(pipeline.stages.size).to eq 1
+      expect(pipeline.builds.size).to eq 1
+      expect(pipeline.builds.first.name).to eq 'rspec'
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index 5c12c6e63929a6cb90613f3eaf1da9186bbd01e7..c53294d091cde7d80f422ff949403e8c1dc40fa2 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -76,28 +76,6 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
     end
   end
 
-  context 'when pipeline has no stages / jobs' do
-    let(:config) do
-      { rspec: {
-          script: 'ls',
-          only: ['something']
-      } }
-    end
-
-    let(:pipeline) do
-      build(:ci_pipeline, project: project, config: config)
-    end
-
-    it 'appends an error about missing stages' do
-      expect(pipeline.errors.to_a)
-        .to include 'No stages / jobs for this pipeline.'
-    end
-
-    it 'breaks the chain' do
-      expect(step.break?).to be true
-    end
-  end
-
   context 'when pipeline contains configuration validation errors' do
     let(:config) { { rspec: {} } }
 
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
index 86234dfb9e53c8261263611af54ac5cfa2814474..1ccb792d1dade3e4b769b1ef99b16b11796eb344 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -73,6 +73,22 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
         expect(token).not_to be_nil
         expect(token.build.evaluate).to eq 'some " string'
       end
+
+      it 'allows to use an empty string inside single quotes' do
+        scanner = StringScanner.new(%(''))
+
+        token = described_class.scan(scanner)
+
+        expect(token.build.evaluate).to eq ''
+      end
+
+      it 'allow to use an empty string inside double quotes' do
+        scanner = StringScanner.new(%(""))
+
+        token = described_class.scan(scanner)
+
+        expect(token.build.evaluate).to eq ''
+      end
     end
   end
 
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index 472a58599d8eb7de0858dfe7143897c9de971541..6685bf5385b272c1812497b4393697d91d83c9db 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,14 +1,23 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Pipeline::Expression::Statement do
-  let(:pipeline) { build(:ci_pipeline) }
-
   subject do
-    described_class.new(text, pipeline)
+    described_class.new(text, variables)
+  end
+
+  let(:variables) do
+    { 'PRESENT_VARIABLE' => 'my variable',
+      EMPTY_VARIABLE: '' }
   end
 
-  before do
-    pipeline.variables.build([key: 'VARIABLE', value: 'my variable'])
+  describe '.new' do
+    context 'when variables are not provided' do
+      it 'allows to properly initializes the statement' do
+        statement = described_class.new('$PRESENT_VARIABLE')
+
+        expect(statement.evaluate).to be_nil
+      end
+    end
   end
 
   describe '#parse_tree' do
@@ -23,18 +32,26 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
 
     context 'when expression grammar is incorrect' do
       table = [
-        '$VAR "text"',      # missing operator
-        '== "123"',         # invalid right side
-        "'single quotes'",  # single quotes string
-        '$VAR ==',          # invalid right side
-        '12345',            # unknown syntax
-        ''                  # empty statement
+        '$VAR "text"',   # missing operator
+        '== "123"',      # invalid left side
+        '"some string"', # only string provided
+        '$VAR ==',       # invalid right side
+        '12345',         # unknown syntax
+        ''               # empty statement
       ]
 
       table.each do |syntax|
-        it "raises an error when syntax is `#{syntax}`" do
-          expect { described_class.new(syntax, pipeline).parse_tree }
-            .to raise_error described_class::StatementError
+        context "when expression grammar is #{syntax.inspect}" do
+          let(:text) { syntax }
+
+          it 'aises a statement error exception' do
+            expect { subject.parse_tree }
+              .to raise_error described_class::StatementError
+          end
+
+          it 'is an invalid statement' do
+            expect(subject).not_to be_valid
+          end
         end
       end
     end
@@ -47,10 +64,14 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
           expect(subject.parse_tree)
             .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
         end
+
+        it 'is a valid statement' do
+          expect(subject).to be_valid
+        end
       end
 
       context 'when using a single token' do
-        let(:text) { '$VARIABLE' }
+        let(:text) { '$PRESENT_VARIABLE' }
 
         it 'returns a single token instance' do
           expect(subject.parse_tree)
@@ -62,14 +83,17 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
 
   describe '#evaluate' do
     statements = [
-      ['$VARIABLE == "my variable"', true],
-      ["$VARIABLE == 'my variable'", true],
-      ['"my variable" == $VARIABLE', true],
-      ['$VARIABLE == null', false],
-      ['$VAR == null', true],
-      ['null == $VAR', true],
-      ['$VARIABLE', 'my variable'],
-      ['$VAR', nil]
+      ['$PRESENT_VARIABLE == "my variable"', true],
+      ["$PRESENT_VARIABLE == 'my variable'", true],
+      ['"my variable" == $PRESENT_VARIABLE', true],
+      ['$PRESENT_VARIABLE == null', false],
+      ['$EMPTY_VARIABLE == null', false],
+      ['"" == $EMPTY_VARIABLE', true],
+      ['$EMPTY_VARIABLE', ''],
+      ['$UNDEFINED_VARIABLE == null', true],
+      ['null == $UNDEFINED_VARIABLE', true],
+      ['$PRESENT_VARIABLE', 'my variable'],
+      ['$UNDEFINED_VARIABLE', nil]
     ]
 
     statements.each do |expression, value|
@@ -82,4 +106,25 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
       end
     end
   end
+
+  describe '#truthful?' do
+    statements = [
+      ['$PRESENT_VARIABLE == "my variable"', true],
+      ["$PRESENT_VARIABLE == 'no match'", false],
+      ['$UNDEFINED_VARIABLE == null', true],
+      ['$PRESENT_VARIABLE', true],
+      ['$UNDEFINED_VARIABLE', false],
+      ['$EMPTY_VARIABLE', false]
+    ]
+
+    statements.each do |expression, value|
+      context "when using expression `#{expression}`" do
+        let(:text) { expression }
+
+        it "returns `#{value.inspect}`" do
+          expect(subject.truthful?).to eq value
+        end
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fffa727c2ed80ee1269ff4a0d9c32e0548b12cee
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -0,0 +1,232 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Seed::Build do
+  let(:pipeline) { create(:ci_empty_pipeline) }
+
+  let(:attributes) do
+    { name: 'rspec',
+      ref: 'master',
+      commands: 'rspec' }
+  end
+
+  subject do
+    described_class.new(pipeline, attributes)
+  end
+
+  describe '#attributes' do
+    it 'returns hash attributes of a build' do
+      expect(subject.attributes).to be_a Hash
+      expect(subject.attributes)
+        .to include(:name, :project, :ref, :commands)
+    end
+  end
+
+  describe '#to_resource' do
+    it 'returns a valid build resource' do
+      expect(subject.to_resource).to be_a(::Ci::Build)
+      expect(subject.to_resource).to be_valid
+    end
+
+    it 'memoizes a resource object' do
+      build = subject.to_resource
+
+      expect(build.object_id).to eq subject.to_resource.object_id
+    end
+
+    it 'can not be persisted without explicit assignment' do
+      build = subject.to_resource
+
+      pipeline.save!
+
+      expect(build).not_to be_persisted
+    end
+  end
+
+  describe 'applying only/except policies' do
+    context 'when no branch policy is specified' do
+      let(:attributes) { { name: 'rspec' } }
+
+      it { is_expected.to be_included }
+    end
+
+    context 'when branch policy does not match' do
+      context 'when using only' do
+        let(:attributes) { { name: 'rspec', only: { refs: ['deploy'] } } }
+
+        it { is_expected.not_to be_included }
+      end
+
+      context 'when using except' do
+        let(:attributes) { { name: 'rspec', except: { refs: ['deploy'] } } }
+
+        it { is_expected.to be_included }
+      end
+    end
+
+    context 'when branch regexp policy does not match' do
+      context 'when using only' do
+        let(:attributes) { { name: 'rspec', only: { refs: ['/^deploy$/'] } } }
+
+        it { is_expected.not_to be_included }
+      end
+
+      context 'when using except' do
+        let(:attributes) { { name: 'rspec', except: { refs: ['/^deploy$/'] } } }
+
+        it { is_expected.to be_included }
+      end
+    end
+
+    context 'when branch policy matches' do
+      context 'when using only' do
+        let(:attributes) { { name: 'rspec', only: { refs: %w[deploy master] } } }
+
+        it { is_expected.to be_included }
+      end
+
+      context 'when using except' do
+        let(:attributes) { { name: 'rspec', except: { refs: %w[deploy master] } } }
+
+        it { is_expected.not_to be_included }
+      end
+    end
+
+    context 'when keyword policy matches' do
+      context 'when using only' do
+        let(:attributes) { { name: 'rspec', only: { refs: ['branches'] } } }
+
+        it { is_expected.to be_included }
+      end
+
+      context 'when using except' do
+        let(:attributes) { { name: 'rspec', except: { refs: ['branches'] } } }
+
+        it { is_expected.not_to be_included }
+      end
+    end
+
+    context 'when keyword policy does not match' do
+      context 'when using only' do
+        let(:attributes) { { name: 'rspec', only: { refs: ['tags'] } } }
+
+        it { is_expected.not_to be_included }
+      end
+
+      context 'when using except' do
+        let(:attributes) { { name: 'rspec', except: { refs: ['tags'] } } }
+
+        it { is_expected.to be_included }
+      end
+    end
+
+    context 'when keywords and pipeline source policy matches' do
+      possibilities = [%w[pushes push],
+                       %w[web web],
+                       %w[triggers trigger],
+                       %w[schedules schedule],
+                       %w[api api],
+                       %w[external external]]
+
+      context 'when using only' do
+        possibilities.each do |keyword, source|
+          context "when using keyword `#{keyword}` and source `#{source}`" do
+            let(:pipeline) do
+              build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+            end
+
+            let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+
+            it { is_expected.to be_included }
+          end
+        end
+      end
+
+      context 'when using except' do
+        possibilities.each do |keyword, source|
+          context "when using keyword `#{keyword}` and source `#{source}`" do
+            let(:pipeline) do
+              build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+            end
+
+            let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+
+            it { is_expected.not_to be_included }
+          end
+        end
+      end
+    end
+
+    context 'when keywords and pipeline source does not match' do
+      possibilities = [%w[pushes web],
+                       %w[web push],
+                       %w[triggers schedule],
+                       %w[schedules external],
+                       %w[api trigger],
+                       %w[external api]]
+
+      context 'when using only' do
+        possibilities.each do |keyword, source|
+          context "when using keyword `#{keyword}` and source `#{source}`" do
+            let(:pipeline) do
+              build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+            end
+
+            let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+
+            it { is_expected.not_to be_included }
+          end
+        end
+      end
+
+      context 'when using except' do
+        possibilities.each do |keyword, source|
+          context "when using keyword `#{keyword}` and source `#{source}`" do
+            let(:pipeline) do
+              build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+            end
+
+            let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+
+            it { is_expected.to be_included }
+          end
+        end
+      end
+    end
+
+    context 'when repository path matches' do
+      context 'when using only' do
+        let(:attributes) do
+          { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } }
+        end
+
+        it { is_expected.to be_included }
+      end
+
+      context 'when using except' do
+        let(:attributes) do
+          { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } }
+        end
+
+        it { is_expected.not_to be_included }
+      end
+    end
+
+    context 'when repository path does not matches' do
+      context 'when using only' do
+        let(:attributes) do
+          { name: 'rspec', only: { refs: ['branches@fork'] } }
+        end
+
+        it { is_expected.not_to be_included }
+      end
+
+      context 'when using except' do
+        let(:attributes) do
+          { name: 'rspec', except: { refs: ['branches@fork'] } }
+        end
+
+        it { is_expected.to be_included }
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb1b285c7bd32f44b3e400690f5393329336e1e4
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Seed::Stage do
+  let(:pipeline) { create(:ci_empty_pipeline) }
+
+  let(:attributes) do
+    { name: 'test',
+      index: 0,
+      builds: [{ name: 'rspec' },
+               { name: 'spinach' },
+               { name: 'deploy', only: { refs: ['feature'] } }] }
+  end
+
+  subject do
+    described_class.new(pipeline, attributes)
+  end
+
+  describe '#size' do
+    it 'returns a number of jobs in the stage' do
+      expect(subject.size).to eq 2
+    end
+  end
+
+  describe '#attributes' do
+    it 'returns hash attributes of a stage' do
+      expect(subject.attributes).to be_a Hash
+      expect(subject.attributes).to include(:name, :project)
+    end
+  end
+
+  describe '#included?' do
+    context 'when it contains builds seeds' do
+      let(:attributes) do
+        { name: 'test',
+          index: 0,
+          builds: [{ name: 'deploy', only: { refs: ['master'] } }] }
+      end
+
+      it { is_expected.to be_included }
+    end
+
+    context 'when it does not contain build seeds' do
+      let(:attributes) do
+        { name: 'test',
+          index: 0,
+          builds: [{ name: 'deploy', only: { refs: ['feature'] } }] }
+      end
+
+      it { is_expected.not_to be_included }
+    end
+  end
+
+  describe '#seeds' do
+    it 'returns build seeds' do
+      expect(subject.seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Build)
+    end
+
+    it 'returns build seeds including valid attributes' do
+      expect(subject.seeds.size).to eq 2
+      expect(subject.seeds.map(&:attributes)).to all(include(ref: 'master'))
+      expect(subject.seeds.map(&:attributes)).to all(include(tag: false))
+      expect(subject.seeds.map(&:attributes)).to all(include(project: pipeline.project))
+      expect(subject.seeds.map(&:attributes))
+        .to all(include(trigger_request: pipeline.trigger_requests.first))
+    end
+
+    context 'when a ref is protected' do
+      before do
+        allow_any_instance_of(Project).to receive(:protected_for?).and_return(true)
+      end
+
+      it 'returns protected builds' do
+        expect(subject.seeds.map(&:attributes)).to all(include(protected: true))
+      end
+    end
+
+    context 'when a ref is not protected' do
+      before do
+        allow_any_instance_of(Project).to receive(:protected_for?).and_return(false)
+      end
+
+      it 'returns unprotected builds' do
+        expect(subject.seeds.map(&:attributes)).to all(include(protected: false))
+      end
+    end
+
+    it 'filters seeds using only/except policies' do
+      expect(subject.seeds.map(&:attributes)).to satisfy do |seeds|
+        seeds.any? { |hash| hash.fetch(:name) == 'rspec' }
+      end
+
+      expect(subject.seeds.map(&:attributes)).not_to satisfy do |seeds|
+        seeds.any? { |hash| hash.fetch(:name) == 'deploy' }
+      end
+    end
+  end
+
+  describe '#to_resource' do
+    it 'builds a valid stage object with all builds' do
+      subject.to_resource.save!
+
+      expect(pipeline.reload.stages.count).to eq 1
+      expect(pipeline.reload.builds.count).to eq 2
+      expect(pipeline.builds).to all(satisfy { |job| job.stage_id.present? })
+      expect(pipeline.builds).to all(satisfy { |job| job.pipeline.present? })
+      expect(pipeline.builds).to all(satisfy { |job| job.project.present? })
+      expect(pipeline.stages)
+        .to all(satisfy { |stage| stage.pipeline.present? })
+      expect(pipeline.stages)
+        .to all(satisfy { |stage| stage.project.present? })
+    end
+
+    it 'can not be persisted without explicit pipeline assignment' do
+      stage = subject.to_resource
+
+      pipeline.save!
+
+      expect(stage).not_to be_persisted
+      expect(pipeline.reload.stages.count).to eq 0
+      expect(pipeline.reload.builds.count).to eq 0
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/stage/seed_spec.rb b/spec/lib/gitlab/ci/stage/seed_spec.rb
deleted file mode 100644
index 3fe8d50c49a6bb656a27438a4b2f5998be8a24f4..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/ci/stage/seed_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Ci::Stage::Seed do
-  let(:pipeline) { create(:ci_empty_pipeline) }
-
-  let(:builds) do
-    [{ name: 'rspec' }, { name: 'spinach' }]
-  end
-
-  subject do
-    described_class.new(pipeline, 'test', builds)
-  end
-
-  describe '#size' do
-    it 'returns a number of jobs in the stage' do
-      expect(subject.size).to eq 2
-    end
-  end
-
-  describe '#stage' do
-    it 'returns hash attributes of a stage' do
-      expect(subject.stage).to be_a Hash
-      expect(subject.stage).to include(:name, :project)
-    end
-  end
-
-  describe '#builds' do
-    it 'returns hash attributes of all builds' do
-      expect(subject.builds.size).to eq 2
-      expect(subject.builds).to all(include(ref: 'master'))
-      expect(subject.builds).to all(include(tag: false))
-      expect(subject.builds).to all(include(project: pipeline.project))
-      expect(subject.builds)
-        .to all(include(trigger_request: pipeline.trigger_requests.first))
-    end
-
-    context 'when a ref is protected' do
-      before do
-        allow_any_instance_of(Project).to receive(:protected_for?).and_return(true)
-      end
-
-      it 'returns protected builds' do
-        expect(subject.builds).to all(include(protected: true))
-      end
-    end
-
-    context 'when a ref is unprotected' do
-      before do
-        allow_any_instance_of(Project).to receive(:protected_for?).and_return(false)
-      end
-
-      it 'returns unprotected builds' do
-        expect(subject.builds).to all(include(protected: false))
-      end
-    end
-  end
-
-  describe '#user=' do
-    let(:user) { build(:user) }
-
-    it 'assignes relevant pipeline attributes' do
-      subject.user = user
-
-      expect(subject.builds).to all(include(user: user))
-    end
-  end
-
-  describe '#create!' do
-    it 'creates all stages and builds' do
-      subject.create!
-
-      expect(pipeline.reload.stages.count).to eq 1
-      expect(pipeline.reload.builds.count).to eq 2
-      expect(pipeline.builds).to all(satisfy { |job| job.stage_id.present? })
-      expect(pipeline.builds).to all(satisfy { |job| job.pipeline.present? })
-      expect(pipeline.builds).to all(satisfy { |job| job.project.present? })
-      expect(pipeline.stages)
-        .to all(satisfy { |stage| stage.pipeline.present? })
-      expect(pipeline.stages)
-        .to all(satisfy { |stage| stage.project.present? })
-    end
-  end
-end
diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb
index d612d29e3e0df41228c91580055a4c6efe9f90e7..bdec582b57bbe304be2325a7f1f8bd6cc118f16b 100644
--- a/spec/lib/gitlab/ci/status/build/action_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/action_spec.rb
@@ -53,4 +53,14 @@ describe Gitlab::Ci::Status::Build::Action do
       end
     end
   end
+
+  describe '#badge_tooltip' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build, :non_playable) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    it 'returns the status' do
+      expect(subject.badge_tooltip).to eq('created')
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 9cdebaa5cf28a4ae5eeb3aeb88fc113dc2078865..78d6fa65b5a69f58ad8efc82b0e375a622393564 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -40,6 +40,24 @@ describe Gitlab::Ci::Status::Build::Cancelable do
     end
   end
 
+  describe '#status_tooltip' do
+    it 'does not override status status_tooltip' do
+      expect(status).to receive(:status_tooltip)
+
+      subject.status_tooltip
+    end
+  end
+
+  describe '#badge_tooltip' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    it 'returns the status' do
+      expect(subject.badge_tooltip).to eq('pending')
+    end
+  end
+
   describe 'action details' do
     let(:user) { create(:user) }
     let(:build) { create(:ci_build) }
@@ -72,6 +90,10 @@ describe Gitlab::Ci::Status::Build::Cancelable do
     describe '#action_title' do
       it { expect(subject.action_title).to eq 'Cancel' }
     end
+
+    describe '#action_button_title' do
+      it { expect(subject.action_button_title).to eq 'Cancel this job' }
+    end
   end
 
   describe '.matches?' do
diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c6b5cc6877073272400ad9bf70c410d563fc3999
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Canceled do
+  let(:user) { create(:user) }
+
+  subject do
+    described_class.new(double('subject'))
+  end
+
+  describe '#illustration' do
+    it { expect(subject.illustration).to include(:image, :size, :title) }
+  end
+
+  describe '.matches?' do
+    subject {described_class.matches?(build, user) }
+
+    context 'when build is canceled' do
+      let(:build) { create(:ci_build, :canceled) }
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not canceled' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 2cce7a23ea7f7b4b0c32879ad740a68930951288..ca3c66f0152a854a1982a2d3f44ec978e3f594e2 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -38,4 +38,10 @@ describe Gitlab::Ci::Status::Build::Common do
       expect(subject.details_path).to include "jobs/#{build.id}"
     end
   end
+
+  describe '#illustration' do
+    it 'provides a fallback empty state illustration' do
+      expect(subject.illustration).not_to be_empty
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8bdfe6ef7a2cd5021db650cfbd09c70a3233b01a
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/created_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Created do
+  let(:user) { create(:user) }
+
+  subject do
+    described_class.new(double('subject'))
+  end
+
+  describe '#illustration' do
+    it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+  end
+
+  describe '.matches?' do
+    subject {described_class.matches?(build, user) }
+
+    context 'when build is created' do
+      let(:build) { create(:ci_build, :created) }
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not created' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/erased_spec.rb b/spec/lib/gitlab/ci/status/build/erased_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0acd271e375f4abb5aa9a2d5f1eb527a97950570
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/erased_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Erased do
+  let(:user) { create(:user) }
+
+  subject do
+    described_class.new(double('subject'))
+  end
+
+  describe '#illustration' do
+    it { expect(subject.illustration).to include(:image, :size, :title) }
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is erased' do
+      let(:build) { create(:ci_build, :success, :erased) }
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not erased' do
+      let(:build) { create(:ci_build, :success, :trace_artifact) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index d196bc6a4c27c0658a78de40586873f04fcdfc78..d53a7d468e366a84636c48aec489598d12e9d0fa 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Build::Factory do
   end
 
   context 'when build is successful' do
-    let(:build) { create(:ci_build, :success) }
+    let(:build) { create(:ci_build, :success, :trace_artifact) }
 
     it 'matches correct core status' do
       expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
@@ -38,6 +38,33 @@ describe Gitlab::Ci::Status::Build::Factory do
     end
   end
 
+  context 'when build is erased' do
+    let(:build) { create(:ci_build, :success, :erased) }
+
+    it 'matches correct core status' do
+      expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
+    end
+
+    it 'matches correct extended statuses' do
+      expect(factory.extended_statuses)
+        .to eq [Gitlab::Ci::Status::Build::Erased,
+                Gitlab::Ci::Status::Build::Retryable]
+    end
+
+    it 'fabricates a retryable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'passed'
+      expect(status.icon).to eq 'status_success'
+      expect(status.favicon).to eq 'favicon_status_success'
+      expect(status.label).to eq 'passed'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
   context 'when build is failed' do
     context 'when build is not allowed to fail' do
       let(:build) { create(:ci_build, :failed) }
@@ -48,11 +75,12 @@ describe Gitlab::Ci::Status::Build::Factory do
 
       it 'matches correct extended statuses' do
         expect(factory.extended_statuses)
-          .to eq [Gitlab::Ci::Status::Build::Retryable]
+          .to eq [Gitlab::Ci::Status::Build::Retryable,
+                  Gitlab::Ci::Status::Build::Failed]
       end
 
-      it 'fabricates a retryable build status' do
-        expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+      it 'fabricates a failed build status' do
+        expect(status).to be_a Gitlab::Ci::Status::Build::Failed
       end
 
       it 'fabricates status with correct details' do
@@ -60,13 +88,14 @@ describe Gitlab::Ci::Status::Build::Factory do
         expect(status.icon).to eq 'status_failed'
         expect(status.favicon).to eq 'favicon_status_failed'
         expect(status.label).to eq 'failed'
+        expect(status.status_tooltip).to eq 'failed <br> (unknown failure)'
         expect(status).to have_details
         expect(status).to have_action
       end
     end
 
     context 'when build is allowed to fail' do
-      let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
+      let(:build) { create(:ci_build, :failed, :allowed_to_fail, :trace_artifact) }
 
       it 'matches correct core status' do
         expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
@@ -75,6 +104,7 @@ describe Gitlab::Ci::Status::Build::Factory do
       it 'matches correct extended statuses' do
         expect(factory.extended_statuses)
           .to eq [Gitlab::Ci::Status::Build::Retryable,
+                  Gitlab::Ci::Status::Build::Failed,
                   Gitlab::Ci::Status::Build::FailedAllowed]
       end
 
@@ -104,7 +134,7 @@ describe Gitlab::Ci::Status::Build::Factory do
 
     it 'matches correct extended statuses' do
       expect(factory.extended_statuses)
-        .to eq [Gitlab::Ci::Status::Build::Retryable]
+        .to eq [Gitlab::Ci::Status::Build::Canceled, Gitlab::Ci::Status::Build::Retryable]
     end
 
     it 'fabricates a retryable build status' do
@@ -115,6 +145,7 @@ describe Gitlab::Ci::Status::Build::Factory do
       expect(status.text).to eq 'canceled'
       expect(status.icon).to eq 'status_canceled'
       expect(status.favicon).to eq 'favicon_status_canceled'
+      expect(status.illustration).to include(:image, :size, :title)
       expect(status.label).to eq 'canceled'
       expect(status).to have_details
       expect(status).to have_action
@@ -156,7 +187,7 @@ describe Gitlab::Ci::Status::Build::Factory do
 
     it 'matches correct extended statuses' do
       expect(factory.extended_statuses)
-        .to eq [Gitlab::Ci::Status::Build::Cancelable]
+        .to eq [Gitlab::Ci::Status::Build::Pending, Gitlab::Ci::Status::Build::Cancelable]
     end
 
     it 'fabricates a cancelable build status' do
@@ -167,6 +198,7 @@ describe Gitlab::Ci::Status::Build::Factory do
       expect(status.text).to eq 'pending'
       expect(status.icon).to eq 'status_pending'
       expect(status.favicon).to eq 'favicon_status_pending'
+      expect(status.illustration).to include(:image, :size, :title, :content)
       expect(status.label).to eq 'pending'
       expect(status).to have_details
       expect(status).to have_action
@@ -180,18 +212,19 @@ describe Gitlab::Ci::Status::Build::Factory do
       expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
     end
 
-    it 'does not match extended statuses' do
-      expect(factory.extended_statuses).to be_empty
+    it 'matches correct extended statuses' do
+      expect(factory.extended_statuses).to eq [Gitlab::Ci::Status::Build::Skipped]
     end
 
-    it 'fabricates a core skipped status' do
-      expect(status).to be_a Gitlab::Ci::Status::Skipped
+    it 'fabricates a skipped build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Skipped
     end
 
     it 'fabricates status with correct details' do
       expect(status.text).to eq 'skipped'
       expect(status.icon).to eq 'status_skipped'
       expect(status.favicon).to eq 'favicon_status_skipped'
+      expect(status.illustration).to include(:image, :size, :title)
       expect(status.label).to eq 'skipped'
       expect(status).to have_details
       expect(status).not_to have_action
@@ -208,7 +241,8 @@ describe Gitlab::Ci::Status::Build::Factory do
 
       it 'matches correct extended statuses' do
         expect(factory.extended_statuses)
-          .to eq [Gitlab::Ci::Status::Build::Play,
+          .to eq [Gitlab::Ci::Status::Build::Manual,
+                  Gitlab::Ci::Status::Build::Play,
                   Gitlab::Ci::Status::Build::Action]
       end
 
@@ -221,6 +255,7 @@ describe Gitlab::Ci::Status::Build::Factory do
         expect(status.group).to eq 'manual'
         expect(status.icon).to eq 'status_manual'
         expect(status.favicon).to eq 'favicon_status_manual'
+        expect(status.illustration).to include(:image, :size, :title, :content)
         expect(status.label).to include 'manual play action'
         expect(status).to have_details
         expect(status.action_path).to include 'play'
@@ -255,7 +290,8 @@ describe Gitlab::Ci::Status::Build::Factory do
 
       it 'matches correct extended statuses' do
         expect(factory.extended_statuses)
-          .to eq [Gitlab::Ci::Status::Build::Stop,
+          .to eq [Gitlab::Ci::Status::Build::Manual,
+                  Gitlab::Ci::Status::Build::Stop,
                   Gitlab::Ci::Status::Build::Action]
       end
 
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
index 99a5a7e4acae8991c1630290e3c8657e9b3dc847..bfaa508785e36b0b512978f5a3617bc67633c7e9 100644
--- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
 describe Gitlab::Ci::Status::Build::FailedAllowed do
   let(:status) { double('core status') }
   let(:user) { double('user') }
+  let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
 
   subject do
     described_class.new(status)
@@ -68,6 +69,28 @@ describe Gitlab::Ci::Status::Build::FailedAllowed do
     end
   end
 
+  describe '#badge_tooltip' do
+    let(:user) { create(:user) }
+    let(:failed_status) { Gitlab::Ci::Status::Failed.new(build, user) }
+    let(:build_status) { Gitlab::Ci::Status::Build::Failed.new(failed_status) }
+    let(:status) { described_class.new(build_status) }
+
+    it 'does override badge_tooltip' do
+      expect(status.badge_tooltip).to eq('failed <br> (unknown failure)')
+    end
+  end
+
+  describe '#status_tooltip' do
+    let(:user) { create(:user) }
+    let(:failed_status) { Gitlab::Ci::Status::Failed.new(build, user) }
+    let(:build_status) { Gitlab::Ci::Status::Build::Failed.new(failed_status) }
+    let(:status) { described_class.new(build_status) }
+
+    it 'does override status_tooltip' do
+      expect(status.status_tooltip).to eq 'failed <br> (unknown failure) (allowed to fail)'
+    end
+  end
+
   describe '.matches?' do
     subject { described_class.matches?(build, user) }
 
diff --git a/spec/lib/gitlab/ci/status/build/failed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cadb424ea2cd122dd73ad43a9b0924325ac62bce
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/failed_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Failed do
+  let(:build) { create(:ci_build, :script_failure) }
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject { described_class.new(status) }
+
+  describe '#text' do
+    it 'does not override status text' do
+      expect(status).to receive(:text)
+
+      subject.text
+    end
+  end
+
+  describe '#icon' do
+    it 'does not override status icon' do
+      expect(status).to receive(:icon)
+
+      subject.icon
+    end
+  end
+
+  describe '#group' do
+    it 'does not override status group' do
+      expect(status).to receive(:group)
+
+      subject.group
+    end
+  end
+
+  describe '#favicon' do
+    it 'does not override status label' do
+      expect(status).to receive(:favicon)
+
+      subject.favicon
+    end
+  end
+
+  describe '#label' do
+    it 'does not override label' do
+      expect(status).to receive(:label)
+
+      subject.label
+    end
+  end
+
+  describe '#badge_tooltip' do
+    let(:user) { create(:user) }
+    let(:status) { Gitlab::Ci::Status::Failed.new(build, user) }
+
+    it 'does override badge_tooltip' do
+      expect(subject.badge_tooltip).to eq 'failed <br> (script failure)'
+    end
+  end
+
+  describe '#status_tooltip' do
+    let(:user) { create(:user) }
+    let(:status) { Gitlab::Ci::Status::Failed.new(build, user) }
+
+    it 'does override status_tooltip' do
+      expect(subject.status_tooltip).to eq 'failed <br> (script failure)'
+    end
+  end
+
+  describe '.matches?' do
+    context 'with a failed build' do
+      it 'returns true' do
+        expect(described_class.matches?(build, user)).to be_truthy
+      end
+    end
+
+    context 'with any other type of build' do
+      let(:build) { create(:ci_build, :success) }
+
+      it 'returns false' do
+        expect(described_class.matches?(build, user)).to be_falsy
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6386296f992bb8939a53548cf4f38effec62b5b6
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Manual do
+  let(:user) { create(:user) }
+
+  subject do
+    build = create(:ci_build, :manual)
+    described_class.new(Gitlab::Ci::Status::Core.new(build, user))
+  end
+
+  describe '#illustration' do
+    it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+  end
+
+  describe '.matches?' do
+    subject {described_class.matches?(build, user) }
+
+    context 'when build is manual' do
+      let(:build) { create(:ci_build, :manual) }
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not manual' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4cf70828e53d9e27dc87f2b8b7715d05889edfaf
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Pending do
+  let(:user) { create(:user) }
+
+  subject do
+    described_class.new(double('subject'))
+  end
+
+  describe '#illustration' do
+    it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+  end
+
+  describe '.matches?' do
+    subject {described_class.matches?(build, user) }
+
+    context 'when build is pending' do
+      let(:build) { create(:ci_build, :pending) }
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not pending' do
+      let(:build) { create(:ci_build, :success) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index 81d5f553fd1d0aca94dad324e45770772ae2068a..f128c1d4ca48420f4082dd8a0711d0531efee8a6 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -14,6 +14,22 @@ describe Gitlab::Ci::Status::Build::Play do
     end
   end
 
+  describe '#status_tooltip' do
+    it 'does not override status status_tooltip' do
+      expect(status).to receive(:status_tooltip)
+
+      subject.status_tooltip
+    end
+  end
+
+  describe '#badge_tooltip' do
+    it 'does not override status badge_tooltip' do
+      expect(status).to receive(:badge_tooltip)
+
+      subject.badge_tooltip
+    end
+  end
+
   describe '#has_action?' do
     context 'when user is allowed to update build' do
       context 'when user is allowed to trigger protected action' do
@@ -53,6 +69,10 @@ describe Gitlab::Ci::Status::Build::Play do
     it { expect(subject.action_title).to eq 'Play' }
   end
 
+  describe '#action_button_title' do
+    it { expect(subject.action_button_title).to eq 'Trigger this manual action' }
+  end
+
   describe '.matches?' do
     subject { described_class.matches?(build, user) }
 
diff --git a/spec/lib/gitlab/ci/status/build/retried_spec.rb b/spec/lib/gitlab/ci/status/build/retried_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ee9acaf1c21943b17260b4d0dbae8dc0b22a9ce0
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/retried_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Retried do
+  let(:build) { create(:ci_build, :retried) }
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject { described_class.new(status) }
+
+  describe '#text' do
+    it 'does not override status text' do
+      expect(status).to receive(:text)
+
+      subject.text
+    end
+  end
+
+  describe '#icon' do
+    it 'does not override status icon' do
+      expect(status).to receive(:icon)
+
+      subject.icon
+    end
+  end
+
+  describe '#group' do
+    it 'does not override status group' do
+      expect(status).to receive(:group)
+
+      subject.group
+    end
+  end
+
+  describe '#favicon' do
+    it 'does not override status label' do
+      expect(status).to receive(:favicon)
+
+      subject.favicon
+    end
+  end
+
+  describe '#label' do
+    it 'does not override status label' do
+      expect(status).to receive(:label)
+
+      subject.label
+    end
+  end
+
+  describe '#badge_tooltip' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build, :retried) }
+    let(:status) { Gitlab::Ci::Status::Success.new(build, user) }
+
+    it 'returns status' do
+      expect(status.badge_tooltip).to eq('pending')
+    end
+  end
+
+  describe '#status_tooltip' do
+    let(:user) { create(:user) }
+
+    context 'with a failed build' do
+      let(:build) { create(:ci_build, :failed, :retried) }
+      let(:failed_status) { Gitlab::Ci::Status::Failed.new(build, user) }
+      let(:status) { Gitlab::Ci::Status::Build::Failed.new(failed_status) }
+
+      it 'does override status_tooltip' do
+        expect(subject.status_tooltip).to eq 'failed <br> (unknown failure) (retried)'
+      end
+    end
+
+    context 'with another build' do
+      let(:build) { create(:ci_build, :retried) }
+      let(:status) { Gitlab::Ci::Status::Success.new(build, user) }
+
+      it 'does override status_tooltip' do
+        expect(subject.status_tooltip).to eq 'passed (retried)'
+      end
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'with a retried build' do
+      it { is_expected.to be_truthy }
+    end
+
+    context 'with a build that has not been retried' do
+      let(:build) { create(:ci_build, :success) }
+
+      it { is_expected.to be_falsy }
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 14d42e0d70f7e4f6ee4f5bc13059d4e877d42cec..84d98588f2d72d0901e3e0a7e24fc3487e716072 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -40,6 +40,24 @@ describe Gitlab::Ci::Status::Build::Retryable do
     end
   end
 
+  describe '#status_tooltip' do
+    it 'does not override status status_tooltip' do
+      expect(status).to receive(:status_tooltip)
+
+      subject.status_tooltip
+    end
+  end
+
+  describe '#badge_tooltip' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    it 'does return status' do
+      expect(status.badge_tooltip).to eq('pending')
+    end
+  end
+
   describe 'action details' do
     let(:user) { create(:user) }
     let(:build) { create(:ci_build) }
@@ -72,6 +90,10 @@ describe Gitlab::Ci::Status::Build::Retryable do
     describe '#action_title' do
       it { expect(subject.action_title).to eq 'Retry' }
     end
+
+    describe '#action_button_title' do
+      it { expect(subject.action_button_title).to eq 'Retry this job' }
+    end
   end
 
   describe '.matches?' do
diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..46f6933025add2ca6bd4d3ae93aefc115ec330b0
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Skipped do
+  let(:user) { create(:user) }
+
+  subject do
+    described_class.new(double('subject'))
+  end
+
+  describe '#illustration' do
+    it { expect(subject.illustration).to include(:image, :size, :title) }
+  end
+
+  describe '.matches?' do
+    subject {described_class.matches?(build, user) }
+
+    context 'when build is skipped' do
+      let(:build) { create(:ci_build, :skipped) }
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not skipped' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index 18e250772f010f315d24c176f9d69217e4ef4083..5b7534c96c1cf6aa2f09d5ea5d7efe38f9ff76d2 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -44,6 +44,10 @@ describe Gitlab::Ci::Status::Build::Stop do
     describe '#action_title' do
       it { expect(subject.action_title).to eq 'Stop' }
     end
+
+    describe '#action_button_title' do
+      it { expect(subject.action_button_title).to eq 'Stop this environment' }
+    end
   end
 
   describe '.matches?' do
@@ -77,4 +81,24 @@ describe Gitlab::Ci::Status::Build::Stop do
       end
     end
   end
+
+  describe '#status_tooltip' do
+    it 'does not override status status_tooltip' do
+      expect(status).to receive(:status_tooltip)
+
+      subject.status_tooltip
+    end
+  end
+
+  describe '#badge_tooltip' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build, :playable) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    it 'does not override status badge_tooltip' do
+      expect(status).to receive(:badge_tooltip)
+
+      subject.badge_tooltip
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index 4582354e739a60e34fc3f101aa9d7c84ce5a8581..6d05545d1d826d63aa3374d2967bae47e64fe245 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -1,8 +1,10 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::SuccessWarning do
+  let(:status) { double('status') }
+
   subject do
-    described_class.new(double('status'))
+    described_class.new(status)
   end
 
   describe '#test' do
diff --git a/spec/lib/gitlab/ci/trace/http_io_spec.rb b/spec/lib/gitlab/ci/trace/http_io_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5474e2f518c761aeb4be55cbe1f6548d2981449b
--- /dev/null
+++ b/spec/lib/gitlab/ci/trace/http_io_spec.rb
@@ -0,0 +1,315 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Trace::HttpIO do
+  include HttpIOHelpers
+
+  let(:http_io) { described_class.new(url, size) }
+  let(:url) { remote_trace_url }
+  let(:size) { remote_trace_size }
+
+  describe '#close' do
+    subject { http_io.close }
+
+    it { is_expected.to be_nil }
+  end
+
+  describe '#binmode' do
+    subject { http_io.binmode }
+
+    it { is_expected.to be_nil }
+  end
+
+  describe '#binmode?' do
+    subject { http_io.binmode? }
+
+    it { is_expected.to be_truthy }
+  end
+
+  describe '#path' do
+    subject { http_io.path }
+
+    it { is_expected.to be_nil }
+  end
+
+  describe '#url' do
+    subject { http_io.url }
+
+    it { is_expected.to eq(url) }
+  end
+
+  describe '#seek' do
+    subject { http_io.seek(pos, where) }
+
+    context 'when moves pos to end of the file' do
+      let(:pos) { 0 }
+      let(:where) { IO::SEEK_END }
+
+      it { is_expected.to eq(size) }
+    end
+
+    context 'when moves pos to middle of the file' do
+      let(:pos) { size / 2 }
+      let(:where) { IO::SEEK_SET }
+
+      it { is_expected.to eq(size / 2) }
+    end
+
+    context 'when moves pos around' do
+      it 'matches the result' do
+        expect(http_io.seek(0)).to eq(0)
+        expect(http_io.seek(100, IO::SEEK_CUR)).to eq(100)
+        expect { http_io.seek(size + 1, IO::SEEK_CUR) }.to raise_error('new position is outside of file')
+      end
+    end
+  end
+
+  describe '#eof?' do
+    subject { http_io.eof? }
+
+    context 'when current pos is at end of the file' do
+      before do
+        http_io.seek(size, IO::SEEK_SET)
+      end
+
+      it { is_expected.to be_truthy }
+    end
+
+    context 'when current pos is not at end of the file' do
+      before do
+        http_io.seek(0, IO::SEEK_SET)
+      end
+
+      it { is_expected.to be_falsey }
+    end
+  end
+
+  describe '#each_line' do
+    subject { http_io.each_line }
+
+    let(:string_io) { StringIO.new(remote_trace_body) }
+
+    before do
+      stub_remote_trace_206
+    end
+
+    it 'yields lines' do
+      expect { |b| http_io.each_line(&b) }.to yield_successive_args(*string_io.each_line.to_a)
+    end
+
+    context 'when buckets on GCS' do
+      context 'when BUFFER_SIZE is larger than file size' do
+        before do
+          stub_remote_trace_200
+          set_larger_buffer_size_than(size)
+        end
+
+        it 'calls get_chunk only once' do
+          expect_any_instance_of(Net::HTTP).to receive(:request).once.and_call_original
+
+          http_io.each_line { |line| }
+        end
+      end
+    end
+  end
+
+  describe '#read' do
+    subject { http_io.read(length) }
+
+    context 'when there are no network issue' do
+      before do
+        stub_remote_trace_206
+      end
+
+      context 'when read whole size' do
+        let(:length) { nil }
+
+        context 'when BUFFER_SIZE is smaller than file size' do
+          before do
+            set_smaller_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to eq(remote_trace_body)
+          end
+        end
+
+        context 'when BUFFER_SIZE is larger than file size' do
+          before do
+            set_larger_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to eq(remote_trace_body)
+          end
+        end
+      end
+
+      context 'when read only first 100 bytes' do
+        let(:length) { 100 }
+
+        context 'when BUFFER_SIZE is smaller than file size' do
+          before do
+            set_smaller_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to eq(remote_trace_body[0, length])
+          end
+        end
+
+        context 'when BUFFER_SIZE is larger than file size' do
+          before do
+            set_larger_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to eq(remote_trace_body[0, length])
+          end
+        end
+      end
+
+      context 'when tries to read oversize' do
+        let(:length) { size + 1000 }
+
+        context 'when BUFFER_SIZE is smaller than file size' do
+          before do
+            set_smaller_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to eq(remote_trace_body)
+          end
+        end
+
+        context 'when BUFFER_SIZE is larger than file size' do
+          before do
+            set_larger_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to eq(remote_trace_body)
+          end
+        end
+      end
+
+      context 'when tries to read 0 bytes' do
+        let(:length) { 0 }
+
+        context 'when BUFFER_SIZE is smaller than file size' do
+          before do
+            set_smaller_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to be_empty
+          end
+        end
+
+        context 'when BUFFER_SIZE is larger than file size' do
+          before do
+            set_larger_buffer_size_than(size)
+          end
+
+          it 'reads a trace' do
+            is_expected.to be_empty
+          end
+        end
+      end
+    end
+
+    context 'when there is anetwork issue' do
+      let(:length) { nil }
+
+      before do
+        stub_remote_trace_500
+      end
+
+      it 'reads a trace' do
+        expect { subject }.to raise_error(Gitlab::Ci::Trace::HttpIO::FailedToGetChunkError)
+      end
+    end
+  end
+
+  describe '#readline' do
+    subject { http_io.readline }
+
+    let(:string_io) { StringIO.new(remote_trace_body) }
+
+    before do
+      stub_remote_trace_206
+    end
+
+    shared_examples 'all line matching' do
+      it 'reads a line' do
+        (0...remote_trace_body.lines.count).each do
+          expect(http_io.readline).to eq(string_io.readline)
+        end
+      end
+    end
+
+    context 'when there is anetwork issue' do
+      let(:length) { nil }
+
+      before do
+        stub_remote_trace_500
+      end
+
+      it 'reads a trace' do
+        expect { subject }.to raise_error(Gitlab::Ci::Trace::HttpIO::FailedToGetChunkError)
+      end
+    end
+
+    context 'when BUFFER_SIZE is smaller than file size' do
+      before do
+        set_smaller_buffer_size_than(size)
+      end
+
+      it_behaves_like 'all line matching'
+    end
+
+    context 'when BUFFER_SIZE is larger than file size' do
+      before do
+        set_larger_buffer_size_than(size)
+      end
+
+      it_behaves_like 'all line matching'
+    end
+
+    context 'when pos is at middle of the file' do
+      before do
+        set_smaller_buffer_size_than(size)
+
+        http_io.seek(size / 2)
+        string_io.seek(size / 2)
+      end
+
+      it 'reads from pos' do
+        expect(http_io.readline).to eq(string_io.readline)
+      end
+    end
+  end
+
+  describe '#write' do
+    subject { http_io.write(nil) }
+
+    it { expect { subject }.to raise_error(NotImplementedError) }
+  end
+
+  describe '#truncate' do
+    subject { http_io.truncate(nil) }
+
+    it { expect { subject }.to raise_error(NotImplementedError) }
+  end
+
+  describe '#flush' do
+    subject { http_io.flush }
+
+    it { expect { subject }.to raise_error(NotImplementedError) }
+  end
+
+  describe '#present?' do
+    subject { http_io.present? }
+
+    it { is_expected.to be_truthy }
+  end
+end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 91c9625ba0666bc1bd256b483699e9bc072fb2d5..6a9c6442282ad2e2a7af9f628752dfc7baa1e92a 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -399,4 +399,162 @@ describe Gitlab::Ci::Trace do
       end
     end
   end
+
+  describe '#archive!' do
+    subject { trace.archive! }
+
+    shared_examples 'archive trace file' do
+      it do
+        expect { subject }.to change { Ci::JobArtifact.count }.by(1)
+
+        build.reload
+        expect(build.trace.exist?).to be_truthy
+        expect(build.job_artifacts_trace.file.exists?).to be_truthy
+        expect(build.job_artifacts_trace.file.filename).to eq('job.log')
+        expect(File.exist?(src_path)).to be_falsy
+        expect(src_checksum)
+          .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+        expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
+      end
+    end
+
+    shared_examples 'source trace file stays intact' do |error:|
+      it do
+        expect { subject }.to raise_error(error)
+
+        build.reload
+        expect(build.trace.exist?).to be_truthy
+        expect(build.job_artifacts_trace).to be_nil
+        expect(File.exist?(src_path)).to be_truthy
+      end
+    end
+
+    shared_examples 'archive trace in database' do
+      it do
+        expect { subject }.to change { Ci::JobArtifact.count }.by(1)
+
+        build.reload
+        expect(build.trace.exist?).to be_truthy
+        expect(build.job_artifacts_trace.file.exists?).to be_truthy
+        expect(build.job_artifacts_trace.file.filename).to eq('job.log')
+        expect(build.old_trace).to be_nil
+        expect(src_checksum)
+          .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+        expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
+      end
+    end
+
+    shared_examples 'source trace in database stays intact' do |error:|
+      it do
+        expect { subject }.to raise_error(error)
+
+        build.reload
+        expect(build.trace.exist?).to be_truthy
+        expect(build.job_artifacts_trace).to be_nil
+        expect(build.old_trace).to eq(trace_content)
+      end
+    end
+
+    context 'when job does not have trace artifact' do
+      context 'when trace file stored in default path' do
+        let!(:build) { create(:ci_build, :success, :trace_live) }
+        let!(:src_path) { trace.read { |s| s.path } }
+        let!(:src_checksum) { Digest::SHA256.file(src_path).hexdigest }
+
+        it_behaves_like 'archive trace file'
+
+        context 'when failed to create clone file' do
+          before do
+            allow(IO).to receive(:copy_stream).and_return(0)
+          end
+
+          it_behaves_like 'source trace file stays intact', error: Gitlab::Ci::Trace::ArchiveError
+        end
+
+        context 'when failed to create job artifact record' do
+          before do
+            allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false)
+            allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages)
+              .and_return(%w[Error Error])
+          end
+
+          it_behaves_like 'source trace file stays intact', error: ActiveRecord::RecordInvalid
+        end
+      end
+
+      context 'when trace is stored in database' do
+        let(:build) { create(:ci_build, :success) }
+        let(:trace_content) { 'Sample trace' }
+        let!(:src_checksum) { Digest::SHA256.hexdigest(trace_content) }
+
+        before do
+          build.update_column(:trace, trace_content)
+        end
+
+        it_behaves_like 'archive trace in database'
+
+        context 'when failed to create clone file' do
+          before do
+            allow(IO).to receive(:copy_stream).and_return(0)
+          end
+
+          it_behaves_like 'source trace in database stays intact', error: Gitlab::Ci::Trace::ArchiveError
+        end
+
+        context 'when failed to create job artifact record' do
+          before do
+            allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false)
+            allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages)
+              .and_return(%w[Error Error])
+          end
+
+          it_behaves_like 'source trace in database stays intact', error: ActiveRecord::RecordInvalid
+        end
+
+        context 'when there is a validation error on Ci::Build' do
+          before do
+            allow_any_instance_of(Ci::Build).to receive(:save).and_return(false)
+            allow_any_instance_of(Ci::Build).to receive_message_chain(:errors, :full_messages)
+              .and_return(%w[Error Error])
+          end
+
+          context "when erase old trace with 'save'" do
+            before do
+              build.send(:write_attribute, :trace, nil)
+              build.save
+            end
+
+            it 'old trace is not deleted' do
+              build.reload
+              expect(build.trace.raw).to eq(trace_content)
+            end
+          end
+
+          it_behaves_like 'archive trace in database'
+        end
+      end
+    end
+
+    context 'when job has trace artifact' do
+      before do
+        create(:ci_job_artifact, :trace, job: build)
+      end
+
+      it 'does not archive' do
+        expect_any_instance_of(described_class).not_to receive(:archive_stream!)
+        expect { subject }.to raise_error('Already archived')
+        expect(build.job_artifacts_trace.file.exists?).to be_truthy
+      end
+    end
+
+    context 'when job is not finished yet' do
+      let!(:build) { create(:ci_build, :running, :trace_live) }
+
+      it 'does not archive' do
+        expect_any_instance_of(described_class).not_to receive(:archive_stream!)
+        expect { subject }.to raise_error('Job is not finished yet')
+        expect(build.trace.exist?).to be_truthy
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e79f0a7f257f7addcce17afe11d07273c57c3300
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Variables::Collection::Item do
+  let(:variable) do
+    { key: 'VAR', value: 'something', public: true }
+  end
+
+  describe '.new' do
+    it 'raises error if unknown key i specified' do
+      expect { described_class.new(key: 'VAR', value: 'abc', files: true) }
+        .to raise_error ArgumentError, 'unknown keyword: files'
+    end
+
+    it 'raises error when required keywords are not specified' do
+      expect { described_class.new(key: 'VAR') }
+        .to raise_error ArgumentError, 'missing keyword: value'
+    end
+  end
+
+  describe '.fabricate' do
+    it 'supports using a hash' do
+      resource = described_class.fabricate(variable)
+
+      expect(resource).to be_a(described_class)
+      expect(resource).to eq variable
+    end
+
+    it 'supports using an active record resource' do
+      variable = create(:ci_variable, key: 'CI_VAR', value: '123')
+      resource = described_class.fabricate(variable)
+
+      expect(resource).to be_a(described_class)
+      expect(resource).to eq(key: 'CI_VAR', value: '123', public: false)
+    end
+
+    it 'supports using another collection item' do
+      item = described_class.new(**variable)
+
+      resource = described_class.fabricate(item)
+
+      expect(resource).to be_a(described_class)
+      expect(resource).to eq variable
+      expect(resource.object_id).not_to eq item.object_id
+    end
+  end
+
+  describe '#==' do
+    it 'compares a hash representation of a variable' do
+      expect(described_class.new(**variable) == variable).to be true
+    end
+  end
+
+  describe '#[]' do
+    it 'behaves like a hash accessor' do
+      item = described_class.new(**variable)
+
+      expect(item[:key]).to eq 'VAR'
+    end
+  end
+
+  describe '#to_runner_variable' do
+    context 'when variable is not a file-related' do
+      it 'returns a runner-compatible hash representation' do
+        runner_variable = described_class
+          .new(**variable)
+          .to_runner_variable
+
+        expect(runner_variable).to eq variable
+      end
+    end
+
+    context 'when variable is file-related' do
+      it 'appends file description component' do
+        runner_variable = described_class
+          .new(key: 'VAR', value: 'value', file: true)
+          .to_runner_variable
+
+        expect(runner_variable)
+          .to eq(key: 'VAR', value: 'value', public: true, file: true)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cb2f7718c9cf67770da45a5625d341ddd2497df2
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Variables::Collection do
+  describe '.new' do
+    it 'can be initialized with an array' do
+      variable = { key: 'VAR', value: 'value', public: true }
+
+      collection = described_class.new([variable])
+
+      expect(collection.first.to_runner_variable).to eq variable
+    end
+
+    it 'can be initialized without an argument' do
+      expect(subject).to be_none
+    end
+  end
+
+  describe '#append' do
+    it 'appends a hash' do
+      subject.append(key: 'VARIABLE', value: 'something')
+
+      expect(subject).to be_one
+    end
+
+    it 'appends a Ci::Variable' do
+      subject.append(build(:ci_variable))
+
+      expect(subject).to be_one
+    end
+
+    it 'appends an internal resource' do
+      collection = described_class.new([{ key: 'TEST', value: 1 }])
+
+      subject.append(collection.first)
+
+      expect(subject).to be_one
+    end
+
+    it 'returns self' do
+      expect(subject.append(key: 'VAR', value: 'test'))
+        .to eq subject
+    end
+  end
+
+  describe '#concat' do
+    it 'appends all elements from an array' do
+      collection = described_class.new([{ key: 'VAR_1', value: '1' }])
+      variables = [{ key: 'VAR_2', value: '2' }, { key: 'VAR_3', value: '3' }]
+
+      collection.concat(variables)
+
+      expect(collection).to include(key: 'VAR_1', value: '1', public: true)
+      expect(collection).to include(key: 'VAR_2', value: '2', public: true)
+      expect(collection).to include(key: 'VAR_3', value: '3', public: true)
+    end
+
+    it 'appends all elements from other collection' do
+      collection = described_class.new([{ key: 'VAR_1', value: '1' }])
+      additional = described_class.new([{ key: 'VAR_2', value: '2' },
+                                        { key: 'VAR_3', value: '3' }])
+
+      collection.concat(additional)
+
+      expect(collection).to include(key: 'VAR_1', value: '1', public: true)
+      expect(collection).to include(key: 'VAR_2', value: '2', public: true)
+      expect(collection).to include(key: 'VAR_3', value: '3', public: true)
+    end
+
+    it 'returns self' do
+      expect(subject.concat([key: 'VAR', value: 'test']))
+        .to eq subject
+    end
+  end
+
+  describe '#+' do
+    it 'makes it possible to combine with an array' do
+      collection = described_class.new([{ key: 'TEST', value: 1 }])
+      variables = [{ key: 'TEST', value: 'something' }]
+
+      expect((collection + variables).count).to eq 2
+    end
+
+    it 'makes it possible to combine with another collection' do
+      collection = described_class.new([{ key: 'TEST', value: 1 }])
+      other = described_class.new([{ key: 'TEST', value: 2 }])
+
+      expect((collection + other).count).to eq 2
+    end
+  end
+
+  describe '#to_runner_variables' do
+    it 'creates an array of hashes in a runner-compatible format' do
+      collection = described_class.new([{ key: 'TEST', value: 1 }])
+
+      expect(collection.to_runner_variables)
+        .to eq [{ key: 'TEST', value: 1, public: true }]
+    end
+  end
+
+  describe '#to_hash' do
+    it 'returns regular hash in valid order without duplicates' do
+      collection = described_class.new
+        .append(key: 'TEST1', value: 'test-1')
+        .append(key: 'TEST2', value: 'test-2')
+        .append(key: 'TEST1', value: 'test-3')
+
+      expect(collection.to_hash).to eq('TEST1' => 'test-3',
+                                       'TEST2' => 'test-2')
+
+      expect(collection.to_hash).to include(TEST1: 'test-3')
+      expect(collection.to_hash).not_to include(TEST1: 'test-1')
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index f83f932e61e5fb15f8aaaaea0444f752ece7d304..ecb16daec960a19e119ab7d9020c38efad3a41bf 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -18,6 +18,34 @@ module Gitlab
       describe '#build_attributes' do
         subject { described_class.new(config).build_attributes(:rspec) }
 
+        describe 'attributes list' do
+          let(:config) do
+            YAML.dump(
+              before_script: ['pwd'],
+              rspec: { script: 'rspec' }
+            )
+          end
+
+          it 'returns valid build attributes' do
+            expect(subject).to eq({
+              stage: "test",
+              stage_idx: 1,
+              name: "rspec",
+              commands: "pwd\nrspec",
+              coverage_regex: nil,
+              tag_list: [],
+              options: {
+                before_script: ["pwd"],
+                script: ["rspec"]
+              },
+              allow_failure: false,
+              when: "on_success",
+              environment: nil,
+              yaml_variables: []
+            })
+          end
+        end
+
         describe 'coverage entry' do
           describe 'code coverage regexp' do
             let(:config) do
@@ -105,512 +133,118 @@ module Gitlab
         end
       end
 
-      describe '#stage_seeds' do
-        context 'when no refs policy is specified' do
-          let(:config) do
-            YAML.dump(production: { stage: 'deploy', script: 'cap prod' },
-                      rspec: { stage: 'test', script: 'rspec' },
-                      spinach: { stage: 'test', script: 'spinach' })
-          end
-
-          let(:pipeline) { create(:ci_empty_pipeline) }
-
-          it 'correctly fabricates a stage seeds object' do
-            seeds = subject.stage_seeds(pipeline)
-
-            expect(seeds.size).to eq 2
-            expect(seeds.first.stage[:name]).to eq 'test'
-            expect(seeds.second.stage[:name]).to eq 'deploy'
-            expect(seeds.first.builds.dig(0, :name)).to eq 'rspec'
-            expect(seeds.first.builds.dig(1, :name)).to eq 'spinach'
-            expect(seeds.second.builds.dig(0, :name)).to eq 'production'
-          end
-        end
-
-        context 'when refs policy is specified' do
-          let(:config) do
-            YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
-                      spinach: { stage: 'test', script: 'spinach', only: ['tags'] })
-          end
-
-          let(:pipeline) do
-            create(:ci_empty_pipeline, ref: 'feature', tag: true)
-          end
-
-          it 'returns stage seeds only assigned to master to master' do
-            seeds = subject.stage_seeds(pipeline)
-
-            expect(seeds.size).to eq 1
-            expect(seeds.first.stage[:name]).to eq 'test'
-            expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
-          end
-        end
-
-        context 'when source policy is specified' do
-          let(:config) do
-            YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
-                      spinach: { stage: 'test', script: 'spinach', only: ['schedules'] })
-          end
-
-          let(:pipeline) do
-            create(:ci_empty_pipeline, source: :schedule)
-          end
-
-          it 'returns stage seeds only assigned to schedules' do
-            seeds = subject.stage_seeds(pipeline)
-
-            expect(seeds.size).to eq 1
-            expect(seeds.first.stage[:name]).to eq 'test'
-            expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
-          end
+      describe '#stages_attributes' do
+        let(:config) do
+          YAML.dump(
+            rspec: { script: 'rspec', stage: 'test', only: ['branches'] },
+            prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] }
+          )
         end
 
-        context 'when kubernetes policy is specified' do
-          let(:config) do
-            YAML.dump(
-              spinach: { stage: 'test', script: 'spinach' },
-              production: {
-                stage: 'deploy',
-                script: 'cap',
-                only: { kubernetes: 'active' }
-              }
-            )
-          end
-
-          context 'when kubernetes is active' do
-            shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
-              it 'returns seeds for kubernetes dependent job' do
-                seeds = subject.stage_seeds(pipeline)
-
-                expect(seeds.size).to eq 2
-                expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
-                expect(seeds.second.builds.dig(0, :name)).to eq 'production'
-              end
-            end
-
-            context 'when user configured kubernetes from Integration > Kubernetes' do
-              let(:project) { create(:kubernetes_project) }
-              let(:pipeline) { create(:ci_empty_pipeline, project: project) }
-
-              it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
-            end
-
-            context 'when user configured kubernetes from CI/CD > Clusters' do
-              let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
-              let(:project) { cluster.project }
-              let(:pipeline) { create(:ci_empty_pipeline, project: project) }
-
-              it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
-            end
-          end
-
-          context 'when kubernetes is not active' do
-            it 'does not return seeds for kubernetes dependent job' do
-              seeds = subject.stage_seeds(pipeline)
-
-              expect(seeds.size).to eq 1
-              expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
-            end
-          end
+        let(:attributes) do
+          [{ name: "build",
+             index: 0,
+             builds: [] },
+           { name: "test",
+             index: 1,
+             builds:
+               [{ stage_idx: 1,
+                  stage: "test",
+                  commands: "rspec",
+                  tag_list: [],
+                  name: "rspec",
+                  allow_failure: false,
+                  when: "on_success",
+                  environment: nil,
+                  coverage_regex: nil,
+                  yaml_variables: [],
+                  options: { script: ["rspec"] },
+                  only: { refs: ["branches"] },
+                  except: {} }] },
+           { name: "deploy",
+             index: 2,
+             builds:
+               [{ stage_idx: 2,
+                  stage: "deploy",
+                  commands: "cap prod",
+                  tag_list: [],
+                  name: "prod",
+                  allow_failure: false,
+                  when: "on_success",
+                  environment: nil,
+                  coverage_regex: nil,
+                  yaml_variables: [],
+                  options: { script: ["cap prod"] },
+                  only: { refs: ["tags"] },
+                  except: {} }] }]
+        end
+
+        it 'returns stages seed attributes' do
+          expect(subject.stages_attributes).to eq attributes
         end
       end
 
-      describe "#pipeline_stage_builds" do
-        let(:type) { 'test' }
-
-        it "returns builds if no branch specified" do
-          config = YAML.dump({
-            before_script: ["pwd"],
-            rspec: { script: "rspec" }
-          })
-
-          config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-          expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
-          expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).first).to eq({
-            stage: "test",
-            stage_idx: 1,
-            name: "rspec",
-            commands: "pwd\nrspec",
-            coverage_regex: nil,
-            tag_list: [],
-            options: {
-              before_script: ["pwd"],
-              script: ["rspec"]
-            },
-            allow_failure: false,
-            when: "on_success",
-            environment: nil,
-            yaml_variables: []
-          })
-        end
-
-        describe 'only' do
-          it "does not return builds if only has another branch" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", only: ["deploy"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0)
-          end
-
-          it "does not return builds if only has regexp with another branch" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", only: ["/^deploy$/"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0)
-          end
-
-          it "returns builds if only has specified this branch" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", only: ["master"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
-          end
-
-          it "returns builds if only has a list of branches including specified" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, only: %w(master deploy) }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
-          end
-
-          it "returns builds if only has a branches keyword specified" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, only: ["branches"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
-          end
-
-          it "does not return builds if only has a tags keyword" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, only: ["tags"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
-          end
-
-          it "returns builds if only has special keywords specified and source matches" do
-            possibilities = [{ keyword: 'pushes', source: 'push' },
-                             { keyword: 'web', source: 'web' },
-                             { keyword: 'triggers', source: 'trigger' },
-                             { keyword: 'schedules', source: 'schedule' },
-                             { keyword: 'api', source: 'api' },
-                             { keyword: 'external', source: 'external' }]
-
-            possibilities.each do |possibility|
-              config = YAML.dump({
-                                   before_script: ["pwd"],
-                                   rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
-                                 })
+      describe 'only / except policies validations' do
+        context 'when `only` has an invalid value' do
+          let(:config) { { rspec: { script: "rspec", type: "test", only: only } } }
+          let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
 
-              config_processor = Gitlab::Ci::YamlProcessor.new(config)
+          context 'when it is integer' do
+            let(:only) { 1 }
 
-              expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(1)
+            it do
+              expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+                                                  'jobs:rspec:only has to be either an array of conditions or a hash')
             end
           end
 
-          it "does not return builds if only has special keywords specified and source doesn't match" do
-            possibilities = [{ keyword: 'pushes', source: 'web' },
-                             { keyword: 'web', source: 'push' },
-                             { keyword: 'triggers', source: 'schedule' },
-                             { keyword: 'schedules', source: 'external' },
-                             { keyword: 'api', source: 'trigger' },
-                             { keyword: 'external', source: 'api' }]
-
-            possibilities.each do |possibility|
-              config = YAML.dump({
-                                   before_script: ["pwd"],
-                                   rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
-                                 })
+          context 'when it is an array of integers' do
+            let(:only) { [1, 1] }
 
-              config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-              expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(0)
+            it do
+              expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+                                                  'jobs:rspec:only config should be an array of strings or regexps')
             end
           end
 
-          it "returns builds if only has current repository path" do
-            seed_pipeline = pipeline(ref: 'deploy')
-
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: {
-                                   script: "rspec",
-                                   type: type,
-                                   only: ["branches@#{seed_pipeline.project_full_path}"]
-                                 }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, seed_pipeline).size).to eq(1)
-          end
-
-          it "does not return builds if only has different repository path" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, only: ["branches@fork"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
-          end
-
-          it "returns build only for specified type" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: "test", only: %w(master deploy) },
-                                 staging: { script: "deploy", type: "deploy", only: %w(master deploy) },
-                                 production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds("deploy", pipeline(ref: "deploy")).size).to eq(2)
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "deploy")).size).to eq(1)
-            expect(config_processor.pipeline_stage_builds("deploy", pipeline(ref: "master")).size).to eq(1)
-          end
-
-          context 'for invalid value' do
-            let(:config) { { rspec: { script: "rspec", type: "test", only: only } } }
-            let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
-
-            context 'when it is integer' do
-              let(:only) { 1 }
-
-              it do
-                expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
-                                                    'jobs:rspec:only has to be either an array of conditions or a hash')
-              end
-            end
-
-            context 'when it is an array of integers' do
-              let(:only) { [1, 1] }
+          context 'when it is invalid regex' do
+            let(:only) { ["/*invalid/"] }
 
-              it do
-                expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
-                                                    'jobs:rspec:only config should be an array of strings or regexps')
-              end
-            end
-
-            context 'when it is invalid regex' do
-              let(:only) { ["/*invalid/"] }
-
-              it do
-                expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
-                                                    'jobs:rspec:only config should be an array of strings or regexps')
-              end
+            it do
+              expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+                                                  'jobs:rspec:only config should be an array of strings or regexps')
             end
           end
         end
 
-        describe 'except' do
-          it "returns builds if except has another branch" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", except: ["deploy"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
-          end
-
-          it "returns builds if except has regexp with another branch" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", except: ["/^deploy$/"] }
-                               })
+        context 'when `except` has an invalid value' do
+          let(:config) { { rspec: { script: "rspec", except: except } } }
+          let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
 
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1)
-          end
-
-          it "does not return builds if except has specified this branch" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", except: ["master"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0)
-          end
-
-          it "does not return builds if except has a list of branches including specified" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, except: %w(master deploy) }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
+          context 'when it is integer' do
+            let(:except) { 1 }
 
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
-          end
-
-          it "does not return builds if except has a branches keyword specified" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, except: ["branches"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0)
-          end
-
-          it "returns builds if except has a tags keyword" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, except: ["tags"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
-          end
-
-          it "does not return builds if except has special keywords specified and source matches" do
-            possibilities = [{ keyword: 'pushes', source: 'push' },
-                             { keyword: 'web', source: 'web' },
-                             { keyword: 'triggers', source: 'trigger' },
-                             { keyword: 'schedules', source: 'schedule' },
-                             { keyword: 'api', source: 'api' },
-                             { keyword: 'external', source: 'external' }]
-
-            possibilities.each do |possibility|
-              config = YAML.dump({
-                                   before_script: ["pwd"],
-                                   rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
-                                 })
-
-              config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-              expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(0)
+            it do
+              expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+                                                  'jobs:rspec:except has to be either an array of conditions or a hash')
             end
           end
 
-          it "returns builds if except has special keywords specified and source doesn't match" do
-            possibilities = [{ keyword: 'pushes', source: 'web' },
-                             { keyword: 'web', source: 'push' },
-                             { keyword: 'triggers', source: 'schedule' },
-                             { keyword: 'schedules', source: 'external' },
-                             { keyword: 'api', source: 'trigger' },
-                             { keyword: 'external', source: 'api' }]
-
-            possibilities.each do |possibility|
-              config = YAML.dump({
-                                   before_script: ["pwd"],
-                                   rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
-                                 })
-
-              config_processor = Gitlab::Ci::YamlProcessor.new(config)
+          context 'when it is an array of integers' do
+            let(:except) { [1, 1] }
 
-              expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(1)
+            it do
+              expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+                                                  'jobs:rspec:except config should be an array of strings or regexps')
             end
           end
 
-          it "does not return builds if except has current repository path" do
-            seed_pipeline = pipeline(ref: 'deploy')
+          context 'when it is invalid regex' do
+            let(:except) { ["/*invalid/"] }
 
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: {
-                                   script: "rspec",
-                                   type: type,
-                                   except: ["branches@#{seed_pipeline.project_full_path}"]
-                                 }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, seed_pipeline).size).to eq(0)
-          end
-
-          it "returns builds if except has different repository path" do
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: type, except: ["branches@fork"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1)
-          end
-
-          it "returns build except specified type" do
-            master_pipeline = pipeline(ref: 'master')
-            test_pipeline = pipeline(ref: 'test')
-            deploy_pipeline = pipeline(ref: 'deploy')
-
-            config = YAML.dump({
-                                 before_script: ["pwd"],
-                                 rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@#{test_pipeline.project_full_path}"] },
-                                 staging: { script: "deploy", type: "deploy", except: ["master"] },
-                                 production: { script: "deploy", type: "deploy", except: ["master@#{master_pipeline.project_full_path}"] }
-                               })
-
-            config_processor = Gitlab::Ci::YamlProcessor.new(config)
-
-            expect(config_processor.pipeline_stage_builds("deploy", deploy_pipeline).size).to eq(2)
-            expect(config_processor.pipeline_stage_builds("test", test_pipeline).size).to eq(0)
-            expect(config_processor.pipeline_stage_builds("deploy", master_pipeline).size).to eq(0)
-          end
-
-          context 'for invalid value' do
-            let(:config) { { rspec: { script: "rspec", except: except } } }
-            let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
-
-            context 'when it is integer' do
-              let(:except) { 1 }
-
-              it do
-                expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
-                                                    'jobs:rspec:except has to be either an array of conditions or a hash')
-              end
-            end
-
-            context 'when it is an array of integers' do
-              let(:except) { [1, 1] }
-
-              it do
-                expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
-                                                    'jobs:rspec:except config should be an array of strings or regexps')
-              end
-            end
-
-            context 'when it is invalid regex' do
-              let(:except) { ["/*invalid/"] }
-
-              it do
-                expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
-                                                    'jobs:rspec:except config should be an array of strings or regexps')
-              end
+            it do
+              expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+                                                  'jobs:rspec:except config should be an array of strings or regexps')
             end
           end
         end
@@ -620,7 +254,7 @@ module Gitlab
         let(:config_data) { YAML.dump(config) }
         let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config_data) }
 
-        subject { config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first }
+        subject { config_processor.stage_builds_attributes('test').first }
 
         describe "before_script" do
           context "in global context" do
@@ -703,8 +337,8 @@ module Gitlab
 
             config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
+            expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+            expect(config_processor.stage_builds_attributes("test").first).to eq({
               stage: "test",
               stage_idx: 1,
               name: "rspec",
@@ -738,8 +372,8 @@ module Gitlab
 
             config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
+            expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+            expect(config_processor.stage_builds_attributes("test").first).to eq({
               stage: "test",
               stage_idx: 1,
               name: "rspec",
@@ -771,8 +405,8 @@ module Gitlab
 
             config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
+            expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+            expect(config_processor.stage_builds_attributes("test").first).to eq({
               stage: "test",
               stage_idx: 1,
               name: "rspec",
@@ -800,8 +434,8 @@ module Gitlab
 
             config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-            expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
+            expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+            expect(config_processor.stage_builds_attributes("test").first).to eq({
               stage: "test",
               stage_idx: 1,
               name: "rspec",
@@ -946,8 +580,8 @@ module Gitlab
                                })
 
             config_processor = Gitlab::Ci::YamlProcessor.new(config)
+            builds = config_processor.stage_builds_attributes("test")
 
-            builds = config_processor.pipeline_stage_builds("test", pipeline(ref: "master"))
             expect(builds.size).to eq(1)
             expect(builds.first[:when]).to eq(when_state)
           end
@@ -978,8 +612,8 @@ module Gitlab
 
           config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq(
+          expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+          expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq(
             paths: ["logs/", "binaries/"],
             untracked: true,
             key: 'key',
@@ -997,8 +631,8 @@ module Gitlab
 
           config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq(
+          expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+          expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq(
             paths: ["logs/", "binaries/"],
             untracked: true,
             key: 'key',
@@ -1017,8 +651,8 @@ module Gitlab
 
           config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq(
+          expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+          expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq(
             paths: ["test/"],
             untracked: false,
             key: 'local',
@@ -1046,8 +680,8 @@ module Gitlab
 
           config_processor = Gitlab::Ci::YamlProcessor.new(config)
 
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1)
-          expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({
+          expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+          expect(config_processor.stage_builds_attributes("test").first).to eq({
             stage: "test",
             stage_idx: 1,
             name: "rspec",
@@ -1083,8 +717,8 @@ module Gitlab
                                })
 
             config_processor = Gitlab::Ci::YamlProcessor.new(config)
+            builds = config_processor.stage_builds_attributes("test")
 
-            builds = config_processor.pipeline_stage_builds("test", pipeline(ref: "master"))
             expect(builds.size).to eq(1)
             expect(builds.first[:options][:artifacts][:when]).to eq(when_state)
           end
@@ -1099,7 +733,7 @@ module Gitlab
         end
 
         let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
-        let(:builds) { processor.pipeline_stage_builds('deploy', pipeline(ref: 'master')) }
+        let(:builds) { processor.stage_builds_attributes('deploy') }
 
         context 'when a production environment is specified' do
           let(:environment) { 'production' }
@@ -1256,7 +890,7 @@ module Gitlab
 
       describe "Hidden jobs" do
         let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
-        subject { config_processor.pipeline_stage_builds("test", pipeline(ref: "master")) }
+        subject { config_processor.stage_builds_attributes("test") }
 
         shared_examples 'hidden_job_handling' do
           it "doesn't create jobs that start with dot" do
@@ -1304,7 +938,7 @@ module Gitlab
 
       describe "YAML Alias/Anchor" do
         let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
-        subject { config_processor.pipeline_stage_builds("build", pipeline(ref: "master")) }
+        subject { config_processor.stage_builds_attributes("build") }
 
         shared_examples 'job_templates_handling' do
           it "is correctly supported for jobs" do
@@ -1344,13 +978,13 @@ module Gitlab
 
         context 'when template is a job' do
           let(:config) do
-            <<EOT
-job1: &JOBTMPL
-  stage: build
-  script: execute-script-for-job
+            <<~EOT
+            job1: &JOBTMPL
+              stage: build
+              script: execute-script-for-job
 
-job2: *JOBTMPL
-EOT
+            job2: *JOBTMPL
+            EOT
           end
 
           it_behaves_like 'job_templates_handling'
@@ -1358,15 +992,15 @@ EOT
 
         context 'when template is a hidden job' do
           let(:config) do
-            <<EOT
-.template: &JOBTMPL
-  stage: build
-  script: execute-script-for-job
+            <<~EOT
+            .template: &JOBTMPL
+              stage: build
+              script: execute-script-for-job
 
-job1: *JOBTMPL
+            job1: *JOBTMPL
 
-job2: *JOBTMPL
-EOT
+            job2: *JOBTMPL
+            EOT
           end
 
           it_behaves_like 'job_templates_handling'
@@ -1374,18 +1008,18 @@ EOT
 
         context 'when job adds its own keys to a template definition' do
           let(:config) do
-            <<EOT
-.template: &JOBTMPL
-  stage: build
-
-job1:
-  <<: *JOBTMPL
-  script: execute-script-for-job
-
-job2:
-  <<: *JOBTMPL
-  script: execute-script-for-job
-EOT
+            <<~EOT
+            .template: &JOBTMPL
+              stage: build
+
+            job1:
+              <<: *JOBTMPL
+              script: execute-script-for-job
+
+            job2:
+              <<: *JOBTMPL
+              script: execute-script-for-job
+            EOT
           end
 
           it_behaves_like 'job_templates_handling'
@@ -1677,6 +1311,14 @@ EOT
             Gitlab::Ci::YamlProcessor.new(config)
           end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
         end
+
+        it 'returns errors if pipeline variables expression is invalid' do
+          config = YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } })
+
+          expect { Gitlab::Ci::YamlProcessor.new(config) }
+            .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+                            'jobs:rspec:only variables invalid expression syntax')
+        end
       end
 
       describe "Validate configuration templates" do
@@ -1724,10 +1366,6 @@ EOT
           it { is_expected.to be_nil }
         end
       end
-
-      def pipeline(**attributes)
-        build_stubbed(:ci_empty_pipeline, **attributes)
-      end
     end
   end
 end
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
index 5944ce8049ab47cdba0047823bd9fd978c8911a3..c93912db4119b5506b165a3cce00335775ecdfe8 100644
--- a/spec/lib/gitlab/conflict/file_collection_spec.rb
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -10,6 +10,38 @@ describe Gitlab::Conflict::FileCollection do
     end
   end
 
+  describe '#cache' do
+    it 'specifies a custom namespace with the merge request commit ids' do
+      our_commit = merge_request.source_branch_head.raw
+      their_commit = merge_request.target_branch_head.raw
+      custom_namespace = "#{our_commit.id}:#{their_commit.id}"
+
+      expect(file_collection.send(:cache).namespace).to include(custom_namespace)
+    end
+  end
+
+  describe '#can_be_resolved_in_ui?' do
+    it 'returns true if conflicts for this collection can be resolved in the UI' do
+      expect(file_collection.can_be_resolved_in_ui?).to be true
+    end
+
+    it "returns false if conflicts for this collection can't be resolved in the UI" do
+      expect(file_collection).to receive(:files).and_raise(Gitlab::Git::Conflict::Resolver::ConflictSideMissing)
+
+      expect(file_collection.can_be_resolved_in_ui?).to be false
+    end
+
+    it 'caches the result' do
+      expect(file_collection).to receive(:files).twice.and_call_original
+
+      expect(file_collection.can_be_resolved_in_ui?).to be true
+
+      expect(file_collection).not_to receive(:files)
+
+      expect(file_collection.can_be_resolved_in_ui?).to be true
+    end
+  end
+
   describe '#default_commit_message' do
     it 'matches the format of the git CLI commit message' do
       expect(file_collection.default_commit_message).to eq(<<EOM.chomp)
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 49a179ba8755b59f4010734b69a588a6d9923cfc..2c63f3b0455f1f13a643fbacdc5debef8bf9489d 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::ContributionsCalendar do
   end
 
   let(:public_project) do
-    create(:project, :public) do |project|
+    create(:project, :public, :repository) do |project|
       create(:project_member, user: contributor, project: project)
     end
   end
@@ -40,13 +40,13 @@ describe Gitlab::ContributionsCalendar do
     described_class.new(contributor, current_user)
   end
 
-  def create_event(project, day, hour = 0)
+  def create_event(project, day, hour = 0, action = Event::CREATED, target_symbol = :issue)
     @targets ||= {}
-    @targets[project] ||= create(:issue, project: project, author: contributor)
+    @targets[project] ||= create(target_symbol, project: project, author: contributor)
 
     Event.create!(
       project: project,
-      action: Event::CREATED,
+      action: action,
       target: @targets[project],
       author: contributor,
       created_at: DateTime.new(day.year, day.month, day.day, hour)
@@ -71,6 +71,19 @@ describe Gitlab::ContributionsCalendar do
       expect(calendar(contributor).activity_dates[today]).to eq(2)
     end
 
+    it "counts the diff notes on merge request" do
+      create_event(public_project, today, 0, Event::COMMENTED, :diff_note_on_merge_request)
+
+      expect(calendar(contributor).activity_dates[today]).to eq(1)
+    end
+
+    it "counts the discussions on merge requests and issues" do
+      create_event(public_project, today, 0, Event::COMMENTED, :discussion_note_on_merge_request)
+      create_event(public_project, today, 2, Event::COMMENTED, :discussion_note_on_issue)
+
+      expect(calendar(contributor).activity_dates[today]).to eq(2)
+    end
+
     context "when events fall under different dates depending on the time zone" do
       before do
         create_event(public_project, today, 1)
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 91c43f2bdc0cb82350fb4c78892367e1cb77f059..ee91decafadec901ac2bb1a00cf962f70ad86711 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::DataBuilder::Build do
     it { expect(data[:build_status]).to eq(build.status) }
     it { expect(data[:build_allow_failure]).to eq(false) }
     it { expect(data[:project_id]).to eq(build.project.id) }
-    it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
+    it { expect(data[:project_name]).to eq(build.project.full_name) }
 
     context 'commit author_url' do
       context 'when no commit present' do
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index aaa42566a4dd9f6353740a4d923d94b3ee9ebc5b..4f8412108ba9351520e8ea2f09a255b4daa47aa2 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -55,6 +55,14 @@ describe Gitlab::DataBuilder::Note do
         .to be > issue.hook_attrs['updated_at']
     end
 
+    context 'with confidential issue' do
+      let(:issue) { create(:issue, project: project, confidential: true) }
+
+      it 'sets event_type to confidential_note' do
+        expect(data[:event_type]).to eq('confidential_note')
+      end
+    end
+
     include_examples 'project hook data'
     include_examples 'deprecated repository hook data'
   end
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index f13041e498c46510d92d50f3a7481e7fe8b2395b..9ca960502c8e6b454a5dd1feb8627cb16502f26b 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -26,6 +26,7 @@ describe Gitlab::DataBuilder::Pipeline do
     it { expect(attributes[:tag]).to eq(pipeline.tag) }
     it { expect(attributes[:id]).to eq(pipeline.id) }
     it { expect(attributes[:status]).to eq(pipeline.status) }
+    it { expect(attributes[:detailed_status]).to eq('passed') }
 
     it { expect(build_data).to be_a(Hash) }
     it { expect(build_data[:id]).to eq(build.id) }
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 1de3a14b809d48fdaba1b0a4e43ef7183fae481f..280f799f2aba48c409d2df0a2708d680630349aa 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -67,17 +67,35 @@ describe Gitlab::Database::MigrationHelpers do
 
           model.add_concurrent_index(:users, :foo, unique: true)
         end
+
+        it 'does nothing if the index exists already' do
+          expect(model).to receive(:index_exists?)
+            .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(true)
+          expect(model).not_to receive(:add_index)
+
+          model.add_concurrent_index(:users, :foo, unique: true)
+        end
       end
 
       context 'using MySQL' do
-        it 'creates a regular index' do
-          expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+        before do
+          allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+        end
 
+        it 'creates a regular index' do
           expect(model).to receive(:add_index)
             .with(:users, :foo, {})
 
           model.add_concurrent_index(:users, :foo)
         end
+
+        it 'does nothing if the index exists already' do
+          expect(model).to receive(:index_exists?)
+            .with(:users, :foo, { unique: true }).and_return(true)
+          expect(model).not_to receive(:add_index)
+
+          model.add_concurrent_index(:users, :foo, unique: true)
+        end
       end
     end
 
@@ -95,6 +113,7 @@ describe Gitlab::Database::MigrationHelpers do
     context 'outside a transaction' do
       before do
         allow(model).to receive(:transaction_open?).and_return(false)
+        allow(model).to receive(:index_exists?).and_return(true)
       end
 
       context 'using PostgreSQL' do
@@ -103,18 +122,41 @@ describe Gitlab::Database::MigrationHelpers do
           allow(model).to receive(:disable_statement_timeout)
         end
 
-        it 'removes the index concurrently by column name' do
-          expect(model).to receive(:remove_index)
-            .with(:users, { algorithm: :concurrently, column: :foo })
+        describe 'by column name' do
+          it 'removes the index concurrently' do
+            expect(model).to receive(:remove_index)
+              .with(:users, { algorithm: :concurrently, column: :foo })
 
-          model.remove_concurrent_index(:users, :foo)
+            model.remove_concurrent_index(:users, :foo)
+          end
+
+          it 'does nothing if the index does not exist' do
+            expect(model).to receive(:index_exists?)
+              .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(false)
+            expect(model).not_to receive(:remove_index)
+
+            model.remove_concurrent_index(:users, :foo, unique: true)
+          end
         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" })
+        describe 'by index name' do
+          before do
+            allow(model).to receive(:index_exists_by_name?).with(:users, "index_x_by_y").and_return(true)
+          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" })
+
+            model.remove_concurrent_index_by_name(:users, "index_x_by_y")
+          end
+
+          it 'does nothing if the index does not exist' do
+            expect(model).to receive(:index_exists_by_name?).with(:users, "index_x_by_y").and_return(false)
+            expect(model).not_to receive(:remove_index)
 
-          model.remove_concurrent_index_by_name(:users, "index_x_by_y")
+            model.remove_concurrent_index_by_name(:users, "index_x_by_y")
+          end
         end
       end
 
@@ -141,6 +183,10 @@ describe Gitlab::Database::MigrationHelpers do
   end
 
   describe '#add_concurrent_foreign_key' do
+    before do
+      allow(model).to receive(:foreign_key_exists?).and_return(false)
+    end
+
     context 'inside a transaction' do
       it 'raises an error' do
         expect(model).to receive(:transaction_open?).and_return(true)
@@ -157,14 +203,23 @@ describe Gitlab::Database::MigrationHelpers do
       end
 
       context 'using MySQL' do
-        it 'creates a regular foreign key' do
+        before do
           allow(Gitlab::Database).to receive(:mysql?).and_return(true)
+        end
 
+        it 'creates a regular foreign key' do
           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
+
+        it 'does not create a foreign key if it exists already' do
+          expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
+          expect(model).not_to receive(:add_foreign_key)
+
+          model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+        end
       end
 
       context 'using PostgreSQL' do
@@ -189,6 +244,14 @@ describe Gitlab::Database::MigrationHelpers do
                                            column: :user_id,
                                            on_delete: :nullify)
         end
+
+        it 'does not create a foreign key if it exists already' do
+          expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
+          expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
+          expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
+
+          model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+        end
       end
     end
   end
@@ -203,6 +266,29 @@ describe Gitlab::Database::MigrationHelpers do
     end
   end
 
+  describe '#foreign_key_exists?' do
+    before do
+      key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(:projects, :users, { column: :non_standard_id })
+      allow(model).to receive(:foreign_keys).with(:projects).and_return([key])
+    end
+
+    it 'finds existing foreign keys by column' do
+      expect(model.foreign_key_exists?(:projects, :users, column: :non_standard_id)).to be_truthy
+    end
+
+    it 'finds existing foreign keys by target table only' do
+      expect(model.foreign_key_exists?(:projects, :users)).to be_truthy
+    end
+
+    it 'compares by column name if given' do
+      expect(model.foreign_key_exists?(:projects, :users, column: :user_id)).to be_falsey
+    end
+
+    it 'compares by target if no column given' do
+      expect(model.foreign_key_exists?(:projects, :other_table)).to be_falsey
+    end
+  end
+
   describe '#disable_statement_timeout' do
     context 'using PostgreSQL' do
       it 'disables statement timeouts' do
@@ -1125,4 +1211,33 @@ describe Gitlab::Database::MigrationHelpers do
       expect(model.perform_background_migration_inline?).to eq(false)
     end
   end
+
+  describe '#index_exists_by_name?' do
+    it 'returns true if an index exists' do
+      expect(model.index_exists_by_name?(:projects, 'index_projects_on_path'))
+        .to be_truthy
+    end
+
+    it 'returns false if the index does not exist' do
+      expect(model.index_exists_by_name?(:projects, 'this_does_not_exist'))
+        .to be_falsy
+    end
+
+    context 'when an index with a function exists', :postgresql do
+      before do
+        ActiveRecord::Base.connection.execute(
+          'CREATE INDEX test_index ON projects (LOWER(path));'
+        )
+      end
+
+      after do
+        'DROP INDEX IF EXISTS test_index;'
+      end
+
+      it 'returns true if an index exists' do
+        expect(model.index_exists_by_name?(:projects, 'test_index'))
+          .to be_truthy
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
index 62c1d37ea1c8d422e2065343367e4f330d99147a..778bfa2cc47ebd27dd14d9f0cf8ccc1f4d406d02 100644
--- a/spec/lib/gitlab/database/sha_attribute_spec.rb
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -19,15 +19,15 @@ describe Gitlab::Database::ShaAttribute do
 
   let(:attribute) { described_class.new }
 
-  describe '#type_cast_from_database' do
+  describe '#deserialize' do
     it 'converts the binary SHA to a String' do
-      expect(attribute.type_cast_from_database(binary_from_db)).to eq(sha)
+      expect(attribute.deserialize(binary_from_db)).to eq(sha)
     end
   end
 
-  describe '#type_cast_for_database' do
+  describe '#serialize' do
     it 'converts a SHA String to binary data' do
-      expect(attribute.type_cast_for_database(sha).to_s).to eq(binary_sha)
+      expect(attribute.serialize(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 b2f13fae73fdc9f98f4079d8c3b8f7c2794802f9..1fe1d3926add1bf910390dc395793b6132be5628 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -287,6 +287,29 @@ describe Gitlab::Database do
     end
   end
 
+  describe '.cached_column_exists?' do
+    it 'only retrieves data once' do
+      expect(ActiveRecord::Base.connection).to receive(:columns).once.and_call_original
+
+      2.times do
+        expect(described_class.cached_column_exists?(:projects, :id)).to be_truthy
+        expect(described_class.cached_column_exists?(:projects, :bogus_column)).to be_falsey
+      end
+    end
+  end
+
+  describe '.cached_table_exists?' do
+    it 'only retrieves data once per table' do
+      expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original
+      expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original
+
+      2.times do
+        expect(described_class.cached_table_exists?(:projects)).to be_truthy
+        expect(described_class.cached_table_exists?(:bogus_table_name)).to be_falsey
+      end
+    end
+  end
+
   describe '#true_value' do
     it 'returns correct value for PostgreSQL' do
       expect(described_class).to receive(:postgresql?).and_return(true)
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index a067c42b75b77de7b8bd4d70dcb18fcb7399c414..f48ee8924e8c8a0ebde967e0d0583ad1e2077795 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
     diff_files
   end
 
-  it 'does not files marked as undiffable in .gitattributes' do
+  it 'does not highlight files marked as undiffable in .gitattributes' do
     allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false)
 
     expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 9204ea3796309644e380be27101edf4ed52fadc2..0c2e18c268ac0a07d2b2ff541fa6ae9c6f4b2770 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -455,5 +455,17 @@ describe Gitlab::Diff::File do
         expect(diff_file.size).to be_zero
       end
     end
+
+    describe '#different_type?' do
+      it 'returns false' do
+        expect(diff_file).not_to be_different_type
+      end
+    end
+
+    describe '#content_changed?' do
+      it 'returns false' do
+        expect(diff_file).not_to be_content_changed
+      end
+    end
   end
 end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 73d60c021c8aec0f57ffa5779240d6035270c369..7c9e8c8d04e5f0b0b065812cd5b0f4b246616631 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -79,6 +79,8 @@ describe Gitlab::Diff::Highlight do
         end
 
         it 'keeps the original rich line' do
+          allow(Gitlab::Sentry).to receive(:track_exception)
+
           code = %q{+      raise RuntimeError, "System commands must be given as an array of strings"}
 
           expect(subject[5].text).to eq(code)
@@ -86,12 +88,9 @@ describe Gitlab::Diff::Highlight do
         end
 
         it 'reports to Sentry if configured' do
-          allow(Gitlab::Sentry).to receive(:enabled?).and_return(true)
-
-          expect(Gitlab::Sentry).to receive(:context)
-          expect(Raven).to receive(:capture_exception)
+          expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
 
-          subject
+          expect { subject }. to raise_exception(RangeError)
         end
       end
     end
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 031efcf1291459d2acf47214eafcb2a9d38180c2..53899e00b53c0d6ea280ab4a5b6ce04d6d780377 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -55,8 +55,8 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
       expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
     end
 
-    context 'because the note was commands only' do
-      let!(:email_raw) { fixture_file("emails/commands_only_reply.eml") }
+    context 'because the note was update commands only' do
+      let!(:email_raw) { fixture_file("emails/update_commands_only_reply.eml") }
 
       context 'and current user cannot update noteable' do
         it 'raises a CommandsOnlyNoteError' do
@@ -70,13 +70,10 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
         end
 
         it 'does not raise an error' do
-          expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
-
           # One system note is created for the 'close' event
           expect { receiver.execute }.to change { noteable.notes.count }.by(1)
 
           expect(noteable.reload).to be_closed
-          expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy
         end
       end
     end
@@ -85,15 +82,13 @@ describe Gitlab::Email::Handler::CreateNoteHandler 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
-      it 'post a note and does not update the noteable' do
-        expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
-
-        # One system note is created for the new note
-        expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+    context 'and current user cannot update the noteable' do
+      it 'only executes the commands that the user can perform' do
+        expect { receiver.execute }
+          .to change { noteable.notes.user.count }.by(1)
+          .and change { user.todos_pending_count }.from(0).to(1)
 
         expect(noteable.reload).to be_open
-        expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
       end
     end
 
@@ -102,14 +97,14 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
         project.add_developer(user)
       end
 
-      it 'post a note and updates the noteable' do
+      it 'posts a note and updates the noteable' do
         expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
 
-        # One system note is created for the new note, one for the 'close' event
-        expect { receiver.execute }.to change { noteable.notes.count }.by(2)
+        expect { receiver.execute }
+          .to change { noteable.notes.user.count }.by(1)
+          .and change { user.todos_pending_count }.from(0).to(1)
 
         expect(noteable.reload).to be_closed
-        expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy
       end
     end
   end
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index 650b01c4df443f123c2153806a6278f85f7ab85f..cedbfcc0d1828a520824787ecfbc7b401195891b 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -14,4 +14,34 @@ describe Gitlab::Email::Handler do
       expect(described_class.for('email', '')).to be_nil
     end
   end
+
+  describe 'regexps are set properly' do
+    let(:addresses) do
+      %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
+    end
+
+    it 'picks each handler at least once' do
+      matched_handlers = addresses.map do |address|
+        described_class.for('email', address).class
+      end
+
+      expect(matched_handlers.uniq).to match_array(ce_handlers)
+    end
+
+    it 'can pick exactly one handler for each address' do
+      addresses.each do |address|
+        matched_handlers = ce_handlers.select do |handler|
+          handler.new('email', address).can_handle?
+        end
+
+        expect(matched_handlers.count).to eq(1), "#{address} matches #{matched_handlers.count} handlers: #{matched_handlers}"
+      end
+    end
+  end
+
+  def ce_handlers
+    @ce_handlers ||= Gitlab::Email::Handler::HANDLERS.reject do |handler|
+      handler.name.start_with?('Gitlab::Email::Handler::EE::')
+    end
+  end
 end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 83d431a7458e1aef3ec933c924aa84848bfece15..e68c9850f6bcfdce030327db4d5667f363711930 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -161,6 +161,11 @@ describe Gitlab::EncodingHelper do
         'removes invalid bytes from ASCII-8bit encoded multibyte string.',
         "Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'),
         "Lorem ipsum\n dolor sit amet, xy脿y霉abcd霉efg"
+      ],
+      [
+        'handles UTF-16BE encoded strings',
+        "\xFE\xFF\x00\x41".force_encoding('ASCII-8BIT'), # An "A" prepended with UTF-16 BOM
+        "\xEF\xBB\xBFA" # An "A" prepended with UTF-8 BOM
       ]
     ].each do |description, test_string, xpect|
       it description do
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 6193e177668dbe35f6b74bceb1a9ad5efeb7c149..aed7d8d81cef9b82d1c3c572f5d572a893a183d4 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -88,4 +88,16 @@ describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
       expect(lease.ttl).to be_nil
     end
   end
+
+  describe '.reset_all!' do
+    it 'removes all existing lease keys from redis' do
+      uuid = described_class.new(unique_key, timeout: 3600).try_obtain
+
+      expect(described_class.get_uuid(unique_key)).to eq(uuid)
+
+      described_class.reset_all!
+
+      expect(described_class.get_uuid(unique_key)).to be_falsey
+    end
+  end
 end
diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb
index 323334e99a501210602ae2a699a3e79a2129c479..2d10312399842ce7ad831e97919e98b916786c2c 100644
--- a/spec/lib/gitlab/git/attributes_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_parser_spec.rb
@@ -66,18 +66,6 @@ describe Gitlab::Git::AttributesParser, seed_helper: true do
       end
     end
 
-    context 'when attributes data is a file handle' do
-      subject do
-        File.open(attributes_path, 'r') do |file_handle|
-          described_class.new(file_handle)
-        end
-      end
-
-      it 'returns the attributes as a Hash' do
-        expect(subject.attributes('test.txt')).to eq({ 'text' => true })
-      end
-    end
-
     context 'when attributes data is nil' do
       let(:data) { nil }
 
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index a6341cd509bed0c843f970ce1281898d479121b6..67d898e787e2d49f47c8cb0854a412e918a193bd 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -500,4 +500,33 @@ describe Gitlab::Git::Blob, seed_helper: true do
       end
     end
   end
+
+  describe '#load_all_data!' do
+    let(:full_data) { 'abcd' }
+    let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: 'abc') }
+
+    subject { blob.load_all_data!(repository) }
+
+    it 'loads missing data' do
+      expect(Gitlab::GitalyClient).to receive(:migrate)
+        .with(:git_blob_load_all_data).and_return(full_data)
+
+      subject
+
+      expect(blob.data).to eq(full_data)
+    end
+
+    context 'with a fully loaded blob' do
+      let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: full_data) }
+
+      it "doesn't perform any loading" do
+        expect(Gitlab::GitalyClient).not_to receive(:migrate)
+          .with(:git_blob_load_all_data)
+
+        subject
+
+        expect(blob.data).to eq(full_data)
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 708870060e793a8ad28d6d6927cfc3c17b908c36..a19155ed5b08f20c59f6a82f3815c1a3c0399e09 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -59,5 +59,69 @@ describe Gitlab::Git::Branch, seed_helper: true do
     it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) }
   end
 
+  context 'with active, stale and future branches' do
+    let(:repository) do
+      Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
+    end
+
+    let(:user) { create(:user) }
+    let(:committer) do
+      Gitlab::Git.committer_hash(email: user.email, name: user.name)
+    end
+    let(:params) do
+      parents = [repository.rugged.head.target]
+      tree = parents.first.tree
+
+      {
+        message: 'commit message',
+        author: committer,
+        committer: committer,
+        tree: tree,
+        parents: parents
+      }
+    end
+    let(:stale_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } }
+    let(:active_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } }
+    let(:future_sha) { Timecop.freeze(100.days.since) { create_commit } }
+
+    before do
+      repository.create_branch('stale-1', stale_sha)
+      repository.create_branch('active-1', active_sha)
+      repository.create_branch('future-1', future_sha)
+    end
+
+    after do
+      ensure_seeds
+    end
+
+    describe 'examine if the branch is active or stale' do
+      let(:stale_branch) { repository.find_branch('stale-1') }
+      let(:active_branch) { repository.find_branch('active-1') }
+      let(:future_branch) { repository.find_branch('future-1') }
+
+      describe '#active?' do
+        it { expect(stale_branch.active?).to be_falsey }
+        it { expect(active_branch.active?).to be_truthy }
+        it { expect(future_branch.active?).to be_truthy }
+      end
+
+      describe '#stale?' do
+        it { expect(stale_branch.stale?).to be_truthy }
+        it { expect(active_branch.stale?).to be_falsey }
+        it { expect(future_branch.stale?).to be_falsey }
+      end
+
+      describe '#state' do
+        it { expect(stale_branch.state).to eq(:stale) }
+        it { expect(active_branch.state).to eq(:active) }
+        it { expect(future_branch.state).to eq(:active) }
+      end
+    end
+  end
+
   it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) }
+
+  def create_commit
+    repository.create_commit(params.merge(committer: committer.merge(time: Time.now)))
+  end
 end
diff --git a/spec/lib/gitlab/git/conflict/file_spec.rb b/spec/lib/gitlab/git/conflict/file_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..afed6c32af6165d16fda5280f69c98741934803f
--- /dev/null
+++ b/spec/lib/gitlab/git/conflict/file_spec.rb
@@ -0,0 +1,50 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe Gitlab::Git::Conflict::File do
+  let(:conflict) { { theirs: { path: 'foo', mode: 33188 }, ours: { path: 'foo', mode: 33188 } } }
+  let(:invalid_content) { described_class.new(nil, nil, conflict, "a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }
+  let(:valid_content) { described_class.new(nil, nil, conflict, "Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) }
+
+  describe '#lines' do
+    context 'when the content contains non-UTF-8 characters' do
+      it 'raises UnsupportedEncoding' do
+        expect { invalid_content.lines }
+          .to raise_error(described_class::UnsupportedEncoding)
+      end
+    end
+
+    context 'when the content can be converted to UTF-8' do
+      it 'sets lines to the lines' do
+        expect(valid_content.lines).to eq([{
+                                             full_line: 'Espa帽a',
+                                             type: nil,
+                                             line_obj_index: 0,
+                                             line_old: 1,
+                                             line_new: 1
+                                           }])
+      end
+
+      it 'sets the type to text' do
+        expect(valid_content.type).to eq('text')
+      end
+    end
+  end
+
+  describe '#content' do
+    context 'when the content contains non-UTF-8 characters' do
+      it 'raises UnsupportedEncoding' do
+        expect { invalid_content.content }
+          .to raise_error(described_class::UnsupportedEncoding)
+      end
+    end
+
+    context 'when the content can be converted to UTF-8' do
+      it 'returns a valid UTF-8 string' do
+        expect(valid_content.content).to eq('Espa帽a')
+        expect(valid_content.content).to be_valid_encoding
+        expect(valid_content.content.encoding).to eq(Encoding::UTF_8)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb
index 7b035a381f1a477a766f7df4e1fff67e8d291cbd..29a1702a1c641df933010c36f798cf02f67537e6 100644
--- a/spec/lib/gitlab/git/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/git/conflict/parser_spec.rb
@@ -212,13 +212,6 @@ CONFLICT
             .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::Git::Conflict::Parser::UnsupportedEncoding)
-        end
-      end
     end
   end
 end
diff --git a/spec/lib/gitlab/git/env_spec.rb b/spec/lib/gitlab/git/env_spec.rb
deleted file mode 100644
index 03836d49518998836bbceadeccce51013f2ee5d8..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/git/env_spec.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Env do
-  describe ".set" do
-    context 'with RequestStore.store disabled' do
-      before do
-        allow(RequestStore).to receive(:active?).and_return(false)
-      end
-
-      it 'does not store anything' do
-        described_class.set(GIT_OBJECT_DIRECTORY: 'foo')
-
-        expect(described_class.all).to be_empty
-      end
-    end
-
-    context 'with RequestStore.store enabled' do
-      before do
-        allow(RequestStore).to receive(:active?).and_return(true)
-      end
-
-      it 'whitelist some `GIT_*` variables and stores them using RequestStore' do
-        described_class.set(
-          GIT_OBJECT_DIRECTORY: 'foo',
-          GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar',
-          GIT_EXEC_PATH: 'baz',
-          PATH: '~/.bin:/bin')
-
-        expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
-        expect(described_class[:GIT_ALTERNATE_OBJECT_DIRECTORIES]).to eq('bar')
-        expect(described_class[:GIT_EXEC_PATH]).to be_nil
-        expect(described_class[:bar]).to be_nil
-      end
-    end
-  end
-
-  describe ".all" do
-    context 'with RequestStore.store enabled' do
-      before do
-        allow(RequestStore).to receive(:active?).and_return(true)
-        described_class.set(
-          GIT_OBJECT_DIRECTORY: 'foo',
-          GIT_ALTERNATE_OBJECT_DIRECTORIES: ['bar'])
-      end
-
-      it 'returns an env hash' do
-        expect(described_class.all).to eq({
-          'GIT_OBJECT_DIRECTORY' => 'foo',
-          'GIT_ALTERNATE_OBJECT_DIRECTORIES' => ['bar']
-        })
-      end
-    end
-  end
-
-  describe ".to_env_hash" do
-    context 'with RequestStore.store enabled' do
-      using RSpec::Parameterized::TableSyntax
-
-      let(:key) { 'GIT_OBJECT_DIRECTORY' }
-      subject { described_class.to_env_hash }
-
-      where(:input, :output) do
-        nil         | nil
-        'foo'       | 'foo'
-        []          | ''
-        ['foo']     | 'foo'
-        %w[foo bar] | 'foo:bar'
-      end
-
-      with_them do
-        before do
-          allow(RequestStore).to receive(:active?).and_return(true)
-          described_class.set(key.to_sym => input)
-        end
-
-        it 'puts the right value in the hash' do
-          if output
-            expect(subject.fetch(key)).to eq(output)
-          else
-            expect(subject.has_key?(key)).to eq(false)
-          end
-        end
-      end
-    end
-  end
-
-  describe ".[]" do
-    context 'with RequestStore.store enabled' do
-      before do
-        allow(RequestStore).to receive(:active?).and_return(true)
-      end
-
-      before do
-        described_class.set(
-          GIT_OBJECT_DIRECTORY: 'foo',
-          GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar')
-      end
-
-      it 'returns a stored value for an existing key' do
-        expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
-      end
-
-      it 'returns nil for an non-existing key' do
-        expect(described_class[:foo]).to be_nil
-      end
-    end
-  end
-
-  describe 'thread-safety' do
-    context 'with RequestStore.store enabled' do
-      before do
-        allow(RequestStore).to receive(:active?).and_return(true)
-        described_class.set(GIT_OBJECT_DIRECTORY: 'foo')
-      end
-
-      it 'is thread-safe' do
-        another_thread = Thread.new do
-          described_class.set(GIT_OBJECT_DIRECTORY: 'bar')
-
-          Thread.stop
-          described_class[:GIT_OBJECT_DIRECTORY]
-        end
-
-        # Ensure another_thread runs first
-        sleep 0.1 until another_thread.stop?
-
-        expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
-
-        another_thread.run
-        expect(another_thread.value).to eq('bar')
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
index f4b964e1ee9832d22150cd2f5fb7fe721a993e3f..8b715d717c1e65c3373471fa4931d634494ec18d 100644
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::Git::GitlabProjects do
   let(:tmp_repos_path) { TestEnv.repos_path }
   let(:repo_name) { project.disk_path + '.git' }
   let(:tmp_repo_path) { File.join(tmp_repos_path, repo_name) }
-  let(:gl_projects) { build_gitlab_projects(tmp_repos_path, repo_name) }
+  let(:gl_projects) { build_gitlab_projects(TestEnv::REPOS_STORAGE, repo_name) }
 
   describe '#initialize' do
     it { expect(gl_projects.shard_path).to eq(tmp_repos_path) }
@@ -28,7 +28,7 @@ describe Gitlab::Git::GitlabProjects do
   describe '#push_branches' do
     let(:remote_name) { 'remote-name' }
     let(:branch_name) { 'master' }
-    let(:cmd) { %W(git push -- #{remote_name} #{branch_name}) }
+    let(:cmd) { %W(#{Gitlab.config.git.bin_path} push -- #{remote_name} #{branch_name}) }
     let(:force) { false }
 
     subject { gl_projects.push_branches(remote_name, 600, force, [branch_name]) }
@@ -46,7 +46,7 @@ describe Gitlab::Git::GitlabProjects do
     end
 
     context 'with --force' do
-      let(:cmd) { %W(git push --force -- #{remote_name} #{branch_name}) }
+      let(:cmd) { %W(#{Gitlab.config.git.bin_path} push --force -- #{remote_name} #{branch_name}) }
       let(:force) { true }
 
       it 'executes the command' do
@@ -61,10 +61,11 @@ describe Gitlab::Git::GitlabProjects do
     let(:remote_name) { 'remote-name' }
     let(:branch_name) { 'master' }
     let(:force) { false }
+    let(:prune) { true }
     let(:tags) { true }
-    let(:args) { { force: force, tags: tags }.merge(extra_args) }
+    let(:args) { { force: force, tags: tags, prune: prune }.merge(extra_args) }
     let(:extra_args) { {} }
-    let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --tags) }
+    let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --tags) }
 
     subject { gl_projects.fetch_remote(remote_name, 600, args) }
 
@@ -97,7 +98,7 @@ describe Gitlab::Git::GitlabProjects do
 
     context 'with --force' do
       let(:force) { true }
-      let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --force --tags) }
+      let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --force --tags) }
 
       it 'executes the command with forced option' do
         stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
@@ -108,7 +109,18 @@ describe Gitlab::Git::GitlabProjects do
 
     context 'with --no-tags' do
       let(:tags) { false }
-      let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --no-tags) }
+      let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --no-tags) }
+
+      it 'executes the command' do
+        stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
+
+        is_expected.to be_truthy
+      end
+    end
+
+    context 'with no prune' do
+      let(:prune) { false }
+      let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --tags) }
 
       it 'executes the command' do
         stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
@@ -153,7 +165,7 @@ describe Gitlab::Git::GitlabProjects do
   describe '#import_project' do
     let(:project) { create(:project) }
     let(:import_url) { TestEnv.factory_repo_path_bare }
-    let(:cmd) { %W(git clone --bare -- #{import_url} #{tmp_repo_path}) }
+    let(:cmd) { %W(#{Gitlab.config.git.bin_path} clone --bare -- #{import_url} #{tmp_repo_path}) }
     let(:timeout) { 600 }
 
     subject { gl_projects.import_project(import_url, timeout) }
@@ -211,11 +223,12 @@ describe Gitlab::Git::GitlabProjects do
   end
 
   describe '#fork_repository' do
+    let(:dest_repos) { TestEnv::REPOS_STORAGE }
     let(:dest_repos_path) { tmp_repos_path }
     let(:dest_repo_name) { File.join('@hashed', 'aa', 'bb', 'xyz.git') }
     let(:dest_repo) { File.join(dest_repos_path, dest_repo_name) }
 
-    subject { gl_projects.fork_repository(dest_repos_path, dest_repo_name) }
+    subject { gl_projects.fork_repository(dest_repos, dest_repo_name) }
 
     before do
       FileUtils.mkdir_p(dest_repos_path)
@@ -256,7 +269,12 @@ describe Gitlab::Git::GitlabProjects do
       # that is not very straight-forward so I'm leaving this test here for now till
       # https://gitlab.com/gitlab-org/gitlab-ce/issues/41393 is fixed.
       context 'different storages' do
-        let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), 'alternative') }
+        let(:dest_repos) { 'alternative' }
+        let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), dest_repos) }
+
+        before do
+          stub_storage_settings(dest_repos => { 'path' => dest_repos_path })
+        end
 
         it 'forks the repo' do
           is_expected.to be_truthy
diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
index 143aa2218c9a4771d01d353d2273e02b6d1833ea..6fd2b33486b20fad5475fea11950614517616188 100644
--- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb
+++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::Git::GitmodulesParser do
   it 'should parse a .gitmodules file correctly' do
-    parser = described_class.new(<<-'GITMODULES'.strip_heredoc)
+    data = <<~GITMODULES
       [submodule "vendor/libgit2"]
          path = vendor/libgit2
       [submodule "vendor/libgit2"]
@@ -16,6 +16,7 @@ describe Gitlab::Git::GitmodulesParser do
           url = https://example.com/another/project
     GITMODULES
 
+    parser = described_class.new(data.gsub("\n", "\r\n"))
     modules = parser.parse
 
     expect(modules).to eq({
diff --git a/spec/lib/gitlab/git/hook_env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e6aa5ad8c903fca39647ad07063be112c6c15cf6
--- /dev/null
+++ b/spec/lib/gitlab/git/hook_env_spec.rb
@@ -0,0 +1,119 @@
+require 'spec_helper'
+
+describe Gitlab::Git::HookEnv do
+  let(:gl_repository) { 'project-123' }
+
+  describe ".set" do
+    context 'with RequestStore.store disabled' do
+      before do
+        allow(RequestStore).to receive(:active?).and_return(false)
+      end
+
+      it 'does not store anything' do
+        described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
+
+        expect(described_class.all(gl_repository)).to be_empty
+      end
+    end
+
+    context 'with RequestStore.store enabled' do
+      before do
+        allow(RequestStore).to receive(:active?).and_return(true)
+      end
+
+      it 'whitelist some `GIT_*` variables and stores them using RequestStore' do
+        described_class.set(
+          gl_repository,
+          GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
+          GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: 'bar',
+          GIT_EXEC_PATH: 'baz',
+          PATH: '~/.bin:/bin')
+
+        git_env = described_class.all(gl_repository)
+
+        expect(git_env[:GIT_OBJECT_DIRECTORY_RELATIVE]).to eq('foo')
+        expect(git_env[:GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE]).to eq('bar')
+        expect(git_env[:GIT_EXEC_PATH]).to be_nil
+        expect(git_env[:PATH]).to be_nil
+        expect(git_env[:bar]).to be_nil
+      end
+    end
+  end
+
+  describe ".all" do
+    context 'with RequestStore.store enabled' do
+      before do
+        allow(RequestStore).to receive(:active?).and_return(true)
+        described_class.set(
+          gl_repository,
+          GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
+          GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: ['bar'])
+      end
+
+      it 'returns an env hash' do
+        expect(described_class.all(gl_repository)).to eq({
+          'GIT_OBJECT_DIRECTORY_RELATIVE' => 'foo',
+          'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['bar']
+        })
+      end
+    end
+  end
+
+  describe ".to_env_hash" do
+    context 'with RequestStore.store enabled' do
+      using RSpec::Parameterized::TableSyntax
+
+      let(:key) { 'GIT_OBJECT_DIRECTORY_RELATIVE' }
+      subject { described_class.to_env_hash(gl_repository) }
+
+      where(:input, :output) do
+        nil         | nil
+        'foo'       | 'foo'
+        []          | ''
+        ['foo']     | 'foo'
+        %w[foo bar] | 'foo:bar'
+      end
+
+      with_them do
+        before do
+          allow(RequestStore).to receive(:active?).and_return(true)
+          described_class.set(gl_repository, key.to_sym => input)
+        end
+
+        it 'puts the right value in the hash' do
+          if output
+            expect(subject.fetch(key)).to eq(output)
+          else
+            expect(subject.has_key?(key)).to eq(false)
+          end
+        end
+      end
+    end
+  end
+
+  describe 'thread-safety' do
+    context 'with RequestStore.store enabled' do
+      before do
+        allow(RequestStore).to receive(:active?).and_return(true)
+        described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
+      end
+
+      it 'is thread-safe' do
+        another_thread = Thread.new do
+          described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'bar')
+
+          Thread.stop
+          described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]
+        end
+
+        # Ensure another_thread runs first
+        sleep 0.1 until another_thread.stop?
+
+        expect(described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]).to eq('foo')
+
+        another_thread.run
+        expect(another_thread.value).to eq('bar')
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/git/info_attributes_spec.rb b/spec/lib/gitlab/git/info_attributes_spec.rb
deleted file mode 100644
index ea84909c3e08314712189221368ded58eeba3859..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/git/info_attributes_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::InfoAttributes, seed_helper: true do
-  let(:path) do
-    File.join(SEED_STORAGE_PATH, 'with-git-attributes.git')
-  end
-
-  subject { described_class.new(path) }
-
-  describe '#attributes' do
-    context 'using a path with attributes' do
-      it 'returns the attributes as a Hash' do
-        expect(subject.attributes('test.txt')).to eq({ 'text' => true })
-      end
-
-      it 'returns an empty Hash for a defined path without attributes' do
-        expect(subject.attributes('bla/bla.txt')).to eq({})
-      end
-    end
-  end
-
-  describe '#parser' do
-    it 'parses a file with entries' do
-      expect(subject.patterns).to be_an_instance_of(Hash)
-      expect(subject.patterns["/*.txt"]).to eq({ 'text' => true })
-    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(subject.patterns).to eq({})
-    end
-
-    it 'does not parse attributes files with unsupported encoding' do
-      path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git')
-      subject = described_class.new(path)
-
-      expect(subject.patterns).to eq({})
-    end
-  end
-end
diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb
index c9007d7d456b996fbabb1126395c606b088e3127..d0dd8c6303f3facb8f074755814c64f6e9d0edf1 100644
--- a/spec/lib/gitlab/git/lfs_changes_spec.rb
+++ b/spec/lib/gitlab/git/lfs_changes_spec.rb
@@ -7,34 +7,36 @@ describe Gitlab::Git::LfsChanges do
 
   subject { described_class.new(project.repository, newrev) }
 
-  describe 'new_pointers' do
-    before do
-      allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects).and_yield([blob_object_id])
+  describe '#new_pointers' do
+    shared_examples 'new pointers' do
+      it 'filters new objects to find lfs pointers' do
+        expect(subject.new_pointers(not_in: []).first.id).to eq(blob_object_id)
+      end
+
+      it 'limits new_objects using object_limit' do
+        expect(subject.new_pointers(object_limit: 1)).to eq([])
+      end
     end
 
-    it 'uses rev-list to find new objects' do
-      rev_list = double
-      allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
-
-      expect(rev_list).to receive(:new_objects).and_return([])
-
-      subject.new_pointers
+    context 'with gitaly enabled' do
+      it_behaves_like 'new pointers'
     end
 
-    it 'filters new objects to find lfs pointers' do
-      expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [blob_object_id])
+    context 'with gitaly disabled', :skip_gitaly_mock do
+      it_behaves_like 'new pointers'
 
-      subject.new_pointers(object_limit: 1)
-    end
+      it 'uses rev-list to find new objects' do
+        rev_list = double
+        allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
 
-    it 'limits new_objects using object_limit' do
-      expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [])
+        expect(rev_list).to receive(:new_objects).and_return([])
 
-      subject.new_pointers(object_limit: 0)
+        subject.new_pointers
+      end
     end
   end
 
-  describe 'all_pointers' do
+  describe '#all_pointers', :skip_gitaly_mock do
     it 'uses rev-list to find all objects' do
       rev_list = double
       allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eedde34534f43201e3ddd43e1627b78ac53e0730
--- /dev/null
+++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::Git::RawDiffChange do
+  let(:raw_change) { }
+  let(:change) { described_class.new(raw_change) }
+
+  context 'bad input' do
+    let(:raw_change) { 'foo' }
+
+    it 'does not set most of the attrs' do
+      expect(change.blob_id).to eq('foo')
+      expect(change.operation).to eq(:unknown)
+      expect(change.old_path).to be_blank
+      expect(change.new_path).to be_blank
+      expect(change.blob_size).to be_blank
+    end
+  end
+
+  context 'adding a file' do
+    let(:raw_change) { '93e123ac8a3e6a0b600953d7598af629dec7b735 59 A  bar/branch-test.txt' }
+
+    it 'initialize the proper attrs' do
+      expect(change.operation).to eq(:added)
+      expect(change.old_path).to be_blank
+      expect(change.new_path).to eq('bar/branch-test.txt')
+      expect(change.blob_id).to be_present
+      expect(change.blob_size).to be_present
+    end
+  end
+
+  context 'renaming a file' do
+    let(:raw_change) { "85bc2f9753afd5f4fc5d7c75f74f8d526f26b4f3 107 R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee" }
+
+    it 'initialize the proper attrs' do
+      expect(change.operation).to eq(:renamed)
+      expect(change.old_path).to eq('files/js/commit.js.coffee')
+      expect(change.new_path).to eq('files/js/commit.coffee')
+      expect(change.blob_id).to be_present
+      expect(change.blob_size).to be_present
+    end
+  end
+
+  context 'modifying a file' do
+    let(:raw_change) { 'c60514b6d3d6bf4bec1030f70026e34dfbd69ad5 824 M  README.md' }
+
+    it 'initialize the proper attrs' do
+      expect(change.operation).to eq(:modified)
+      expect(change.old_path).to eq('README.md')
+      expect(change.new_path).to eq('README.md')
+      expect(change.blob_id).to be_present
+      expect(change.blob_size).to be_present
+    end
+  end
+
+  context 'deleting a file' do
+    let(:raw_change) { '60d7a906c2fd9e4509aeb1187b98d0ea7ce827c9 15364 D  files/.DS_Store' }
+
+    it 'initialize the proper attrs' do
+      expect(change.operation).to eq(:deleted)
+      expect(change.old_path).to eq('files/.DS_Store')
+      expect(change.new_path).to be_nil
+      expect(change.blob_id).to be_present
+      expect(change.blob_size).to be_present
+    end
+  end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 25defb98b7c6939a166769ee7df67b06275aeafb..5acf40ea5ce7af01ddc0728ee56cd06f0a4313a9 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -120,7 +120,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
     describe 'alternates keyword argument' do
       context 'with no Git env stored' do
         before do
-          allow(Gitlab::Git::Env).to receive(:all).and_return({})
+          allow(Gitlab::Git::HookEnv).to receive(:all).and_return({})
         end
 
         it "is passed an empty array" do
@@ -132,7 +132,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
 
       context 'with absolute and relative Git object dir envvars stored' do
         before do
-          allow(Gitlab::Git::Env).to receive(:all).and_return({
+          allow(Gitlab::Git::HookEnv).to receive(:all).and_return({
             'GIT_OBJECT_DIRECTORY_RELATIVE' => './objects/foo',
             'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['./objects/bar', './objects/baz'],
             'GIT_OBJECT_DIRECTORY' => 'ignored',
@@ -148,22 +148,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
           repository.rugged
         end
       end
-
-      context 'with only absolute Git object dir envvars stored' do
-        before do
-          allow(Gitlab::Git::Env).to receive(:all).and_return({
-            'GIT_OBJECT_DIRECTORY' => 'foo',
-            'GIT_ALTERNATE_OBJECT_DIRECTORIES' => %w[bar baz],
-            'GIT_OTHER' => 'another_env'
-          })
-        end
-
-        it "is passed the absolute object dir envvars as is" do
-          expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: %w[foo bar baz])
-
-          repository.rugged
-        end
-      end
     end
   end
 
@@ -263,38 +247,44 @@ describe Gitlab::Git::Repository, seed_helper: true do
     end
 
     it 'returns parameterised string for a ref containing slashes' do
-      prefix = repository.archive_prefix('test/branch', 'SHA')
+      prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: nil)
 
       expect(prefix).to eq("#{project_name}-test-branch-SHA")
     end
 
     it 'returns correct string for a ref containing dots' do
-      prefix = repository.archive_prefix('test.branch', 'SHA')
+      prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: nil)
 
       expect(prefix).to eq("#{project_name}-test.branch-SHA")
     end
+
+    it 'returns string with sha when append_sha is false' do
+      prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: false)
+
+      expect(prefix).to eq("#{project_name}-test.branch")
+    end
   end
 
   describe '#archive' do
-    let(:metadata) { repository.archive_metadata('master', '/tmp') }
+    let(:metadata) { repository.archive_metadata('master', '/tmp', append_sha: true) }
 
     it_should_behave_like 'archive check', '.tar.gz'
   end
 
   describe '#archive_zip' do
-    let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') }
+    let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip', append_sha: true) }
 
     it_should_behave_like 'archive check', '.zip'
   end
 
   describe '#archive_bz2' do
-    let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') }
+    let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2', append_sha: true) }
 
     it_should_behave_like 'archive check', '.tar.bz2'
   end
 
   describe '#archive_fallback' do
-    let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') }
+    let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup', append_sha: true) }
 
     it_should_behave_like 'archive check', '.tar.gz'
   end
@@ -480,9 +470,20 @@ describe Gitlab::Git::Repository, seed_helper: true do
           FileUtils.rm_rf(heads_dir)
           FileUtils.mkdir_p(heads_dir)
 
+          repository.expire_has_local_branches_cache
           expect(repository.has_local_branches?).to eq(false)
         end
       end
+
+      context 'memoizes the value' do
+        it 'returns true' do
+          expect(repository).to receive(:uncached_has_local_branches?).once.and_call_original
+
+          2.times do
+            expect(repository.has_local_branches?).to eq(true)
+          end
+        end
+      end
     end
 
     context 'with gitaly' do
@@ -604,17 +605,20 @@ describe Gitlab::Git::Repository, seed_helper: true do
     shared_examples 'returning the right branches' do
       let(:head_id) { repository.rugged.head.target.oid }
       let(:new_branch) { head_id }
+      let(:utf8_branch) { 'branch-茅' }
 
       before do
         repository.create_branch(new_branch, 'master')
+        repository.create_branch(utf8_branch, 'master')
       end
 
       after do
         repository.delete_branch(new_branch)
+        repository.delete_branch(utf8_branch)
       end
 
       it 'displays that branch' do
-        expect(repository.branch_names_contains_sha(head_id)).to include('master', new_branch)
+        expect(repository.branch_names_contains_sha(head_id)).to include('master', new_branch, utf8_branch)
       end
     end
 
@@ -681,7 +685,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
     subject { new_repository.fetch_repository_as_mirror(repository) }
 
     before do
-      Gitlab::Shell.new.add_repository('default', 'my_project')
+      Gitlab::Shell.new.create_repository('default', 'my_project')
     end
 
     after do
@@ -751,255 +755,263 @@ describe Gitlab::Git::Repository, seed_helper: true do
   end
 
   describe "#log" do
-    let(:commit_with_old_name) do
-      Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
-    end
-    let(:commit_with_new_name) do
-      Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
-    end
-    let(:rename_commit) do
-      Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
-    end
-
-    before(:context) do
-      # Add new commits so that there's a renamed file in the commit history
-      repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
-      @commit_with_old_name_id = new_commit_edit_old_file(repo)
-      @rename_commit_id = new_commit_move_file(repo)
-      @commit_with_new_name_id = new_commit_edit_new_file(repo)
-    end
-
-    after(:context) do
-      # Erase our commits so other tests get the original repo
-      repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
-      repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
-    end
-
-    context "where 'follow' == true" do
-      let(:options) { { ref: "master", follow: true } }
+    shared_examples 'repository log' do
+      let(:commit_with_old_name) do
+        Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
+      end
+      let(:commit_with_new_name) do
+        Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
+      end
+      let(:rename_commit) do
+        Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
+      end
 
-      context "and 'path' is a directory" do
-        it "does not follow renames" do
-          log_commits = repository.log(options.merge(path: "encoding"))
+      before(:context) do
+        # Add new commits so that there's a renamed file in the commit history
+        repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+        @commit_with_old_name_id = new_commit_edit_old_file(repo)
+        @rename_commit_id = new_commit_move_file(repo)
+        @commit_with_new_name_id = new_commit_edit_new_file(repo)
+      end
 
-          aggregate_failures do
-            expect(log_commits).to include(commit_with_new_name)
-            expect(log_commits).to include(rename_commit)
-            expect(log_commits).not_to include(commit_with_old_name)
-          end
-        end
+      after(:context) do
+        # Erase our commits so other tests get the original repo
+        repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+        repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
       end
 
-      context "and 'path' is a file that matches the new filename" do
-        context 'without offset' do
-          it "follows renames" do
-            log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
+      context "where 'follow' == true" do
+        let(:options) { { ref: "master", follow: true } }
+
+        context "and 'path' is a directory" do
+          it "does not follow renames" do
+            log_commits = repository.log(options.merge(path: "encoding"))
 
             aggregate_failures do
               expect(log_commits).to include(commit_with_new_name)
               expect(log_commits).to include(rename_commit)
-              expect(log_commits).to include(commit_with_old_name)
+              expect(log_commits).not_to include(commit_with_old_name)
             end
           end
         end
 
-        context 'with offset=1' do
-          it "follows renames and skip the latest commit" do
-            log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
+        context "and 'path' is a file that matches the new filename" do
+          context 'without offset' do
+            it "follows renames" do
+              log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
 
-            aggregate_failures do
-              expect(log_commits).not_to include(commit_with_new_name)
-              expect(log_commits).to include(rename_commit)
-              expect(log_commits).to include(commit_with_old_name)
+              aggregate_failures do
+                expect(log_commits).to include(commit_with_new_name)
+                expect(log_commits).to include(rename_commit)
+                expect(log_commits).to include(commit_with_old_name)
+              end
             end
           end
-        end
 
-        context 'with offset=1', 'and limit=1' do
-          it "follows renames, skip the latest commit and return only one commit" do
-            log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
+          context 'with offset=1' do
+            it "follows renames and skip the latest commit" do
+              log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
 
-            expect(log_commits).to contain_exactly(rename_commit)
+              aggregate_failures do
+                expect(log_commits).not_to include(commit_with_new_name)
+                expect(log_commits).to include(rename_commit)
+                expect(log_commits).to include(commit_with_old_name)
+              end
+            end
           end
-        end
 
-        context 'with offset=1', 'and limit=2' do
-          it "follows renames, skip the latest commit and return only two commits" do
-            log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
+          context 'with offset=1', 'and limit=1' do
+            it "follows renames, skip the latest commit and return only one commit" do
+              log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
 
-            aggregate_failures do
-              expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
+              expect(log_commits).to contain_exactly(rename_commit)
             end
           end
-        end
 
-        context 'with offset=2' do
-          it "follows renames and skip the latest commit" do
-            log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
+          context 'with offset=1', 'and limit=2' do
+            it "follows renames, skip the latest commit and return only two commits" do
+              log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
 
-            aggregate_failures do
-              expect(log_commits).not_to include(commit_with_new_name)
-              expect(log_commits).not_to include(rename_commit)
-              expect(log_commits).to include(commit_with_old_name)
+              aggregate_failures do
+                expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
+              end
+            end
+          end
+
+          context 'with offset=2' do
+            it "follows renames and skip the latest commit" do
+              log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
+
+              aggregate_failures do
+                expect(log_commits).not_to include(commit_with_new_name)
+                expect(log_commits).not_to include(rename_commit)
+                expect(log_commits).to include(commit_with_old_name)
+              end
             end
           end
-        end
 
-        context 'with offset=2', 'and limit=1' do
-          it "follows renames, skip the two latest commit and return only one commit" do
-            log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
+          context 'with offset=2', 'and limit=1' do
+            it "follows renames, skip the two latest commit and return only one commit" do
+              log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
 
-            expect(log_commits).to contain_exactly(commit_with_old_name)
+              expect(log_commits).to contain_exactly(commit_with_old_name)
+            end
+          end
+
+          context 'with offset=2', 'and limit=2' do
+            it "follows renames, skip the two latest commit and return only one commit" do
+              log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
+
+              aggregate_failures do
+                expect(log_commits).not_to include(commit_with_new_name)
+                expect(log_commits).not_to include(rename_commit)
+                expect(log_commits).to include(commit_with_old_name)
+              end
+            end
           end
         end
 
-        context 'with offset=2', 'and limit=2' do
-          it "follows renames, skip the two latest commit and return only one commit" do
-            log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
+        context "and 'path' is a file that matches the old filename" do
+          it "does not follow renames" do
+            log_commits = repository.log(options.merge(path: "CHANGELOG"))
 
             aggregate_failures do
               expect(log_commits).not_to include(commit_with_new_name)
-              expect(log_commits).not_to include(rename_commit)
+              expect(log_commits).to include(rename_commit)
               expect(log_commits).to include(commit_with_old_name)
             end
           end
         end
-      end
 
-      context "and 'path' is a file that matches the old filename" do
-        it "does not follow renames" do
-          log_commits = repository.log(options.merge(path: "CHANGELOG"))
+        context "unknown ref" do
+          it "returns an empty array" do
+            log_commits = repository.log(options.merge(ref: 'unknown'))
 
-          aggregate_failures do
-            expect(log_commits).not_to include(commit_with_new_name)
-            expect(log_commits).to include(rename_commit)
-            expect(log_commits).to include(commit_with_old_name)
+            expect(log_commits).to eq([])
           end
         end
       end
 
-      context "unknown ref" do
-        it "returns an empty array" do
-          log_commits = repository.log(options.merge(ref: 'unknown'))
-
-          expect(log_commits).to eq([])
-        end
-      end
-    end
+      context "where 'follow' == false" do
+        options = { follow: false }
 
-    context "where 'follow' == false" do
-      options = { follow: false }
+        context "and 'path' is a directory" do
+          let(:log_commits) do
+            repository.log(options.merge(path: "encoding"))
+          end
 
-      context "and 'path' is a directory" do
-        let(:log_commits) do
-          repository.log(options.merge(path: "encoding"))
+          it "does not follow renames" do
+            expect(log_commits).to include(commit_with_new_name)
+            expect(log_commits).to include(rename_commit)
+            expect(log_commits).not_to include(commit_with_old_name)
+          end
         end
 
-        it "does not follow renames" do
-          expect(log_commits).to include(commit_with_new_name)
-          expect(log_commits).to include(rename_commit)
-          expect(log_commits).not_to include(commit_with_old_name)
-        end
-      end
+        context "and 'path' is a file that matches the new filename" do
+          let(:log_commits) do
+            repository.log(options.merge(path: "encoding/CHANGELOG"))
+          end
 
-      context "and 'path' is a file that matches the new filename" do
-        let(:log_commits) do
-          repository.log(options.merge(path: "encoding/CHANGELOG"))
+          it "does not follow renames" do
+            expect(log_commits).to include(commit_with_new_name)
+            expect(log_commits).to include(rename_commit)
+            expect(log_commits).not_to include(commit_with_old_name)
+          end
         end
 
-        it "does not follow renames" do
-          expect(log_commits).to include(commit_with_new_name)
-          expect(log_commits).to include(rename_commit)
-          expect(log_commits).not_to include(commit_with_old_name)
-        end
-      end
+        context "and 'path' is a file that matches the old filename" do
+          let(:log_commits) do
+            repository.log(options.merge(path: "CHANGELOG"))
+          end
 
-      context "and 'path' is a file that matches the old filename" do
-        let(:log_commits) do
-          repository.log(options.merge(path: "CHANGELOG"))
+          it "does not follow renames" do
+            expect(log_commits).to include(commit_with_old_name)
+            expect(log_commits).to include(rename_commit)
+            expect(log_commits).not_to include(commit_with_new_name)
+          end
         end
 
-        it "does not follow renames" do
-          expect(log_commits).to include(commit_with_old_name)
-          expect(log_commits).to include(rename_commit)
-          expect(log_commits).not_to include(commit_with_new_name)
+        context "and 'path' includes a directory that used to be a file" do
+          let(:log_commits) do
+            repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
+          end
+
+          it "returns a list of commits" do
+            expect(log_commits.size).to eq(1)
+          end
         end
       end
 
-      context "and 'path' includes a directory that used to be a file" do
-        let(:log_commits) do
-          repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
-        end
+      context "where provides 'after' timestamp" do
+        options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
+
+        it "should returns commits on or after that timestamp" do
+          commits = repository.log(options)
 
-        it "returns a list of commits" do
-          expect(log_commits.size).to eq(1)
+          expect(commits.size).to be > 0
+          expect(commits).to satisfy do |commits|
+            commits.all? { |commit| commit.committed_date >= options[:after] }
+          end
         end
       end
-    end
 
-    context "where provides 'after' timestamp" do
-      options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
+      context "where provides 'before' timestamp" do
+        options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
 
-      it "should returns commits on or after that timestamp" do
-        commits = repository.log(options)
+        it "should returns commits on or before that timestamp" do
+          commits = repository.log(options)
 
-        expect(commits.size).to be > 0
-        expect(commits).to satisfy do |commits|
-          commits.all? { |commit| commit.committed_date >= options[:after] }
+          expect(commits.size).to be > 0
+          expect(commits).to satisfy do |commits|
+            commits.all? { |commit| commit.committed_date <= options[:before] }
+          end
         end
       end
-    end
 
-    context "where provides 'before' timestamp" do
-      options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
+      context 'when multiple paths are provided' do
+        let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
 
-      it "should returns commits on or before that timestamp" do
-        commits = repository.log(options)
-
-        expect(commits.size).to be > 0
-        expect(commits).to satisfy do |commits|
-          commits.all? { |commit| commit.committed_date <= options[:before] }
+        def commit_files(commit)
+          commit.rugged_diff_from_parent.deltas.flat_map do |delta|
+            [delta.old_file[:path], delta.new_file[:path]].uniq.compact
+          end
         end
-      end
-    end
 
-    context 'when multiple paths are provided' do
-      let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
+        it 'only returns commits matching at least one path' do
+          commits = repository.log(options)
 
-      def commit_files(commit)
-        commit.rugged_diff_from_parent.deltas.flat_map do |delta|
-          [delta.old_file[:path], delta.new_file[:path]].uniq.compact
+          expect(commits.size).to be > 0
+          expect(commits).to satisfy do |commits|
+            commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
+          end
         end
       end
 
-      it 'only returns commits matching at least one path' do
-        commits = repository.log(options)
+      context 'limit validation' do
+        where(:limit) do
+          [0, nil, '', 'foo']
+        end
 
-        expect(commits.size).to be > 0
-        expect(commits).to satisfy do |commits|
-          commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
+        with_them do
+          it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
         end
       end
-    end
 
-    context 'limit validation' do
-      where(:limit) do
-        [0, nil, '', 'foo']
-      end
+      context 'with all' do
+        it 'returns a list of commits' do
+          commits = repository.log({ all: true, limit: 50 })
 
-      with_them do
-        it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
+          expect(commits.size).to eq(37)
+        end
       end
     end
 
-    context 'with all' do
-      let(:options) { { all: true, limit: 50 } }
-
-      it 'returns a list of commits' do
-        commits = repository.log(options)
+    context 'when Gitaly find_commits feature is enabled' do
+      it_behaves_like 'repository log'
+    end
 
-        expect(commits.size).to eq(37)
-      end
+    context 'when Gitaly find_commits feature is disabled', :disable_gitaly do
+      it_behaves_like 'repository log'
     end
   end
 
@@ -1042,6 +1054,44 @@ describe Gitlab::Git::Repository, seed_helper: true do
     it { is_expected.to eq(17) }
   end
 
+  describe '#raw_changes_between' do
+    let(:old_rev) { }
+    let(:new_rev) { }
+    let(:changes) { repository.raw_changes_between(old_rev, new_rev) }
+
+    context 'initial commit' do
+      let(:old_rev) { Gitlab::Git::BLANK_SHA }
+      let(:new_rev) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' }
+
+      it 'returns the changes' do
+        expect(changes).to be_present
+        expect(changes.size).to eq(3)
+      end
+    end
+
+    context 'with an invalid rev' do
+      let(:old_rev) { 'foo' }
+      let(:new_rev) { 'bar' }
+
+      it 'returns an error' do
+        expect { changes }.to raise_error(Gitlab::Git::Repository::GitError)
+      end
+    end
+
+    context 'with valid revs' do
+      let(:old_rev) { 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6' }
+      let(:new_rev) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+
+      it 'returns the changes' do
+        expect(changes.size).to eq(9)
+        expect(changes.first.operation).to eq(:modified)
+        expect(changes.first.new_path).to eq('.gitmodules')
+        expect(changes.last.operation).to eq(:added)
+        expect(changes.last.new_path).to eq('files/lfs/picture-invalid.png')
+      end
+    end
+  end
+
   describe '#merge_base' do
     shared_examples '#merge_base' do
       where(:from, :to, :result) do
@@ -1136,14 +1186,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
           expect(repository.count_commits(options)).to eq(10)
         end
       end
-    end
-
-    context 'when Gitaly count_commits feature is enabled' do
-      it_behaves_like 'extended commit counting'
-    end
-
-    context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do
-      it_behaves_like 'extended commit counting'
 
       context "with all" do
         it "returns the number of commits in the whole repository" do
@@ -1155,10 +1197,18 @@ describe Gitlab::Git::Repository, seed_helper: true do
 
       context 'without all or ref being specified' do
         it "raises an ArgumentError" do
-          expect { repository.count_commits({}) }.to raise_error(ArgumentError, "Please specify a valid ref or set the 'all' attribute to true")
+          expect { repository.count_commits({}) }.to raise_error(ArgumentError)
         end
       end
     end
+
+    context 'when Gitaly count_commits feature is enabled' do
+      it_behaves_like 'extended commit counting'
+    end
+
+    context 'when Gitaly count_commits feature is disabled', :disable_gitaly do
+      it_behaves_like 'extended commit counting'
+    end
   end
 
   describe '#autocrlf' do
@@ -2183,6 +2233,55 @@ describe Gitlab::Git::Repository, seed_helper: true do
     end
   end
 
+  describe '#checksum' do
+    shared_examples 'calculating checksum' do
+      it 'calculates the checksum for non-empty repo' do
+        expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4'
+      end
+
+      it 'returns 0000000000000000000000000000000000000000 for an empty repo' do
+        FileUtils.rm_rf(File.join(storage_path, 'empty-repo.git'))
+
+        system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git),
+               chdir: storage_path,
+               out:   '/dev/null',
+               err:   '/dev/null')
+
+        empty_repo = described_class.new('default', 'empty-repo.git', '')
+
+        expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000'
+      end
+
+      it 'raises a no repository exception when there is no repo' do
+        broken_repo = described_class.new('default', 'a/path.git', '')
+
+        expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository)
+      end
+    end
+
+    context 'when calculate_checksum Gitaly feature is enabled' do
+      it_behaves_like 'calculating checksum'
+    end
+
+    context 'when calculate_checksum Gitaly feature is disabled', :disable_gitaly do
+      it_behaves_like 'calculating checksum'
+
+      describe 'when storage is broken', :broken_storage  do
+        it 'raises a storage exception when storage is not available' do
+          broken_repo = described_class.new('broken', 'a/path.git', '')
+
+          expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible)
+        end
+      end
+
+      it "raises a Gitlab::Git::Repository::Failure error if the `popen` call to git returns a non-zero exit code" do
+        allow(repository).to receive(:popen).and_return(['output', nil])
+
+        expect { repository.checksum }.to raise_error Gitlab::Git::Repository::ChecksumError
+      end
+    end
+  end
+
   context 'gitlab_projects commands' do
     let(:gitlab_projects) { repository.gitlab_projects }
     let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
@@ -2256,6 +2355,39 @@ describe Gitlab::Git::Repository, seed_helper: true do
       end
     end
 
+    describe '#clean_stale_repository_files' do
+      let(:worktree_path) { File.join(repository.path, 'worktrees', 'delete-me') }
+
+      it 'cleans up the files' do
+        repository.with_worktree(worktree_path, 'master', env: ENV) do
+          FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
+          # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
+          # but the HEAD must be 40 characters long or git will ignore it.
+          File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
+
+          # git 2.16 fails with "fatal: bad object HEAD"
+          expect { repository.rev_list(including: :all) }.to raise_error(Gitlab::Git::Repository::GitError)
+
+          repository.clean_stale_repository_files
+
+          expect { repository.rev_list(including: :all) }.not_to raise_error
+          expect(File.exist?(worktree_path)).to be_falsey
+        end
+      end
+
+      it 'increments a counter upon an error' do
+        expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError)
+
+        counter = double(:counter)
+
+        expect(counter).to receive(:increment)
+        expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total,
+                                                          'Number of failed repository cleanup events').and_return(counter)
+
+        repository.clean_stale_repository_files
+      end
+    end
+
     describe '#delete_remote_branches' do
       subject do
         repository.delete_remote_branches('downstream-remote', ['master'])
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index 4e0ee206219e50852277f8d26be2123bae486aac..32ec1e029c855f7b4230676f46c03cb29205ac86 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -3,17 +3,6 @@ require 'spec_helper'
 describe Gitlab::Git::RevList do
   let(:repository) { create(:project, :repository).repository.raw }
   let(:rev_list) { described_class.new(repository, newrev: 'newrev') }
-  let(:env_hash) do
-    {
-      'GIT_OBJECT_DIRECTORY' => 'foo',
-      'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
-    }
-  end
-  let(:command_env) { { 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'foo:bar' } }
-
-  before do
-    allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash)
-  end
 
   def args_for_popen(args_list)
     [Gitlab.config.git.bin_path, 'rev-list', *args_list]
@@ -23,7 +12,7 @@ describe Gitlab::Git::RevList do
     params = [
       args_for_popen(additional_args),
       repository.path,
-      command_env,
+      {},
       hash_including(lazy_block: with_lazy_block ? anything : nil)
     ]
 
diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb
index 761f77320365b18bdd3f0b6986d3dbb4c7a5dc37..722d697c28e8cbdaaa43a52a47af5098dab1c65e 100644
--- a/spec/lib/gitlab/git/wiki_spec.rb
+++ b/spec/lib/gitlab/git/wiki_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::Git::Wiki do
   end
 
   def commit_details(name)
-    Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "created page #{name}")
+    Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "created page #{name}")
   end
 
   def destroy_page(title, dir = '')
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 6f07e423c1b7c86527626942933c728966a7a80d..6c6255966059ea54463d5c661d7644da0c70e246 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -10,12 +10,13 @@ describe Gitlab::GitAccess do
   let(:protocol) { 'ssh' }
   let(:authentication_abilities) { %i[read_project download_code push_code] }
   let(:redirected_path) { nil }
+  let(:auth_result_type) { nil }
 
   let(:access) do
     described_class.new(actor, project,
       protocol, authentication_abilities: authentication_abilities,
                 namespace_path: namespace_path, project_path: project_path,
-                redirected_path: redirected_path)
+                redirected_path: redirected_path, auth_result_type: auth_result_type)
   end
 
   let(:changes) { '_any' }
@@ -45,6 +46,7 @@ describe Gitlab::GitAccess do
 
       before do
         disable_protocol('http')
+        project.add_master(user)
       end
 
       it 'blocks http push and pull' do
@@ -53,6 +55,26 @@ describe Gitlab::GitAccess do
           expect { pull_access_check }.to raise_unauthorized('Git access over HTTP is not allowed')
         end
       end
+
+      context 'when request is made from CI' do
+        let(:auth_result_type) { :build }
+
+        it "doesn't block http pull" do
+          aggregate_failures do
+            expect { pull_access_check }.not_to raise_unauthorized('Git access over HTTP is not allowed')
+          end
+        end
+
+        context 'when legacy CI credentials are used' do
+          let(:auth_result_type) { :ci }
+
+          it "doesn't block http pull" do
+            aggregate_failures do
+              expect { pull_access_check }.not_to raise_unauthorized('Git access over HTTP is not allowed')
+            end
+          end
+        end
+      end
     end
   end
 
@@ -123,6 +145,33 @@ describe Gitlab::GitAccess do
             expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload])
           end
         end
+
+        context 'when actor is DeployToken' do
+          let(:actor) { create(:deploy_token, projects: [project]) }
+
+          context 'when DeployToken is active and belongs to project' do
+            it 'allows pull access' do
+              expect { pull_access_check }.not_to raise_error
+            end
+
+            it 'blocks the push' do
+              expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload])
+            end
+          end
+
+          context 'when DeployToken does not belong to project' do
+            let(:another_project) { create(:project) }
+            let(:actor) { create(:deploy_token, projects: [another_project]) }
+
+            it 'blocks pull access' do
+              expect { pull_access_check }.to raise_not_found
+            end
+
+            it 'blocks the push' do
+              expect { push_access_check }.to raise_not_found
+            end
+          end
+        end
       end
 
       context 'when actor is nil' do
@@ -240,14 +289,21 @@ describe Gitlab::GitAccess do
   end
 
   shared_examples 'check_project_moved' do
-    it 'enqueues a redirected message' do
+    it 'enqueues a redirected message for pushing' do
       push_access_check
 
       expect(Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)).not_to be_nil
     end
+
+    it 'allows push and pull access' do
+      aggregate_failures do
+        expect { push_access_check }.not_to raise_error
+        expect { pull_access_check }.not_to raise_error
+      end
+    end
   end
 
-  describe '#check_project_moved!', :clean_gitlab_redis_shared_state do
+  describe '#add_project_moved_message!', :clean_gitlab_redis_shared_state do
     before do
       project.add_master(user)
     end
@@ -261,62 +317,18 @@ describe Gitlab::GitAccess do
       end
     end
 
-    context 'when a permanent redirect and ssh protocol' do
+    context 'with a redirect and ssh protocol' do
       let(:redirected_path) { 'some/other-path' }
 
-      before do
-        allow_any_instance_of(Gitlab::Checks::ProjectMoved).to receive(:permanent_redirect?).and_return(true)
-      end
-
-      it 'allows push and pull access' do
-        aggregate_failures do
-          expect { push_access_check }.not_to raise_error
-        end
-      end
-
       it_behaves_like 'check_project_moved'
     end
 
-    context 'with a permanent redirect and http protocol' do
+    context 'with a redirect and http protocol' do
       let(:redirected_path) { 'some/other-path' }
       let(:protocol) { 'http' }
 
-      before do
-        allow_any_instance_of(Gitlab::Checks::ProjectMoved).to receive(:permanent_redirect?).and_return(true)
-      end
-
-      it 'allows_push and pull access' do
-        aggregate_failures do
-          expect { push_access_check }.not_to raise_error
-        end
-      end
-
       it_behaves_like 'check_project_moved'
     end
-
-    context 'with a temporal redirect and ssh protocol' do
-      let(:redirected_path) { 'some/other-path' }
-
-      it 'blocks push and pull access' do
-        aggregate_failures do
-          expect { push_access_check }.to raise_error(described_class::ProjectMovedError, /Project '#{redirected_path}' was moved to '#{project.full_path}'/)
-          expect { push_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.ssh_url_to_repo}/)
-
-          expect { pull_access_check }.to raise_error(described_class::ProjectMovedError, /Project '#{redirected_path}' was moved to '#{project.full_path}'/)
-          expect { pull_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.ssh_url_to_repo}/)
-        end
-      end
-    end
-
-    context 'with a temporal redirect and http protocol' do
-      let(:redirected_path) { 'some/other-path' }
-      let(:protocol) { 'http' }
-
-      it 'does not allow to push and pull access' do
-        expect { push_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.http_url_to_repo}/)
-        expect { pull_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.http_url_to_repo}/)
-      end
-    end
   end
 
   describe '#check_authentication_abilities!' do
@@ -609,6 +621,41 @@ describe Gitlab::GitAccess do
       end
     end
 
+    describe 'deploy token permissions' do
+      let(:deploy_token) { create(:deploy_token) }
+      let(:actor) { deploy_token }
+
+      context 'pull code' do
+        context 'when project is authorized' do
+          before do
+            deploy_token.projects << project
+          end
+
+          it { expect { pull_access_check }.not_to raise_error }
+        end
+
+        context 'when unauthorized' do
+          context 'from public project' do
+            let(:project) { create(:project, :public, :repository) }
+
+            it { expect { pull_access_check }.not_to raise_error }
+          end
+
+          context 'from internal project' do
+            let(:project) { create(:project, :internal, :repository) }
+
+            it { expect { pull_access_check }.to raise_not_found }
+          end
+
+          context 'from private project' do
+            let(:project) { create(:project, :private, :repository) }
+
+            it { expect { pull_access_check }.to raise_not_found }
+          end
+        end
+      end
+    end
+
     describe 'build authentication_abilities permissions' do
       let(:authentication_abilities) { build_authentication_abilities }
 
@@ -870,6 +917,20 @@ describe Gitlab::GitAccess do
                                                             admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
       end
     end
+
+    context 'when pushing to a project' do
+      let(:project) { create(:project, :public, :repository) }
+      let(:changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow" }
+
+      before do
+        project.add_developer(user)
+      end
+
+      it 'cleans up the files' do
+        expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
+        expect { push_access_check }.not_to raise_error
+      end
+    end
   end
 
   describe 'build authentication abilities' do
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a2770ef2fe464744308feb5853cb09b332e78a2f
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::BlobService do
+  let(:project) { create(:project, :repository) }
+  let(:storage_name) { project.repository_storage }
+  let(:relative_path) { project.disk_path + '.git' }
+  let(:repository) { project.repository }
+  let(:client) { described_class.new(repository) }
+
+  describe '#get_new_lfs_pointers' do
+    let(:revision) { 'master' }
+    let(:limit) { 5 }
+    let(:not_in) { ['branch-a', 'branch-b'] }
+    let(:expected_params) do
+      { revision: revision, limit: limit, not_in_refs: not_in, not_in_all: false }
+    end
+
+    subject { client.get_new_lfs_pointers(revision, limit, not_in) }
+
+    it 'sends a get_new_lfs_pointers message' do
+      expect_any_instance_of(Gitaly::BlobService::Stub)
+        .to receive(:get_new_lfs_pointers)
+        .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+        .and_return([])
+
+      subject
+    end
+
+    context 'with not_in = :all' do
+      let(:not_in) { :all }
+      let(:expected_params) do
+        { revision: revision, limit: limit, not_in_refs: [], not_in_all: true }
+      end
+
+      it 'sends the correct message' do
+        expect_any_instance_of(Gitaly::BlobService::Stub)
+          .to receive(:get_new_lfs_pointers)
+          .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+          .and_return([])
+
+        subject
+      end
+    end
+  end
+
+  describe '#get_all_lfs_pointers' do
+    let(:revision) { 'master' }
+
+    subject { client.get_all_lfs_pointers(revision) }
+
+    it 'sends a get_all_lfs_pointers message' do
+      expect_any_instance_of(Gitaly::BlobService::Stub)
+        .to receive(:get_all_lfs_pointers)
+        .with(gitaly_request_with_params(revision: revision), kind_of(Hash))
+        .and_return([])
+
+      subject
+    end
+  end
+end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 9be3fa633a7ee09237b2830e643de51575fa6e76..7951cbe7b1dc741d07e164c7437170f7883fc85d 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::GitalyClient::CommitService do
         initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863').raw
         request        = Gitaly::CommitDiffRequest.new(
           repository: repository_message,
-          left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+          left_commit_id: Gitlab::Git::EMPTY_TREE_ID,
           right_commit_id: initial_commit.id,
           collapse_diffs: true,
           enforce_limits: true,
@@ -77,7 +77,7 @@ describe Gitlab::GitalyClient::CommitService do
         initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
         request        = Gitaly::CommitDeltaRequest.new(
           repository: repository_message,
-          left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+          left_commit_id: Gitlab::Git::EMPTY_TREE_ID,
           right_commit_id: initial_commit.id
         )
 
@@ -90,7 +90,7 @@ describe Gitlab::GitalyClient::CommitService do
 
   describe '#between' do
     let(:from) { 'master' }
-    let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
+    let(:to) { Gitlab::Git::EMPTY_TREE_ID }
 
     it 'sends an RPC request' do
       request = Gitaly::CommitsBetweenRequest.new(
@@ -155,7 +155,7 @@ describe Gitlab::GitalyClient::CommitService do
   end
 
   describe '#find_commit' do
-    let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
+    let(:revision) { Gitlab::Git::EMPTY_TREE_ID }
     it 'sends an RPC request' do
       request = Gitaly::FindCommitRequest.new(
         repository: repository_message, revision: revision
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index 872377c93d8ab121f67709c732076d7ccf0c97ae..f03c7e3f04b423cd442dc069b3d56261ceffeaef 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -58,4 +58,14 @@ describe Gitlab::GitalyClient::RemoteService do
       client.update_remote_mirror(ref_name, only_branches_matching)
     end
   end
+
+  describe '.exists?' do
+    context "when the remote doesn't exist" do
+      let(:url) { 'https://gitlab.com/gitlab-org/ik-besta-niet-of-ik-word-geplaagd.git' }
+
+      it 'returns false' do
+        expect(described_class.exists?(url)).to be(false)
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index c50e73cecfcd29219449cf735e93ddb1538b1dc7..074323d47d274323252ed24e8abd967788f05e96 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -17,6 +17,16 @@ describe Gitlab::GitalyClient::RepositoryService do
     end
   end
 
+  describe '#cleanup' do
+    it 'sends a cleanup message' do
+      expect_any_instance_of(Gitaly::RepositoryService::Stub)
+        .to receive(:cleanup)
+        .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+
+      client.cleanup
+    end
+  end
+
   describe '#garbage_collect' do
     it 'sends a garbage_collect message' do
       expect_any_instance_of(Gitaly::RepositoryService::Stub)
@@ -74,6 +84,17 @@ describe Gitlab::GitalyClient::RepositoryService do
     end
   end
 
+  describe '#info_attributes' do
+    it 'reads the info attributes' do
+      expect_any_instance_of(Gitaly::RepositoryService::Stub)
+        .to receive(:get_info_attributes)
+        .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+        .and_return([])
+
+      client.info_attributes
+    end
+  end
+
   describe '#has_local_branches?' do
     it 'sends a has_local_branches message' do
       expect_any_instance_of(Gitaly::RepositoryService::Stub)
@@ -85,6 +106,20 @@ describe Gitlab::GitalyClient::RepositoryService do
     end
   end
 
+  describe '#fetch_remote' do
+    let(:ssh_auth) { double(:ssh_auth, ssh_import?: true, ssh_key_auth?: false, ssh_known_hosts: nil) }
+    let(:import_url) { 'ssh://example.com' }
+
+    it 'sends a fetch_remote_request message' do
+      expect_any_instance_of(Gitaly::RepositoryService::Stub)
+        .to receive(:fetch_remote)
+        .with(gitaly_request_with_params(no_prune: false), kind_of(Hash))
+        .and_return(double(value: true))
+
+      client.fetch_remote(import_url, ssh_auth: ssh_auth, forced: false, no_tags: false, timeout: 60)
+    end
+  end
+
   describe '#rebase_in_progress?' do
     let(:rebase_id) { 1 }
 
@@ -110,4 +145,15 @@ describe Gitlab::GitalyClient::RepositoryService do
       client.squash_in_progress?(squash_id)
     end
   end
+
+  describe '#calculate_checksum' do
+    it 'sends a calculate_checksum message' do
+      expect_any_instance_of(Gitaly::RepositoryService::Stub)
+        .to receive(:calculate_checksum)
+        .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+        .and_return(double(checksum: 0))
+
+      client.calculate_checksum
+    end
+  end
 end
diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb
index d1e0136f8c1ff435cd1515c5d982e727be17034b..550db6db6d9f0226d8c27de3a93e8091cf36b060 100644
--- a/spec/lib/gitlab/gitaly_client/util_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/util_spec.rb
@@ -7,16 +7,19 @@ describe Gitlab::GitalyClient::Util do
     let(:gl_repository) { 'project-1' }
     let(:git_object_directory) { '.git/objects' }
     let(:git_alternate_object_directory) { ['/dir/one', '/dir/two'] }
+    let(:git_env) do
+      {
+        'GIT_OBJECT_DIRECTORY_RELATIVE' => git_object_directory,
+        'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => git_alternate_object_directory
+      }
+    end
 
     subject do
       described_class.repository(repository_storage, relative_path, gl_repository)
     end
 
     it 'creates a Gitaly::Repository with the given data' do
-      allow(Gitlab::Git::Env).to receive(:[]).with('GIT_OBJECT_DIRECTORY_RELATIVE')
-        .and_return(git_object_directory)
-      allow(Gitlab::Git::Env).to receive(:[]).with('GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE')
-        .and_return(git_alternate_object_directory)
+      allow(Gitlab::Git::HookEnv).to receive(:all).with(gl_repository).and_return(git_env)
 
       expect(subject).to be_a(Gitaly::Repository)
       expect(subject.storage_name).to eq(repository_storage)
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index 5bedfc79dd31ab67dea58ebb8a07cdf0bc222bdc..879b1d9fb0f1a10dfb618d8a1b98f4b29e192639 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
       :project,
       import_url: 'foo.git',
       import_source: 'foo/bar',
-      repository_storage_path: 'foo',
+      repository_storage: 'foo',
       disk_path: 'foo',
       repository: repository,
       create_wiki: true
@@ -38,8 +38,12 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
       expect(project)
         .to receive(:wiki_repository_exists?)
         .and_return(false)
+      expect(Gitlab::GitalyClient::RemoteService)
+        .to receive(:exists?)
+        .with("foo.wiki.git")
+        .and_return(true)
 
-      expect(importer.import_wiki?).to eq(true)
+      expect(importer.import_wiki?).to be(true)
     end
 
     it 'returns false if the GitHub wiki is disabled' do
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 4c1ca4349ea2b7c4c39ce54ad5038d19bcaa6d74..9dcf272d25ea61644dafb0f0b6d777e74b9a917a 100644
--- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
@@ -26,7 +26,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
 
   let(:storages_paths) do
     {
-      default: { path: tmp_dir }
+      default: Gitlab::GitalyClient::StorageSettings.new('path' => tmp_dir)
     }.with_indifferent_access
   end
 
@@ -56,7 +56,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
       context 'storage points to not existing folder' do
         let(:storages_paths) do
           {
-            default: { path: 'tmp/this/path/doesnt/exist' }
+            default: Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/this/path/doesnt/exist')
           }.with_indifferent_access
         end
 
@@ -102,7 +102,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
       context 'storage points to not existing folder' do
         let(:storages_paths) do
           {
-            default: { path: 'tmp/this/path/doesnt/exist' }
+            default: Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/this/path/doesnt/exist')
           }.with_indifferent_access
         end
 
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d0dadfa78da2ba678a0001f4ca2da16012e90cb6
--- /dev/null
+++ b/spec/lib/gitlab/http_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::HTTP do
+  describe 'allow_local_requests_from_hooks_and_services is' do
+    before do
+      WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
+    end
+
+    context 'disabled' do
+      before do
+        allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(false)
+      end
+
+      it 'deny requests to localhost' do
+        expect { described_class.get('http://localhost:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+      end
+
+      it 'deny requests to private network' do
+        expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+      end
+
+      context 'if allow_local_requests set to true' do
+        it 'override the global value and allow requests to localhost or private network' do
+          expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error
+        end
+      end
+    end
+
+    context 'enabled' do
+      before do
+        allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(true)
+      end
+
+      it 'allow requests to localhost' do
+        expect { described_class.get('http://localhost:3003') }.not_to raise_error
+      end
+
+      it 'allow requests to private network' do
+        expect { described_class.get('http://192.168.1.2:3003') }.not_to raise_error
+      end
+
+      context 'if allow_local_requests set to false' do
+        it 'override the global value and ban requests to localhost or private network' do
+          expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ed54d87de4aa9121c0813c45902a603a3a8ca5b2
--- /dev/null
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
+  let!(:service) { described_class.new }
+  let!(:project) { create(:project, :with_export) }
+  let(:shared) { project.import_export_shared }
+  let!(:user) { create(:user) }
+
+  describe '#execute' do
+    before do
+      allow(service).to receive(:strategy_execute)
+    end
+
+    it 'returns if project exported file is not found' do
+      allow(project).to receive(:export_project_path).and_return(nil)
+
+      expect(service).not_to receive(:strategy_execute)
+
+      service.execute(user, project)
+    end
+
+    it 'creates a lock file in the export dir' do
+      allow(service).to receive(:delete_after_export_lock)
+
+      service.execute(user, project)
+
+      expect(lock_path_exist?).to be_truthy
+    end
+
+    context 'when the method succeeds' do
+      it 'removes the lock file' do
+        service.execute(user, project)
+
+        expect(lock_path_exist?).to be_falsey
+      end
+    end
+
+    context 'when the method fails' do
+      before do
+        allow(service).to receive(:strategy_execute).and_call_original
+      end
+
+      context 'when validation fails' do
+        before do
+          allow(service).to receive(:invalid?).and_return(true)
+        end
+
+        it 'does not create the lock file' do
+          expect(service).not_to receive(:create_or_update_after_export_lock)
+
+          service.execute(user, project)
+        end
+
+        it 'does not execute main logic' do
+          expect(service).not_to receive(:strategy_execute)
+
+          service.execute(user, project)
+        end
+
+        it 'logs validation errors in shared context' do
+          expect(service).to receive(:log_validation_errors)
+
+          service.execute(user, project)
+        end
+      end
+
+      context 'when an exception is raised' do
+        it 'removes the lock' do
+          expect { service.execute(user, project) }.to raise_error(NotImplementedError)
+
+          expect(lock_path_exist?).to be_falsey
+        end
+      end
+    end
+  end
+
+  describe '#log_validation_errors' do
+    it 'add the message to the shared context' do
+      errors = %w(test_message test_message2)
+
+      allow(service).to receive(:invalid?).and_return(true)
+      allow(service.errors).to receive(:full_messages).and_return(errors)
+
+      expect(shared).to receive(:add_error_message).twice.and_call_original
+
+      service.execute(user, project)
+
+      expect(shared.errors).to eq errors
+    end
+  end
+
+  describe '#to_json' do
+    it 'adds the current strategy class to the serialized attributes' do
+      params = { param1: 1 }
+      result = params.merge(klass: described_class.to_s).to_json
+
+      expect(described_class.new(params).to_json).to eq result
+    end
+  end
+
+  def lock_path_exist?
+    File.exist?(described_class.lock_file_path(project))
+  end
+end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5fe57d9987b992c35e99a1e736712d42949c2f3c
--- /dev/null
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
+  let(:example_url) { 'http://www.example.com' }
+  let(:strategy) { subject.new(url: example_url, http_method: 'post') }
+  let!(:project) { create(:project, :with_export) }
+  let!(:user) { build(:user) }
+
+  subject { described_class }
+
+  describe 'validations' do
+    it 'only POST and PUT method allowed' do
+      %w(POST post PUT put).each do |method|
+        expect(subject.new(url: example_url, http_method: method)).to be_valid
+      end
+
+      expect(subject.new(url: example_url, http_method: 'whatever')).not_to be_valid
+    end
+
+    it 'onyl allow urls as upload urls' do
+      expect(subject.new(url: example_url)).to be_valid
+      expect(subject.new(url: 'whatever')).not_to be_valid
+    end
+  end
+
+  describe '#execute' do
+    it 'removes the exported project file after the upload' do
+      allow(strategy).to receive(:send_file)
+      allow(strategy).to receive(:handle_response_error)
+
+      expect(project).to receive(:remove_exported_project_file)
+
+      strategy.execute(user, project)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bf727285a9fe2a2e8b415ce16beb6e3f8237c6b7
--- /dev/null
+++ b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AfterExportStrategyBuilder do
+  let!(:strategies_namespace) { 'Gitlab::ImportExport::AfterExportStrategies' }
+
+  describe '.build!' do
+    context 'when klass param is' do
+      it 'null it returns the default strategy' do
+        expect(described_class.build!(nil).class).to eq described_class.default_strategy
+      end
+
+      it 'not a valid class it raises StrategyNotFoundError exception' do
+        expect { described_class.build!('Whatever') }.to raise_error(described_class::StrategyNotFoundError)
+      end
+
+      it 'not a descendant of AfterExportStrategy' do
+        expect { described_class.build!('User') }.to raise_error(described_class::StrategyNotFoundError)
+      end
+    end
+
+    it 'initializes strategy with attributes param' do
+      params = { param1: 1, param2: 2, param3: 3 }
+
+      strategy = described_class.build!("#{strategies_namespace}::DownloadNotificationStrategy", params)
+
+      params.each { |k, v| expect(strategy.public_send(k)).to eq v }
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 41a55027f4dcaca455167d6d0dd8cc904a8bbf2e..897a5984782ebb3bcbbc987c5d3d6d09f7c86807 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -18,6 +18,7 @@ issues:
 - metrics
 - timelogs
 - issue_assignees
+- closed_by
 events:
 - author
 - project
@@ -144,6 +145,9 @@ pipeline_schedule:
 - pipelines
 pipeline_schedule_variables:
 - pipeline_schedule
+deploy_tokens:
+- project_deploy_tokens
+- projects
 deploy_keys:
 - user
 - deploy_keys_projects
@@ -277,6 +281,11 @@ project:
 - fork_network
 - custom_attributes
 - lfs_file_locks
+- project_badges
+- source_of_merge_requests
+- internal_ids
+- project_deploy_tokens
+- deploy_tokens
 award_emoji:
 - awardable
 - user
@@ -293,3 +302,5 @@ issue_assignees:
 - assignee
 lfs_file_locks:
 - user
+project_badges:
+- project
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
index a93a921e4593813da5f2a73104d891229678c594..4897d604bc16e162332ba7a89635f527bc4dfb53 100644
--- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::ImportExport::AvatarRestorer do
   include UploadHelpers
 
-  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+  let(:shared) { project.import_export_shared }
   let(:project) { create(:project) }
 
   before do
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index 3fb5ddde8b55a5d2086f7213503058b1c6e97a4c..f40d4bc2d089e89967772b3d4f1d849382bcfcf1 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::ImportExport::AvatarSaver do
-  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+  let(:shared) { project.import_export_shared }
   let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
   let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
   let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 5cdc5138fda736072c5ef1a3fcc472aef882e8ed..58b9fb06cc5fec47086d6c6101783f4010f85532 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::ImportExport::FileImporter do
-  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+  let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
   let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
   let(:valid_file) { "#{shared.export_path}/valid.json" }
   let(:symlink_file) { "#{shared.export_path}/invalid.json" }
@@ -12,6 +12,7 @@ describe Gitlab::ImportExport::FileImporter do
     stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
     allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
     allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
+    allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test')
     allow(SecureRandom).to receive(:hex).and_return('abcd')
     setup_files
   end
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index cfb15ee7e8b74f29f747dbbd9831f63049a5059d..17e06a6a83f3e6a1ccc3a58ee85dfcd1ef9e5dd8 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -7,7 +7,7 @@ describe 'forked project import' do
   let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
   let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') }
   let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
-  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+  let(:shared) { project.import_export_shared }
   let(:forked_from_project) { create(:project, :repository) }
   let(:forked_project) { fork_project(project_with_repo, nil, repository: true) }
   let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..991e354f499f7a218e5c0219c04f0c7349d46d52
--- /dev/null
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::Importer do
+  let(:user) { create(:user) }
+  let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
+  let(:shared) { project.import_export_shared }
+  let(:project) { create(:project, import_source: File.join(test_path, 'exported-project.gz')) }
+
+  subject(:importer) { described_class.new(project) }
+
+  before do
+    allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
+    FileUtils.mkdir_p(shared.export_path)
+    FileUtils.cp(Rails.root.join('spec', 'fixtures', 'exported-project.gz'), test_path)
+    allow(subject).to receive(:remove_import_file)
+  end
+
+  after do
+    FileUtils.rm_rf(test_path)
+  end
+
+  describe '#execute' do
+    it 'succeeds' do
+      importer.execute
+
+      expect(shared.errors).to be_empty
+    end
+
+    it 'extracts the archive'  do
+      expect(Gitlab::ImportExport::FileImporter).to receive(:import).and_call_original
+
+      importer.execute
+    end
+
+    it 'checks the version' do
+      expect(Gitlab::ImportExport::VersionChecker).to receive(:check!).and_call_original
+
+      importer.execute
+    end
+
+    context 'all restores are executed' do
+      [
+        Gitlab::ImportExport::AvatarRestorer,
+        Gitlab::ImportExport::RepoRestorer,
+        Gitlab::ImportExport::WikiRestorer,
+        Gitlab::ImportExport::UploadsRestorer,
+        Gitlab::ImportExport::LfsRestorer,
+        Gitlab::ImportExport::StatisticsRestorer
+      ].each do |restorer|
+        it "calls the #{restorer}" do
+          fake_restorer = double(restorer.to_s)
+
+          expect(fake_restorer).to receive(:restore).and_return(true).at_least(1)
+          expect(restorer).to receive(:new).and_return(fake_restorer).at_least(1)
+
+          importer.execute
+        end
+      end
+
+      it 'restores the ProjectTree' do
+        expect(Gitlab::ImportExport::ProjectTreeRestorer).to receive(:new).and_call_original
+
+        importer.execute
+      end
+    end
+
+    context 'when project successfully restored' do
+      let!(:existing_project) { create(:project, namespace: user.namespace) }
+      let(:project) { create(:project, namespace: user.namespace, name: 'whatever', path: 'whatever') }
+
+      before do
+        restorers = double
+
+        allow(subject).to receive(:import_file).and_return(true)
+        allow(subject).to receive(:check_version!).and_return(true)
+        allow(subject).to receive(:restorers).and_return(restorers)
+        allow(restorers).to receive(:all?).and_return(true)
+        allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path }))
+      end
+
+      context 'when import_data' do
+        context 'has original_path' do
+          it 'overwrites existing project' do
+            expect_any_instance_of(::Projects::OverwriteProjectService).to receive(:execute).with(existing_project)
+
+            subject.execute
+          end
+        end
+
+        context 'has not original_path' do
+          before do
+            allow(project).to receive(:import_data).and_return(double(data: {}))
+          end
+
+          it 'does not call the overwrite service' do
+            expect_any_instance_of(::Projects::OverwriteProjectService).not_to receive(:execute).with(existing_project)
+
+            subject.execute
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..70eeb9ee66be130256e8d69d414c72c5f6de74be
--- /dev/null
+++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::LfsRestorer do
+  include UploadHelpers
+
+  let(:export_path) { "#{Dir.tmpdir}/lfs_object_restorer_spec" }
+  let(:project) { create(:project) }
+  let(:shared) { project.import_export_shared }
+  subject(:restorer) { described_class.new(project: project, shared: shared) }
+
+  before do
+    allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+    FileUtils.mkdir_p(shared.export_path)
+  end
+
+  after do
+    FileUtils.rm_rf(shared.export_path)
+  end
+
+  describe '#restore' do
+    context 'when the archive contains lfs files' do
+      let(:dummy_lfs_file_path) { File.join(shared.export_path, 'lfs-objects', 'dummy') }
+
+      def create_lfs_object_with_content(content)
+        dummy_lfs_file = Tempfile.new('existing')
+        File.write(dummy_lfs_file.path, content)
+        size = dummy_lfs_file.size
+        oid = LfsObject.calculate_oid(dummy_lfs_file.path)
+        LfsObject.create!(oid: oid, size: size, file: dummy_lfs_file)
+      end
+
+      before do
+        FileUtils.mkdir_p(File.dirname(dummy_lfs_file_path))
+        File.write(dummy_lfs_file_path, 'not very large')
+        allow(restorer).to receive(:lfs_file_paths).and_return([dummy_lfs_file_path])
+      end
+
+      it 'creates an lfs object for the project' do
+        expect { restorer.restore }.to change { project.reload.lfs_objects.size }.by(1)
+      end
+
+      it 'assigns the file correctly' do
+        restorer.restore
+
+        expect(project.lfs_objects.first.file.read).to eq('not very large')
+      end
+
+      it 'links an existing LFS object if it existed' do
+        lfs_object = create_lfs_object_with_content('not very large')
+
+        restorer.restore
+
+        expect(project.lfs_objects).to include(lfs_object)
+      end
+
+      it 'succeeds' do
+        expect(restorer.restore).to be_truthy
+        expect(shared.errors).to be_empty
+      end
+
+      it 'stores the upload' do
+        expect_any_instance_of(LfsObjectUploader).to receive(:store!)
+
+        restorer.restore
+      end
+    end
+
+    context 'without any LFS-objects' do
+      it 'succeeds' do
+        expect(restorer.restore).to be_truthy
+        expect(shared.errors).to be_empty
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9b0e21deb2e8ba9f49cc5008753ed98c222407a7
--- /dev/null
+++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::LfsSaver do
+  let(:shared) { project.import_export_shared }
+  let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+  let(:project) { create(:project) }
+
+  subject(:saver) { described_class.new(project: project, shared: shared) }
+
+  before do
+    allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+    FileUtils.mkdir_p(shared.export_path)
+  end
+
+  after do
+    FileUtils.rm_rf(shared.export_path)
+  end
+
+  describe '#save' do
+    context 'when the project has LFS objects locally stored' do
+      let(:lfs_object) { create(:lfs_object, :with_file) }
+
+      before do
+        project.lfs_objects << lfs_object
+      end
+
+      it 'does not cause errors' do
+        saver.save
+
+        expect(shared.errors).to be_empty
+      end
+
+      it 'copies the file in the correct location when there is an lfs object' do
+        saver.save
+
+        expect(File).to exist("#{shared.export_path}/lfs-objects/#{lfs_object.oid}")
+      end
+    end
+
+    context 'when the LFS objects are stored in object storage' do
+      let(:lfs_object) { create(:lfs_object, :object_storage) }
+
+      before do
+        allow(LfsObjectUploader).to receive(:object_store_enabled?).and_return(true)
+        allow(lfs_object.file).to receive(:url).and_return('http://my-object-storage.local')
+        project.lfs_objects << lfs_object
+      end
+
+      it 'downloads the file to include in an archive' do
+        fake_uri = double
+        exported_file_path = "#{shared.export_path}/lfs-objects/#{lfs_object.oid}"
+
+        expect(fake_uri).to receive(:open).and_return(StringIO.new('LFS file content'))
+        expect(URI).to receive(:parse).with('http://my-object-storage.local').and_return(fake_uri)
+
+        saver.save
+
+        expect(File.read(exported_file_path)).to eq('LFS file content')
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index b6c1f0c81cb57883d78d601b827607179d49c7ee..6d63749296e455b6a90d4256ef6e17865da84866 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2,7 +2,6 @@
   "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
   "visibility_level": 10,
   "archived": false,
-  "description_html": "description",
   "labels": [
     {
       "id": 2,
@@ -14,8 +13,7 @@
       "template": false,
       "description": "",
       "type": "ProjectLabel",
-      "priorities": [
-      ]
+      "priorities": []
     },
     {
       "id": 3,
@@ -44,7 +42,6 @@
     {
       "id": 40,
       "title": "Voluptatem",
-      "assignee_id": 1,
       "author_id": 22,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:08.340Z",
@@ -62,7 +59,23 @@
       "issue_assignees": [
         {
           "user_id": 1,
-          "issue_id": 1
+          "issue_id": 40
+        },
+        {
+          "user_id": 15,
+          "issue_id": 40
+        },
+        {
+          "user_id": 16,
+          "issue_id": 40
+        },
+        {
+          "user_id": 16,
+          "issue_id": 40
+        },
+        {
+          "user_id": 6,
+          "issue_id": 40
         }
       ],
       "milestone": {
@@ -160,9 +173,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 352,
@@ -184,9 +195,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 353,
@@ -208,9 +217,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 354,
@@ -232,9 +239,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 355,
@@ -256,9 +261,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 356,
@@ -280,9 +283,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 357,
@@ -304,9 +305,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 358,
@@ -328,16 +327,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 39,
-      "title": "Delectus veniam ratione in eos culpa et natus molestiae earum aut.",
-      "assignee_id": 20,
+      "title": "Issue without assignees",
       "author_id": 22,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:08.233Z",
@@ -351,6 +347,7 @@
       "confidential": false,
       "due_date": null,
       "moved_to_id": null,
+      "issue_assignees": [],
       "milestone": {
         "id": 1,
         "title": "test milestone",
@@ -395,9 +392,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 360,
@@ -419,9 +414,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 361,
@@ -443,9 +436,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 362,
@@ -467,9 +458,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 363,
@@ -491,9 +480,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 364,
@@ -515,9 +502,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 365,
@@ -539,9 +524,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 366,
@@ -563,16 +546,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 38,
       "title": "Quasi adipisci non cupiditate dolorem quo qui earum sed.",
-      "assignee_id": 1,
       "author_id": 6,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:08.154Z",
@@ -628,9 +608,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 368,
@@ -652,9 +630,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 369,
@@ -676,9 +652,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 370,
@@ -700,9 +674,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 371,
@@ -724,9 +696,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 372,
@@ -748,9 +718,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 373,
@@ -772,9 +740,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 374,
@@ -796,16 +762,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 37,
       "title": "Cupiditate quo aut ducimus minima molestiae vero numquam possimus.",
-      "assignee_id": 15,
       "author_id": 20,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:08.051Z",
@@ -840,9 +803,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 376,
@@ -864,9 +825,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 377,
@@ -888,9 +847,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 378,
@@ -912,9 +869,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 379,
@@ -936,9 +891,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 380,
@@ -960,9 +913,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 381,
@@ -984,9 +935,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 382,
@@ -1008,16 +957,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 36,
       "title": "Necessitatibus dolor est enim quia rem suscipit quidem voluptas ullam.",
-      "assignee_id": 20,
       "author_id": 16,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:07.958Z",
@@ -1052,9 +998,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 384,
@@ -1076,9 +1020,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 385,
@@ -1100,9 +1042,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 386,
@@ -1124,9 +1064,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 387,
@@ -1148,9 +1086,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 388,
@@ -1172,9 +1108,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 389,
@@ -1196,9 +1130,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 390,
@@ -1220,16 +1152,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 35,
       "title": "Repellat praesentium deserunt maxime incidunt harum porro qui.",
-      "assignee_id": 6,
       "author_id": 20,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:07.832Z",
@@ -1264,9 +1193,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 392,
@@ -1288,9 +1215,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 393,
@@ -1312,9 +1237,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 394,
@@ -1336,9 +1259,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 395,
@@ -1360,9 +1281,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 396,
@@ -1384,9 +1303,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 397,
@@ -1408,9 +1325,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 398,
@@ -1432,16 +1347,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 34,
       "title": "Ullam expedita deserunt libero consequatur quia dolor harum perferendis facere quidem.",
-      "assignee_id": 20,
       "author_id": 1,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:07.717Z",
@@ -1476,9 +1388,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 400,
@@ -1500,9 +1410,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 401,
@@ -1524,9 +1432,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 402,
@@ -1548,9 +1454,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 403,
@@ -1572,9 +1476,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 404,
@@ -1596,9 +1498,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 405,
@@ -1620,9 +1520,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 406,
@@ -1644,16 +1542,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 33,
       "title": "Numquam accusamus eos iste exercitationem magni non inventore.",
-      "assignee_id": 15,
       "author_id": 26,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:07.611Z",
@@ -1688,9 +1583,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 408,
@@ -1712,9 +1605,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 409,
@@ -1736,9 +1627,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 410,
@@ -1760,9 +1649,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 411,
@@ -1784,9 +1671,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 412,
@@ -1808,9 +1693,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 413,
@@ -1832,9 +1715,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 414,
@@ -1856,16 +1737,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 32,
       "title": "Necessitatibus magnam qui at velit consequatur perspiciatis.",
-      "assignee_id": 22,
       "author_id": 15,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:07.431Z",
@@ -1900,9 +1778,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 416,
@@ -1924,9 +1800,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 417,
@@ -1948,9 +1822,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 418,
@@ -1972,9 +1844,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 419,
@@ -1996,9 +1866,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 420,
@@ -2020,9 +1888,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 421,
@@ -2044,9 +1910,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 422,
@@ -2068,16 +1932,13 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     },
     {
       "id": 31,
       "title": "Libero nam magnam incidunt eaque placeat error et.",
-      "assignee_id": 1,
       "author_id": 16,
       "project_id": 5,
       "created_at": "2016-06-14T15:02:07.280Z",
@@ -2112,9 +1973,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 424,
@@ -2136,9 +1995,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 425,
@@ -2160,9 +2017,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 426,
@@ -2184,9 +2039,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 427,
@@ -2208,9 +2061,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 428,
@@ -2232,9 +2083,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 429,
@@ -2256,9 +2105,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 430,
@@ -2280,9 +2127,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ]
     }
@@ -2378,12 +2223,8 @@
       ]
     }
   ],
-  "snippets": [
-
-  ],
-  "releases": [
-
-  ],
+  "snippets": [],
+  "releases": [],
   "project_members": [
     {
       "id": 36,
@@ -2515,9 +2356,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 672,
@@ -2539,9 +2378,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 673,
@@ -2563,9 +2400,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 674,
@@ -2587,9 +2422,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 675,
@@ -2611,9 +2444,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 676,
@@ -2635,9 +2466,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 677,
@@ -2659,9 +2488,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 678,
@@ -2683,9 +2510,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -2696,7 +2521,7 @@
             "merge_request_diff_id": 27,
             "relative_order": 0,
             "sha": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
-            "message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-08-06T08:35:52.000+02:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2708,7 +2533,7 @@
             "merge_request_diff_id": 27,
             "relative_order": 1,
             "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
-            "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T10:01:38.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2720,7 +2545,7 @@
             "merge_request_diff_id": 27,
             "relative_order": 2,
             "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
-            "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:57:31.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2732,7 +2557,7 @@
             "merge_request_diff_id": 27,
             "relative_order": 3,
             "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
-            "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:54:21.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2744,7 +2569,7 @@
             "merge_request_diff_id": 27,
             "relative_order": 4,
             "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
-            "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:49:50.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2756,7 +2581,7 @@
             "merge_request_diff_id": 27,
             "relative_order": 5,
             "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
-            "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:48:32.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2834,7 +2659,7 @@
           {
             "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",
+            "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\" => path }\n-    options = { chdir: path }\n+\n+    vars = {\n+      \"PWD\" => 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 << stdout.read\n       @cmd_output << stderr.read\n",
             "new_path": "files/ruby/popen.rb",
             "old_path": "files/ruby/popen.rb",
             "a_mode": "100644",
@@ -2958,9 +2783,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 680,
@@ -2982,9 +2805,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 681,
@@ -3006,9 +2827,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 682,
@@ -3030,9 +2849,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 683,
@@ -3054,9 +2871,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 684,
@@ -3078,9 +2893,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 685,
@@ -3102,9 +2915,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 686,
@@ -3126,9 +2937,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -3139,7 +2948,7 @@
             "merge_request_diff_id": 26,
             "sha": "0b4bc9a49b562e85de7cc9e834518ea6828729b9",
             "relative_order": 0,
-            "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:26:01.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3237,9 +3046,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 778,
@@ -3261,9 +3068,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 779,
@@ -3285,9 +3090,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 780,
@@ -3309,9 +3112,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 781,
@@ -3333,9 +3134,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 782,
@@ -3357,9 +3156,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 783,
@@ -3381,9 +3178,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 784,
@@ -3405,9 +3200,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -3516,9 +3309,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 786,
@@ -3540,9 +3331,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 787,
@@ -3564,9 +3353,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 788,
@@ -3588,9 +3375,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 789,
@@ -3612,9 +3397,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 790,
@@ -3636,9 +3419,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 791,
@@ -3660,9 +3441,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 792,
@@ -3684,9 +3463,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -3877,7 +3654,7 @@
             "merge_request_diff_id": 14,
             "relative_order": 15,
             "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
-            "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T10:01:38.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3889,7 +3666,7 @@
             "merge_request_diff_id": 14,
             "relative_order": 16,
             "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
-            "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:57:31.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3901,7 +3678,7 @@
             "merge_request_diff_id": 14,
             "relative_order": 17,
             "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
-            "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:54:21.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3913,7 +3690,7 @@
             "merge_request_diff_id": 14,
             "relative_order": 18,
             "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
-            "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:49:50.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3925,7 +3702,7 @@
             "merge_request_diff_id": 14,
             "relative_order": 19,
             "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
-            "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:48:32.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -4016,7 +3793,7 @@
           {
             "merge_request_diff_id": 14,
             "relative_order": 6,
-            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+    \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+    \u003ctitle\u003ewm\u003c/title\u003e\n+    \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+    \u003cdefs\u003e\n+        \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+    \u003c/defs\u003e\n+    \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+        \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+        \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+            \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+                \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+                    \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+                \u003c/g\u003e\n+                \u003cg id=\"g16\"\u003e\n+                    \u003cg id=\"g18-Clipped\"\u003e\n+                        \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+                            \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+                        \u003c/mask\u003e\n+                        \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+                        \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+                            \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+                                \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+                                    \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+                                    \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+                                    \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+                                    \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+                                    \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+                                    \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                            \u003c/g\u003e\n+                        \u003c/g\u003e\n+                    \u003c/g\u003e\n+                \u003c/g\u003e\n+            \u003c/g\u003e\n+        \u003c/g\u003e\n+    \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+    <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+    <title>wm</title>\n+    <desc>Created with Sketch.</desc>\n+    <defs>\n+        <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+    </defs>\n+    <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+        <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+        <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+            <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+                <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+                    <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+                </g>\n+                <g id=\"g16\">\n+                    <g id=\"g18-Clipped\">\n+                        <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+                            <use xlink:href=\"#path-1\"></use>\n+                        </mask>\n+                        <g id=\"path22\"></g>\n+                        <g id=\"g18\" mask=\"url(#mask-2)\">\n+                            <g transform=\"translate(382.736659, 312.879425)\">\n+                                <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+                                    <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+                                    <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+                                    <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+                                    <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+                                    <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+                                    <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path54\"></g>\n+                                </g>\n+                                <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+                                    <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <g id=\"path62\"></g>\n+                                </g>\n+                                <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+                                    <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path70\"></g>\n+                                </g>\n+                                <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+                                    <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+                                    <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+                                    <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                            </g>\n+                        </g>\n+                    </g>\n+                </g>\n+            </g>\n+        </g>\n+    </g>\n+</svg>\n\\ No newline at end of file\n",
             "new_path": "files/images/wm.svg",
             "old_path": "files/images/wm.svg",
             "a_mode": "0",
@@ -4042,7 +3819,7 @@
           {
             "merge_request_diff_id": 14,
             "relative_order": 8,
-            "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",
+            "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\" => path }\n-    options = { chdir: path }\n+\n+    vars = {\n+      \"PWD\" => 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 << stdout.read\n       @cmd_output << stderr.read\n",
             "new_path": "files/ruby/popen.rb",
             "old_path": "files/ruby/popen.rb",
             "a_mode": "100644",
@@ -4207,7 +3984,7 @@
           },
           "events": [
             {
-            "merge_request_diff_id": 14,
+              "merge_request_diff_id": 14,
               "id": 529,
               "target_type": "Note",
               "target_id": 793,
@@ -4239,9 +4016,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 795,
@@ -4263,9 +4038,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 796,
@@ -4287,9 +4060,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 797,
@@ -4311,9 +4082,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 798,
@@ -4335,9 +4104,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 799,
@@ -4359,9 +4126,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 800,
@@ -4383,9 +4148,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -4603,7 +4366,7 @@
           {
             "merge_request_diff_id": 13,
             "relative_order": 2,
-            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+    \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+    \u003ctitle\u003ewm\u003c/title\u003e\n+    \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+    \u003cdefs\u003e\n+        \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+    \u003c/defs\u003e\n+    \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+        \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+        \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+            \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+                \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+                    \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+                \u003c/g\u003e\n+                \u003cg id=\"g16\"\u003e\n+                    \u003cg id=\"g18-Clipped\"\u003e\n+                        \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+                            \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+                        \u003c/mask\u003e\n+                        \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+                        \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+                            \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+                                \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+                                    \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+                                    \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+                                    \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+                                    \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+                                    \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+                                    \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                            \u003c/g\u003e\n+                        \u003c/g\u003e\n+                    \u003c/g\u003e\n+                \u003c/g\u003e\n+            \u003c/g\u003e\n+        \u003c/g\u003e\n+    \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+    <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+    <title>wm</title>\n+    <desc>Created with Sketch.</desc>\n+    <defs>\n+        <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+    </defs>\n+    <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+        <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+        <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+            <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+                <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+                    <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+                </g>\n+                <g id=\"g16\">\n+                    <g id=\"g18-Clipped\">\n+                        <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+                            <use xlink:href=\"#path-1\"></use>\n+                        </mask>\n+                        <g id=\"path22\"></g>\n+                        <g id=\"g18\" mask=\"url(#mask-2)\">\n+                            <g transform=\"translate(382.736659, 312.879425)\">\n+                                <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+                                    <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+                                    <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+                                    <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+                                    <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+                                    <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+                                    <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path54\"></g>\n+                                </g>\n+                                <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+                                    <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <g id=\"path62\"></g>\n+                                </g>\n+                                <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+                                    <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path70\"></g>\n+                                </g>\n+                                <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+                                    <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+                                    <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+                                    <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                            </g>\n+                        </g>\n+                    </g>\n+                </g>\n+            </g>\n+        </g>\n+    </g>\n+</svg>\n\\ No newline at end of file\n",
             "new_path": "files/images/wm.svg",
             "old_path": "files/images/wm.svg",
             "a_mode": "0",
@@ -4740,9 +4503,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 802,
@@ -4764,9 +4525,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 803,
@@ -4788,9 +4547,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 804,
@@ -4812,9 +4569,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 805,
@@ -4836,9 +4591,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 806,
@@ -4860,9 +4613,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 807,
@@ -4884,9 +4635,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 808,
@@ -4908,9 +4657,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -5104,7 +4851,7 @@
           {
             "merge_request_diff_id": 12,
             "relative_order": 2,
-            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+    \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+    \u003ctitle\u003ewm\u003c/title\u003e\n+    \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+    \u003cdefs\u003e\n+        \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+    \u003c/defs\u003e\n+    \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+        \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+        \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+            \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+                \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+                    \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+                \u003c/g\u003e\n+                \u003cg id=\"g16\"\u003e\n+                    \u003cg id=\"g18-Clipped\"\u003e\n+                        \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+                            \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+                        \u003c/mask\u003e\n+                        \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+                        \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+                            \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+                                \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+                                    \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+                                    \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+                                    \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+                                    \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+                                    \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+                                    \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                            \u003c/g\u003e\n+                        \u003c/g\u003e\n+                    \u003c/g\u003e\n+                \u003c/g\u003e\n+            \u003c/g\u003e\n+        \u003c/g\u003e\n+    \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+    <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+    <title>wm</title>\n+    <desc>Created with Sketch.</desc>\n+    <defs>\n+        <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+    </defs>\n+    <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+        <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+        <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+            <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+                <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+                    <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+                </g>\n+                <g id=\"g16\">\n+                    <g id=\"g18-Clipped\">\n+                        <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+                            <use xlink:href=\"#path-1\"></use>\n+                        </mask>\n+                        <g id=\"path22\"></g>\n+                        <g id=\"g18\" mask=\"url(#mask-2)\">\n+                            <g transform=\"translate(382.736659, 312.879425)\">\n+                                <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+                                    <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+                                    <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+                                    <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+                                    <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+                                    <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+                                    <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path54\"></g>\n+                                </g>\n+                                <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+                                    <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <g id=\"path62\"></g>\n+                                </g>\n+                                <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+                                    <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path70\"></g>\n+                                </g>\n+                                <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+                                    <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+                                    <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+                                    <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                            </g>\n+                        </g>\n+                    </g>\n+                </g>\n+            </g>\n+        </g>\n+    </g>\n+</svg>\n\\ No newline at end of file\n",
             "new_path": "files/images/wm.svg",
             "old_path": "files/images/wm.svg",
             "a_mode": "0",
@@ -5228,9 +4975,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 810,
@@ -5252,9 +4997,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 811,
@@ -5276,9 +5019,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 812,
@@ -5300,9 +5041,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 813,
@@ -5324,9 +5063,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 814,
@@ -5348,9 +5085,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 815,
@@ -5372,9 +5107,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 816,
@@ -5396,18 +5129,14 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
         "id": 11,
         "state": "empty",
-        "merge_request_diff_commits": [
-        ],
-        "merge_request_diff_files": [
-        ],
+        "merge_request_diff_commits": [],
+        "merge_request_diff_files": [],
         "merge_request_id": 11,
         "created_at": "2016-06-14T15:02:23.772Z",
         "updated_at": "2016-06-14T15:02:23.833Z",
@@ -5482,9 +5211,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 818,
@@ -5506,9 +5233,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 819,
@@ -5530,9 +5255,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 820,
@@ -5554,9 +5277,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 821,
@@ -5578,9 +5299,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 822,
@@ -5602,9 +5321,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 823,
@@ -5626,9 +5343,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 824,
@@ -5650,9 +5365,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -5843,7 +5556,7 @@
             "merge_request_diff_id": 10,
             "relative_order": 16,
             "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
-            "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T10:01:38.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5855,7 +5568,7 @@
             "merge_request_diff_id": 10,
             "relative_order": 17,
             "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
-            "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:57:31.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5867,7 +5580,7 @@
             "merge_request_diff_id": 10,
             "relative_order": 18,
             "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
-            "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:54:21.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5879,7 +5592,7 @@
             "merge_request_diff_id": 10,
             "relative_order": 19,
             "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
-            "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:49:50.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5891,7 +5604,7 @@
             "merge_request_diff_id": 10,
             "relative_order": 20,
             "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
-            "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+            "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
             "authored_date": "2014-02-27T09:48:32.000+01:00",
             "author_name": "Dmitriy Zaporozhets",
             "author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5982,7 +5695,7 @@
           {
             "merge_request_diff_id": 10,
             "relative_order": 6,
-            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+    \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+    \u003ctitle\u003ewm\u003c/title\u003e\n+    \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+    \u003cdefs\u003e\n+        \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+    \u003c/defs\u003e\n+    \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+        \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+        \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+            \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+                \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+                    \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+                \u003c/g\u003e\n+                \u003cg id=\"g16\"\u003e\n+                    \u003cg id=\"g18-Clipped\"\u003e\n+                        \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+                            \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+                        \u003c/mask\u003e\n+                        \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+                        \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+                            \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+                                \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+                                    \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+                                    \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+                                    \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+                                    \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+                                    \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+                                    \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+                                    \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+                                    \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+                                    \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                                \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+                                    \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+                                \u003c/g\u003e\n+                            \u003c/g\u003e\n+                        \u003c/g\u003e\n+                    \u003c/g\u003e\n+                \u003c/g\u003e\n+            \u003c/g\u003e\n+        \u003c/g\u003e\n+    \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+            "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+    <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+    <title>wm</title>\n+    <desc>Created with Sketch.</desc>\n+    <defs>\n+        <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+    </defs>\n+    <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+        <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+        <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+            <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+                <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+                    <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+                </g>\n+                <g id=\"g16\">\n+                    <g id=\"g18-Clipped\">\n+                        <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+                            <use xlink:href=\"#path-1\"></use>\n+                        </mask>\n+                        <g id=\"path22\"></g>\n+                        <g id=\"g18\" mask=\"url(#mask-2)\">\n+                            <g transform=\"translate(382.736659, 312.879425)\">\n+                                <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+                                    <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+                                    <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+                                    <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+                                    <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+                                <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+                                    <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+                                    <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path54\"></g>\n+                                </g>\n+                                <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+                                    <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <g id=\"path62\"></g>\n+                                </g>\n+                                <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+                                    <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+                                    <g id=\"path70\"></g>\n+                                </g>\n+                                <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+                                    <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+                                    <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+                                    <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                                <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+                                    <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+                                </g>\n+                            </g>\n+                        </g>\n+                    </g>\n+                </g>\n+            </g>\n+        </g>\n+    </g>\n+</svg>\n\\ No newline at end of file\n",
             "new_path": "files/images/wm.svg",
             "old_path": "files/images/wm.svg",
             "a_mode": "0",
@@ -6008,7 +5721,7 @@
           {
             "merge_request_diff_id": 10,
             "relative_order": 8,
-            "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",
+            "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\" => path }\n-    options = { chdir: path }\n+\n+    vars = {\n+      \"PWD\" => 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 << stdout.read\n       @cmd_output << stderr.read\n",
             "new_path": "files/ruby/popen.rb",
             "old_path": "files/ruby/popen.rb",
             "a_mode": "100644",
@@ -6171,9 +5884,7 @@
           "author": {
             "name": "User 4"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 826,
@@ -6195,9 +5906,7 @@
           "author": {
             "name": "User 3"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 827,
@@ -6219,9 +5928,7 @@
           "author": {
             "name": "User 0"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 828,
@@ -6243,9 +5950,7 @@
           "author": {
             "name": "Ottis Schuster II"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 829,
@@ -6267,9 +5972,7 @@
           "author": {
             "name": "Rhett Emmerich IV"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 830,
@@ -6291,9 +5994,7 @@
           "author": {
             "name": "Burdette Bernier"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 831,
@@ -6315,9 +6016,7 @@
           "author": {
             "name": "Ari Wintheiser"
           },
-          "events": [
-
-          ]
+          "events": []
         },
         {
           "id": 832,
@@ -6339,9 +6038,7 @@
           "author": {
             "name": "Administrator"
           },
-          "events": [
-
-          ]
+          "events": []
         }
       ],
       "merge_request_diff": {
@@ -6483,12 +6180,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": null
-              },
-              "artifacts_metadata": {
-                "url": null
-              },
               "erased_by_id": null,
               "erased_at": null,
               "type": "Ci::Build",
@@ -6521,12 +6212,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts.zip"
-              },
-              "artifacts_metadata": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts_metadata.gz"
-              },
               "erased_by_id": null,
               "erased_at": null
             }
@@ -6595,12 +6280,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts.zip"
-              },
-              "artifacts_metadata": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts_metadata.gz"
-              },
               "erased_by_id": null,
               "erased_at": null
             },
@@ -6630,12 +6309,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": null
-              },
-              "artifacts_metadata": {
-                "url": null
-              },
               "erased_by_id": null,
               "erased_at": null
             }
@@ -6695,12 +6368,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts.zip"
-              },
-              "artifacts_metadata": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts_metadata.gz"
-              },
               "erased_by_id": null,
               "erased_at": null
             },
@@ -6730,12 +6397,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts.zip"
-              },
-              "artifacts_metadata": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts_metadata.gz"
-              },
               "erased_by_id": null,
               "erased_at": null
             }
@@ -6795,12 +6456,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts.zip"
-              },
-              "artifacts_metadata": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts_metadata.gz"
-              },
               "erased_by_id": null,
               "erased_at": null
             },
@@ -6830,12 +6485,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts.zip"
-              },
-              "artifacts_metadata": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts_metadata.gz"
-              },
               "erased_by_id": null,
               "erased_at": null
             }
@@ -6895,12 +6544,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": null
-              },
-              "artifacts_metadata": {
-                "url": null
-              },
               "erased_by_id": null,
               "erased_at": null
             },
@@ -6930,12 +6573,6 @@
               "user_id": null,
               "target_url": null,
               "description": null,
-              "artifacts_file": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts.zip"
-              },
-              "artifacts_metadata": {
-                "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts_metadata.gz"
-              },
               "erased_by_id": null,
               "erased_at": null
             }
@@ -6953,9 +6590,7 @@
       "updated_at": "2017-01-16T15:25:28.637Z"
     }
   ],
-  "deploy_keys": [
-
-  ],
+  "deploy_keys": [],
   "services": [
     {
       "id": 100,
@@ -6964,9 +6599,7 @@
       "created_at": "2016-06-14T15:01:51.315Z",
       "updated_at": "2016-06-14T15:01:51.315Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7008,9 +6641,7 @@
       "created_at": "2016-06-14T15:01:51.289Z",
       "updated_at": "2016-06-14T15:01:51.289Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7030,9 +6661,7 @@
       "created_at": "2016-06-14T15:01:51.277Z",
       "updated_at": "2016-06-14T15:01:51.277Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7052,9 +6681,7 @@
       "created_at": "2016-06-14T15:01:51.267Z",
       "updated_at": "2016-06-14T15:01:51.267Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7097,9 +6724,7 @@
       "created_at": "2016-06-14T15:01:51.232Z",
       "updated_at": "2016-06-14T15:01:51.232Z",
       "active": true,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7141,9 +6766,7 @@
       "created_at": "2016-06-14T15:01:51.202Z",
       "updated_at": "2016-06-14T15:01:51.202Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7163,9 +6786,7 @@
       "created_at": "2016-06-14T15:01:51.182Z",
       "updated_at": "2016-06-14T15:01:51.182Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7185,9 +6806,7 @@
       "created_at": "2016-06-14T15:01:51.166Z",
       "updated_at": "2016-06-14T15:01:51.166Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7207,9 +6826,7 @@
       "created_at": "2016-06-14T15:01:51.153Z",
       "updated_at": "2016-06-14T15:01:51.153Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7229,9 +6846,7 @@
       "created_at": "2016-06-14T15:01:51.139Z",
       "updated_at": "2016-06-14T15:01:51.139Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7251,9 +6866,7 @@
       "created_at": "2016-06-14T15:01:51.125Z",
       "updated_at": "2016-06-14T15:01:51.125Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7273,9 +6886,7 @@
       "created_at": "2016-06-14T15:01:51.113Z",
       "updated_at": "2016-06-14T15:01:51.113Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7295,9 +6906,7 @@
       "created_at": "2016-06-14T15:01:51.080Z",
       "updated_at": "2016-06-14T15:01:51.080Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7317,9 +6926,7 @@
       "created_at": "2016-06-14T15:01:51.067Z",
       "updated_at": "2016-06-14T15:01:51.067Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7339,9 +6946,7 @@
       "created_at": "2016-06-14T15:01:51.047Z",
       "updated_at": "2016-06-14T15:01:51.047Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7361,9 +6966,7 @@
       "created_at": "2016-06-14T15:01:51.031Z",
       "updated_at": "2016-06-14T15:01:51.031Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7383,9 +6986,7 @@
       "created_at": "2016-06-14T15:01:51.031Z",
       "updated_at": "2016-06-14T15:01:51.031Z",
       "active": false,
-      "properties": {
-
-      },
+      "properties": {},
       "template": false,
       "push_events": true,
       "issues_events": true,
@@ -7399,9 +7000,7 @@
       "type": "JenkinsDeprecatedService"
     }
   ],
-  "hooks": [
-
-  ],
+  "hooks": [],
   "protected_branches": [
     {
       "id": 1,
@@ -7475,5 +7074,25 @@
       "key": "bar",
       "value": "bar"
     }
+  ],
+  "project_badges": [
+    {
+      "id": 1,
+      "created_at": "2017-10-19T15:36:23.466Z",
+      "updated_at": "2017-10-19T15:36:23.466Z",
+      "project_id": 5,
+      "type": "ProjectBadge",
+      "link_url": "http://www.example.com",
+      "image_url": "http://www.example.com"
+    },
+    {
+      "id": 2,
+      "created_at": "2017-10-19T15:36:23.466Z",
+      "updated_at": "2017-10-19T15:36:23.466Z",
+      "project_id": 5,
+      "type": "ProjectBadge",
+      "link_url": "http://www.example.com",
+      "image_url": "http://www.example.com"
+    }
   ]
 }
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 d076007e4bcc450219b5b7ded35cc8720fd060d0..13a8c9adceee536838a1d8561e6f820b66f62a13 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -4,12 +4,17 @@ include ImportExport::CommonUtil
 describe Gitlab::ImportExport::ProjectTreeRestorer do
   describe 'restore project tree' do
     before(:context) do
-      @user = create(:user)
+      # Using an admin for import, so we can check assignment of existing members
+      @user = create(:admin)
+      @existing_members = [
+        create(:user, username: 'bernard_willms'),
+        create(:user, username: 'saul_will')
+      ]
 
       RSpec::Mocks.with_temporary_scope do
-        @shared = Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path')
-        allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
         @project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
+        @shared = @project.import_export_shared
+        allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
 
         allow_any_instance_of(Repository).to receive(:fetch_ref).and_return(true)
         allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
@@ -37,8 +42,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
         expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
       end
 
-      it 'has the project html description' do
-        expect(Project.find_by_path('project').description_html).to eq('description')
+      it 'has the project description' do
+        expect(Project.find_by_path('project').description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.')
       end
 
       it 'has the same label associated to two issues' do
@@ -63,8 +68,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
         expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
       end
 
-      it 'has issue assignees' do
-        expect(Issue.where(title: 'Voluptatem').first.issue_assignees).not_to be_empty
+      it 'has multiple issue assignees' do
+        expect(Issue.find_by(title: 'Voluptatem').assignees).to contain_exactly(@user, *@existing_members)
+        expect(Issue.find_by(title: 'Issue without assignees').assignees).to be_empty
       end
 
       it 'contains the merge access levels on a protected branch' do
@@ -129,6 +135,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
         expect(@project.custom_attributes.count).to eq(2)
       end
 
+      it 'has badges' do
+        expect(@project.project_badges.count).to eq(2)
+      end
+
       it 'restores the correct service' do
         expect(CustomIssueTrackerService.first).not_to be_nil
       end
@@ -259,7 +269,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
 
   context 'Light JSON' do
     let(:user) { create(:user) }
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
+    let(:shared) { project.import_export_shared }
     let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
     let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
     let(:restored_project_json) { project_tree_restorer.restore }
@@ -303,6 +313,24 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
       end
     end
 
+    context 'when the project has overriden params in import data' do
+      it 'overwrites the params stored in the JSON' do
+        project.create_import_data(data: { override_params: { description: "Overridden" } })
+
+        restored_project_json
+
+        expect(project.description).to eq("Overridden")
+      end
+
+      it 'does not allow setting params that are excluded from import_export settings' do
+        project.create_import_data(data: { override_params: { lfs_enabled: true } })
+
+        restored_project_json
+
+        expect(project.lfs_enabled).to be_nil
+      end
+    end
+
     context 'with a project that has a group' do
       let!(:project) do
         create(:project,
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 5804c45871e95b76f5c6ca37078676d167f336f5..2b8a11ce8f951e18fe87f1a938c58f5af76cad96 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::ImportExport::ProjectTreeSaver do
   describe 'saves the project tree into a json object' do
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+    let(:shared) { project.import_export_shared }
     let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) }
     let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
     let(:user) { create(:user) }
@@ -29,8 +29,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
         project_json(project_tree_saver.full_path)
       end
 
+      context 'with description override' do
+        let(:params) { { description: 'Foo Bar' } }
+        let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared, params: params) }
+
+        it 'overrides the project description' do
+          expect(saved_project_json).to include({ 'description' => params[:description] })
+        end
+      end
+
       it 'saves the correct json' do
-        expect(saved_project_json).to include({ "visibility_level" => 20 })
+        expect(saved_project_json).to include({ 'description' => 'description', 'visibility_level' => 20 })
       end
 
       it 'has milestones' do
@@ -180,6 +189,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
         expect(saved_project_json['custom_attributes'].count).to eq(2)
       end
 
+      it 'has badges' do
+        expect(saved_project_json['project_badges'].count).to eq(2)
+      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'")
 
@@ -232,10 +245,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
       end
 
       context 'project attributes' do
-        it 'contains the html description' do
-          expect(saved_project_json).to include("description_html" => 'description')
-        end
-
         it 'does not contain the runners token' do
           expect(saved_project_json).not_to include("runners_token" => 'token')
         end
@@ -255,12 +264,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
                      :issues_disabled,
                      :wiki_enabled,
                      :builds_private,
+                     description: 'description',
                      issues: [issue],
                      snippets: [snippet],
                      releases: [release],
                      group: group
                     )
-    project.update_column(:description_html, 'description')
     project_label = create(:label, project: project)
     group_label = create(:group_label, group: group)
     create(:label_link, label: project_label, target: issue)
@@ -288,6 +297,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
     create(:project_custom_attribute, project: project)
     create(:project_custom_attribute, project: project)
 
+    create(:project_badge, project: project)
+    create(:project_badge, project: project)
+
     project
   end
 
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index e9f5273725d0d99ce90ff04e7edd9ce11693f047..1ef024d3078ccda9e07420252f81dc5e4a30d8be 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::ImportExport::Reader  do
-  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
+  let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
   let(:test_config) { 'spec/support/import_export/import_export.yml' }
   let(:project_tree_hash) do
     {
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index c49af602a01ad37c906a44cd970c58ab0b8ca2fa..dc806d036ff5b11aa5daaccec32c291cfcbd0806 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::ImportExport::RepoRestorer do
     let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
     let!(:project) { create(:project) }
     let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+    let(:shared) { project.import_export_shared }
     let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
     let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
     let(:restorer) do
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index 44f972fe530b180581175f28e599dd596568fd74..187ec8fcfa2ff9bff80aad4017b047d30a33e61b 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ImportExport::RepoSaver do
     let(:user) { create(:user) }
     let!(:project) { create(:project, :public, name: 'searchable_project') }
     let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+    let(:shared) { project.import_export_shared }
     let(:bundler) { described_class.new(project: project, shared: shared) }
 
     before do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index feaab6673cdd138b398cb8729f746aa6a3c3a7ae..f84a777a27f3724c480789629297c6be7d10d755 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -15,6 +15,7 @@ Issue:
 - updated_by_id
 - confidential
 - closed_at
+- closed_by_id
 - due_date
 - moved_to_id
 - lock_version
@@ -168,6 +169,7 @@ MergeRequest:
 - last_edited_by_id
 - head_pipeline_id
 - discussion_locked
+- allow_maintainer_to_push
 MergeRequestDiff:
 - id
 - state
@@ -264,7 +266,9 @@ CommitStatus:
 - target_url
 - description
 - artifacts_file
+- artifacts_file_store
 - artifacts_metadata
+- artifacts_metadata_store
 - erased_by_id
 - erased_at
 - artifacts_expire_at
@@ -386,6 +390,7 @@ Service:
 - default
 - wiki_page_events
 - confidential_issues_events
+- confidential_note_events
 ProjectHook:
 - id
 - url
@@ -406,6 +411,7 @@ ProjectHook:
 - token
 - group_id
 - confidential_issues_events
+- confidential_note_events
 - repository_update_events
 ProtectedBranch:
 - id
@@ -457,6 +463,7 @@ Project:
 - merge_requests_ff_only_enabled
 - merge_requests_rebase_enabled
 - jobs_cache_index
+- pages_https_only
 Author:
 - name
 ProjectFeature:
@@ -536,3 +543,12 @@ LfsFileLock:
 - user_id
 - project_id
 - created_at
+Badge:
+- id
+- link_url
+- image_url
+- project_id
+- group_id
+- created_at
+- updated_at
+- type
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index 8a3a244be2137e8eb77a711b499b4aee373c5d94..acef97459b830efd1759ffbaaf5993fde9585f46 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::ImportExport::UploadsRestorer do
   describe 'bundle a project Git repo' do
     let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+    let(:shared) { project.import_export_shared }
 
     before do
       allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index 177036c109bb758ce3c26776e7686867d94819a9..1304d8fabfc341e6156546a061bf9ba6df93f68a 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::ImportExport::UploadsSaver do
   describe 'bundle a project Git repo' do
     let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
     let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+    let(:shared) { project.import_export_shared }
 
     before do
       allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index e7d50f75682e0579a05742cff9803139b9d3277f..49d857d9483d49c5379935fa356e4deb974164d0 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -2,12 +2,13 @@ require 'spec_helper'
 include ImportExport::CommonUtil
 
 describe Gitlab::ImportExport::VersionChecker do
-  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
+  let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
 
   describe 'bundle a project Git repo' do
     let(:version) { Gitlab::ImportExport.version }
 
     before do
+      allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('')
       allow(File).to receive(:open).and_return(version)
     end
 
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 1d1e7e7f89a227612e9d77e81802a3d99d0d2990..d2bd8ccdf3f30b306cd2f61f3e91613343e76f03 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ImportExport::WikiRepoSaver do
     let(:user) { create(:user) }
     let!(:project) { create(:project, :public, name: 'searchable_project') }
     let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+    let(:shared) { project.import_export_shared }
     let(:wiki_bundler) { described_class.new(project: project, shared: shared) }
     let!(:project_wiki) { ProjectWiki.new(project, user) }
 
diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
index 81b654e9c5f860209f2f852ad617ceb0b18c91ee..5c01ee0ebb892f82aae24aed8a29c72259212eaa 100644
--- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::ImportExport::WikiRestorer do
     let!(:project_without_wiki) { create(:project) }
     let!(:project) { create(:project) }
     let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
-    let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+    let(:shared) { project.import_export_shared }
     let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) }
     let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
     let(:restorer) do
diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb
index b3c987f93443b41d56b5829c9764b71c71200d5c..e098612f6fbb1d24dd168a55d1e8ad04f00924fb 100644
--- a/spec/lib/gitlab/kubernetes/namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Kubernetes::Namespace do
 
   describe '#exists?' do
     context 'when namespace do not exits' do
-      let(:exception) { ::KubeException.new(404, "namespace #{name} not found", nil) }
+      let(:exception) { ::Kubeclient::HttpError.new(404, "namespace #{name} not found", nil) }
 
       it 'returns false' do
         expect(client).to receive(:get_namespace).with(name).once.and_raise(exception)
diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
index 6721e02fb85bbd7debfbef7de9ed3289a7097b9a..61eb059a7314fc5b36af068de8c3833521427d23 100644
--- a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
@@ -38,7 +38,9 @@ describe Gitlab::Metrics::SidekiqMetricsExporter do
 
             expect(::WEBrick::HTTPServer).to have_received(:new).with(
               Port: port,
-              BindAddress: address
+              BindAddress: address,
+              Logger: anything,
+              AccessLog: anything
             )
           end
         end
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index 07ba11b93a3e3b29372c95abe09ce1cdfe80c226..39ec2f37a8386154326ad5f20f97bfcc7ab241f7 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -11,15 +11,17 @@ describe Gitlab::Middleware::ReadOnly do
 
   RSpec::Matchers.define :disallow_request do
     match do |middleware|
-      flash = middleware.send(:rack_flash)
-      flash['alert'] && flash['alert'].include?('You cannot do writing operations')
+      alert = middleware.env['rack.session'].to_hash
+        .dig('flash', 'flashes', 'alert')
+
+      alert&.include?('You cannot perform write operations')
     end
   end
 
   RSpec::Matchers.define :disallow_request_in_json do
     match do |response|
       json_response = JSON.parse(response.body)
-      response.body.include?('You cannot do writing operations') && json_response.key?('message')
+      response.body.include?('You cannot perform write operations') && json_response.key?('message')
     end
   end
 
@@ -34,10 +36,25 @@ describe Gitlab::Middleware::ReadOnly do
     rack.to_app
   end
 
-  subject { described_class.new(fake_app) }
+  let(:observe_env) do
+    Module.new do
+      attr_reader :env
+
+      def call(env)
+        @env = env
+        super
+      end
+    end
+  end
 
   let(:request) { Rack::MockRequest.new(rack_stack) }
 
+  subject do
+    described_class.new(fake_app).tap do |app|
+      app.extend(observe_env)
+    end
+  end
+
   context 'normal requests to a read-only Gitlab instance' do
     let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } }
 
diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5e3aa877409a85dc3ca54bedaa1e34c0cee9a1c6
--- /dev/null
+++ b/spec/lib/gitlab/middleware/release_env_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe Gitlab::Middleware::ReleaseEnv do
+  let(:inner_app) { double(:app, call: 'yay') }
+  let(:app) { described_class.new(inner_app) }
+  let(:env) { { 'action_controller.instance' => 'something' } }
+
+  describe '#call' do
+    it 'calls the app and clears the env' do
+      result = app.call(env)
+
+      expect(result).to eq('yay')
+      expect(env).to be_empty
+    end
+  end
+end
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d808b4d49e0788fc1dc44f27efd15c96c52eaa1a
--- /dev/null
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Gitlab::OmniauthInitializer do
+  let(:devise_config) { class_double(Devise) }
+
+  subject { described_class.new(devise_config) }
+
+  describe '#execute' do
+    it 'configures providers from array' do
+      generic_config = { 'name' => 'generic' }
+
+      expect(devise_config).to receive(:omniauth).with(:generic)
+
+      subject.execute([generic_config])
+    end
+
+    it 'allows "args" array for app_id and app_secret' do
+      legacy_config = { 'name' => 'legacy', 'args' => %w(123 abc) }
+
+      expect(devise_config).to receive(:omniauth).with(:legacy, '123', 'abc')
+
+      subject.execute([legacy_config])
+    end
+
+    it 'passes app_id and app_secret as additional arguments' do
+      twitter_config = { 'name' => 'twitter', 'app_id' => '123', 'app_secret' => 'abc' }
+
+      expect(devise_config).to receive(:omniauth).with(:twitter, '123', 'abc')
+
+      subject.execute([twitter_config])
+    end
+
+    it 'passes "args" hash as symbolized hash argument' do
+      hash_config = { 'name' => 'hash', 'args' => { 'custom' => 'format' } }
+
+      expect(devise_config).to receive(:omniauth).with(:hash, custom: 'format')
+
+      subject.execute([hash_config])
+    end
+
+    it 'configures fail_with_empty_uid for shibboleth' do
+      shibboleth_config = { 'name' => 'shibboleth', 'args' => {} }
+
+      expect(devise_config).to receive(:omniauth).with(:shibboleth, fail_with_empty_uid: true)
+
+      subject.execute([shibboleth_config])
+    end
+
+    it 'configures remote_sign_out_handler proc for authentiq' do
+      authentiq_config = { 'name' => 'authentiq', 'args' => {} }
+
+      expect(devise_config).to receive(:omniauth).with(:authentiq, remote_sign_out_handler: an_instance_of(Proc))
+
+      subject.execute([authentiq_config])
+    end
+
+    it 'configures on_single_sign_out proc for cas3' do
+      cas3_config = { 'name' => 'cas3', 'args' => {} }
+
+      expect(devise_config).to receive(:omniauth).with(:cas3, on_single_sign_out: an_instance_of(Proc))
+
+      subject.execute([cas3_config])
+    end
+  end
+end
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index b8a2267f1a4f2655f5db4889ef86f170d505278e..f480376acb444a7a8d1f0a99a06244e67d23b7c1 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -25,6 +25,12 @@ describe Gitlab::PerformanceBar do
       expect(described_class.enabled?(nil)).to be_falsy
     end
 
+    it 'returns true when given user is an admin' do
+      user = build_stubbed(:user, :admin)
+
+      expect(described_class.enabled?(user)).to be_truthy
+    end
+
     it 'returns false when allowed_group_id is nil' do
       expect(described_class).to receive(:allowed_group_id).and_return(nil)
 
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index f02b1cf55fbb2cd0b2a5664351751b2dcc16acbe..548eb28fe4d0a22279a1dbb9a818f43e54c5541e 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -94,10 +94,12 @@ describe Gitlab::Profiler do
 
       it 'strips out the private token' do
         expect(custom_logger).to receive(:add) do |severity, _progname, message|
+          next if message.include?('spec/')
+
           expect(severity).to eq(Logger::DEBUG)
           expect(message).to include('public').and include(described_class::FILTERED_STRING)
           expect(message).not_to include(private_token)
-        end
+        end.twice
 
         custom_logger.debug("public #{private_token}")
       end
@@ -108,8 +110,8 @@ describe Gitlab::Profiler do
         custom_logger.debug('User Load (1.3ms)')
         custom_logger.debug('Project Load (10.4ms)')
 
-        expect(custom_logger.load_times_by_model).to eq('User' => 2.5,
-                                                        'Project' => 10.4)
+        expect(custom_logger.load_times_by_model).to eq('User' => [1.2, 1.3],
+                                                        'Project' => [10.4])
       end
 
       it 'logs the backtrace, ignoring lines as appropriate' do
@@ -162,4 +164,24 @@ describe Gitlab::Profiler do
       end
     end
   end
+
+  describe '.log_load_times_by_model' do
+    it 'logs the model, query count, and time by slowest first' do
+      expect(null_logger).to receive(:load_times_by_model).and_return(
+        'User' => [1.2, 1.3],
+        'Project' => [10.4]
+      )
+
+      expect(null_logger).to receive(:info).with('Project total (1): 10.4ms')
+      expect(null_logger).to receive(:info).with('User total (2): 2.5ms')
+
+      described_class.log_load_times_by_model(null_logger)
+    end
+
+    it 'does nothing when called with a logger that does not have load times' do
+      expect(null_logger).not_to receive(:info)
+
+      expect(described_class.log_load_times_by_model(null_logger)).to be_nil
+    end
+  end
 end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index d8250e4b4c62e1ca8d7fd7c954eb6f48cfe560e2..8351b967133d29c207cf57952b2d3144516a66a9 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -83,19 +83,19 @@ describe Gitlab::ProjectSearchResults do
       end
 
       context 'when the matching filename contains a colon' do
-        let(:search_result) { "\nmaster:testdata/project::function1.yaml\x001\x00---\n" }
+        let(:search_result) { "master:testdata/project::function1.yaml\x001\x00---\n" }
 
         it 'returns a valid FoundBlob' do
           expect(subject.filename).to eq('testdata/project::function1.yaml')
           expect(subject.basename).to eq('testdata/project::function1')
           expect(subject.ref).to eq('master')
           expect(subject.startline).to eq(1)
-          expect(subject.data).to eq('---')
+          expect(subject.data).to eq("---\n")
         end
       end
 
       context 'when the matching content contains a number surrounded by colons' do
-        let(:search_result) { "\nmaster:testdata/foo.txt\x001\x00blah:9:blah" }
+        let(:search_result) { "master:testdata/foo.txt\x001\x00blah:9:blah" }
 
         it 'returns a valid FoundBlob' do
           expect(subject.filename).to eq('testdata/foo.txt')
@@ -106,16 +106,40 @@ describe Gitlab::ProjectSearchResults do
         end
       end
 
+      context 'when the search result ends with an empty line' do
+        let(:results) { project.repository.search_files_by_content('Role models', 'master') }
+
+        it 'returns a valid FoundBlob that ends with an empty line' do
+          expect(subject.filename).to eq('files/markdown/ruby-style-guide.md')
+          expect(subject.basename).to eq('files/markdown/ruby-style-guide')
+          expect(subject.ref).to eq('master')
+          expect(subject.startline).to eq(1)
+          expect(subject.data).to eq("# Prelude\n\n> Role models are important. <br/>\n> -- Officer Alex J. Murphy / RoboCop\n\n")
+        end
+      end
+
       context 'when the search returns non-ASCII data' do
         context 'with UTF-8' do
-          let(:results) { project.repository.search_files_by_content("褎邪泄谢", 'master') }
+          let(:results) { project.repository.search_files_by_content('褎邪泄谢', 'master') }
 
           it 'returns results as UTF-8' do
             expect(subject.filename).to eq('encoding/russian.rb')
             expect(subject.basename).to eq('encoding/russian')
             expect(subject.ref).to eq('master')
             expect(subject.startline).to eq(1)
-            expect(subject.data).to eq("啸芯褉芯褕懈泄 褎邪泄谢")
+            expect(subject.data).to eq("啸芯褉芯褕懈泄 褎邪泄谢\n")
+          end
+        end
+
+        context 'with UTF-8 in the filename' do
+          let(:results) { project.repository.search_files_by_content('webhook', 'master') }
+
+          it 'returns results as UTF-8' do
+            expect(subject.filename).to eq('encoding/銉嗐偣銉�.txt')
+            expect(subject.basename).to eq('encoding/銉嗐偣銉�')
+            expect(subject.ref).to eq('master')
+            expect(subject.startline).to eq(3)
+            expect(subject.data).to include('WebHook銇⒑瑾�')
           end
         end
 
@@ -127,7 +151,7 @@ describe Gitlab::ProjectSearchResults do
             expect(subject.basename).to eq('encoding/iso8859')
             expect(subject.ref).to eq('master')
             expect(subject.startline).to eq(1)
-            expect(subject.data).to eq("脛眉\n\nfoo")
+            expect(subject.data).to eq("脛眉\n\nfoo\n")
           end
         end
       end
@@ -217,7 +241,7 @@ describe Gitlab::ProjectSearchResults do
       expect(issues).to include issue
       expect(issues).not_to include security_issue_1
       expect(issues).not_to include security_issue_2
-      expect(results.issues_count).to eq 1
+      expect(results.limited_issues_count).to eq 1
     end
 
     it 'does not list project confidential issues for project members with guest role' do
@@ -229,7 +253,7 @@ describe Gitlab::ProjectSearchResults do
       expect(issues).to include issue
       expect(issues).not_to include security_issue_1
       expect(issues).not_to include security_issue_2
-      expect(results.issues_count).to eq 1
+      expect(results.limited_issues_count).to eq 1
     end
 
     it 'lists project confidential issues for author' do
@@ -239,7 +263,7 @@ describe Gitlab::ProjectSearchResults do
       expect(issues).to include issue
       expect(issues).to include security_issue_1
       expect(issues).not_to include security_issue_2
-      expect(results.issues_count).to eq 2
+      expect(results.limited_issues_count).to eq 2
     end
 
     it 'lists project confidential issues for assignee' do
@@ -249,7 +273,7 @@ describe Gitlab::ProjectSearchResults do
       expect(issues).to include issue
       expect(issues).not_to include security_issue_1
       expect(issues).to include security_issue_2
-      expect(results.issues_count).to eq 2
+      expect(results.limited_issues_count).to eq 2
     end
 
     it 'lists project confidential issues for project members' do
@@ -261,7 +285,7 @@ describe Gitlab::ProjectSearchResults do
       expect(issues).to include issue
       expect(issues).to include security_issue_1
       expect(issues).to include security_issue_2
-      expect(results.issues_count).to eq 3
+      expect(results.limited_issues_count).to eq 3
     end
 
     it 'lists all project issues for admin' do
@@ -271,7 +295,7 @@ describe Gitlab::ProjectSearchResults do
       expect(issues).to include issue
       expect(issues).to include security_issue_1
       expect(issues).to include security_issue_2
-      expect(results.issues_count).to eq 3
+      expect(results.limited_issues_count).to eq 3
     end
   end
 
@@ -304,6 +328,35 @@ describe Gitlab::ProjectSearchResults do
     end
   end
 
+  describe '#limited_notes_count' do
+    let(:project) { create(:project, :public) }
+    let(:note) { create(:note_on_issue, project: project) }
+    let(:results) { described_class.new(user, project, note.note) }
+
+    context 'when count_limit is lower than total amount' do
+      before do
+        allow(results).to receive(:count_limit).and_return(1)
+      end
+
+      it 'calls note finder once to get the limited amount of notes' do
+        expect(results).to receive(:notes_finder).once.and_call_original
+        expect(results.limited_notes_count).to eq(1)
+      end
+    end
+
+    context 'when count_limit is higher than total amount' do
+      it 'calls note finder multiple times to get the limited amount of notes' do
+        project = create(:project, :public)
+        note = create(:note_on_issue, project: project)
+
+        results = described_class.new(user, project, note.note)
+
+        expect(results).to receive(:notes_finder).exactly(4).times.and_call_original
+        expect(results.limited_notes_count).to eq(1)
+      end
+    end
+  end
+
   # Examples for commit access level test
   #
   # params:
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index 10c5fb148cd171394ddc4569fa5866dc0169df10..0b9b1f537b5b78f9e87516bcf787d4d6dc2ef56b 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -21,30 +21,77 @@ describe Gitlab::ProjectTransfer do
 
   describe '#move_project' do
     it "moves project upload to another namespace" do
-      FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+      path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
+      expected_path = File.join(@root_dir, @namespace_path, @project_path)
+      FileUtils.mkdir_p(path_to_be_moved)
+
       @project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
 
-      expected_path = File.join(@root_dir, @namespace_path, @project_path)
       expect(Dir.exist?(expected_path)).to be_truthy
     end
   end
 
+  describe '#move_namespace' do
+    context 'when moving namespace from root into another namespace' do
+      it "moves namespace projects' upload" do
+        child_namespace = 'test_child_namespace'
+        path_to_be_moved = File.join(@root_dir, child_namespace, @project_path)
+        expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
+        FileUtils.mkdir_p(path_to_be_moved)
+
+        @project_transfer.move_namespace(child_namespace, nil, @namespace_path)
+
+        expect(Dir.exist?(expected_path)).to be_truthy
+      end
+    end
+
+    context 'when moving namespace from one parent to another' do
+      it "moves namespace projects' upload" do
+        child_namespace = 'test_child_namespace'
+        path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
+        expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
+        FileUtils.mkdir_p(path_to_be_moved)
+
+        @project_transfer.move_namespace(child_namespace, @namespace_path_was, @namespace_path)
+
+        expect(Dir.exist?(expected_path)).to be_truthy
+      end
+    end
+
+    context 'when moving namespace from having a parent to root' do
+      it "moves namespace projects' upload" do
+        child_namespace = 'test_child_namespace'
+        path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
+        expected_path = File.join(@root_dir, child_namespace, @project_path)
+        FileUtils.mkdir_p(path_to_be_moved)
+
+        @project_transfer.move_namespace(child_namespace, @namespace_path_was, nil)
+
+        expect(Dir.exist?(expected_path)).to be_truthy
+      end
+    end
+  end
+
   describe '#rename_project' do
     it "renames project" do
-      FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was))
+      path_to_be_moved = File.join(@root_dir, @namespace_path, @project_path_was)
+      expected_path = File.join(@root_dir, @namespace_path, @project_path)
+      FileUtils.mkdir_p(path_to_be_moved)
+
       @project_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
 
-      expected_path = File.join(@root_dir, @namespace_path, @project_path)
       expect(Dir.exist?(expected_path)).to be_truthy
     end
   end
 
   describe '#rename_namespace' do
     it "renames namespace" do
-      FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+      path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
+      expected_path = File.join(@root_dir, @namespace_path, @project_path)
+      FileUtils.mkdir_p(path_to_be_moved)
+
       @project_transfer.rename_namespace(@namespace_path_was, @namespace_path)
 
-      expected_path = File.join(@root_dir, @namespace_path, @project_path)
       expect(Dir.exist?(expected_path)).to be_truthy
     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
index 0697cb2def64506273db416fc43126bd53a8b9b9..c7169717fc1fc03df7d04d626f6e85fe75ee8c8d 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
 
   include_examples 'additional metrics query' do
     let(:deployment) { create(:deployment, environment: environment) }
-    let(:query_params) { [environment.id, deployment.id] }
+    let(:query_params) { [deployment.id] }
 
     it 'queries using specific time' do
       expect(client).to receive(:query_range).with(anything,
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
index 84dc31d97322b8526b07850c873d5ac5f324a27a..ffe3ad85baa39f0e5d11cf00eeae9913c8846182 100644
--- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -31,7 +31,7 @@ describe Gitlab::Prometheus::Queries::DeploymentQuery do
     expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
                                            time: stop_time)
 
-    expect(subject.query(environment.id, deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil,
-                                                               cpu_values: nil, cpu_before: nil, cpu_after: nil)
+    expect(subject.query(deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil,
+                                               cpu_values: nil, cpu_before: nil, cpu_after: nil)
   end
 end
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..420218a695a5f34d8dd2e8b063906f5a90567fac
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus::Queries::MatchedMetricQuery 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(:common_metrics).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(:common_metrics).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(:common_metrics).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/queries/matched_metrics_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb
deleted file mode 100644
index c95719eff1dbf5f8868f1375b0558a214ab96924..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Prometheus::Queries::MatchedMetricsQuery 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(:common_metrics).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(:common_metrics).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(:common_metrics).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/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index b67bcc77bd4174e2e147a94e70dad6d24e12d759..f030f3713720f045f0d4cb5e64839d1851721d76 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -48,8 +48,8 @@ describe ::Gitlab::RepoPath do
   describe '.strip_storage_path' do
     before do
       allow(Gitlab.config.repositories).to receive(:storages).and_return({
-        'storage1' => { 'path' => '/foo' },
-        'storage2' => { 'path' => '/bar' }
+        'storage1' => Gitlab::GitalyClient::StorageSettings.new('path' => '/foo'),
+        'storage2' => Gitlab::GitalyClient::StorageSettings.new('path' => '/bar')
       })
     end
 
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85971f2a7ef02bb870ed8313034615f76ddaf206
--- /dev/null
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+describe Gitlab::RepositoryCacheAdapter do
+  let(:project) { create(:project, :repository) }
+  let(:repository) { project.repository }
+  let(:cache) { repository.send(:cache) }
+
+  describe '#cache_method_output', :use_clean_rails_memory_store_caching do
+    let(:fallback) { 10 }
+
+    context 'with a non-existing repository' do
+      let(:project) { create(:project) } # No repository
+
+      subject do
+        repository.cache_method_output(:cats, fallback: fallback) do
+          repository.cats_call_stub
+        end
+      end
+
+      it 'returns the fallback value' do
+        expect(subject).to eq(fallback)
+      end
+
+      it 'avoids calling the original method' do
+        expect(repository).not_to receive(:cats_call_stub)
+
+        subject
+      end
+    end
+
+    context 'with a method throwing a non-existing-repository error' do
+      subject do
+        repository.cache_method_output(:cats, fallback: fallback) do
+          raise Gitlab::Git::Repository::NoRepository
+        end
+      end
+
+      it 'returns the fallback value' do
+        expect(subject).to eq(fallback)
+      end
+
+      it 'does not cache the data' do
+        subject
+
+        expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+        expect(cache.exist?(:cats)).to eq(false)
+      end
+    end
+
+    context 'with an existing repository' do
+      it 'caches the output' do
+        object = double
+
+        expect(object).to receive(:number).once.and_return(10)
+
+        2.times do
+          val = repository.cache_method_output(:cats) { object.number }
+
+          expect(val).to eq(10)
+        end
+
+        expect(repository.send(:cache).exist?(:cats)).to eq(true)
+        expect(repository.instance_variable_get(:@cats)).to eq(10)
+      end
+    end
+  end
+
+  describe '#expire_method_caches' do
+    it 'expires the caches of the given methods' do
+      expect(cache).to receive(:expire).with(:readme)
+      expect(cache).to receive(:expire).with(:gitignore)
+
+      repository.expire_method_caches(%i(readme gitignore))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc259cf1208445e9e56cfc84f6f9e852c0810f89
--- /dev/null
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::RepositoryCache do
+  let(:backend) { double('backend').as_null_object }
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+  let(:namespace) { "#{repository.full_path}:#{project.id}" }
+  let(:cache) { described_class.new(repository, backend: backend) }
+
+  describe '#cache_key' do
+    subject { cache.cache_key(:foo) }
+
+    it 'includes the namespace' do
+      expect(subject).to eq "foo:#{namespace}"
+    end
+
+    context 'with a given namespace' do
+      let(:extra_namespace) { 'my:data' }
+      let(:cache) do
+        described_class.new(repository, extra_namespace: extra_namespace,
+                                        backend: backend)
+      end
+
+      it 'includes the full namespace' do
+        expect(subject).to eq "foo:#{namespace}:#{extra_namespace}"
+      end
+    end
+  end
+
+  describe '#expire' do
+    it 'expires the given key from the cache' do
+      cache.expire(:foo)
+      expect(backend).to have_received(:delete).with("foo:#{namespace}")
+    end
+  end
+
+  describe '#fetch' do
+    it 'fetches the given key from the cache' do
+      cache.fetch(:bar)
+      expect(backend).to have_received(:fetch).with("bar:#{namespace}")
+    end
+
+    it 'accepts a block' do
+      p = -> {}
+
+      cache.fetch(:baz, &p)
+      expect(backend).to have_received(:fetch).with("baz:#{namespace}", &p)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 9dbab95f70eeb87310bc316423e4100f7422b41d..87288baedb02a7be1a77af4eb94ef88ea2b8569f 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -29,30 +29,6 @@ describe Gitlab::SearchResults do
       end
     end
 
-    describe '#projects_count' do
-      it 'returns the total amount of projects' do
-        expect(results.projects_count).to eq(1)
-      end
-    end
-
-    describe '#issues_count' do
-      it 'returns the total amount of issues' do
-        expect(results.issues_count).to eq(1)
-      end
-    end
-
-    describe '#merge_requests_count' do
-      it 'returns the total amount of merge requests' do
-        expect(results.merge_requests_count).to eq(1)
-      end
-    end
-
-    describe '#milestones_count' do
-      it 'returns the total amount of milestones' do
-        expect(results.milestones_count).to eq(1)
-      end
-    end
-
     context "when count_limit is lower than total amount" do
       before do
         allow(results).to receive(:count_limit).and_return(1)
@@ -183,7 +159,7 @@ describe Gitlab::SearchResults do
       expect(issues).not_to include security_issue_3
       expect(issues).not_to include security_issue_4
       expect(issues).not_to include security_issue_5
-      expect(results.issues_count).to eq 1
+      expect(results.limited_issues_count).to eq 1
     end
 
     it 'does not list confidential issues for project members with guest role' do
@@ -199,7 +175,7 @@ describe Gitlab::SearchResults do
       expect(issues).not_to include security_issue_3
       expect(issues).not_to include security_issue_4
       expect(issues).not_to include security_issue_5
-      expect(results.issues_count).to eq 1
+      expect(results.limited_issues_count).to eq 1
     end
 
     it 'lists confidential issues for author' do
@@ -212,7 +188,7 @@ describe Gitlab::SearchResults do
       expect(issues).to include security_issue_3
       expect(issues).not_to include security_issue_4
       expect(issues).not_to include security_issue_5
-      expect(results.issues_count).to eq 3
+      expect(results.limited_issues_count).to eq 3
     end
 
     it 'lists confidential issues for assignee' do
@@ -225,7 +201,7 @@ describe Gitlab::SearchResults do
       expect(issues).not_to include security_issue_3
       expect(issues).to include security_issue_4
       expect(issues).not_to include security_issue_5
-      expect(results.issues_count).to eq 3
+      expect(results.limited_issues_count).to eq 3
     end
 
     it 'lists confidential issues for project members' do
@@ -241,7 +217,7 @@ describe Gitlab::SearchResults do
       expect(issues).to include security_issue_3
       expect(issues).not_to include security_issue_4
       expect(issues).not_to include security_issue_5
-      expect(results.issues_count).to eq 4
+      expect(results.limited_issues_count).to eq 4
     end
 
     it 'lists all issues for admin' do
@@ -254,7 +230,7 @@ describe Gitlab::SearchResults do
       expect(issues).to include security_issue_3
       expect(issues).to include security_issue_4
       expect(issues).not_to include security_issue_5
-      expect(results.issues_count).to eq 5
+      expect(results.limited_issues_count).to eq 5
     end
   end
 
diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb
index 8c211d1c63fe6dd868bcf6248bba88a5411fefa1..499757da06175488d20e0e8f6678fa26652239d4 100644
--- a/spec/lib/gitlab/sentry_spec.rb
+++ b/spec/lib/gitlab/sentry_spec.rb
@@ -7,7 +7,49 @@ describe Gitlab::Sentry do
 
       described_class.context(nil)
 
-      expect(Raven.tags_context[:locale]).to eq(I18n.locale.to_s)
+      expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s)
+    end
+  end
+
+  describe '.track_exception' do
+    let(:exception) { RuntimeError.new('boom') }
+
+    before do
+      allow(described_class).to receive(:enabled?).and_return(true)
+    end
+
+    it 'raises the exception if it should' do
+      expect(described_class).to receive(:should_raise?).and_return(true)
+      expect { described_class.track_exception(exception) }
+        .to raise_error(RuntimeError)
+    end
+
+    context 'when exceptions should not be raised' do
+      before do
+        allow(described_class).to receive(:should_raise?).and_return(false)
+      end
+
+      it 'logs the exception with all attributes passed' do
+        expected_extras = {
+          some_other_info: 'info',
+          issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
+        }
+
+        expect(Raven).to receive(:capture_exception)
+                           .with(exception, extra: a_hash_including(expected_extras))
+
+        described_class.track_exception(
+          exception,
+          issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1',
+          extra: { some_other_info: 'info' }
+        )
+      end
+
+      it 'sets the context' do
+        expect(described_class).to receive(:context)
+
+        described_class.track_exception(exception)
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 4506cbc3982fc2092366e216603b539cb1c50666..7f579df1c36c79f2978d57b4e153bc765543789d 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -14,13 +14,13 @@ describe Gitlab::Shell do
     allow(Project).to receive(:find).and_return(project)
 
     allow(gitlab_shell).to receive(:gitlab_projects)
-      .with(project.repository_storage_path, project.disk_path + '.git')
+      .with(project.repository_storage, project.disk_path + '.git')
       .and_return(gitlab_projects)
   end
 
   it { is_expected.to respond_to :add_key }
   it { is_expected.to respond_to :remove_key }
-  it { is_expected.to respond_to :add_repository }
+  it { is_expected.to respond_to :create_repository }
   it { is_expected.to respond_to :remove_repository }
   it { is_expected.to respond_to :fork_repository }
 
@@ -402,10 +402,10 @@ describe Gitlab::Shell do
       allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
     end
 
-    describe '#add_repository' do
-      shared_examples '#add_repository' do
+    describe '#create_repository' do
+      shared_examples '#create_repository' do
         let(:repository_storage) { 'default' }
-        let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
+        let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
         let(:repo_name) { 'project/path' }
         let(:created_path) { File.join(repository_storage_path, repo_name + '.git') }
 
@@ -414,7 +414,7 @@ describe Gitlab::Shell do
         end
 
         it 'creates a repository' do
-          expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_truthy
+          expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_truthy
 
           expect(File.stat(created_path).mode & 0o777).to eq(0o770)
 
@@ -426,19 +426,19 @@ describe Gitlab::Shell do
         it 'returns false when the command fails' do
           FileUtils.mkdir_p(File.dirname(created_path))
           # This file will block the creation of the repo's .git directory. That
-          # should cause #add_repository to fail.
+          # should cause #create_repository to fail.
           FileUtils.touch(created_path)
 
-          expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_falsy
+          expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_falsy
         end
       end
 
       context 'with gitaly' do
-        it_behaves_like '#add_repository'
+        it_behaves_like '#create_repository'
       end
 
       context 'without gitaly', :skip_gitaly_mock do
-        it_behaves_like '#add_repository'
+        it_behaves_like '#create_repository'
       end
     end
 
@@ -487,29 +487,29 @@ describe Gitlab::Shell do
     describe '#fork_repository' do
       subject do
         gitlab_shell.fork_repository(
-          project.repository_storage_path,
+          project.repository_storage,
           project.disk_path,
-          'new/storage',
+          'nfs-file05',
           'fork/path'
         )
       end
 
       it 'returns true when the command succeeds' do
-        expect(gitlab_projects).to receive(:fork_repository).with('new/storage', 'fork/path.git') { true }
+        expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { true }
 
         is_expected.to be_truthy
       end
 
       it 'return false when the command fails' do
-        expect(gitlab_projects).to receive(:fork_repository).with('new/storage', 'fork/path.git') { false }
+        expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { false }
 
         is_expected.to be_falsy
       end
     end
 
     shared_examples 'fetch_remote' do |gitaly_on|
-      def fetch_remote(ssh_auth = nil)
-        gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth)
+      def fetch_remote(ssh_auth = nil, prune = true)
+        gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth, prune: prune)
       end
 
       def expect_gitlab_projects(fail = false, options = {})
@@ -555,27 +555,33 @@ describe Gitlab::Shell do
       end
 
       it 'returns true when the command succeeds' do
-        expect_call(false, force: false, tags: true)
+        expect_call(false, force: false, tags: true, prune: true)
 
         expect(fetch_remote).to be_truthy
       end
 
+      it 'returns true when the command succeeds' do
+        expect_call(false, force: false, tags: true, prune: false)
+
+        expect(fetch_remote(nil, false)).to be_truthy
+      end
+
       it 'raises an exception when the command fails' do
-        expect_call(true, force: false, tags: true)
+        expect_call(true, force: false, tags: true, prune: true)
 
         expect { fetch_remote }.to raise_error(Gitlab::Shell::Error)
       end
 
       it 'allows forced and no_tags to be changed' do
-        expect_call(false, force: true, tags: false)
+        expect_call(false, force: true, tags: false, prune: true)
 
-        result = gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', forced: true, no_tags: true)
+        result = gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', forced: true, no_tags: true, prune: true)
         expect(result).to be_truthy
       end
 
       context 'SSH auth' do
         it 'passes the SSH key if specified' do
-          expect_call(false, force: false, tags: true, ssh_key: 'foo')
+          expect_call(false, force: false, tags: true, prune: true, ssh_key: 'foo')
 
           ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo')
 
@@ -583,7 +589,7 @@ describe Gitlab::Shell do
         end
 
         it 'does not pass an empty SSH key' do
-          expect_call(false, force: false, tags: true)
+          expect_call(false, force: false, tags: true, prune: true)
 
           ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '')
 
@@ -591,7 +597,7 @@ describe Gitlab::Shell do
         end
 
         it 'does not pass the key unless SSH key auth is to be used' do
-          expect_call(false, force: false, tags: true)
+          expect_call(false, force: false, tags: true, prune: true)
 
           ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo')
 
@@ -599,7 +605,7 @@ describe Gitlab::Shell do
         end
 
         it 'passes the known_hosts data if specified' do
-          expect_call(false, force: false, tags: true, known_hosts: 'foo')
+          expect_call(false, force: false, tags: true, prune: true, known_hosts: 'foo')
 
           ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo')
 
@@ -607,7 +613,7 @@ describe Gitlab::Shell do
         end
 
         it 'does not pass empty known_hosts data' do
-          expect_call(false, force: false, tags: true)
+          expect_call(false, force: false, tags: true, prune: true)
 
           ssh_auth = build_ssh_auth(ssh_known_hosts: '')
 
@@ -615,7 +621,7 @@ describe Gitlab::Shell do
         end
 
         it 'does not pass known_hosts data unless SSH is to be used' do
-          expect_call(false, force: false, tags: true)
+          expect_call(false, force: false, tags: true, prune: true)
 
           ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo')
 
@@ -642,7 +648,7 @@ describe Gitlab::Shell do
 
         it 'passes the correct params to the gitaly service' do
           expect(repository.gitaly_repository_client).to receive(:fetch_remote)
-            .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, timeout: timeout)
+            .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, prune: true, timeout: timeout)
 
           subject
         end
@@ -655,7 +661,7 @@ describe Gitlab::Shell do
       it 'returns true when the command succeeds' do
         expect(gitlab_projects).to receive(:import_project).with(import_url, timeout) { true }
 
-        result = gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, import_url)
+        result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
 
         expect(result).to be_truthy
       end
@@ -665,7 +671,7 @@ describe Gitlab::Shell do
         expect(gitlab_projects).to receive(:import_project) { false }
 
         expect do
-          gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, import_url)
+          gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
         end.to raise_error(Gitlab::Shell::Error, "error")
       end
     end
@@ -673,7 +679,7 @@ describe Gitlab::Shell do
 
   describe 'namespace actions' do
     subject { described_class.new }
-    let(:storage_path) { Gitlab.config.repositories.storages.default.path }
+    let(:storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path }
 
     describe '#add_namespace' do
       it 'creates a namespace' do
@@ -721,7 +727,7 @@ describe Gitlab::Shell do
 
   def find_in_authorized_keys_file(key_id)
     gitlab_shell.batch_read_key_ids do |ids|
-      return true if ids.include?(key_id)
+      return true if ids.include?(key_id) # rubocop:disable Cop/AvoidReturnFromBlocks
     end
 
     false
diff --git a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fed9aeba30ced1210a3953702e4889bcd336d3cb
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqLogging::JSONFormatter do
+  let(:hash_input) { { foo: 1, bar: 'test' } }
+  let(:message) { 'This is a test' }
+  let(:timestamp) { Time.now }
+
+  it 'wraps a Hash' do
+    result = subject.call('INFO', timestamp, 'my program', hash_input)
+
+    data = JSON.parse(result)
+    expected_output = hash_input.stringify_keys
+    expected_output['severity'] = 'INFO'
+    expected_output['time'] = timestamp.utc.iso8601(3)
+
+    expect(data).to eq(expected_output)
+  end
+
+  it 'wraps a String' do
+    result = subject.call('DEBUG', timestamp, 'my string', message)
+
+    data = JSON.parse(result)
+    expected_output = {
+      severity: 'DEBUG',
+      time: timestamp.utc.iso8601(3),
+      message: message
+    }
+
+    expect(data).to eq(expected_output.stringify_keys)
+  end
+end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2421b1e5a1a58ee60d14a71c36cb9ebc2bab00ce
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqLogging::StructuredLogger do
+  describe '#call' do
+    let(:timestamp) { Time.new('2018-01-01 12:00:00').utc }
+    let(:job) do
+      {
+        "class" => "TestWorker",
+        "args" => [1234, 'hello'],
+        "retry" => false,
+        "queue" => "cronjob:test_queue",
+        "queue_namespace" => "cronjob",
+        "jid" => "da883554ee4fe414012f5f42",
+        "created_at" => timestamp.to_f,
+        "enqueued_at" => timestamp.to_f
+      }
+    end
+    let(:logger) { double() }
+    let(:start_payload) do
+      job.merge(
+        'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
+        'job_status' => 'start',
+        'pid' => Process.pid,
+        'created_at' => timestamp.iso8601(3),
+        'enqueued_at' => timestamp.iso8601(3)
+      )
+    end
+    let(:end_payload) do
+      start_payload.merge(
+        'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
+        'job_status' => 'done',
+        'duration' => 0.0,
+        "completed_at" => timestamp.iso8601(3)
+      )
+    end
+    let(:exception_payload) do
+      end_payload.merge(
+        'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
+        'job_status' => 'fail',
+        'error' => ArgumentError,
+        'error_message' => 'some exception'
+      )
+    end
+
+    before do
+      allow(Sidekiq).to receive(:logger).and_return(logger)
+
+      allow(subject).to receive(:current_time).and_return(timestamp.to_f)
+    end
+
+    subject { described_class.new }
+
+    context 'with SIDEKIQ_LOG_ARGUMENTS enabled' do
+      before do
+        stub_env('SIDEKIQ_LOG_ARGUMENTS', '1')
+      end
+
+      it 'logs start and end of job' do
+        Timecop.freeze(timestamp) do
+          expect(logger).to receive(:info).with(start_payload).ordered
+          expect(logger).to receive(:info).with(end_payload).ordered
+          expect(subject).to receive(:log_job_start).and_call_original
+          expect(subject).to receive(:log_job_done).and_call_original
+
+          subject.call(job, 'test_queue') { }
+        end
+      end
+
+      it 'logs an exception in job' do
+        Timecop.freeze(timestamp) do
+          expect(logger).to receive(:info).with(start_payload)
+          # This excludes the exception_backtrace
+          expect(logger).to receive(:warn).with(hash_including(exception_payload))
+          expect(subject).to receive(:log_job_start).and_call_original
+          expect(subject).to receive(:log_job_done).and_call_original
+
+          expect do
+            subject.call(job, 'test_queue') do
+              raise ArgumentError, 'some exception'
+            end
+          end.to raise_error(ArgumentError)
+        end
+      end
+    end
+
+    context 'with SIDEKIQ_LOG_ARGUMENTS disabled' do
+      it 'logs start and end of job' do
+        Timecop.freeze(timestamp) do
+          start_payload.delete('args')
+
+          expect(logger).to receive(:info).with(start_payload).ordered
+          expect(logger).to receive(:info).with(end_payload).ordered
+          expect(subject).to receive(:log_job_start).and_call_original
+          expect(subject).to receive(:log_job_done).and_call_original
+
+          subject.call(job, 'test_queue') { }
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index e3447d974aa0147f9f75dae64e01ad57354dfcd8..194cae8c645dae2092fcfec79f1a57ebecb31889 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -108,5 +108,10 @@ describe Gitlab::SlashCommands::Command do
 
       it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) }
     end
+
+    context 'IssueMove is triggered' do
+      let(:params) { { text: 'issue move #78291 to gitlab/gitlab-ci' } }
+      it { is_expected.to eq(Gitlab::SlashCommands::IssueMove) }
+    end
   end
 end
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d41441c9472a44a8ed64c4bef995a3e84c93f351
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::IssueMove, service: true do
+  describe '#match' do
+    shared_examples_for 'move command' do |text_command|
+      it 'can be parsed to extract the needed fields' do
+        match_data = described_class.match(text_command)
+
+        expect(match_data['iid']).to eq('123456')
+        expect(match_data['project_path']).to eq('gitlab/gitlab-ci')
+      end
+    end
+
+    it_behaves_like 'move command', 'issue move #123456 to gitlab/gitlab-ci'
+    it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci'
+    it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci '
+    it_behaves_like 'move command', 'issue move 123456 to gitlab/gitlab-ci'
+    it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci'
+    it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci '
+  end
+
+  describe '#execute' do
+    set(:user) { create(:user) }
+    set(:issue) { create(:issue) }
+    set(:chat_name) { create(:chat_name, user: user) }
+    set(:project) { issue.project }
+    set(:other_project) { create(:project, namespace: project.namespace) }
+
+    before do
+      [project, other_project].each { |prj| prj.add_master(user) }
+    end
+
+    subject { described_class.new(project, chat_name) }
+
+    def process_message(message)
+      subject.execute(described_class.match(message))
+    end
+
+    context 'when the user can move the issue' do
+      context 'when the move fails' do
+        it 'returns the error message' do
+          message = "issue move #{issue.iid} #{project.full_path}"
+
+          expect(process_message(message)).to include(response_type: :ephemeral,
+                                                      text: a_string_matching('Cannot move issue'))
+        end
+      end
+
+      context 'when the move succeeds' do
+        let(:message) { "issue move #{issue.iid} #{other_project.full_path}" }
+
+        it 'moves the issue to the new destination' do
+          expect { process_message(message) }.to change { Issue.count }.by(1)
+
+          new_issue = issue.reload.moved_to
+
+          expect(new_issue.state).to eq('opened')
+          expect(new_issue.project_id).to eq(other_project.id)
+          expect(new_issue.author_id).to eq(issue.author_id)
+
+          expect(issue.state).to eq('closed')
+          expect(issue.project_id).to eq(project.id)
+        end
+
+        it 'returns the new issue' do
+          expect(process_message(message))
+            .to include(response_type: :in_channel,
+                        attachments: [a_hash_including(title_link: a_string_including(other_project.full_path))])
+        end
+
+        it 'mentions the old issue' do
+          expect(process_message(message))
+            .to include(attachments: [a_hash_including(pretext: a_string_including(project.full_path))])
+        end
+      end
+    end
+
+    context 'when the issue does not exist' do
+      it 'returns not found' do
+        message = "issue move #{issue.iid.succ} #{other_project.full_path}"
+
+        expect(process_message(message)).to include(response_type: :ephemeral,
+                                                    text: a_string_matching('not found'))
+      end
+    end
+
+    context 'when the target project does not exist' do
+      it 'returns not found' do
+        message = "issue move #{issue.iid} #{other_project.full_path}/foo"
+
+        expect(process_message(message)).to include(response_type: :ephemeral,
+                                                    text: a_string_matching('not found'))
+      end
+    end
+
+    context 'when the user cannot see the target project' do
+      it 'returns not found' do
+        message = "issue move #{issue.iid} #{other_project.full_path}"
+        other_project.team.truncate
+
+        expect(process_message(message)).to include(response_type: :ephemeral,
+                                                    text: a_string_matching('not found'))
+      end
+    end
+
+    context 'when the user does not have the required permissions on the target project' do
+      it 'returns the error message' do
+        message = "issue move #{issue.iid} #{other_project.full_path}"
+        other_project.team.truncate
+        other_project.team.add_guest(user)
+
+        expect(process_message(message)).to include(response_type: :ephemeral,
+                                                    text: a_string_matching('Cannot move issue'))
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..58c341a284e237b40d4d0c337aeb3868fdc563ef
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::IssueMove do
+  set(:admin) { create(:admin) }
+  set(:project) { create(:project) }
+  set(:other_project) { create(:project) }
+  set(:old_issue) { create(:issue, project: project) }
+  set(:new_issue) { Issues::MoveService.new(project, admin).execute(old_issue, other_project) }
+  let(:attachment) { subject[:attachments].first }
+
+  subject { described_class.new(new_issue).present(old_issue) }
+
+  it { is_expected.to be_a(Hash) }
+
+  it 'shows the new issue' do
+    expect(subject[:response_type]).to be(:in_channel)
+    expect(subject).to have_key(:attachments)
+    expect(attachment[:title]).to start_with(new_issue.title)
+    expect(attachment[:title_link]).to include(other_project.full_path)
+  end
+
+  it 'mentions the old issue and the new issue in the pretext' do
+    expect(attachment[:pretext]).to include(project.full_path)
+    expect(attachment[:pretext]).to include(other_project.full_path)
+  end
+end
diff --git a/spec/lib/gitlab/string_placeholder_replacer_spec.rb b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7a03ea4154c115991ef24d0571f868ad2cc9781c
--- /dev/null
+++ b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Gitlab::StringPlaceholderReplacer do
+  describe '.render_url' do
+    it 'returns the nil if the string is blank' do
+      expect(described_class.replace_string_placeholders(nil, /whatever/)).to be_blank
+    end
+
+    it 'returns the string if the placeholder regex' do
+      expect(described_class.replace_string_placeholders('whatever')).to eq 'whatever'
+    end
+
+    it 'returns the string if no block given' do
+      expect(described_class.replace_string_placeholders('whatever', /whatever/)).to eq 'whatever'
+    end
+
+    context 'when all params are valid' do
+      let(:string) { '%{path}/%{id}/%{branch}' }
+      let(:regex) { /(path|id)/ }
+
+      it 'replaces each placeholders with the block result' do
+        result = described_class.replace_string_placeholders(string, regex) do |arg|
+          'WHATEVER'
+        end
+
+        expect(result).to eq 'WHATEVER/WHATEVER/%{branch}'
+      end
+
+      it 'does not replace the placeholder if the block result is nil' do
+        result = described_class.replace_string_placeholders(string, regex) do |arg|
+          arg == 'path' ? nil : 'WHATEVER'
+        end
+
+        expect(result).to eq '%{path}/WHATEVER/%{branch}'
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index d715f9bd641fe4d25b7a9583ff9757d3b9e334b2..37b1298b962d4423e6fed990143bb073ff66a7ba 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -2,17 +2,36 @@ require 'spec_helper'
 
 describe Gitlab::StringRegexMarker do
   describe '#mark' do
-    let(:raw)  { %{"name": "AFNetworking"} }
-    let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
-    subject do
-      described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
-        %{<a href="#">#{text}</a>}
+    context 'with a single occurrence' do
+      let(:raw)  { %{"name": "AFNetworking"} }
+      let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
+
+      subject do
+        described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
+          %{<a href="#">#{text}</a>}
+        end
+      end
+
+      it 'marks the match' do
+        expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
+        expect(subject).to be_html_safe
       end
     end
 
-    it 'marks the inline diffs' do
-      expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
-      expect(subject).to be_html_safe
+    context 'with multiple occurrences' do
+      let(:raw)  { %{a <b> <c> d} }
+      let(:rich) { %{a &lt;b&gt; &lt;c&gt; d}.html_safe }
+
+      subject do
+        described_class.new(raw, rich).mark(/<[a-z]>/) do |text, left:, right:|
+          %{<strong>#{text}</strong>}
+        end
+      end
+
+      it 'marks the matches' do
+        expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d})
+        expect(subject).to be_html_safe
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index d9b3c2350b182fc0d57d8745d80e8be5bf532a05..a3b3dc3be6dd4984bc83f9c4923cc62cada2eb93 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
 
 describe Gitlab::UrlBlocker do
   describe '#blocked_url?' do
+    let(:valid_ports) { Project::VALID_IMPORT_PORTS }
+
     it 'allows imports from configured web host and port' do
       import_url = "http://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git"
       expect(described_class.blocked_url?(import_url)).to be false
@@ -17,7 +19,7 @@ describe Gitlab::UrlBlocker do
     end
 
     it 'returns true for bad port' do
-      expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git')).to be true
+      expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git', valid_ports: valid_ports)).to be true
     end
 
     it 'returns true for alternative version of 127.0.0.1 (0177.1)' do
@@ -71,6 +73,47 @@ describe Gitlab::UrlBlocker do
     it 'returns false for legitimate URL' do
       expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
     end
+
+    context 'when allow_local_network is' do
+      let(:local_ips) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] }
+      let(:fake_domain) { 'www.fakedomain.fake' }
+
+      context 'true (default)' do
+        it 'does not block urls from private networks' do
+          local_ips.each do |ip|
+            stub_domain_resolv(fake_domain, ip)
+
+            expect(described_class).not_to be_blocked_url("http://#{fake_domain}")
+
+            unstub_domain_resolv
+
+            expect(described_class).not_to be_blocked_url("http://#{ip}")
+          end
+        end
+      end
+
+      context 'false' do
+        it 'blocks urls from private networks' do
+          local_ips.each do |ip|
+            stub_domain_resolv(fake_domain, ip)
+
+            expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false)
+
+            unstub_domain_resolv
+
+            expect(described_class).to be_blocked_url("http://#{ip}", allow_local_network: false)
+          end
+        end
+      end
+
+      def stub_domain_resolv(domain, ip)
+        allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true)])
+      end
+
+      def unstub_domain_resolv
+        allow(Addrinfo).to receive(:getaddrinfo).and_call_original
+      end
+    end
   end
 
   # Resolv does not support resolving UTF-8 domain names
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 138d21ede9712aee533642041862989ec8e32200..9e6aa109a4b4a9e9caa846ed317eaf565885d417 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -12,6 +12,14 @@ describe Gitlab::UsageData do
       create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
       create(:service, project: projects[1], type: 'SlackService', active: true)
       create(:service, project: projects[2], type: 'SlackService', active: true)
+
+      gcp_cluster = create(:cluster, :provided_by_gcp)
+      create(:cluster, :provided_by_user)
+      create(:cluster, :provided_by_user, :disabled)
+      create(:clusters_applications_helm, :installed, cluster: gcp_cluster)
+      create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
+      create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster)
+      create(:clusters_applications_runner, :installed, cluster: gcp_cluster)
     end
 
     subject { described_class.data }
@@ -64,6 +72,12 @@ describe Gitlab::UsageData do
         clusters
         clusters_enabled
         clusters_disabled
+        clusters_platforms_gke
+        clusters_platforms_user
+        clusters_applications_helm
+        clusters_applications_ingress
+        clusters_applications_prometheus
+        clusters_applications_runner
         in_review_folder
         groups
         issues
@@ -97,6 +111,15 @@ describe Gitlab::UsageData do
       expect(count_data[:projects_jira_active]).to eq(2)
       expect(count_data[:projects_slack_notifications_active]).to eq(2)
       expect(count_data[:projects_slack_slash_active]).to eq(1)
+
+      expect(count_data[:clusters_enabled]).to eq(6)
+      expect(count_data[:clusters_disabled]).to eq(1)
+      expect(count_data[:clusters_platforms_gke]).to eq(1)
+      expect(count_data[:clusters_platforms_user]).to eq(1)
+      expect(count_data[:clusters_applications_helm]).to eq(1)
+      expect(count_data[:clusters_applications_ingress]).to eq(1)
+      expect(count_data[:clusters_applications_prometheus]).to eq(1)
+      expect(count_data[:clusters_applications_runner]).to eq(1)
     end
   end
 
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 7280acb6c82ff637ceb7c260d1751e4c2051cdbd..40c8286b1b92ecaf8f484430000e2c06f806e223 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe Gitlab::UserAccess do
+  include ProjectForksHelper
+
   let(:access) { described_class.new(user, project: project) }
   let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
@@ -118,6 +120,39 @@ describe Gitlab::UserAccess do
       end
     end
 
+    describe 'allowing pushes to maintainers of forked projects' do
+      let(:canonical_project) { create(:project, :public, :repository) }
+      let(:project) { fork_project(canonical_project, create(:user), repository: true) }
+
+      before do
+        create(
+          :merge_request,
+          target_project: canonical_project,
+          source_project: project,
+          source_branch: 'awesome-feature',
+          allow_maintainer_to_push: true
+        )
+      end
+
+      it 'allows users that have push access to the canonical project to push to the MR branch' do
+        canonical_project.add_developer(user)
+
+        expect(access.can_push_to_branch?('awesome-feature')).to be_truthy
+      end
+
+      it 'does not allow the user to push to other branches' do
+        canonical_project.add_developer(user)
+
+        expect(access.can_push_to_branch?('master')).to be_falsey
+      end
+
+      it 'does not allow the user to push if he does not have push access to the canonical project' do
+        canonical_project.add_guest(user)
+
+        expect(access.can_push_to_branch?('awesome-feature')).to be_falsey
+      end
+    end
+
     describe 'merge to protected branch if allowed for developers' do
       before do
         @branch = create :protected_branch, :developers_can_merge, project: project
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index bda239b7871d54b3cc26b0b9bd6dbc2be9ce9435..4ba99009855fbc3b6037fb05757dca3d756ce84d 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,7 +1,8 @@
 require 'spec_helper'
 
 describe Gitlab::Utils do
-  delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, to: :described_class
+  delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string,
+   :bytes_to_megabytes, to: :described_class
 
   describe '.slugify' do
     {
@@ -83,4 +84,26 @@ describe Gitlab::Utils do
       expect(which('sh', 'PATH' => '/bin')).to eq('/bin/sh')
     end
   end
+
+  describe '.ensure_array_from_string' do
+    it 'returns the same array if given one' do
+      arr = ['a', 4, true, { test: 1 }]
+
+      expect(ensure_array_from_string(arr)).to eq(arr)
+    end
+
+    it 'turns comma-separated strings into arrays' do
+      str = 'seven, eight, 9, 10'
+
+      expect(ensure_array_from_string(str)).to eq(%w[seven eight 9 10])
+    end
+  end
+
+  describe '.bytes_to_megabytes' do
+    it 'converts bytes to megabytes' do
+      bytes = 1.megabyte
+
+      expect(bytes_to_megabytes(bytes)).to eq(1)
+    end
+  end
 end
diff --git a/spec/lib/gitlab/verify/job_artifacts_spec.rb b/spec/lib/gitlab/verify/job_artifacts_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec490bdfde294dafbad6f9bff11a648a80b41065
--- /dev/null
+++ b/spec/lib/gitlab/verify/job_artifacts_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::Verify::JobArtifacts do
+  include GitlabVerifyHelpers
+
+  it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
+    let!(:objects) { create_list(:ci_job_artifact, 3, :archive) }
+  end
+
+  describe '#run_batches' do
+    let(:failures) { collect_failures }
+    let(:failure) { failures[artifact] }
+
+    let!(:artifact) { create(:ci_job_artifact, :archive, :correct_checksum) }
+
+    it 'passes artifacts with the correct file' do
+      expect(failures).to eq({})
+    end
+
+    it 'fails artifacts with a missing file' do
+      FileUtils.rm_f(artifact.file.path)
+
+      expect(failures.keys).to contain_exactly(artifact)
+      expect(failure).to be_a(Errno::ENOENT)
+      expect(failure.to_s).to include(artifact.file.path)
+    end
+
+    it 'fails artifacts with a mismatched checksum' do
+      File.truncate(artifact.file.path, 0)
+
+      expect(failures.keys).to contain_exactly(artifact)
+      expect(failure.to_s).to include('Checksum mismatch')
+    end
+  end
+end
diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0f890e2c7ceee5afc792b05e4dc916bfcd7e6b3d
--- /dev/null
+++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Gitlab::Verify::LfsObjects do
+  include GitlabVerifyHelpers
+
+  it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
+    let!(:objects) { create_list(:lfs_object, 3, :with_file) }
+  end
+
+  describe '#run_batches' do
+    let(:failures) { collect_failures }
+    let(:failure) { failures[lfs_object] }
+
+    let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) }
+
+    it 'passes LFS objects with the correct file' do
+      expect(failures).to eq({})
+    end
+
+    it 'fails LFS objects with a missing file' do
+      FileUtils.rm_f(lfs_object.file.path)
+
+      expect(failures.keys).to contain_exactly(lfs_object)
+      expect(failure).to be_a(Errno::ENOENT)
+      expect(failure.to_s).to include(lfs_object.file.path)
+    end
+
+    it 'fails LFS objects with a mismatched oid' do
+      File.truncate(lfs_object.file.path, 0)
+
+      expect(failures.keys).to contain_exactly(lfs_object)
+      expect(failure.to_s).to include('Checksum mismatch')
+    end
+
+    context 'with remote files' do
+      before do
+        stub_lfs_object_storage
+      end
+
+      it 'skips LFS objects in object storage' do
+        local_failure = create(:lfs_object)
+        create(:lfs_object, :object_storage)
+
+        failures = {}
+        described_class.new(batch_size: 10).run_batches { |_, failed| failures.merge!(failed) }
+
+        expect(failures.keys).to contain_exactly(local_failure)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85768308edc73fa3f33a0f88a504c63b61aaba15
--- /dev/null
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe Gitlab::Verify::Uploads do
+  include GitlabVerifyHelpers
+
+  it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
+    let(:projects) { create_list(:project, 3, :with_avatar) }
+    let!(:objects) { projects.flat_map(&:uploads) }
+  end
+
+  describe '#run_batches' do
+    let(:project) { create(:project, :with_avatar) }
+    let(:failures) { collect_failures }
+    let(:failure) { failures[upload] }
+
+    let!(:upload) { project.uploads.first }
+
+    it 'passes uploads with the correct file' do
+      expect(failures).to eq({})
+    end
+
+    it 'fails uploads with a missing file' do
+      FileUtils.rm_f(upload.absolute_path)
+
+      expect(failures.keys).to contain_exactly(upload)
+      expect(failure).to be_a(Errno::ENOENT)
+      expect(failure.to_s).to include(upload.absolute_path)
+    end
+
+    it 'fails uploads with a mismatched checksum' do
+      upload.update!(checksum: 'something incorrect')
+
+      expect(failures.keys).to contain_exactly(upload)
+      expect(failure.to_s).to include('Checksum mismatch')
+    end
+
+    it 'fails uploads with a missing precalculated checksum' do
+      upload.update!(checksum: '')
+
+      expect(failures.keys).to contain_exactly(upload)
+      expect(failure.to_s).to include('Checksum missing')
+    end
+
+    context 'with remote files' do
+      before do
+        stub_uploads_object_storage(AvatarUploader)
+      end
+
+      it 'skips uploads in object storage' do
+        local_failure = create(:upload)
+        create(:upload, :object_storage)
+
+        failures = {}
+        described_class.new(batch_size: 10).run_batches { |_, failed| failures.merge!(failed) }
+
+        expect(failures.keys).to contain_exactly(local_failure)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb
index 32a946ca034b03ce71a68a7061f048371d3efffd..4eca53032a28be295d05157ba84b107ebf6ab36b 100644
--- a/spec/lib/gitlab/view/presenter/base_spec.rb
+++ b/spec/lib/gitlab/view/presenter/base_spec.rb
@@ -48,4 +48,11 @@ describe Gitlab::View::Presenter::Base do
       end
     end
   end
+
+  describe '#present' do
+    it 'returns self' do
+      presenter = presenter_class.new(build_stubbed(:project))
+      expect(presenter.present).to eq(presenter)
+    end
+  end
 end
diff --git a/spec/lib/gitlab/wiki/committer_with_hooks_spec.rb b/spec/lib/gitlab/wiki/committer_with_hooks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..830fb8a85983f64287942ce5d211876edaac230b
--- /dev/null
+++ b/spec/lib/gitlab/wiki/committer_with_hooks_spec.rb
@@ -0,0 +1,154 @@
+require 'spec_helper'
+
+describe Gitlab::Wiki::CommitterWithHooks, seed_helper: true do
+  shared_examples 'calling wiki hooks' do
+    let(:project) { create(:project) }
+    let(:user) { project.owner }
+    let(:project_wiki) { ProjectWiki.new(project, user) }
+    let(:wiki) { project_wiki.wiki }
+    let(:options) do
+      {
+        id: user.id,
+        username: user.username,
+        name: user.name,
+        email: user.email,
+        message: 'commit message'
+      }
+    end
+
+    subject { described_class.new(wiki, options) }
+
+    before do
+      project_wiki.create_page('home', 'test content')
+    end
+
+    shared_examples 'failing pre-receive hook' do
+      before do
+        expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, ''])
+        expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update')
+        expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
+      end
+
+      it 'raises exception' do
+        expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+      end
+
+      it 'does not create a new commit inside the repository' do
+        current_rev = find_current_rev
+
+        expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+
+        expect(current_rev).to eq find_current_rev
+      end
+    end
+
+    shared_examples 'failing update hook' do
+      before do
+        expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
+        expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, ''])
+        expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
+      end
+
+      it 'raises exception' do
+        expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+      end
+
+      it 'does not create a new commit inside the repository' do
+        current_rev = find_current_rev
+
+        expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+
+        expect(current_rev).to eq find_current_rev
+      end
+    end
+
+    shared_examples 'failing post-receive hook' do
+      before do
+        expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
+        expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, ''])
+        expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, ''])
+      end
+
+      it 'does not raise exception' do
+        expect { subject.commit }.not_to raise_error
+      end
+
+      it 'creates the commit' do
+        current_rev = find_current_rev
+
+        subject.commit
+
+        expect(current_rev).not_to eq find_current_rev
+      end
+    end
+
+    shared_examples 'when hooks call succceeds' do
+      let(:hook) { double(:hook) }
+
+      it 'calls the three hooks' do
+        expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
+        expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil])
+
+        subject.commit
+      end
+
+      it 'creates the commit' do
+        current_rev = find_current_rev
+
+        subject.commit
+
+        expect(current_rev).not_to eq find_current_rev
+      end
+    end
+
+    context 'when creating a page' do
+      before do
+        project_wiki.create_page('index', 'test content')
+      end
+
+      it_behaves_like 'failing pre-receive hook'
+      it_behaves_like 'failing update hook'
+      it_behaves_like 'failing post-receive hook'
+      it_behaves_like 'when hooks call succceeds'
+    end
+
+    context 'when updating a page' do
+      before do
+        project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown)
+      end
+
+      it_behaves_like 'failing pre-receive hook'
+      it_behaves_like 'failing update hook'
+      it_behaves_like 'failing post-receive hook'
+      it_behaves_like 'when hooks call succceeds'
+    end
+
+    context 'when deleting a page' do
+      before do
+        project_wiki.delete_page(find_page('home'))
+      end
+
+      it_behaves_like 'failing pre-receive hook'
+      it_behaves_like 'failing update hook'
+      it_behaves_like 'failing post-receive hook'
+      it_behaves_like 'when hooks call succceeds'
+    end
+
+    def find_current_rev
+      wiki.gollum_wiki.repo.commits.first&.sha
+    end
+
+    def find_page(name)
+      wiki.page(title: name)
+    end
+  end
+
+  # TODO: Uncomment once Gitaly updates the ruby vendor code
+  # context 'when Gitaly is enabled' do
+  #   it_behaves_like 'calling wiki hooks'
+  # end
+
+  context 'when Gitaly is disabled', :skip_gitaly_mock do
+    it_behaves_like 'calling wiki hooks'
+  end
+end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 37a0bf1ad36575b7c247a5a10044236109458e21..d64ea72e3464a1c5866ed72674cca571f418ae2a 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::Workhorse do
     let(:ref) { 'master' }
     let(:format) { 'zip' }
     let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path }
-    let(:base_params) { repository.archive_metadata(ref, storage_path, format) }
+    let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) }
     let(:gitaly_params) do
       base_params.merge(
         'GitalyServer' => {
@@ -29,7 +29,7 @@ describe Gitlab::Workhorse do
     let(:cache_disabled) { false }
 
     subject do
-      described_class.send_git_archive(repository, ref: ref, format: format)
+      described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil)
     end
 
     before do
@@ -55,7 +55,7 @@ describe Gitlab::Workhorse do
       end
     end
 
-    context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do
+    context 'when Gitaly workhorse_archive feature is disabled', :disable_gitaly do
       it 'sets the header correctly' do
         key, command, params = decode_workhorse_header(subject)
 
@@ -100,7 +100,7 @@ describe Gitlab::Workhorse do
       end
     end
 
-    context 'when Gitaly workhorse_send_git_patch feature is disabled', :skip_gitaly_mock do
+    context 'when Gitaly workhorse_send_git_patch feature is disabled', :disable_gitaly do
       it 'sets the header correctly' do
         key, command, params = decode_workhorse_header(subject)
 
@@ -173,7 +173,7 @@ describe Gitlab::Workhorse do
       end
     end
 
-    context 'when Gitaly workhorse_send_git_diff feature is disabled', :skip_gitaly_mock do
+    context 'when Gitaly workhorse_send_git_diff feature is disabled', :disable_gitaly do
       it 'sets the header correctly' do
         key, command, params = decode_workhorse_header(subject)
 
@@ -275,7 +275,7 @@ describe Gitlab::Workhorse do
 
   describe '.git_http_ok' do
     let(:user) { create(:user) }
-    let(:repo_path) { repository.path_to_repo }
+    let(:repo_path) { 'ignored but not allowed to be empty in gitlab-workhorse' }
     let(:action) { 'info_refs' }
     let(:params) do
       {
@@ -455,7 +455,7 @@ describe Gitlab::Workhorse do
       end
     end
 
-    context 'when Gitaly workhorse_raw_show feature is disabled', :skip_gitaly_mock do
+    context 'when Gitaly workhorse_raw_show feature is disabled', :disable_gitaly do
       it 'sets the header correctly' do
         key, command, params = decode_workhorse_header(subject)
 
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index f97136f01917b79fe17f53a9e25ff0b60dfafff8..bd443a5d9e7cbdf6c1872ac292c663f06977e11b 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -14,6 +14,12 @@ describe Gitlab do
       expect(described_class.com?).to eq true
     end
 
+    it 'is true when on other gitlab subdomain' do
+      stub_config_setting(url: 'https://example.gitlab.com')
+
+      expect(described_class.com?).to eq true
+    end
+
     it 'is false when not on GitLab.com' do
       stub_config_setting(url: 'http://example.com')
 
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index 369e7b181b9cc7d237f826e16e429d1667f067d2..8ba15ae0f388bc89abbd490b8147e73ca2dd93ce 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -4,10 +4,11 @@ describe Mattermost::Command do
   let(:params) { { 'token' => 'token', team_id: 'abc' } }
 
   before do
-    Mattermost::Session.base_uri('http://mattermost.example.com')
+    session = Mattermost::Session.new(nil)
+    session.base_uri = 'http://mattermost.example.com'
 
     allow_any_instance_of(Mattermost::Client).to receive(:with_session)
-      .and_yield(Mattermost::Session.new(nil))
+      .and_yield(session)
   end
 
   describe '#create' do
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 3db19d0630579c1af62b7566d17df77fd899ac41..c855643c4d8a8c4773898c9e7116d1363ab04c21 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -15,7 +15,7 @@ describe Mattermost::Session, type: :request do
   it { is_expected.to respond_to(:strategy) }
 
   before do
-    described_class.base_uri(mattermost_url)
+    subject.base_uri = mattermost_url
   end
 
   describe '#with session' do
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index e638ad7a2c9413a54a8728b9f0072dae987fcf45..2cfa68026125db0d92742bf7cfdeb5db23d5600f 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -2,10 +2,11 @@ require 'spec_helper'
 
 describe Mattermost::Team do
   before do
-    Mattermost::Session.base_uri('http://mattermost.example.com')
+    session = Mattermost::Session.new(nil)
+    session.base_uri = 'http://mattermost.example.com'
 
     allow_any_instance_of(Mattermost::Client).to receive(:with_session)
-      .and_yield(Mattermost::Session.new(nil))
+      .and_yield(session)
   end
 
   describe '#all' do
@@ -64,4 +65,108 @@ describe Mattermost::Team do
       end
     end
   end
+
+  describe '#create' do
+    subject { described_class.new(nil).create(name: "devteam", display_name: "Dev Team", type: "O") }
+
+    context 'for a new team' do
+      let(:response) do
+        {
+          "id" => "cuojfcetjty7tb4pxe47pwpndo",
+          "create_at" => 1517688728701,
+          "update_at" => 1517688728701,
+          "delete_at" => 0,
+          "display_name" => "Dev Team",
+          "name" => "devteam",
+          "description" => "",
+          "email" => "admin@example.com",
+          "type" => "O",
+          "company_name" => "",
+          "allowed_domains" => "",
+          "invite_id" => "7mp9d3ayaj833ymmkfnid8js6w",
+          "allow_open_invite" => false
+        }
+      end
+
+      before do
+        stub_request(:post, "http://mattermost.example.com/api/v3/teams/create")
+          .to_return(
+            status: 200,
+            body: response.to_json,
+            headers: { 'Content-Type' => 'application/json' }
+          )
+      end
+
+      it 'returns the new team' do
+        is_expected.to eq(response)
+      end
+    end
+
+    context 'for existing team' do
+      before do
+        stub_request(:post, 'http://mattermost.example.com/api/v3/teams/create')
+          .to_return(
+            status: 400,
+            headers: { 'Content-Type' => 'application/json' },
+            body: {
+                id: "store.sql_team.save.domain_exists.app_error",
+                message: "A team with that name already exists",
+                detailed_error: "",
+                request_id: "1hsb5bxs97r8bdggayy7n9gxaw",
+                status_code: 400
+            }.to_json
+          )
+      end
+
+      it 'raises an error with message' do
+        expect { subject }.to raise_error(Mattermost::Error, 'A team with that name already exists')
+      end
+    end
+  end
+
+  describe '#delete' do
+    subject { described_class.new(nil).destroy(team_id: "cuojfcetjty7tb4pxe47pwpndo") }
+
+    context 'for an existing team' do
+      let(:response) do
+        {
+            "status" => "OK"
+        }
+      end
+
+      before do
+        stub_request(:delete, "http://mattermost.example.com/api/v4/teams/cuojfcetjty7tb4pxe47pwpndo")
+          .to_return(
+            status: 200,
+            body: response.to_json,
+            headers: { 'Content-Type' => 'application/json' }
+          )
+      end
+
+      it 'returns team status' do
+        is_expected.to eq(response)
+      end
+    end
+
+    context 'for an unknown team' do
+      before do
+        stub_request(:delete, "http://mattermost.example.com/api/v4/teams/cuojfcetjty7tb4pxe47pwpndo")
+          .to_return(
+            status: 404,
+            body: {
+              id: "store.sql_team.get.find.app_error",
+              message: "We couldn't find the existing team",
+              detailed_error: "",
+              request_id: "my114ab5nbnui8c9pes4kz8mza",
+              status_code: 404
+            }.to_json,
+            headers: { 'Content-Type' => 'application/json' }
+          )
+      end
+
+      it 'raises an error with message' do
+        expect { subject }.to raise_error(Mattermost::Error, "We couldn't find the existing team")
+      end
+    end
+  end
 end
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
deleted file mode 100644
index 8b0c7254b5e13da80848b86ff963875c5cd0c00f..0000000000000000000000000000000000000000
--- a/spec/lib/repository_cache_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe RepositoryCache do
-  let(:project) { create(:project) }
-  let(:backend) { double('backend').as_null_object }
-  let(:cache) { described_class.new('example', project.id, backend) }
-
-  describe '#cache_key' do
-    it 'includes the namespace' do
-      expect(cache.cache_key(:foo)).to eq "foo:example:#{project.id}"
-    end
-  end
-
-  describe '#expire' do
-    it 'expires the given key from the cache' do
-      cache.expire(:foo)
-      expect(backend).to have_received(:delete).with("foo:example:#{project.id}")
-    end
-  end
-
-  describe '#fetch' do
-    it 'fetches the given key from the cache' do
-      cache.fetch(:bar)
-      expect(backend).to have_received(:fetch).with("bar:example:#{project.id}")
-    end
-
-    it 'accepts a block' do
-      p = -> {}
-
-      cache.fetch(:baz, &p)
-      expect(backend).to have_received(:fetch).with("baz:example:#{project.id}", &p)
-    end
-  end
-end
diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb
index 83556787e8524abeceb61c6b369cd03806d166a1..4a71b1feebd7c3792596d0e11d93226e83a85f6e 100644
--- a/spec/lib/rspec_flaky/config_spec.rb
+++ b/spec/lib/rspec_flaky/config_spec.rb
@@ -16,23 +16,25 @@ describe RspecFlaky::Config, :aggregate_failures do
       end
     end
 
-    context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'false'" do
-      before do
-        stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false')
-      end
-
-      it 'returns false' do
-        expect(described_class).not_to be_generate_report
+    context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set" do
+      using RSpec::Parameterized::TableSyntax
+
+      where(:env_value, :result) do
+        '1'      | true
+        'true'   | true
+        'foo'    | false
+        '0'      | false
+        'false'  | false
       end
-    end
 
-    context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'true'" do
-      before do
-        stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true')
-      end
+      with_them do
+        before do
+          stub_env('FLAKY_RSPEC_GENERATE_REPORT', env_value)
+        end
 
-      it 'returns true' do
-        expect(described_class).to be_generate_report
+        it 'returns false' do
+          expect(described_class.generate_report?).to be(result)
+        end
       end
     end
   end
diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
index 06a8ba0d02e663ea6d0ab7d874f8d859a3596d7d..6731a27ed1733cb54caaa5e077b7de5e38ab1548 100644
--- a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
@@ -24,14 +24,6 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
     }
   end
 
-  describe '.from_json' do
-    it 'accepts a JSON' do
-      collection = described_class.from_json(JSON.pretty_generate(collection_hash))
-
-      expect(collection.to_report).to eq(described_class.new(collection_hash).to_report)
-    end
-  end
-
   describe '#initialize' do
     it 'accepts no argument' do
       expect { described_class.new }.not_to raise_error
@@ -46,11 +38,11 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
     end
   end
 
-  describe '#to_report' do
+  describe '#to_h' do
     it 'calls #to_h on the values' do
       collection = described_class.new(collection_hash)
 
-      expect(collection.to_report).to eq(collection_report)
+      expect(collection.to_h).to eq(collection_report)
     end
   end
 
@@ -61,7 +53,7 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
         a: { example_id: 'spec/foo/bar_spec.rb:2' },
         c: { example_id: 'spec/bar/baz_spec.rb:4' })
 
-      expect((collection2 - collection1).to_report).to eq(
+      expect((collection2 - collection1).to_h).to eq(
         c: {
           example_id: 'spec/bar/baz_spec.rb:4',
           first_flaky_at: nil,
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
index bfb7648b48662c1a61bc8e81aa29f0f7b3c0c893..ef085445081c7a8de30518c4b348951b63080ec1 100644
--- a/spec/lib/rspec_flaky/listener_spec.rb
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -4,7 +4,7 @@ describe RspecFlaky::Listener, :aggregate_failures do
   let(:already_flaky_example_uid) { '6e869794f4cfd2badd93eb68719371d1' }
   let(:suite_flaky_example_report) do
     {
-      already_flaky_example_uid => {
+      "#{already_flaky_example_uid}": {
         example_id: 'spec/foo/bar_spec.rb:2',
         file: 'spec/foo/bar_spec.rb',
         line: 2,
@@ -55,8 +55,7 @@ describe RspecFlaky::Listener, :aggregate_failures do
       it 'returns a valid Listener instance' do
         listener = described_class.new
 
-        expect(listener.to_report(listener.suite_flaky_examples))
-          .to eq(expected_suite_flaky_examples)
+        expect(listener.suite_flaky_examples.to_h).to eq(expected_suite_flaky_examples)
         expect(listener.flaky_examples).to eq({})
       end
     end
@@ -65,25 +64,35 @@ describe RspecFlaky::Listener, :aggregate_failures do
       it_behaves_like 'a valid Listener instance'
     end
 
-    context 'when a report file exists and set by SUITE_FLAKY_RSPEC_REPORT_PATH' do
-      let(:report_file) do
-        Tempfile.new(%w[rspec_flaky_report .json]).tap do |f|
-          f.write(JSON.pretty_generate(suite_flaky_example_report))
-          f.rewind
-        end
-      end
+    context 'when SUITE_FLAKY_RSPEC_REPORT_PATH is set' do
+      let(:report_file_path) { 'foo/report.json' }
 
       before do
-        stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file.path)
+        stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file_path)
       end
 
-      after do
-        report_file.close
-        report_file.unlink
+      context 'and report file exists' do
+        before do
+          expect(File).to receive(:exist?).with(report_file_path).and_return(true)
+        end
+
+        it 'delegates the load to RspecFlaky::Report' do
+          report = RspecFlaky::Report.new(RspecFlaky::FlakyExamplesCollection.new(suite_flaky_example_report))
+
+          expect(RspecFlaky::Report).to receive(:load).with(report_file_path).and_return(report)
+          expect(described_class.new.suite_flaky_examples.to_h).to eq(report.flaky_examples.to_h)
+        end
       end
 
-      it_behaves_like 'a valid Listener instance' do
-        let(:expected_suite_flaky_examples) { suite_flaky_example_report }
+      context 'and report file does not exist' do
+        before do
+          expect(File).to receive(:exist?).with(report_file_path).and_return(false)
+        end
+
+        it 'return an empty hash' do
+          expect(RspecFlaky::Report).not_to receive(:load)
+          expect(described_class.new.suite_flaky_examples.to_h).to eq({})
+        end
       end
     end
   end
@@ -186,74 +195,21 @@ describe RspecFlaky::Listener, :aggregate_failures do
     let(:notification_already_flaky_rspec_example) { double(example: already_flaky_rspec_example) }
 
     context 'when a report file path is set by FLAKY_RSPEC_REPORT_PATH' do
-      let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') }
-      let(:new_report_file_path) { Rails.root.join('tmp', 'rspec_flaky_new_report.json') }
+      it 'delegates the writes to RspecFlaky::Report' do
+        listener.example_passed(notification_new_flaky_rspec_example)
+        listener.example_passed(notification_already_flaky_rspec_example)
 
-      before do
-        stub_env('FLAKY_RSPEC_REPORT_PATH', report_file_path)
-        stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', new_report_file_path)
-        FileUtils.rm(report_file_path) if File.exist?(report_file_path)
-        FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path)
-      end
+        report1 = double
+        report2 = double
 
-      after do
-        FileUtils.rm(report_file_path) if File.exist?(report_file_path)
-        FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path)
-      end
+        expect(RspecFlaky::Report).to receive(:new).with(listener.flaky_examples).and_return(report1)
+        expect(report1).to receive(:write).with(RspecFlaky::Config.flaky_examples_report_path)
 
-      context 'when FLAKY_RSPEC_GENERATE_REPORT == "false"' do
-        before do
-          stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false')
-        end
-
-        it 'does not write any report file' do
-          listener.example_passed(notification_new_flaky_rspec_example)
+        expect(RspecFlaky::Report).to receive(:new).with(listener.flaky_examples - listener.suite_flaky_examples).and_return(report2)
+        expect(report2).to receive(:write).with(RspecFlaky::Config.new_flaky_examples_report_path)
 
-          listener.dump_summary(nil)
-
-          expect(File.exist?(report_file_path)).to be(false)
-          expect(File.exist?(new_report_file_path)).to be(false)
-        end
+        listener.dump_summary(nil)
       end
-
-      context 'when FLAKY_RSPEC_GENERATE_REPORT == "true"' do
-        before do
-          stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true')
-        end
-
-        around do |example|
-          Timecop.freeze { example.run }
-        end
-
-        it 'writes the report files' do
-          listener.example_passed(notification_new_flaky_rspec_example)
-          listener.example_passed(notification_already_flaky_rspec_example)
-
-          listener.dump_summary(nil)
-
-          expect(File.exist?(report_file_path)).to be(true)
-          expect(File.exist?(new_report_file_path)).to be(true)
-
-          expect(File.read(report_file_path))
-            .to eq(JSON.pretty_generate(listener.to_report(listener.flaky_examples)))
-
-          new_example = RspecFlaky::Example.new(notification_new_flaky_rspec_example)
-          new_flaky_example = RspecFlaky::FlakyExample.new(new_example)
-          new_flaky_example.update_flakiness!
-
-          expect(File.read(new_report_file_path))
-            .to eq(JSON.pretty_generate(listener.to_report(new_example.uid => new_flaky_example)))
-        end
-      end
-    end
-  end
-
-  describe '#to_report' do
-    let(:listener) { described_class.new(suite_flaky_example_report.to_json) }
-
-    it 'transforms the internal hash to a JSON-ready hash' do
-      expect(listener.to_report(already_flaky_example_uid => already_flaky_example))
-        .to match(hash_including(suite_flaky_example_report))
     end
   end
 end
diff --git a/spec/lib/rspec_flaky/report_spec.rb b/spec/lib/rspec_flaky/report_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7d57d99f7e54c781ace2e4274e08c7e166fa1f34
--- /dev/null
+++ b/spec/lib/rspec_flaky/report_spec.rb
@@ -0,0 +1,125 @@
+require 'spec_helper'
+
+describe RspecFlaky::Report, :aggregate_failures do
+  let(:a_hundred_days) { 3600 * 24 * 100 }
+  let(:collection_hash) do
+    {
+      a: { example_id: 'spec/foo/bar_spec.rb:2' },
+      b: { example_id: 'spec/foo/baz_spec.rb:3', first_flaky_at: (Time.now - a_hundred_days).to_s, last_flaky_at: (Time.now - a_hundred_days).to_s }
+    }
+  end
+  let(:suite_flaky_example_report) do
+    {
+      '6e869794f4cfd2badd93eb68719371d1': {
+        example_id: 'spec/foo/bar_spec.rb:2',
+        file: 'spec/foo/bar_spec.rb',
+        line: 2,
+        description: 'hello world',
+        first_flaky_at: 1234,
+        last_flaky_at: 4321,
+        last_attempts_count: 3,
+        flaky_reports: 1,
+        last_flaky_job: nil
+      }
+    }
+  end
+  let(:flaky_examples) { RspecFlaky::FlakyExamplesCollection.new(collection_hash) }
+  let(:report) { described_class.new(flaky_examples) }
+
+  describe '.load' do
+    let!(:report_file) do
+      Tempfile.new(%w[rspec_flaky_report .json]).tap do |f|
+        f.write(JSON.pretty_generate(suite_flaky_example_report))
+        f.rewind
+      end
+    end
+
+    after do
+      report_file.close
+      report_file.unlink
+    end
+
+    it 'loads the report file' do
+      expect(described_class.load(report_file.path).flaky_examples.to_h).to eq(suite_flaky_example_report)
+    end
+  end
+
+  describe '.load_json' do
+    let(:report_json) do
+      JSON.pretty_generate(suite_flaky_example_report)
+    end
+
+    it 'loads the report file' do
+      expect(described_class.load_json(report_json).flaky_examples.to_h).to eq(suite_flaky_example_report)
+    end
+  end
+
+  describe '#initialize' do
+    it 'accepts a RspecFlaky::FlakyExamplesCollection' do
+      expect { report }.not_to raise_error
+    end
+
+    it 'does not accept anything else' do
+      expect { described_class.new([1, 2, 3]) }.to raise_error(ArgumentError, "`flaky_examples` must be a RspecFlaky::FlakyExamplesCollection, Array given!")
+    end
+  end
+
+  it 'delegates to #flaky_examples using SimpleDelegator' do
+    expect(report.__getobj__).to eq(flaky_examples)
+  end
+
+  describe '#write' do
+    let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') }
+
+    before do
+      FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+    end
+
+    after do
+      FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+    end
+
+    context 'when RspecFlaky::Config.generate_report? is false' do
+      before do
+        allow(RspecFlaky::Config).to receive(:generate_report?).and_return(false)
+      end
+
+      it 'does not write any report file' do
+        report.write(report_file_path)
+
+        expect(File.exist?(report_file_path)).to be(false)
+      end
+    end
+
+    context 'when RspecFlaky::Config.generate_report? is true' do
+      before do
+        allow(RspecFlaky::Config).to receive(:generate_report?).and_return(true)
+      end
+
+      it 'delegates the writes to RspecFlaky::Report' do
+        report.write(report_file_path)
+
+        expect(File.exist?(report_file_path)).to be(true)
+        expect(File.read(report_file_path))
+          .to eq(JSON.pretty_generate(report.flaky_examples.to_h))
+      end
+    end
+  end
+
+  describe '#prune_outdated' do
+    it 'returns a new collection without the examples older than 90 days by default' do
+      new_report = flaky_examples.to_h.dup.tap { |r| r.delete(:b) }
+      new_flaky_examples = report.prune_outdated
+
+      expect(new_flaky_examples).to be_a(described_class)
+      expect(new_flaky_examples.to_h).to eq(new_report)
+      expect(flaky_examples).to have_key(:b)
+    end
+
+    it 'accepts a given number of days' do
+      new_flaky_examples = report.prune_outdated(days: 200)
+
+      expect(new_flaky_examples.to_h).to eq(report.to_h)
+    end
+  end
+end
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cc99e7e89117036cfad7d6033ebc4f4d058dc1e6
--- /dev/null
+++ b/spec/lib/uploaded_file_spec.rb
@@ -0,0 +1,116 @@
+require 'spec_helper'
+
+describe UploadedFile do
+  describe ".from_params" do
+    let(:temp_dir) { Dir.tmpdir }
+    let(:temp_file) { Tempfile.new("test", temp_dir) }
+    let(:upload_path) { nil }
+
+    subject do
+      described_class.from_params(params, :file, upload_path)
+    end
+
+    before do
+      FileUtils.touch(temp_file)
+    end
+
+    after do
+      FileUtils.rm_f(temp_file)
+      FileUtils.rm_r(upload_path) if upload_path
+    end
+
+    context 'when valid file is specified' do
+      context 'only local path is specified' do
+        let(:params) do
+          { 'file.path' => temp_file.path }
+        end
+
+        it "succeeds" do
+          is_expected.not_to be_nil
+        end
+
+        it "generates filename from path" do
+          expect(subject.original_filename).to eq(::File.basename(temp_file.path))
+        end
+      end
+
+      context 'all parameters are specified' do
+        let(:params) do
+          { 'file.path' => temp_file.path,
+            'file.name' => 'my_file.txt',
+            'file.type' => 'my/type',
+            'file.sha256' => 'sha256',
+            'file.remote_id' => 'remote_id' }
+        end
+
+        it "succeeds" do
+          is_expected.not_to be_nil
+        end
+
+        it "generates filename from path" do
+          expect(subject.original_filename).to eq('my_file.txt')
+          expect(subject.content_type).to eq('my/type')
+          expect(subject.sha256).to eq('sha256')
+          expect(subject.remote_id).to eq('remote_id')
+        end
+      end
+    end
+
+    context 'when no params are specified' do
+      let(:params) do
+        {}
+      end
+
+      it "does not return an object" do
+        is_expected.to be_nil
+      end
+    end
+
+    context 'when only remote id is specified' do
+      let(:params) do
+        { 'file.remote_id' => 'remote_id' }
+      end
+
+      it "raises an error" do
+        expect { subject }.to raise_error(UploadedFile::InvalidPathError, /file is invalid/)
+      end
+    end
+
+    context 'when verifying allowed paths' do
+      let(:params) do
+        { 'file.path' => temp_file.path }
+      end
+
+      context 'when file is stored in system temporary folder' do
+        let(:temp_dir) { Dir.tmpdir }
+
+        it "succeeds" do
+          is_expected.not_to be_nil
+        end
+      end
+
+      context 'when file is stored in user provided upload path' do
+        let(:upload_path) { Dir.mktmpdir }
+        let(:temp_dir) { upload_path }
+
+        it "succeeds" do
+          is_expected.not_to be_nil
+        end
+      end
+
+      context 'when file is stored outside of user provided upload path' do
+        let!(:generated_dir) { Dir.mktmpdir }
+        let!(:temp_dir) { Dir.mktmpdir }
+
+        before do
+          # We overwrite default temporary path
+          allow(Dir).to receive(:tmpdir).and_return(generated_dir)
+        end
+
+        it "raises an error" do
+          expect { subject }.to raise_error(UploadedFile::InvalidPathError, /insecure path used/)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index bcbb9287199b6a7aafcd159225ee485d2a0c7337..43e419cd7de1be49b74976a2ea894967deaa354f 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -389,6 +389,48 @@ describe Notify do
           end
         end
       end
+
+      shared_examples 'a push to an existing merge request' do
+        let(:push_user) { create(:user) }
+
+        subject do
+          described_class.push_to_merge_request_email(recipient.id, merge_request.id, push_user.id, new_commits: merge_request.commits, existing_commits: existing_commits)
+        end
+
+        it_behaves_like 'a multiple recipients email'
+        it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+          let(:model) { merge_request }
+        end
+        it_behaves_like 'it should show Gmail Actions View Merge request link'
+        it_behaves_like 'an unsubscribeable thread'
+
+        it 'is sent as the push user' do
+          sender = subject.header[:from].addrs[0]
+
+          expect(sender.display_name).to eq(push_user.name)
+          expect(sender.address).to eq(gitlab_sender)
+        end
+
+        it 'has the correct subject and body' do
+          aggregate_failures do
+            is_expected.to have_referable_subject(merge_request, reply: true)
+            is_expected.to have_body_text("#{push_user.name} pushed new commits")
+            is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+          end
+        end
+      end
+
+      describe 'that have new commits' do
+        let(:existing_commits) { [] }
+
+        it_behaves_like 'a push to an existing merge request'
+      end
+
+      describe 'that have new commits on top of an existing one' do
+        let(:existing_commits) { [merge_request.commits.first] }
+
+        it_behaves_like 'a push to an existing merge request'
+      end
     end
 
     context 'for issue notes' do
@@ -457,7 +499,7 @@ describe Notify do
 
       it 'has the correct subject and body' do
         is_expected.to have_subject("#{project.name} | Project was moved")
-        is_expected.to have_html_escaped_body_text project.name_with_namespace
+        is_expected.to have_html_escaped_body_text project.full_name
         is_expected.to have_body_text(project.ssh_url_to_repo)
       end
     end
@@ -483,8 +525,8 @@ describe Notify do
         to_emails = subject.header[:to].addrs.map(&:address)
         expect(to_emails).to eq([recipient.notification_email])
 
-        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_subject "Request to join the #{project.full_name} project"
+        is_expected.to have_html_escaped_body_text project.full_name
         is_expected.to have_body_text project_project_members_url(project)
         is_expected.to have_body_text project_member.human_access
       end
@@ -503,8 +545,8 @@ describe Notify do
       it_behaves_like "a user cannot unsubscribe through footer link"
 
       it 'contains all the useful information' do
-        is_expected.to have_subject "Access to the #{project.name_with_namespace} project was denied"
-        is_expected.to have_html_escaped_body_text project.name_with_namespace
+        is_expected.to have_subject "Access to the #{project.full_name} project was denied"
+        is_expected.to have_html_escaped_body_text project.full_name
         is_expected.to have_body_text project.web_url
       end
     end
@@ -520,8 +562,8 @@ describe Notify do
       it_behaves_like "a user cannot unsubscribe through footer link"
 
       it 'contains all the useful information' do
-        is_expected.to have_subject "Access to the #{project.name_with_namespace} project was granted"
-        is_expected.to have_html_escaped_body_text project.name_with_namespace
+        is_expected.to have_subject "Access to the #{project.full_name} project was granted"
+        is_expected.to have_html_escaped_body_text project.full_name
         is_expected.to have_body_text project.web_url
         is_expected.to have_body_text project_member.human_access
       end
@@ -550,8 +592,8 @@ describe Notify do
       it_behaves_like "a user cannot unsubscribe through footer link"
 
       it 'contains all the useful information' do
-        is_expected.to have_subject "Invitation to join the #{project.name_with_namespace} project"
-        is_expected.to have_html_escaped_body_text project.name_with_namespace
+        is_expected.to have_subject "Invitation to join the #{project.full_name} project"
+        is_expected.to have_html_escaped_body_text project.full_name
         is_expected.to have_body_text project.web_url
         is_expected.to have_body_text project_member.human_access
         is_expected.to have_body_text project_member.invite_token
@@ -575,7 +617,7 @@ describe Notify do
 
       it 'contains all the useful information' do
         is_expected.to have_subject 'Invitation accepted'
-        is_expected.to have_html_escaped_body_text project.name_with_namespace
+        is_expected.to have_html_escaped_body_text project.full_name
         is_expected.to have_body_text project.web_url
         is_expected.to have_body_text project_member.invite_email
         is_expected.to have_html_escaped_body_text invited_user.name
@@ -598,7 +640,7 @@ describe Notify do
 
       it 'contains all the useful information' do
         is_expected.to have_subject 'Invitation declined'
-        is_expected.to have_html_escaped_body_text project.name_with_namespace
+        is_expected.to have_html_escaped_body_text project.full_name
         is_expected.to have_body_text project.web_url
         is_expected.to have_body_text project_member.invite_email
       end
diff --git a/spec/mailers/previews/email_rejection_mailer_preview.rb b/spec/mailers/previews/email_rejection_mailer_preview.rb
new file mode 100644
index 0000000000000000000000000000000000000000..639e8471232f0704e5ff6fff25ab2b9ec3b00a3d
--- /dev/null
+++ b/spec/mailers/previews/email_rejection_mailer_preview.rb
@@ -0,0 +1,5 @@
+class EmailRejectionMailerPreview < ActionMailer::Preview
+  def rejection
+    EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message
+  end
+end
diff --git a/spec/mailers/previews/notify_preview.rb b/spec/mailers/previews/notify_preview.rb
index 580f0d56a929aedc48cc11309f1af7794bce605e..e32fd0bd12090b1e2d27697a4bb13b1a52d3e3dd 100644
--- a/spec/mailers/previews/notify_preview.rb
+++ b/spec/mailers/previews/notify_preview.rb
@@ -58,14 +58,87 @@ class NotifyPreview < ActionMailer::Preview
     end
   end
 
+  def closed_issue_email
+    Notify.closed_issue_email(user.id, issue.id, user.id).message
+  end
+
+  def issue_status_changed_email
+    Notify.issue_status_changed_email(user.id, issue.id, 'closed', user.id).message
+  end
+
+  def closed_merge_request_email
+    Notify.closed_merge_request_email(user.id, issue.id, user.id).message
+  end
+
+  def merge_request_status_email
+    Notify.merge_request_status_email(user.id, merge_request.id, 'closed', user.id).message
+  end
+
+  def merged_merge_request_email
+    Notify.merged_merge_request_email(user.id, merge_request.id, user.id).message
+  end
+
+  def member_access_denied_email
+    Notify.member_access_denied_email('project', project.id, user.id).message
+  end
+
+  def member_access_granted_email
+    Notify.member_access_granted_email('project', user.id).message
+  end
+
+  def member_access_requested_email
+    Notify.member_access_requested_email('group', user.id, 'some@example.com').message
+  end
+
+  def member_invite_accepted_email
+    Notify.member_invite_accepted_email('project', user.id).message
+  end
+
+  def member_invite_declined_email
+    Notify.member_invite_declined_email(
+      'project',
+      project.id,
+      'invite@example.com',
+      user.id
+    ).message
+  end
+
+  def member_invited_email
+    Notify.member_invited_email('project', user.id, '1234').message
+  end
+
+  def pages_domain_enabled_email
+    cleanup do
+      pages_domain = PagesDomain.new(domain: 'my.example.com', project: project, verified_at: Time.now, enabled_until: 1.week.from_now)
+
+      Notify.pages_domain_enabled_email(pages_domain, user).message
+    end
+  end
+
+  def pipeline_success_email
+    Notify.pipeline_success_email(pipeline, pipeline.user.try(:email))
+  end
+
+  def pipeline_failed_email
+    Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email))
+  end
+
   private
 
   def project
     @project ||= Project.find_by_full_path('gitlab-org/gitlab-test')
   end
 
+  def issue
+    @merge_request ||= project.issues.first
+  end
+
   def merge_request
-    @merge_request ||= project.merge_requests.find_by(source_branch: 'master', target_branch: 'feature')
+    @merge_request ||= project.merge_requests.first
+  end
+
+  def pipeline
+    @pipeline = Ci::Pipeline.last
   end
 
   def user
@@ -94,14 +167,4 @@ class NotifyPreview < ActionMailer::Preview
 
     email
   end
-
-  def pipeline_success_email
-    pipeline = Ci::Pipeline.last
-    Notify.pipeline_success_email(pipeline, pipeline.user.try(:email))
-  end
-
-  def pipeline_failed_email
-    pipeline = Ci::Pipeline.last
-    Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email))
-  end
 end
diff --git a/spec/mailers/previews/repository_check_mailer_preview.rb b/spec/mailers/previews/repository_check_mailer_preview.rb
new file mode 100644
index 0000000000000000000000000000000000000000..19d4eab18054dc3cddf62a2ac1d5fde7abbdd9b1
--- /dev/null
+++ b/spec/mailers/previews/repository_check_mailer_preview.rb
@@ -0,0 +1,5 @@
+class RepositoryCheckMailerPreview < ActionMailer::Preview
+  def notify
+    RepositoryCheckMailer.notify(3).message
+  end
+end
diff --git a/spec/migrations/active_record/schedule_set_confidential_note_events_on_services_spec.rb b/spec/migrations/active_record/schedule_set_confidential_note_events_on_services_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4395e2f82641a377cb1f335d87f7684a42e27249
--- /dev/null
+++ b/spec/migrations/active_record/schedule_set_confidential_note_events_on_services_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180122154930_schedule_set_confidential_note_events_on_services.rb')
+
+describe ScheduleSetConfidentialNoteEventsOnServices, :migration, :sidekiq do
+  let(:services_table) { table(:services) }
+  let(:migration_class) { Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices }
+  let(:migration_name)  { migration_class.to_s.demodulize }
+
+  let!(:service_1)        { services_table.create!(confidential_note_events: nil, note_events: true) }
+  let!(:service_2)        { services_table.create!(confidential_note_events: nil, note_events: true) }
+  let!(:service_migrated) { services_table.create!(confidential_note_events: true, note_events: true) }
+  let!(:service_skip)     { services_table.create!(confidential_note_events: nil, note_events: false) }
+  let!(:service_new)      { services_table.create!(confidential_note_events: false, note_events: true) }
+  let!(:service_4)        { services_table.create!(confidential_note_events: nil, note_events: true) }
+
+  before do
+    stub_const("#{described_class}::BATCH_SIZE", 1)
+  end
+
+  it 'schedules background migrations at correct time' do
+    Sidekiq::Testing.fake! do
+      Timecop.freeze do
+        migrate!
+
+        expect(migration_name).to be_scheduled_delayed_migration(20.minutes, service_1.id, service_1.id)
+        expect(migration_name).to be_scheduled_delayed_migration(40.minutes, service_2.id, service_2.id)
+        expect(migration_name).to be_scheduled_delayed_migration(60.minutes, service_4.id, service_4.id)
+        expect(BackgroundMigrationWorker.jobs.size).to eq 3
+      end
+    end
+  end
+
+  it 'correctly processes services' do
+    Sidekiq::Testing.inline! do
+      expect(services_table.where(confidential_note_events: nil).count).to eq 4
+      expect(services_table.where(confidential_note_events: true).count).to eq 1
+
+      migrate!
+
+      expect(services_table.where(confidential_note_events: nil).count).to eq 1
+      expect(services_table.where(confidential_note_events: true).count).to eq 4
+    end
+  end
+end
diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb
index 4a22bd6f342a8200e136a9ad2657e5e95c9fc210..bf2fa5c0f562bd71ae663abefcd0e969dd164483 100644
--- a/spec/migrations/add_foreign_keys_to_todos_spec.rb
+++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb
@@ -4,8 +4,8 @@ require Rails.root.join('db', 'migrate', '20180201110056_add_foreign_keys_to_tod
 describe AddForeignKeysToTodos, :migration do
   let(:todos) { table(:todos) }
 
-  let(:project) { create(:project) }
-  let(:user) { create(:user) }
+  let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   context 'add foreign key on user_id' do
     let!(:todo_with_user) { create_todo(user_id: user.id) }
@@ -34,7 +34,7 @@ describe AddForeignKeysToTodos, :migration do
   end
 
   context 'add foreign key on note_id' do
-    let(:note) { create(:note) }
+    let(:note) { create(:note) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
     let!(:todo_with_note) { create_todo(note_id: note.id) }
     let!(:todo_with_invalid_note) { create_todo(note_id: 4711) }
     let!(:todo_without_note) { create_todo(note_id: nil) }
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 63defcb39bfdc6f5a658da1b2a3cca4fea0eadea..d8dd7a2fb83cd671a2cdd9ed218f4ebcb0375884 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
@@ -6,18 +6,18 @@ describe AddHeadPipelineForEachMergeRequest, :delete do
 
   let(:migration) { described_class.new }
 
-  let!(:project) { create(:project) }
+  let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
   let!(:other_project) { fork_project(project) }
 
-  let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") }
-  let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
-  let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
-  let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") }
+  let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
-  let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") }
-  let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") }
-  let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") }
-  let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") }
+  let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   context "#up" do
     context "when source_project and source_branch of pipeline are the same of merge request" do
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
index f3a460253767bbe3a00e02bd67ff9b003d64e9d7..19f06810e5400f45fb8d43c91980ecdc50641c89 100644
--- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
+++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
@@ -6,7 +6,7 @@ require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev
 describe CalculateConvDevIndexPercentages, :delete do
   let(:migration) { described_class.new }
   let!(:conv_dev_index) do
-    create(:conversational_development_index_metric,
+    create(:conversational_development_index_metric, # rubocop:disable RSpec/FactoriesInMigrationSpecs
       leader_notes: 0,
       instance_milestones: 0,
       percentage_issues: 0,
diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
index 033d0e7584ddf950c59048eac10adcb78c8456e0..b5980cb9ddb1e1c2aa54c3d2912c05bab130d022 100644
--- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
@@ -10,9 +10,9 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
 
   describe '#up' do
     it 'only cleans up pending delete projects' do
-      create(:project)
-      create(:project, pending_delete: true)
-      project = build(:project, pending_delete: true, namespace_id: nil)
+      create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+      create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+      project = build(:project, pending_delete: true, namespace_id: nil) # rubocop:disable RSpec/FactoriesInMigrationSpecs
       project.save(validate: false)
 
       expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -21,8 +21,8 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
     end
 
     it 'does nothing when no pending delete projects without namespace found' do
-      create(:project)
-      create(:project, pending_delete: true)
+      create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+      create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
       expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
 
diff --git a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
index 7879105a3344dd0d2120282084a5e2be72e563a0..8f40ac3e38be08837a99a1b66769f41f6a49aaf8 100644
--- a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
@@ -9,11 +9,11 @@ describe CleanupNonexistingNamespacePendingDeleteProjects do
   end
 
   describe '#up' do
-    set(:some_project) { create(:project) }
+    set(:some_project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
     it 'only cleans up when namespace does not exist' do
-      create(:project, pending_delete: true)
-      project = build(:project, pending_delete: true, namespace: nil, namespace_id: Namespace.maximum(:id).to_i.succ)
+      create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+      project = build(:project, pending_delete: true, namespace: nil, namespace_id: Namespace.maximum(:id).to_i.succ) # rubocop:disable RSpec/FactoriesInMigrationSpecs
       project.save(validate: false)
 
       expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -22,7 +22,7 @@ describe CleanupNonexistingNamespacePendingDeleteProjects do
     end
 
     it 'does nothing when no pending delete projects without namespace found' do
-      create(:project, pending_delete: true, namespace: create(:namespace))
+      create(:project, pending_delete: true, namespace: create(:namespace)) # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
       expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
 
diff --git a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
index d2eef81f396b5c7e947d51edf5ccb0da9a6d241e..dd2b08099f283ca4820581424860e74228c83ea4 100644
--- a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
+++ b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
@@ -5,9 +5,9 @@ require Rails.root.join('db', 'migrate', '20171106151218_issues_moved_to_id_fore
 # only_mirror_protected_branches column in the projects table to create a
 # project via FactoryBot.
 describe IssuesMovedToIdForeignKey, :migration, schema: 20171114150259 do
-  let!(:issue_first) { create(:issue, moved_to_id: issue_second.id) }
-  let!(:issue_second) { create(:issue, moved_to_id: issue_third.id) }
-  let!(:issue_third) { create(:issue) }
+  let!(:issue_first) { create(:issue, moved_to_id: issue_second.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:issue_second) { create(:issue, moved_to_id: issue_third.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:issue_third) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   subject { described_class.new }
 
diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c18ae3b76d305f1fb42de866e3a20ff72e739c71
--- /dev/null
+++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb')
+
+describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
+  include Gitlab::Database::MigrationHelpers
+
+  context 'when there are jobs in the queues' do
+    it 'correctly migrates queue when migrating up' do
+      Sidekiq::Testing.disable! do
+        stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+
+        described_class.new.up
+
+        expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 0
+        expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 2
+      end
+    end
+
+    it 'does not affect other queues under the same namespace' do
+      Sidekiq::Testing.disable! do
+        stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+
+        described_class.new.up
+
+        expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1
+        expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1
+        expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1
+        expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1
+        expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1
+      end
+    end
+
+    it 'correctly migrates queue when migrating down' do
+      Sidekiq::Testing.disable! do
+        stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+
+        described_class.new.down
+
+        expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 1
+        expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 0
+      end
+    end
+  end
+
+  context 'when there are no jobs in the queues' do
+    it 'does not raise error when migrating up' do
+      expect { described_class.new.up }.not_to raise_error
+    end
+
+    it 'does not raise error when migrating down' do
+      expect { described_class.new.down }.not_to raise_error
+    end
+  end
+
+  def stubbed_worker(queue:)
+    Class.new do
+      include Sidekiq::Worker
+      sidekiq_options queue: queue
+    end
+  end
+end
diff --git a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
index c81ec887dedee7117f7d3bd5a7c2898b18c2d8d0..df009cec25ca377f3c8a8e87031f700ede31558c 100644
--- a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
+++ b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
@@ -4,8 +4,24 @@ require Rails.root.join('db', 'post_migrate', '20171013104327_migrate_gcp_cluste
 describe MigrateGcpClustersToNewClustersArchitectures, :migration do
   let(:projects) { table(:projects) }
   let(:project) { projects.create }
-  let(:user) { create(:user) }
-  let(:service) { create(:kubernetes_service, project_id: project.id) }
+  let(:users) { table(:users) }
+  let(:user) { users.create! }
+  let(:service) { GcpMigrationSpec::KubernetesService.create!(project_id: project.id) }
+
+  module GcpMigrationSpec
+    class KubernetesService < ActiveRecord::Base
+      self.table_name = 'services'
+
+      serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
+
+      default_value_for :active, true
+      default_value_for :type, 'KubernetesService'
+      default_value_for :properties, {
+        api_url: 'https://kubernetes.example.com',
+        token: 'a' * 40
+      }
+    end
+  end
 
   context 'when cluster is being created' do
     let(:project_id) { project.id }
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index 92eb1d9ce865e9cdd943126129244f20eb1b81e6..4187ab149a5a97eb967ffea6cae687db5100d039 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -16,18 +16,18 @@ describe MigrateOldArtifacts do
   end
 
   context 'with migratable data' do
-    set(:project1) { create(:project, ci_id: 2) }
-    set(:project2) { create(:project, ci_id: 3) }
-    set(:project3) { create(:project) }
+    set(:project1) { create(:project, ci_id: 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    set(:project2) { create(:project, ci_id: 3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    set(:project3) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
-    set(:pipeline1) { create(:ci_empty_pipeline, project: project1) }
-    set(:pipeline2) { create(:ci_empty_pipeline, project: project2) }
-    set(:pipeline3) { create(:ci_empty_pipeline, project: project3) }
+    set(:pipeline1) { create(:ci_empty_pipeline, project: project1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    set(:pipeline2) { create(:ci_empty_pipeline, project: project2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    set(:pipeline3) { create(:ci_empty_pipeline, project: project3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
-    let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) }
-    let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) }
-    let!(:build2) { create(:ci_build, pipeline: pipeline2) }
-    let!(:build3) { create(:ci_build, pipeline: pipeline3) }
+    let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    let!(:build2) { create(:ci_build, pipeline: pipeline2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    let!(:build3) { create(:ci_build, pipeline: pipeline3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
     before do
       setup_builds(build2, build3)
@@ -66,7 +66,7 @@ describe MigrateOldArtifacts do
         end
 
         it 'all files do have artifacts' do
-          Ci::Build.with_artifacts do |build|
+          Ci::Build.with_artifacts_archive do |build|
             expect(build).to have_artifacts
           end
         end
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index 657113812bd809e69499dc198b1d4242a721c2b1..4ee1d255fbd08f380c514466c5532252ba3fa346 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
 require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb')
 
 describe MigrateProcessCommitWorkerJobs do
-  let(:project) { create(:project, :legacy_storage, :repository) }
-  let(:user) { create(:user) }
+  let(:project) { create(:project, :legacy_storage, :repository) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
   let(:commit) { project.commit.raw.rugged_commit }
 
   describe 'Project' do
diff --git a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5e3b20ab4a8eb750950d55574e0d9c269874d351
--- /dev/null
+++ b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180307012445_migrate_update_head_pipeline_for_merge_request_sidekiq_queue.rb')
+
+describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis do
+  include Gitlab::Database::MigrationHelpers
+
+  context 'when there are jobs in the queues' do
+    it 'correctly migrates queue when migrating up' do
+      Sidekiq::Testing.disable! do
+        stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+
+        described_class.new.up
+
+        expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 0
+        expect(sidekiq_queue_length('pipeline_processing:update_head_pipeline_for_merge_request')).to eq 2
+      end
+    end
+
+    it 'does not affect other queues under the same namespace' do
+      Sidekiq::Testing.disable! do
+        stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
+        stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
+
+        described_class.new.up
+
+        expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1
+        expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1
+        expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1
+        expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1
+      end
+    end
+
+    it 'correctly migrates queue when migrating down' do
+      Sidekiq::Testing.disable! do
+        stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+
+        described_class.new.down
+
+        expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1
+        expect(sidekiq_queue_length('pipeline_processing:update_head_pipeline_for_merge_request')).to eq 0
+      end
+    end
+  end
+
+  context 'when there are no jobs in the queues' do
+    it 'does not raise error when migrating up' do
+      expect { described_class.new.up }.not_to raise_error
+    end
+
+    it 'does not raise error when migrating down' do
+      expect { described_class.new.down }.not_to raise_error
+    end
+  end
+
+  def stubbed_worker(queue:)
+    Class.new do
+      include Sidekiq::Worker
+      sidekiq_options queue: queue
+    end
+  end
+end
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 a17c9c72bdec495cf83f868bdc52bce88b4cf08a..9917370819068c5b24a34647a54996eba882f15e 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
@@ -5,8 +5,8 @@ require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activ
 
 describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :delete do
   let(:migration) { described_class.new }
-  let!(:user_active_1) { create(:user) }
-  let!(:user_active_2) { create(:user) }
+  let!(:user_active_1) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:user_active_2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   def record_activity(user, time)
     Gitlab::Redis::SharedState.with do |redis|
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index 31d16e17d7b8d158c9ac2d20fa382c6d343f68d1..80468b9d01eab37ca551b40f01b7ae108d8e442b 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -5,7 +5,7 @@ require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_proje
 
 describe MigrateUserProjectView, :delete do
   let(:migration) { described_class.new }
-  let!(:user) { create(:user, project_view: 'readme') }
+  let!(:user) { create(:user, project_view: 'readme') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   describe '#up' do
     it 'updates project view setting with new value' do
diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb
index 1a319eccc0d74b4469f47059665e857c062601db..1f39ad98fb846e4da69d402cd021bba1fbbaaf20 100644
--- a/spec/migrations/move_personal_snippets_files_spec.rb
+++ b/spec/migrations/move_personal_snippets_files_spec.rb
@@ -16,14 +16,14 @@ describe MovePersonalSnippetsFiles do
 
   describe "#up" do
     let(:snippet) do
-      snippet = create(:personal_snippet)
+      snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
       create_upload('picture.jpg', snippet)
       snippet.update(description: markdown_linking_file('picture.jpg', snippet))
       snippet
     end
 
     let(:snippet_with_missing_file) do
-      snippet = create(:snippet)
+      snippet = create(:snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
       create_upload('picture.jpg', snippet, create_file: false)
       snippet.update(description: markdown_linking_file('picture.jpg', snippet))
       snippet
@@ -62,7 +62,7 @@ describe MovePersonalSnippetsFiles do
         secret = "secret#{snippet.id}"
         file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
         markdown = markdown_linking_file('picture.jpg', snippet)
-        note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}")
+        note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
         migration.up
 
@@ -73,14 +73,14 @@ describe MovePersonalSnippetsFiles do
 
   describe "#down" do
     let(:snippet) do
-      snippet = create(:personal_snippet)
+      snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
       create_upload('picture.jpg', snippet, in_new_path: true)
       snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
       snippet
     end
 
     let(:snippet_with_missing_file) do
-      snippet = create(:personal_snippet)
+      snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
       create_upload('picture.jpg', snippet, create_file: false, in_new_path: true)
       snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
       snippet
@@ -119,7 +119,7 @@ describe MovePersonalSnippetsFiles do
         markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true)
         secret = "secret#{snippet.id}"
         file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
-        note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}")
+        note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
         migration.down
 
@@ -135,7 +135,7 @@ describe MovePersonalSnippetsFiles do
 
       secret = '123456789'
       filename = 'hello.jpg'
-      snippet = create(:personal_snippet)
+      snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
       path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
       path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
@@ -161,7 +161,7 @@ describe MovePersonalSnippetsFiles do
       FileUtils.touch(absolute_path)
     end
 
-    create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader)
+    create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader) # rubocop:disable RSpec/FactoriesInMigrationSpecs
   end
 
   def markdown_linking_file(filename, snippet, in_new_path: false)
diff --git a/spec/migrations/remove_dot_git_from_usernames_spec.rb b/spec/migrations/remove_dot_git_from_usernames_spec.rb
index 129374cb38c50fa0cdd4cdc48d7f83073bcfcf3f..f11880a83e9c3521a242f7b36dede983e47e7781 100644
--- a/spec/migrations/remove_dot_git_from_usernames_spec.rb
+++ b/spec/migrations/remove_dot_git_from_usernames_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
 require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb')
 
 describe RemoveDotGitFromUsernames do
-  let(:user) { create(:user) }
+  let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
   let(:migration) { described_class.new }
 
   describe '#up' do
@@ -23,13 +23,15 @@ describe RemoveDotGitFromUsernames do
 
   context 'when new path exists already' do
     describe '#up' do
-      let(:user2) { create(:user) }
+      let(:user2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
       before do
         update_namespace(user, 'test.git')
         update_namespace(user2, 'test_git')
 
-        storages = { 'default' => 'tmp/tests/custom_repositories' }
+        default_hash = Gitlab.config.repositories.storages.default.to_h
+        default_hash['path'] = 'tmp/tests/custom_repositories'
+        storages = { 'default' => Gitlab::GitalyClient::StorageSettings.new(default_hash) }
 
         allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
         allow(migration).to receive(:route_exists?).with('test_git').and_return(true)
diff --git a/spec/migrations/remove_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb
index e51872239ade529b1df52e715d227ae0cd1262c2..2509ac6afd6094b9f3a63a6642b69f5083a634f1 100644
--- a/spec/migrations/remove_duplicate_mr_events_spec.rb
+++ b/spec/migrations/remove_duplicate_mr_events_spec.rb
@@ -5,17 +5,17 @@ describe RemoveDuplicateMrEvents, :delete do
   let(:migration) { described_class.new }
 
   describe '#up' do
-    let(:user) { create(:user) }
-    let(:merge_requests) { create_list(:merge_request, 2) }
-    let(:issue) { create(:issue) }
+    let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    let(:merge_requests) { create_list(:merge_request, 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    let(:issue) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
     let!(:events) do
       [
-        create(:event, :created, author: user, target: merge_requests.first),
-        create(:event, :created, author: user, target: merge_requests.first),
-        create(:event, :updated, author: user, target: merge_requests.first),
-        create(:event, :created, author: user, target: merge_requests.second),
-        create(:event, :created, author: user, target: issue),
-        create(:event, :created, author: user, target: issue)
+        create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+        create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+        create(:event, :updated, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+        create(:event, :created, author: user, target: merge_requests.second), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+        create(:event, :created, author: user, target: issue), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+        create(:event, :created, author: user, target: issue) # rubocop:disable RSpec/FactoriesInMigrationSpecs
       ]
     end
 
diff --git a/spec/migrations/remove_empty_extern_uid_auth0_identities_spec.rb b/spec/migrations/remove_empty_extern_uid_auth0_identities_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..441c4295a4055fc48a31bc7fb2a09df6787e6f11
--- /dev/null
+++ b/spec/migrations/remove_empty_extern_uid_auth0_identities_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180220150310_remove_empty_extern_uid_auth0_identities.rb')
+
+describe RemoveEmptyExternUidAuth0Identities, :migration do
+  let(:identities) { table(:identities) }
+
+  before do
+    identities.create(provider: 'auth0', extern_uid: '')
+    identities.create(provider: 'auth0', extern_uid: 'valid')
+    identities.create(provider: 'github', extern_uid: '')
+
+    migrate!
+  end
+
+  it 'leaves the correct auth0 identity' do
+    expect(identities.where(provider: 'auth0').pluck(:extern_uid)).to eq(['valid'])
+  end
+
+  it 'leaves the correct github identity' do
+    expect(identities.where(provider: 'github').count).to eq(1)
+  end
+end
diff --git a/spec/migrations/remove_empty_fork_networks_spec.rb b/spec/migrations/remove_empty_fork_networks_spec.rb
index 7f7ce91378b5e65574cc239c1f6c348afe0d28d3..f6d030ab25c1010037e6abafabb7f07029d41558 100644
--- a/spec/migrations/remove_empty_fork_networks_spec.rb
+++ b/spec/migrations/remove_empty_fork_networks_spec.rb
@@ -19,6 +19,10 @@ describe RemoveEmptyForkNetworks, :migration do
     deleted_project.destroy!
   end
 
+  after do
+    Upload.reset_column_information
+  end
+
   it 'deletes only the fork network without members' do
     expect(fork_networks.count).to eq(2)
 
diff --git a/spec/migrations/remove_project_labels_group_id_spec.rb b/spec/migrations/remove_project_labels_group_id_spec.rb
index d80d61af20b9f81769682531a9922ebaee3ef6f3..01b09e71d83f3e6fd7d6695864189cb58cdf126d 100644
--- a/spec/migrations/remove_project_labels_group_id_spec.rb
+++ b/spec/migrations/remove_project_labels_group_id_spec.rb
@@ -5,9 +5,9 @@ require Rails.root.join('db', 'post_migrate', '20180202111106_remove_project_lab
 
 describe RemoveProjectLabelsGroupId, :delete do
   let(:migration) { described_class.new }
-  let(:group) { create(:group) }
-  let!(:project_label) { create(:label, group_id: group.id) }
-  let!(:group_label) { create(:group_label) }
+  let(:group) { create(:group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:project_label) { create(:label, group_id: group.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:group_label) { create(:group_label) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   describe '#up' do
     it 'updates the project labels group ID' do
diff --git a/spec/migrations/remove_soft_removed_objects_spec.rb b/spec/migrations/remove_soft_removed_objects_spec.rb
index ec089f9106d931a5d5d0000fc84a6888f0350c68..fb70c284f5e9f6511f54e6af2ba595b652f94e5a 100644
--- a/spec/migrations/remove_soft_removed_objects_spec.rb
+++ b/spec/migrations/remove_soft_removed_objects_spec.rb
@@ -8,7 +8,7 @@ describe RemoveSoftRemovedObjects, :migration do
         create_with_deleted_at(:issue)
       end
 
-      regular_issue = create(:issue)
+      regular_issue = create(:issue) # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
       run_migration
 
@@ -28,7 +28,7 @@ describe RemoveSoftRemovedObjects, :migration do
 
     it 'removes routes of soft removed personal namespaces' do
       namespace = create_with_deleted_at(:namespace)
-      group = create(:group)
+      group = create(:group) # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
       expect(Route.where(source: namespace).exists?).to eq(true)
       expect(Route.where(source: group).exists?).to eq(true)
@@ -41,7 +41,7 @@ describe RemoveSoftRemovedObjects, :migration do
 
     it 'schedules the removal of soft removed groups' do
       group = create_with_deleted_at(:group)
-      admin = create(:user, admin: true)
+      admin = create(:user, admin: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
       expect_any_instance_of(GroupDestroyWorker)
         .to receive(:perform)
@@ -67,7 +67,7 @@ describe RemoveSoftRemovedObjects, :migration do
   end
 
   def create_with_deleted_at(*args)
-    row = create(*args)
+    row = create(*args) # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
     # We set "deleted_at" this way so we don't run into any column cache issues.
     row.class.where(id: row.id).update_all(deleted_at: 1.year.ago)
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index 75310075cc5714e4bfd5d693314737050254a360..034e8a6a4e55ce4b4c2e393090282245f1eba600 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -8,7 +8,7 @@ require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserv
 # around this we use the DELETE cleaning strategy.
 describe RenameMoreReservedProjectNames, :delete do
   let(:migration) { described_class.new }
-  let!(:project) { create(:project) }
+  let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   before do
     project.path = 'artifacts'
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 34336d705b16605f55b181c159ad6f3764aaa623..592ac2b5fb9c1b7d0e74d3f9be5c2ce0aeca614f 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -12,7 +12,7 @@ require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_pr
 # Ideally, the test should not use factories and rely on the `table` helper instead.
 describe RenameReservedProjectNames, :migration, schema: :latest do
   let(:migration) { described_class.new }
-  let!(:project) { create(:project) }
+  let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   before do
     project.path = 'projects'
diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
index cbc0ebeb44dee52adc03427160343792ab8c39d0..b8a4dc2b2c0ca5a2af6bdc97a8326dcbdfbfa0fb 100644
--- a/spec/migrations/rename_users_with_renamed_namespace_spec.rb
+++ b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
@@ -3,13 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_
 
 describe RenameUsersWithRenamedNamespace, :delete do
   it 'renames a user that had their namespace renamed to the namespace path' do
-    other_user = create(:user, username: 'kodingu')
-    other_user1 = create(:user, username: 'api0')
+    other_user = create(:user, username: 'kodingu') # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    other_user1 = create(:user, username: 'api0') # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
-    user = create(:user, username: "Users0")
-    user.update_attribute(:username, 'Users')
-    user1 = create(:user, username: "import0")
-    user1.update_attribute(:username, 'import')
+    user = create(:user, username: "Users0") # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    user.update_column(:username, 'Users')
+    user1 = create(:user, username: "import0") # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    user1.update_column(:username, 'import')
 
     described_class.new.up
 
diff --git a/spec/migrations/reschedule_builds_stages_migration_spec.rb b/spec/migrations/reschedule_builds_stages_migration_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3bfd9dd9f6b08582091f282a6b03c6d704377e36
--- /dev/null
+++ b/spec/migrations/reschedule_builds_stages_migration_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180405101928_reschedule_builds_stages_migration')
+
+describe RescheduleBuildsStagesMigration, :sidekiq, :migration do
+  let(:namespaces) { table(:namespaces) }
+  let(:projects) { table(:projects) }
+  let(:pipelines) { table(:ci_pipelines) }
+  let(:stages) { table(:ci_stages) }
+  let(:jobs) { table(:ci_builds) }
+
+  before do
+    stub_const("#{described_class}::BATCH_SIZE", 1)
+
+    namespaces.create(id: 12, name: 'gitlab-org', path: 'gitlab-org')
+    projects.create!(id: 123, namespace_id: 12, name: 'gitlab', path: 'gitlab')
+    pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
+    stages.create!(id: 1, project_id: 123, pipeline_id: 1, name: 'test')
+
+    jobs.create!(id: 11, commit_id: 1, project_id: 123, stage_id: nil)
+    jobs.create!(id: 206, commit_id: 1, project_id: 123, stage_id: nil)
+    jobs.create!(id: 3413, commit_id: 1, project_id: 123, stage_id: nil)
+    jobs.create!(id: 4109, commit_id: 1, project_id: 123, stage_id: 1)
+  end
+
+  it 'schedules delayed background migrations in batches in bulk' do
+    Sidekiq::Testing.fake! do
+      Timecop.freeze do
+        migrate!
+
+        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 11, 11)
+        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 206, 206)
+        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, 3413, 3413)
+        expect(BackgroundMigrationWorker.jobs.size).to eq 3
+      end
+    end
+  end
+end
diff --git a/spec/migrations/reschedule_commits_count_for_merge_request_diff_spec.rb b/spec/migrations/reschedule_commits_count_for_merge_request_diff_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..26489ef58bdcd9513bc986ea6aca10034c81a659
--- /dev/null
+++ b/spec/migrations/reschedule_commits_count_for_merge_request_diff_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20180309121820_reschedule_commits_count_for_merge_request_diff')
+
+describe RescheduleCommitsCountForMergeRequestDiff, :migration, :sidekiq do
+  let(:merge_request_diffs) { table(:merge_request_diffs) }
+  let(:merge_requests) { table(:merge_requests) }
+  let(:projects) { table(:projects) }
+  let(:namespaces) { table(:namespaces) }
+
+  before do
+    stub_const("#{described_class.name}::BATCH_SIZE", 1)
+
+    namespaces.create!(id: 1, name: 'gitlab', path: 'gitlab')
+
+    projects.create!(id: 1, namespace_id: 1)
+
+    merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master')
+
+    merge_request_diffs.create!(id: 1, merge_request_id: 1)
+    merge_request_diffs.create!(id: 2, merge_request_id: 1)
+    merge_request_diffs.create!(id: 3, merge_request_id: 1, commits_count: 0)
+    merge_request_diffs.create!(id: 4, merge_request_id: 1)
+  end
+
+  it 'correctly schedules background migrations' do
+    Sidekiq::Testing.fake! do
+      Timecop.freeze do
+        migrate!
+
+        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1)
+        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 2, 2)
+        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, 4, 4)
+        expect(BackgroundMigrationWorker.jobs.size).to eq 3
+      end
+    end
+  end
+end
diff --git a/spec/migrations/schedule_build_stage_migration_spec.rb b/spec/migrations/schedule_build_stage_migration_spec.rb
deleted file mode 100644
index e2ca35447fb3220d52576e05077ebbae8b725c73..0000000000000000000000000000000000000000
--- a/spec/migrations/schedule_build_stage_migration_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20180212101928_schedule_build_stage_migration')
-
-describe ScheduleBuildStageMigration, :sidekiq, :migration do
-  let(:projects) { table(:projects) }
-  let(:pipelines) { table(:ci_pipelines) }
-  let(:stages) { table(:ci_stages) }
-  let(:jobs) { table(:ci_builds) }
-
-  before do
-    stub_const("#{described_class}::BATCH_SIZE", 1)
-
-    projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce')
-    pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
-    stages.create!(id: 1, project_id: 123, pipeline_id: 1, name: 'test')
-
-    jobs.create!(id: 11, commit_id: 1, project_id: 123, stage_id: nil)
-    jobs.create!(id: 206, commit_id: 1, project_id: 123, stage_id: nil)
-    jobs.create!(id: 3413, commit_id: 1, project_id: 123, stage_id: nil)
-    jobs.create!(id: 4109, commit_id: 1, project_id: 123, stage_id: 1)
-  end
-
-  it 'schedules delayed background migrations in batches in bulk' do
-    Sidekiq::Testing.fake! do
-      Timecop.freeze do
-        migrate!
-
-        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 11, 11)
-        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 206, 206)
-        expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, 3413, 3413)
-        expect(BackgroundMigrationWorker.jobs.size).to eq 3
-      end
-    end
-  end
-end
diff --git a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb
index 65ec07da31c3cf9645d026afe2f386689d44882c..ed306fb3d62c0ec05c500f8613fe9e1742750879 100644
--- a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb
+++ b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb
@@ -3,8 +3,8 @@ require Rails.root.join('db', 'post_migrate', '20171005130944_schedule_create_gp
 
 describe ScheduleCreateGpgKeySubkeysFromGpgKeys, :migration, :sidekiq do
   before do
-    create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key)
-    create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key)
+    create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+    create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs
     # Delete all subkeys so they can be recreated
     GpgKeySubkey.destroy_all
   end
diff --git a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
index 7494624066a157717b1f66ea6ff6d147254525d5..578440cba200c138b8ec5cef31e4eab05431a2b2 100644
--- a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
@@ -8,7 +8,7 @@ describe SchedulePopulateMergeRequestMetricsWithEventsData, :migration, :sidekiq
       .to receive(:commits_count=).and_return(nil)
   end
 
-  let!(:mrs) { create_list(:merge_request, 3) }
+  let!(:mrs) { create_list(:merge_request, 3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   it 'correctly schedules background migrations' do
     stub_const("#{described_class.name}::BATCH_SIZE", 2)
diff --git a/spec/migrations/schedule_set_confidential_note_events_on_webhooks_spec.rb b/spec/migrations/schedule_set_confidential_note_events_on_webhooks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..027f4a91c90c107d2721692b35f9b9b032d37705
--- /dev/null
+++ b/spec/migrations/schedule_set_confidential_note_events_on_webhooks_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180104131052_schedule_set_confidential_note_events_on_webhooks.rb')
+
+describe ScheduleSetConfidentialNoteEventsOnWebhooks, :migration, :sidekiq do
+  let(:web_hooks_table) { table(:web_hooks) }
+  let(:migration_class) { Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks }
+  let(:migration_name)  { migration_class.to_s.demodulize }
+
+  let!(:web_hook_1)        { web_hooks_table.create!(confidential_note_events: nil, note_events: true) }
+  let!(:web_hook_2)        { web_hooks_table.create!(confidential_note_events: nil, note_events: true) }
+  let!(:web_hook_migrated) { web_hooks_table.create!(confidential_note_events: true, note_events: true) }
+  let!(:web_hook_skip)     { web_hooks_table.create!(confidential_note_events: nil, note_events: false) }
+  let!(:web_hook_new)      { web_hooks_table.create!(confidential_note_events: false, note_events: true) }
+  let!(:web_hook_4)        { web_hooks_table.create!(confidential_note_events: nil, note_events: true) }
+
+  before do
+    stub_const("#{described_class}::BATCH_SIZE", 1)
+  end
+
+  it 'schedules background migrations at correct time' do
+    Sidekiq::Testing.fake! do
+      Timecop.freeze do
+        migrate!
+
+        expect(migration_name).to be_scheduled_delayed_migration(5.minutes, web_hook_1.id, web_hook_1.id)
+        expect(migration_name).to be_scheduled_delayed_migration(10.minutes, web_hook_2.id, web_hook_2.id)
+        expect(migration_name).to be_scheduled_delayed_migration(15.minutes, web_hook_4.id, web_hook_4.id)
+        expect(BackgroundMigrationWorker.jobs.size).to eq 3
+      end
+    end
+  end
+
+  it 'correctly processes web hooks' do
+    Sidekiq::Testing.inline! do
+      expect(web_hooks_table.where(confidential_note_events: nil).count).to eq 4
+      expect(web_hooks_table.where(confidential_note_events: true).count).to eq 1
+
+      migrate!
+
+      expect(web_hooks_table.where(confidential_note_events: nil).count).to eq 1
+      expect(web_hooks_table.where(confidential_note_events: true).count).to eq 4
+    end
+  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 528dc54781d1cbac0ac535219d04c889f2606a8b..560409f08de532b0c1ea9e00d8d2101906bc8102 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
@@ -2,10 +2,10 @@ require 'spec_helper'
 require Rails.root.join('db', 'migrate', '20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb')
 
 describe TurnNestedGroupsIntoRegularGroupsForMysql do
-  let!(:parent_group) { create(:group) }
-  let!(:child_group) { create(:group, parent: parent_group) }
-  let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: child_group) }
-  let!(:member) { create(:user) }
+  let!(:parent_group) { create(:group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:child_group) { create(:group, parent: parent_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: child_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:member) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
   let(:migration) { described_class.new }
 
   before do
diff --git a/spec/migrations/update_retried_for_ci_build_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb
index ccb77766b84ba92690c5f64d185dd76210a581e6..637dcbb8e01900406fe06905e90394886444c404 100644
--- a/spec/migrations/update_retried_for_ci_build_spec.rb
+++ b/spec/migrations/update_retried_for_ci_build_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
 require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb')
 
 describe UpdateRetriedForCiBuild, :delete do
-  let(:pipeline) { create(:ci_pipeline) }
-  let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') }
-  let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') }
+  let(:pipeline) { create(:ci_pipeline) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+  let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
 
   before do
     described_class.new.up
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index cd175dba6da6f072c4259d50c55f37e2dd35421d..199f49d0bf224f0279d02ae7d8ffe14863cb6f06 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -7,62 +7,6 @@ describe Ability do
     end
   end
 
-  describe '.can_edit_note?' do
-    let(:project) { create(:project) }
-    let(:note) { create(:note_on_issue, project: project) }
-
-    context 'using an anonymous user' do
-      it 'returns false' do
-        expect(described_class.can_edit_note?(nil, note)).to be_falsy
-      end
-    end
-
-    context 'using a system note' do
-      it 'returns false' do
-        system_note = create(:note, system: true)
-        user = create(:user)
-
-        expect(described_class.can_edit_note?(user, system_note)).to be_falsy
-      end
-    end
-
-    context 'using users with different access levels' do
-      let(:user) { create(:user) }
-
-      it 'returns true for the author' do
-        expect(described_class.can_edit_note?(note.author, note)).to be_truthy
-      end
-
-      it 'returns false for a guest user' do
-        project.add_guest(user)
-
-        expect(described_class.can_edit_note?(user, note)).to be_falsy
-      end
-
-      it 'returns false for a developer' do
-        project.add_developer(user)
-
-        expect(described_class.can_edit_note?(user, note)).to be_falsy
-      end
-
-      it 'returns true for a master' do
-        project.add_master(user)
-
-        expect(described_class.can_edit_note?(user, note)).to be_truthy
-      end
-
-      it 'returns true for a group owner' do
-        group = create(:group)
-        project.project_group_links.create(
-          group: group,
-          group_access: Gitlab::Access::MASTER)
-        group.add_owner(user)
-
-        expect(described_class.can_edit_note?(user, note)).to be_truthy
-      end
-    end
-  end
-
   describe '.users_that_can_read_project' do
     context 'using a public project' do
       it 'returns all the users' do
diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..33dc19e3432ba88f0a4d26013df5649ed767dd5c
--- /dev/null
+++ b/spec/models/badge_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe Badge do
+  let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
+
+  describe 'validations' do
+    # Requires the let variable url_sym
+    shared_examples 'placeholder url' do
+      let(:badge) { build(:badge) }
+
+      it 'allows url with http protocol' do
+        badge[url_sym] = 'http://www.example.com'
+
+        expect(badge).to be_valid
+      end
+
+      it 'allows url with https protocol' do
+        badge[url_sym] = 'https://www.example.com'
+
+        expect(badge).to be_valid
+      end
+
+      it 'cannot be empty' do
+        badge[url_sym] = ''
+
+        expect(badge).not_to be_valid
+      end
+
+      it 'cannot be nil' do
+        badge[url_sym] = nil
+
+        expect(badge).not_to be_valid
+      end
+
+      it 'accept badges placeholders' do
+        badge[url_sym] = placeholder_url
+
+        expect(badge).to be_valid
+      end
+
+      it 'sanitize url' do
+        badge[url_sym] = 'javascript:alert(1)'
+
+        expect(badge).not_to be_valid
+      end
+    end
+
+    context 'link_url format' do
+      let(:url_sym) { :link_url }
+
+      it_behaves_like 'placeholder url'
+    end
+
+    context 'image_url format' do
+      let(:url_sym) { :image_url }
+
+      it_behaves_like 'placeholder url'
+    end
+  end
+
+  shared_examples 'rendered_links' do
+    it 'should use the project information to populate the url placeholders' do
+      stub_project_commit_info(project)
+
+      expect(badge.public_send("rendered_#{method}", project)).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever"
+    end
+
+    it 'returns the url if the project used is nil' do
+      expect(badge.public_send("rendered_#{method}", nil)).to eq placeholder_url
+    end
+
+    def stub_project_commit_info(project)
+      allow(project).to receive(:commit).and_return(double('Commit', sha: 'whatever'))
+      allow(project).to receive(:default_branch).and_return('master')
+    end
+  end
+
+  context 'methods' do
+    let(:badge) { build(:badge, link_url: placeholder_url, image_url: placeholder_url) }
+    let!(:project) { create(:project) }
+
+    context '#rendered_link_url' do
+      let(:method) { :link_url }
+
+      it_behaves_like 'rendered_links'
+    end
+
+    context '#rendered_image_url' do
+      let(:method) { :image_url }
+
+      it_behaves_like 'rendered_links'
+    end
+  end
+end
diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ed7f83d0489ba2fdcd0496128675f4c32902e765
--- /dev/null
+++ b/spec/models/badges/group_badge_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe GroupBadge do
+  describe 'associations' do
+    it { is_expected.to belong_to(:group) }
+  end
+
+  describe 'validations' do
+    it { is_expected.to validate_presence_of(:group) }
+  end
+end
diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0e1a8159cb6c94a9f0782120f924c6aa4b5337ce
--- /dev/null
+++ b/spec/models/badges/project_badge_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe ProjectBadge do
+  let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
+
+  describe 'associations' do
+    it { is_expected.to belong_to(:project) }
+  end
+
+  describe 'validations' do
+    it { is_expected.to validate_presence_of(:project) }
+  end
+
+  shared_examples 'rendered_links' do
+    it 'should use the badge project information to populate the url placeholders' do
+      stub_project_commit_info(project)
+
+      expect(badge.public_send("rendered_#{method}")).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever"
+    end
+
+    def stub_project_commit_info(project)
+      allow(project).to receive(:commit).and_return(double('Commit', sha: 'whatever'))
+      allow(project).to receive(:default_branch).and_return('master')
+    end
+  end
+
+  context 'methods' do
+    let(:badge) { build(:project_badge, link_url: placeholder_url, image_url: placeholder_url) }
+    let!(:project) { badge.project }
+
+    context '#rendered_link_url' do
+      let(:method) { :link_url }
+
+      it_behaves_like 'rendered_links'
+    end
+
+    context '#rendered_image_url' do
+      let(:method) { :image_url }
+
+      it_behaves_like 'rendered_links'
+    end
+  end
+end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 461e754dc1fba5c0e7c07e31e31021efc41468cd..5326f9cb8c09a16efbe01b42edb43d471bc9fc18 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -51,7 +51,11 @@ describe BroadcastMessage do
 
       expect(described_class).to receive(:where).and_call_original.once
 
-      2.times { described_class.current }
+      described_class.current
+
+      Timecop.travel(1.year) do
+        described_class.current
+      end
     end
 
     it 'includes messages that need to be displayed in the future' do
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 4e72d9d748e1bbf60544f0f5a24667c837a30d4d..0014bbcf9f502b2bbed59bb00538dcad04acfa20 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -65,6 +65,19 @@ describe Ci::ArtifactBlob do
         expect(url).not_to be_nil
         expect(url).to eq("http://#{project.namespace.path}.#{Gitlab.config.pages.host}/-/#{project.path}/-/jobs/#{build.id}/artifacts/#{path}")
       end
+
+      context 'when port is configured' do
+        let(:port) { 1234 }
+
+        it 'returns an URL with port number' do
+          allow(Gitlab.config.pages).to receive(:url).and_return("#{Gitlab.config.pages.url}:#{port}")
+
+          url = subject.external_url(build.project, build)
+
+          expect(url).not_to be_nil
+          expect(url).to eq("http://#{project.namespace.path}.#{Gitlab.config.pages.host}:#{port}/-/#{project.path}/-/jobs/#{build.id}/artifacts/#{path}")
+        end
+      end
     end
   end
 
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e75d5a5411885022f10263bd3f74aa2a859c725
--- /dev/null
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe Ci::BuildMetadata do
+  set(:user) { create(:user) }
+  set(:group) { create(:group, :access_requestable) }
+  set(:project) { create(:project, :repository, group: group, build_timeout: 2000) }
+
+  set(:pipeline) do
+    create(:ci_pipeline, project: project,
+                         sha: project.commit.id,
+                         ref: project.default_branch,
+                         status: 'success')
+  end
+
+  let(:build) { create(:ci_build, pipeline: pipeline) }
+  let(:build_metadata) { build.metadata }
+
+  describe '#update_timeout_state' do
+    subject { build_metadata }
+
+    context 'when runner is not assigned to the job' do
+      it "doesn't change timeout value" do
+        expect { subject.update_timeout_state }.not_to change { subject.reload.timeout }
+      end
+
+      it "doesn't change timeout_source value" do
+        expect { subject.update_timeout_state }.not_to change { subject.reload.timeout_source }
+      end
+    end
+
+    context 'when runner is assigned to the job' do
+      before do
+        build.update_attributes(runner: runner)
+      end
+
+      context 'when runner timeout is lower than project timeout' do
+        let(:runner) { create(:ci_runner, maximum_timeout: 1900) }
+
+        it 'sets runner timeout' do
+          expect { subject.update_timeout_state }.to change { subject.reload.timeout }.to(1900)
+        end
+
+        it 'sets runner_timeout_source' do
+          expect { subject.update_timeout_state }.to change { subject.reload.timeout_source }.to('runner_timeout_source')
+        end
+      end
+
+      context 'when runner timeout is higher than project timeout' do
+        let(:runner) { create(:ci_runner, maximum_timeout: 2100) }
+
+        it 'sets project timeout' do
+          expect { subject.update_timeout_state }.to change { subject.reload.timeout }.to(2000)
+        end
+
+        it 'sets project_timeout_source' do
+          expect { subject.update_timeout_state }.to change { subject.reload.timeout_source }.to('project_timeout_source')
+        end
+      end
+    end
+  end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index c27313ed88bbcac6c249be4e721336b30ae1b9b7..fcdc31c898486ef9972acb91b41af08c60b5f5f4 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -80,6 +80,42 @@ describe Ci::Build do
     end
   end
 
+  describe '.with_artifacts_archive' do
+    subject { described_class.with_artifacts_archive }
+
+    context 'when job does not have an archive' do
+      let!(:job) { create(:ci_build) }
+
+      it 'does not return the job' do
+        is_expected.not_to include(job)
+      end
+    end
+
+    context 'when job has a legacy archive' do
+      let!(:job) { create(:ci_build, :legacy_artifacts) }
+
+      it 'returns the job' do
+        is_expected.to include(job)
+      end
+    end
+
+    context 'when job has a job artifact archive' do
+      let!(:job) { create(:ci_build, :artifacts) }
+
+      it 'returns the job' do
+        is_expected.to include(job)
+      end
+    end
+
+    context 'when job has a job artifact trace' do
+      let!(:job) { create(:ci_build, :trace_artifact) }
+
+      it 'does not return the job' do
+        is_expected.not_to include(job)
+      end
+    end
+  end
+
   describe '#actionize' do
     context 'when build is a created' do
       before do
@@ -162,6 +198,16 @@ describe Ci::Build do
     end
 
     context 'when legacy artifacts are used' do
+      let(:build) { create(:ci_build, :legacy_artifacts) }
+
+      subject { build.artifacts? }
+
+      context 'is expired' do
+        let(:build) { create(:ci_build, :legacy_artifacts, :expired) }
+
+        it { is_expected.to be_falsy }
+      end
+
       context 'artifacts archive does not exist' do
         let(:build) { create(:ci_build) }
 
@@ -172,13 +218,25 @@ describe Ci::Build do
         let(:build) { create(:ci_build, :legacy_artifacts) }
 
         it { is_expected.to be_truthy }
+      end
+    end
+  end
 
-        context 'is expired' do
-          let(:build) { create(:ci_build, :legacy_artifacts, :expired) }
+  describe '#browsable_artifacts?' do
+    subject { build.browsable_artifacts? }
 
-          it { is_expected.to be_falsy }
-        end
+    context 'artifacts metadata does not exist' do
+      before do
+        build.update_attributes(legacy_artifacts_metadata: nil)
       end
+
+      it { is_expected.to be_falsy }
+    end
+
+    context 'artifacts metadata does exists' do
+      let(:build) { create(:ci_build, :artifacts) }
+
+      it { is_expected.to be_truthy }
     end
   end
 
@@ -679,21 +737,21 @@ describe Ci::Build do
 
         describe '#erase' do
           before do
-            build.erase(erased_by: user)
+            build.erase(erased_by: erased_by)
           end
 
           context 'erased by user' do
-            let!(:user) { create(:user, username: 'eraser') }
+            let!(:erased_by) { create(:user, username: 'eraser') }
 
             include_examples 'erasable'
 
             it 'records user who erased a build' do
-              expect(build.erased_by).to eq user
+              expect(build.erased_by).to eq erased_by
             end
           end
 
           context 'erased by system' do
-            let(:user) { nil }
+            let(:erased_by) { nil }
 
             include_examples 'erasable'
 
@@ -748,21 +806,21 @@ describe Ci::Build do
 
           describe '#erase' do
             before do
-              build.erase(erased_by: user)
+              build.erase(erased_by: erased_by)
             end
 
             context 'erased by user' do
-              let!(:user) { create(:user, username: 'eraser') }
+              let!(:erased_by) { create(:user, username: 'eraser') }
 
               include_examples 'erasable'
 
               it 'records user who erased a build' do
-                expect(build.erased_by).to eq user
+                expect(build.erased_by).to eq erased_by
               end
             end
 
             context 'erased by system' do
-              let(:user) { nil }
+              let(:erased_by) { nil }
 
               include_examples 'erasable'
 
@@ -1213,12 +1271,6 @@ describe Ci::Build do
   end
 
   describe 'project settings' do
-    describe '#timeout' do
-      it 'returns project timeout configuration' do
-        expect(build.timeout).to eq(project.build_timeout)
-      end
-    end
-
     describe '#allow_git_fetch' do
       it 'return project allow_git_fetch configuration' do
         expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch)
@@ -1332,29 +1384,51 @@ describe Ci::Build do
     end
   end
 
-  describe '#update_project_statistics' do
-    let!(:build) { create(:ci_build, artifacts_size: 23) }
-
-    it 'updates project statistics when the artifact size changes' do
-      expect(ProjectCacheWorker).to receive(:perform_async)
-        .with(build.project_id, [], [:build_artifacts_size])
+  context 'when updating the build' do
+    let(:build) { create(:ci_build, artifacts_size: 23) }
 
+    it 'updates project statistics' do
       build.artifacts_size = 42
-      build.save!
+
+      expect(build).to receive(:update_project_statistics_after_save).and_call_original
+
+      expect { build.save! }
+        .to change { build.project.statistics.reload.build_artifacts_size }
+        .by(19)
     end
 
-    it 'does not update project statistics when the artifact size stays the same' do
-      expect(ProjectCacheWorker).not_to receive(:perform_async)
+    context 'when the artifact size stays the same' do
+      it 'does not update project statistics' do
+        build.name = 'changed'
+
+        expect(build).not_to receive(:update_project_statistics_after_save)
 
-      build.name = 'changed'
-      build.save!
+        build.save!
+      end
+    end
+  end
+
+  context 'when destroying the build' do
+    let!(:build) { create(:ci_build, artifacts_size: 23) }
+
+    it 'updates project statistics' do
+      expect(ProjectStatistics)
+        .to receive(:increment_statistic)
+        .and_call_original
+
+      expect { build.destroy! }
+        .to change { build.project.statistics.reload.build_artifacts_size }
+        .by(-23)
     end
 
-    it 'updates project statistics when the build is destroyed' do
-      expect(ProjectCacheWorker).to receive(:perform_async)
-        .with(build.project_id, [], [:build_artifacts_size])
+    context 'when the build is destroyed due to the project being destroyed' do
+      it 'does not update the project statistics' do
+        expect(ProjectStatistics)
+          .not_to receive(:increment_statistic)
 
-      build.destroy
+        build.project.update_attributes(pending_delete: true)
+        build.project.destroy!
+      end
     end
   end
 
@@ -1411,19 +1485,30 @@ describe Ci::Build do
     let(:container_registry_enabled) { false }
     let(:predefined_variables) do
       [
+        { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
+        { key: 'CI_JOB_TOKEN', value: build.token, public: false },
+        { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
+        { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
+        { 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 },
         { key: 'CI', value: 'true', public: true },
         { key: 'GITLAB_CI', value: 'true', public: true },
-        { key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true },
+        { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true },
         { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
         { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
         { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
-        { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
         { key: 'CI_JOB_NAME', value: 'test', public: true },
         { key: 'CI_JOB_STAGE', value: 'test', public: true },
-        { key: 'CI_JOB_TOKEN', value: build.token, public: false },
         { key: 'CI_COMMIT_SHA', value: build.sha, public: true },
         { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
         { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
+        { key: 'CI_BUILD_REF', value: build.sha, public: true },
+        { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
+        { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true },
+        { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true },
+        { key: 'CI_BUILD_NAME', value: 'test', public: true },
+        { key: 'CI_BUILD_STAGE', value: 'test', public: true },
         { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
         { key: 'CI_PROJECT_NAME', value: project.path, public: true },
         { key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
@@ -1433,9 +1518,7 @@ describe Ci::Build do
         { key: 'CI_PROJECT_VISIBILITY', value: 'private', 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 }
+        { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }
       ]
     end
 
@@ -1834,39 +1917,6 @@ describe Ci::Build do
       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' } }
-      let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
-      let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
-
-      before do
-        allow(build).to receive(:predefined_variables) { [build_pre_var] }
-        allow(build).to receive(:yaml_variables) { [build_yaml_var] }
-
-        allow_any_instance_of(Project)
-          .to receive(:predefined_variables) { [project_pre_var] }
-
-        allow_any_instance_of(Project)
-          .to receive(:secret_variables_for)
-          .with(ref: 'master', environment: nil) do
-          [create(:ci_variable, key: 'secret', value: 'value')]
-        end
-
-        allow_any_instance_of(Ci::Pipeline)
-          .to receive(:predefined_variables) { [pipeline_pre_var] }
-      end
-
-      it do
-        is_expected.to eq(
-          [build_pre_var,
-           project_pre_var,
-           pipeline_pre_var,
-           build_yaml_var,
-           { key: 'secret', value: 'value', public: false }])
-      end
-    end
-
     context 'when using auto devops' do
       context 'and is enabled' do
         before do
@@ -1890,6 +1940,182 @@ describe Ci::Build do
         end
       end
     end
+
+    context 'when pipeline variable overrides build variable' do
+      before do
+        build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }]
+        pipeline.variables.build(key: 'MYVAR', value: 'pipeline value')
+      end
+
+      it 'overrides YAML variable using a pipeline variable' do
+        variables = subject.reverse.uniq { |variable| variable[:key] }.reverse
+
+        expect(variables)
+          .not_to include(key: 'MYVAR', value: 'myvar', public: true)
+        expect(variables)
+          .to include(key: 'MYVAR', value: 'pipeline value', public: false)
+      end
+    end
+
+    describe 'variables ordering' do
+      context 'when variables hierarchy is stubbed' do
+        let(:build_pre_var) { { key: 'build', value: 'value', public: true } }
+        let(:project_pre_var) { { key: 'project', value: 'value', public: true } }
+        let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true } }
+        let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true } }
+
+        before do
+          allow(build).to receive(:predefined_variables) { [build_pre_var] }
+          allow(build).to receive(:yaml_variables) { [build_yaml_var] }
+          allow(build).to receive(:persisted_variables) { [] }
+
+          allow_any_instance_of(Project)
+            .to receive(:predefined_variables) { [project_pre_var] }
+
+          allow_any_instance_of(Project)
+            .to receive(:secret_variables_for)
+            .with(ref: 'master', environment: nil) do
+            [create(:ci_variable, key: 'secret', value: 'value')]
+          end
+
+          allow_any_instance_of(Ci::Pipeline)
+            .to receive(:predefined_variables) { [pipeline_pre_var] }
+        end
+
+        it 'returns variables in order depending on resource hierarchy' do
+          is_expected.to eq(
+            [build_pre_var,
+             project_pre_var,
+             pipeline_pre_var,
+             build_yaml_var,
+             { key: 'secret', value: 'value', public: false }])
+        end
+      end
+
+      context 'when build has environment and user-provided variables' do
+        let(:expected_variables) do
+          predefined_variables.map { |variable| variable.fetch(:key) } +
+            %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
+               CI_ENVIRONMENT_URL]
+        end
+
+        before do
+          create(:environment, project: build.project,
+                               name: 'staging')
+
+          build.yaml_variables = [{ key: 'YAML_VARIABLE',
+                                    value: 'var',
+                                    public: true }]
+          build.environment = 'staging'
+        end
+
+        it 'matches explicit variables ordering' do
+          received_variables = subject.map { |variable| variable.fetch(:key) }
+
+          expect(received_variables).to eq expected_variables
+        end
+      end
+    end
+
+    context 'when build has not been persisted yet' do
+      let(:build) do
+        described_class.new(
+          name: 'rspec',
+          stage: 'test',
+          ref: 'feature',
+          project: project,
+          pipeline: pipeline
+        )
+      end
+
+      it 'returns static predefined variables' do
+        expect(build.variables.size).to be >= 28
+        expect(build.variables)
+          .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+        expect(build).not_to be_persisted
+      end
+    end
+  end
+
+  describe '#scoped_variables' do
+    context 'when build has not been persisted yet' do
+      let(:build) do
+        described_class.new(
+          name: 'rspec',
+          stage: 'test',
+          ref: 'feature',
+          project: project,
+          pipeline: pipeline
+        )
+      end
+
+      it 'does not persist the build' do
+        expect(build).to be_valid
+        expect(build).not_to be_persisted
+
+        build.scoped_variables
+
+        expect(build).not_to be_persisted
+      end
+
+      it 'returns static predefined variables' do
+        keys = %w[CI_JOB_NAME
+                  CI_COMMIT_SHA
+                  CI_COMMIT_REF_NAME
+                  CI_COMMIT_REF_SLUG
+                  CI_JOB_STAGE]
+
+        variables = build.scoped_variables
+
+        variables.map { |env| env[:key] }.tap do |names|
+          expect(names).to include(*keys)
+        end
+
+        expect(variables)
+          .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+      end
+
+      it 'does not return prohibited variables' do
+        keys = %w[CI_JOB_ID
+                  CI_JOB_TOKEN
+                  CI_BUILD_ID
+                  CI_BUILD_TOKEN
+                  CI_REGISTRY_USER
+                  CI_REGISTRY_PASSWORD
+                  CI_REPOSITORY_URL
+                  CI_ENVIRONMENT_URL]
+
+        build.scoped_variables.map { |env| env[:key] }.tap do |names|
+          expect(names).not_to include(*keys)
+        end
+      end
+    end
+  end
+
+  describe '#scoped_variables_hash' do
+    context 'when overriding secret variables' do
+      before do
+        project.variables.create!(key: 'MY_VAR', value: 'my value 1')
+        pipeline.variables.create!(key: 'MY_VAR', value: 'my value 2')
+      end
+
+      it 'returns a regular hash created using valid ordering' do
+        expect(build.scoped_variables_hash).to include('MY_VAR': 'my value 2')
+        expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1')
+      end
+    end
+
+    context 'when overriding user-provided variables' do
+      before do
+        pipeline.variables.build(key: 'MY_VAR', value: 'pipeline value')
+        build.yaml_variables = [{ key: 'MY_VAR', value: 'myvar', public: true }]
+      end
+
+      it 'returns a hash including variable with higher precedence' do
+        expect(build.scoped_variables_hash).to include('MY_VAR': 'pipeline value')
+        expect(build.scoped_variables_hash).not_to include('MY_VAR': 'myvar')
+      end
+    end
   end
 
   describe 'state transition: any => [:pending]' do
@@ -1902,12 +2128,72 @@ describe Ci::Build do
     end
   end
 
+  describe 'state transition: pending: :running' do
+    let(:runner) { create(:ci_runner) }
+    let(:job) { create(:ci_build, :pending, runner: runner) }
+
+    before do
+      job.project.update_attribute(:build_timeout, 1800)
+    end
+
+    def run_job_without_exception
+      job.run!
+    rescue StateMachines::InvalidTransition
+    end
+
+    shared_examples 'saves data on transition' do
+      it 'saves timeout' do
+        expect { job.run! }.to change { job.reload.ensure_metadata.timeout }.from(nil).to(expected_timeout)
+      end
+
+      it 'saves timeout_source' do
+        expect { job.run! }.to change { job.reload.ensure_metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source)
+      end
+
+      context 'when Ci::BuildMetadata#update_timeout_state fails update' do
+        before do
+          allow_any_instance_of(Ci::BuildMetadata).to receive(:update_timeout_state).and_return(false)
+        end
+
+        it "doesn't save timeout" do
+          expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source }
+        end
+
+        it "doesn't save timeout_source" do
+          expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source }
+        end
+      end
+    end
+
+    context 'when runner timeout overrides project timeout' do
+      let(:expected_timeout) { 900 }
+      let(:expected_timeout_source) { 'runner_timeout_source' }
+
+      before do
+        runner.update_attribute(:maximum_timeout, 900)
+      end
+
+      it_behaves_like 'saves data on transition'
+    end
+
+    context "when runner timeout doesn't override project timeout" do
+      let(:expected_timeout) { 1800 }
+      let(:expected_timeout_source) { 'project_timeout_source' }
+
+      before do
+        runner.update_attribute(:maximum_timeout, 3600)
+      end
+
+      it_behaves_like 'saves data on transition'
+    end
+  end
+
   describe 'state transition: any => [:running]' do
     shared_examples 'validation is active' do
       context 'when depended job has not been completed yet' do
         let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
 
-        it { expect { job.run! }.not_to raise_error(Ci::Build::MissingDependenciesError) }
+        it { expect { job.run! }.not_to raise_error }
       end
 
       context 'when artifacts of depended job has been expired' do
@@ -2014,6 +2300,35 @@ describe Ci::Build do
 
         subject.drop!
       end
+
+      context 'when retry service raises Gitlab::Access::AccessDeniedError exception' do
+        let(:retry_service) { Ci::RetryBuildService.new(subject.project, subject.user) }
+
+        before do
+          allow_any_instance_of(Ci::RetryBuildService)
+            .to receive(:execute)
+            .with(subject)
+            .and_raise(Gitlab::Access::AccessDeniedError)
+          allow(Rails.logger).to receive(:error)
+        end
+
+        it 'handles raised exception' do
+          expect { subject.drop! }.not_to raise_exception(Gitlab::Access::AccessDeniedError)
+        end
+
+        it 'logs the error' do
+          subject.drop!
+
+          expect(Rails.logger)
+            .to have_received(:error)
+            .with(a_string_matching("Unable to auto-retry job #{subject.id}"))
+        end
+
+        it 'fails the job' do
+          subject.drop!
+          expect(subject.failed?).to be_truthy
+        end
+      end
     end
 
     context 'when build is not configured to be retried' do
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index a2bd36537e60567762b9c2c471db9a95c33f445a..a3e119cbc27670739294df5fd294af0b1d3bba84 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Ci::JobArtifact do
-  set(:artifact) { create(:ci_job_artifact, :archive) }
+  let(:artifact) { create(:ci_job_artifact, :archive) }
 
   describe "Associations" do
     it { is_expected.to belong_to(:project) }
@@ -15,10 +15,76 @@ describe Ci::JobArtifact do
   it { is_expected.to delegate_method(:open).to(:file) }
   it { is_expected.to delegate_method(:exists?).to(:file) }
 
-  describe '#set_size' do
-    it 'sets the size' do
+  describe 'callbacks' do
+    subject { create(:ci_job_artifact, :archive) }
+
+    describe '#schedule_background_upload' do
+      context 'when object storage is disabled' do
+        before do
+          stub_artifacts_object_storage(enabled: false)
+        end
+
+        it 'does not schedule the migration' do
+          expect(ObjectStorageUploadWorker).not_to receive(:perform_async)
+
+          subject
+        end
+      end
+
+      context 'when object storage is enabled' do
+        context 'when background upload is enabled' do
+          before do
+            stub_artifacts_object_storage(background_upload: true)
+          end
+
+          it 'schedules the model for migration' do
+            expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('JobArtifactUploader', described_class.name, :file, kind_of(Numeric))
+
+            subject
+          end
+        end
+
+        context 'when background upload is disabled' do
+          before do
+            stub_artifacts_object_storage(background_upload: false)
+          end
+
+          it 'schedules the model for migration' do
+            expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+            subject
+          end
+        end
+      end
+    end
+  end
+
+  context 'creating the artifact' do
+    let(:project) { create(:project) }
+    let(:artifact) { create(:ci_job_artifact, :archive, project: project) }
+
+    it 'sets the size from the file size' do
       expect(artifact.size).to eq(106365)
     end
+
+    it 'updates the project statistics' do
+      expect { artifact }
+        .to change { project.statistics.reload.build_artifacts_size }
+        .by(106365)
+    end
+  end
+
+  context 'updating the artifact file' do
+    it 'updates the artifact size' do
+      artifact.update!(file: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png')))
+      expect(artifact.size).to eq(1062)
+    end
+
+    it 'updates the project statistics' do
+      expect { artifact.update!(file: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
+        .to change { artifact.project.statistics.reload.build_artifacts_size }
+        .by(1062 - 106365)
+    end
   end
 
   describe '#file' do
@@ -74,4 +140,71 @@ describe Ci::JobArtifact do
       is_expected.to be_nil
     end
   end
+
+  context 'when destroying the artifact' do
+    let(:project) { create(:project, :repository) }
+    let(:pipeline) { create(:ci_pipeline, project: project) }
+    let!(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+    it 'updates the project statistics' do
+      artifact = build.job_artifacts.first
+
+      expect(ProjectStatistics)
+        .to receive(:increment_statistic)
+        .and_call_original
+
+      expect { artifact.destroy }
+        .to change { project.statistics.reload.build_artifacts_size }
+        .by(-106365)
+    end
+
+    context 'when it is destroyed from the project level' do
+      it 'does not update the project statistics' do
+        expect(ProjectStatistics)
+          .not_to receive(:increment_statistic)
+
+        project.update_attributes(pending_delete: true)
+        project.destroy!
+      end
+    end
+  end
+
+  describe 'file is being stored' do
+    subject { create(:ci_job_artifact, :archive) }
+
+    context 'when object has nil store' do
+      before do
+        subject.update_column(:file_store, nil)
+        subject.reload
+      end
+
+      it 'is stored locally' do
+        expect(subject.file_store).to be(nil)
+        expect(subject.file).to be_file_storage
+        expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL)
+      end
+    end
+
+    context 'when existing object has local store' do
+      it 'is stored locally' do
+        expect(subject.file_store).to be(ObjectStorage::Store::LOCAL)
+        expect(subject.file).to be_file_storage
+        expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL)
+      end
+    end
+
+    context 'when direct upload is enabled' do
+      before do
+        stub_artifacts_object_storage(direct_upload: true)
+      end
+
+      context 'when file is stored' do
+        it 'is stored remotely' do
+          expect(subject.file_store).to eq(ObjectStorage::Store::REMOTE)
+          expect(subject.file).not_to be_file_storage
+          expect(subject.file.object_store).to eq(ObjectStorage::Store::REMOTE)
+        end
+      end
+    end
+  end
 end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 14d234f6aabe15bc52b01e57a2b2af7ad4d3d39c..dd94515b0a454d38e81074ec65ecdaa715ebf5bd 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -170,12 +170,28 @@ describe Ci::Pipeline, :mailer do
   describe '#predefined_variables' do
     subject { pipeline.predefined_variables }
 
-    it { is_expected.to be_an(Array) }
+    it 'includes all predefined variables in a valid order' do
+      keys = subject.map { |variable| variable[:key] }
+
+      expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE]
+    end
+  end
+
+  describe '#protected_ref?' do
+    it 'delegates method to project' do
+      expect(pipeline).not_to be_protected_ref
+    end
+  end
+
+  describe '#legacy_trigger' do
+    let(:trigger_request) { create(:ci_trigger_request) }
 
-    it 'includes the defined keys' do
-      keys = subject.map { |v| v[:key] }
+    before do
+      pipeline.trigger_requests << trigger_request
+    end
 
-      expect(keys).to include('CI_PIPELINE_ID', 'CI_CONFIG_PATH', 'CI_PIPELINE_SOURCE')
+    it 'returns first trigger request' do
+      expect(pipeline.legacy_trigger).to eq trigger_request
     end
   end
 
@@ -217,142 +233,271 @@ describe Ci::Pipeline, :mailer do
   end
 
   describe 'pipeline stages' do
-    before do
-      create(:commit_status, pipeline: pipeline,
-                             stage: 'build',
-                             name: 'linux',
-                             stage_idx: 0,
-                             status: 'success')
-
-      create(:commit_status, pipeline: pipeline,
-                             stage: 'build',
-                             name: 'mac',
-                             stage_idx: 0,
-                             status: 'failed')
-
-      create(:commit_status, pipeline: pipeline,
-                             stage: 'deploy',
-                             name: 'staging',
-                             stage_idx: 2,
-                             status: 'running')
-
-      create(:commit_status, pipeline: pipeline,
-                             stage: 'test',
-                             name: 'rspec',
-                             stage_idx: 1,
-                             status: 'success')
-    end
-
     describe '#stage_seeds' do
-      let(:pipeline) do
-        build(:ci_pipeline, config: { rspec: { script: 'rake' } })
-      end
+      let(:pipeline) { build(:ci_pipeline, config: config) }
+      let(:config) { { rspec: { script: 'rake' } } }
 
       it 'returns preseeded stage seeds object' do
-        expect(pipeline.stage_seeds).to all(be_a Gitlab::Ci::Stage::Seed)
+        expect(pipeline.stage_seeds)
+          .to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
         expect(pipeline.stage_seeds.count).to eq 1
       end
-    end
 
-    describe '#seeds_size' do
-      let(:pipeline) { build(:ci_pipeline_with_one_job) }
+      context 'when no refs policy is specified' do
+        let(:config) do
+          { production: { stage: 'deploy', script: 'cap prod' },
+            rspec: { stage: 'test', script: 'rspec' },
+            spinach: { stage: 'test', script: 'spinach' } }
+        end
 
-      it 'returns number of jobs in stage seeds' do
-        expect(pipeline.seeds_size).to eq 1
+        it 'correctly fabricates a stage seeds object' do
+          seeds = pipeline.stage_seeds
+
+          expect(seeds.size).to eq 2
+          expect(seeds.first.attributes[:name]).to eq 'test'
+          expect(seeds.second.attributes[:name]).to eq 'deploy'
+          expect(seeds.dig(0, 0, :name)).to eq 'rspec'
+          expect(seeds.dig(0, 1, :name)).to eq 'spinach'
+          expect(seeds.dig(1, 0, :name)).to eq 'production'
+        end
       end
-    end
 
-    describe '#legacy_stages' do
-      subject { pipeline.legacy_stages }
+      context 'when refs policy is specified' do
+        let(:pipeline) do
+          build(:ci_pipeline, ref: 'feature', tag: true, config: config)
+        end
+
+        let(:config) do
+          { production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
+            spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
+        end
+
+        it 'returns stage seeds only assigned to master to master' do
+          seeds = pipeline.stage_seeds
 
-      context 'stages list' do
-        it 'returns ordered list of stages' do
-          expect(subject.map(&:name)).to eq(%w[build test deploy])
+          expect(seeds.size).to eq 1
+          expect(seeds.first.attributes[:name]).to eq 'test'
+          expect(seeds.dig(0, 0, :name)).to eq 'spinach'
         end
       end
 
-      context 'stages with statuses' do
-        let(:statuses) do
-          subject.map { |stage| [stage.name, stage.status] }
+      context 'when source policy is specified' do
+        let(:pipeline) { build(:ci_pipeline, source: :schedule, config: config) }
+
+        let(:config) do
+          { production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
+            spinach: { stage: 'test', script: 'spinach', only: ['schedules'] } }
         end
 
-        it 'returns list of stages with correct statuses' do
-          expect(statuses).to eq([%w(build failed),
-                                  %w(test success),
-                                  %w(deploy running)])
+        it 'returns stage seeds only assigned to schedules' do
+          seeds = pipeline.stage_seeds
+
+          expect(seeds.size).to eq 1
+          expect(seeds.first.attributes[:name]).to eq 'test'
+          expect(seeds.dig(0, 0, :name)).to eq 'spinach'
         end
+      end
 
-        context 'when commit status is retried' do
-          before do
-            create(:commit_status, pipeline: pipeline,
-                                   stage: 'build',
-                                   name: 'mac',
-                                   stage_idx: 0,
-                                   status: 'success')
+      context 'when kubernetes policy is specified' do
+        let(:config) do
+          {
+            spinach: { stage: 'test', script: 'spinach' },
+            production: {
+              stage: 'deploy',
+              script: 'cap',
+              only: { kubernetes: 'active' }
+            }
+          }
+        end
+
+        context 'when kubernetes is active' do
+          shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
+            it 'returns seeds for kubernetes dependent job' do
+              seeds = pipeline.stage_seeds
 
-            pipeline.process!
+              expect(seeds.size).to eq 2
+              expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+              expect(seeds.dig(1, 0, :name)).to eq 'production'
+            end
           end
 
-          it 'ignores the previous state' do
-            expect(statuses).to eq([%w(build success),
-                                    %w(test success),
-                                    %w(deploy running)])
+          context 'when user configured kubernetes from Integration > Kubernetes' do
+            let(:project) { create(:kubernetes_project) }
+            let(:pipeline) { build(:ci_pipeline, project: project, config: config) }
+
+            it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
+          end
+
+          context 'when user configured kubernetes from CI/CD > Clusters' do
+            let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+            let(:project) { cluster.project }
+            let(:pipeline) { build(:ci_pipeline, project: project, config: config) }
+
+            it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
+          end
+        end
+
+        context 'when kubernetes is not active' do
+          it 'does not return seeds for kubernetes dependent job' do
+            seeds = pipeline.stage_seeds
+
+            expect(seeds.size).to eq 1
+            expect(seeds.dig(0, 0, :name)).to eq 'spinach'
           end
         end
       end
 
-      context 'when there is a stage with warnings' do
-        before do
-          create(:commit_status, pipeline: pipeline,
-                                 stage: 'deploy',
-                                 name: 'prod:2',
-                                 stage_idx: 2,
-                                 status: 'failed',
-                                 allow_failure: true)
+      context 'when variables policy is specified' do
+        let(:config) do
+          { unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } },
+            feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } } }
         end
 
-        it 'populates stage with correct number of warnings' do
-          deploy_stage = pipeline.legacy_stages.third
+        it 'returns stage seeds only when variables expression is truthy' do
+          seeds = pipeline.stage_seeds
 
-          expect(deploy_stage).not_to receive(:statuses)
-          expect(deploy_stage).to have_warnings
+          expect(seeds.size).to eq 1
+          expect(seeds.dig(0, 0, :name)).to eq 'unit'
         end
       end
     end
 
-    describe '#stages_count' do
-      it 'returns a valid number of stages' do
-        expect(pipeline.stages_count).to eq(3)
-      end
-    end
+    describe '#seeds_size' do
+      context 'when refs policy is specified' do
+        let(:config) do
+          { production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
+            spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
+        end
+
+        let(:pipeline) do
+          build(:ci_pipeline, ref: 'feature', tag: true, config: config)
+        end
 
-    describe '#stages_names' do
-      it 'returns a valid names of stages' do
-        expect(pipeline.stages_names).to eq(%w(build test deploy))
+        it 'returns real seeds size' do
+          expect(pipeline.seeds_size).to eq 1
+        end
       end
     end
-  end
-
-  describe '#legacy_stage' do
-    subject { pipeline.legacy_stage('test') }
 
-    context 'with status in stage' do
+    describe 'legacy stages' do
       before do
-        create(:commit_status, pipeline: pipeline, stage: 'test')
+        create(:commit_status, pipeline: pipeline,
+                               stage: 'build',
+                               name: 'linux',
+                               stage_idx: 0,
+                               status: 'success')
+
+        create(:commit_status, pipeline: pipeline,
+                               stage: 'build',
+                               name: 'mac',
+                               stage_idx: 0,
+                               status: 'failed')
+
+        create(:commit_status, pipeline: pipeline,
+                               stage: 'deploy',
+                               name: 'staging',
+                               stage_idx: 2,
+                               status: 'running')
+
+        create(:commit_status, pipeline: pipeline,
+                               stage: 'test',
+                               name: 'rspec',
+                               stage_idx: 1,
+                               status: 'success')
+      end
+
+      describe '#legacy_stages' do
+        subject { pipeline.legacy_stages }
+
+        context 'stages list' do
+          it 'returns ordered list of stages' do
+            expect(subject.map(&:name)).to eq(%w[build test deploy])
+          end
+        end
+
+        context 'stages with statuses' do
+          let(:statuses) do
+            subject.map { |stage| [stage.name, stage.status] }
+          end
+
+          it 'returns list of stages with correct statuses' do
+            expect(statuses).to eq([%w(build failed),
+                                    %w(test success),
+                                    %w(deploy running)])
+          end
+
+          context 'when commit status is retried' do
+            before do
+              create(:commit_status, pipeline: pipeline,
+                                     stage: 'build',
+                                     name: 'mac',
+                                     stage_idx: 0,
+                                     status: 'success')
+
+              pipeline.process!
+            end
+
+            it 'ignores the previous state' do
+              expect(statuses).to eq([%w(build success),
+                                      %w(test success),
+                                      %w(deploy running)])
+            end
+          end
+        end
+
+        context 'when there is a stage with warnings' do
+          before do
+            create(:commit_status, pipeline: pipeline,
+                                   stage: 'deploy',
+                                   name: 'prod:2',
+                                   stage_idx: 2,
+                                   status: 'failed',
+                                   allow_failure: true)
+          end
+
+          it 'populates stage with correct number of warnings' do
+            deploy_stage = pipeline.legacy_stages.third
+
+            expect(deploy_stage).not_to receive(:statuses)
+            expect(deploy_stage).to have_warnings
+          end
+        end
+      end
+
+      describe '#stages_count' do
+        it 'returns a valid number of stages' do
+          expect(pipeline.stages_count).to eq(3)
+        end
       end
 
-      it { expect(subject).to be_a Ci::LegacyStage }
-      it { expect(subject.name).to eq 'test' }
-      it { expect(subject.statuses).not_to be_empty }
+      describe '#stages_names' do
+        it 'returns a valid names of stages' do
+          expect(pipeline.stages_names).to eq(%w(build test deploy))
+        end
+      end
     end
 
-    context 'without status in stage' do
-      before do
-        create(:commit_status, pipeline: pipeline, stage: 'build')
+    describe '#legacy_stage' do
+      subject { pipeline.legacy_stage('test') }
+
+      context 'with status in stage' do
+        before do
+          create(:commit_status, pipeline: pipeline, stage: 'test')
+        end
+
+        it { expect(subject).to be_a Ci::LegacyStage }
+        it { expect(subject.name).to eq 'test' }
+        it { expect(subject.statuses).not_to be_empty }
       end
 
-      it 'return stage object' do
-        is_expected.to be_nil
+      context 'without status in stage' do
+        before do
+          create(:commit_status, pipeline: pipeline, stage: 'build')
+        end
+
+        it 'return stage object' do
+          is_expected.to be_nil
+        end
       end
     end
   end
@@ -591,20 +736,6 @@ describe Ci::Pipeline, :mailer do
     end
   end
 
-  describe '#has_stage_seeds?' do
-    context 'when pipeline has stage seeds' do
-      subject { build(:ci_pipeline_with_one_job) }
-
-      it { is_expected.to have_stage_seeds }
-    end
-
-    context 'when pipeline does not have stage seeds' do
-      subject { create(:ci_pipeline_without_jobs) }
-
-      it { is_expected.not_to have_stage_seeds }
-    end
-  end
-
   describe '#has_warnings?' do
     subject { pipeline.has_warnings? }
 
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index ba7bad617b44215bd24abbf8ae4dff8a9ab1e8dd..0eb1e3876e287249713aa9c2d7ce61894bcf41fc 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -3,6 +3,18 @@ require 'rails_helper'
 describe Clusters::Applications::Helm do
   include_examples 'cluster application core specs', :clusters_applications_helm
 
+  describe '.installed' do
+    subject { described_class.installed }
+
+    let!(:cluster) { create(:clusters_applications_helm, :installed) }
+
+    before do
+      create(:clusters_applications_helm, :errored)
+    end
+
+    it { is_expected.to contain_exactly(cluster) }
+  end
+
   describe '#install_command' do
     let(:helm) { create(:clusters_applications_helm) }
 
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 03f5b88a5250e7bc9a1923bd6f3cbc7b2adc531f..a47a07d908dc2e7cf7d6dd0e9090bf5d2443059b 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -11,6 +11,18 @@ describe Clusters::Applications::Ingress do
     allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
   end
 
+  describe '.installed' do
+    subject { described_class.installed }
+
+    let!(:cluster) { create(:clusters_applications_ingress, :installed) }
+
+    before do
+      create(:clusters_applications_ingress, :errored)
+    end
+
+    it { is_expected.to contain_exactly(cluster) }
+  end
+
   describe '#make_installed!' do
     before do
       application.make_installed!
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index df8a508e02195fee7483446134d79f8e3f48f8d4..aeca6ee903ad0cb4d7d3cfd536ccb93de332e1e0 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -4,6 +4,18 @@ describe Clusters::Applications::Prometheus do
   include_examples 'cluster application core specs', :clusters_applications_prometheus
   include_examples 'cluster application status specs', :cluster_application_prometheus
 
+  describe '.installed' do
+    subject { described_class.installed }
+
+    let!(:cluster) { create(:clusters_applications_prometheus, :installed) }
+
+    before do
+      create(:clusters_applications_prometheus, :errored)
+    end
+
+    it { is_expected.to contain_exactly(cluster) }
+  end
+
   describe 'transition to installed' do
     let(:project) { create(:project) }
     let(:cluster) { create(:cluster, projects: [project]) }
@@ -22,11 +34,11 @@ describe Clusters::Applications::Prometheus do
     end
   end
 
-  describe '#proxy_client' do
+  describe '#prometheus_client' do
     context 'cluster is nil' do
       it 'returns nil' do
         expect(subject.cluster).to be_nil
-        expect(subject.proxy_client).to be_nil
+        expect(subject.prometheus_client).to be_nil
       end
     end
 
@@ -35,7 +47,7 @@ describe Clusters::Applications::Prometheus do
       subject { create(:clusters_applications_prometheus, cluster: cluster) }
 
       it 'returns nil' do
-        expect(subject.proxy_client).to be_nil
+        expect(subject.prometheus_client).to be_nil
       end
     end
 
@@ -63,15 +75,15 @@ describe Clusters::Applications::Prometheus do
       end
 
       it 'creates proxy prometheus rest client' do
-        expect(subject.proxy_client).to be_instance_of(RestClient::Resource)
+        expect(subject.prometheus_client).to be_instance_of(RestClient::Resource)
       end
 
       it 'creates proper url' do
-        expect(subject.proxy_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80')
+        expect(subject.prometheus_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80')
       end
 
       it 'copies options and headers from kube client to proxy client' do
-        expect(subject.proxy_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers))
+        expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers))
       end
     end
   end
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 612a3c8e413307862cec4a3ef3546db36f3fbf81..64d995a73c11a03c22d4eaa59813ff0aeb495058 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -8,6 +8,18 @@ describe Clusters::Applications::Runner do
 
   it { is_expected.to belong_to(:runner) }
 
+  describe '.installed' do
+    subject { described_class.installed }
+
+    let!(:cluster) { create(:clusters_applications_runner, :installed) }
+
+    before do
+      create(:clusters_applications_runner, :errored)
+    end
+
+    it { is_expected.to contain_exactly(cluster) }
+  end
+
   describe '#install_command' do
     let(:kubeclient) { double('kubernetes client') }
     let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
@@ -34,6 +46,8 @@ describe Clusters::Applications::Runner do
       is_expected.to include('checkInterval')
       is_expected.to include('rbac')
       is_expected.to include('runners')
+      is_expected.to include('privileged: true')
+      is_expected.to include('image: ubuntu:16.04')
       is_expected.to include('resources')
       is_expected.to include("runnerToken: #{ci_runner.token}")
       is_expected.to include("gitlabUrl: #{Gitlab::Routing.url_helpers.root_url}")
@@ -65,5 +79,33 @@ describe Clusters::Applications::Runner do
         expect(gitlab_runner.runner).not_to be_nil
       end
     end
+
+    context 'with duplicated values on vendor/runner/values.yaml' do
+      let(:values) do
+        {
+          "concurrent" => 4,
+          "checkInterval" => 3,
+          "rbac" => {
+            "create" => false
+          },
+          "clusterWideAccess" => false,
+          "runners" => {
+            "privileged" => false,
+            "image" => "ubuntu:16.04",
+            "builds" => {},
+            "services" => {},
+            "helpers" => {}
+          }
+        }
+      end
+
+      before do
+        allow(gitlab_runner).to receive(:chart_values).and_return(values)
+      end
+
+      it 'should overwrite values.yaml' do
+        is_expected.to include("privileged: #{gitlab_runner.privileged}")
+      end
+    end
   end
 end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 8f12a0e308553ed09a7f24492e5bd998e5b02267..b942554d67b3a13f833a7e04ee4be7b924d1bd9f 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -39,6 +39,42 @@ describe Clusters::Cluster do
     it { is_expected.to contain_exactly(cluster) }
   end
 
+  describe '.user_provided' do
+    subject { described_class.user_provided }
+
+    let!(:cluster) { create(:cluster, :provided_by_user) }
+
+    before do
+      create(:cluster, :provided_by_gcp)
+    end
+
+    it { is_expected.to contain_exactly(cluster) }
+  end
+
+  describe '.gcp_provided' do
+    subject { described_class.gcp_provided }
+
+    let!(:cluster) { create(:cluster, :provided_by_gcp) }
+
+    before do
+      create(:cluster, :provided_by_user)
+    end
+
+    it { is_expected.to contain_exactly(cluster) }
+  end
+
+  describe '.gcp_installed' do
+    subject { described_class.gcp_installed }
+
+    let!(:cluster) { create(:cluster, :provided_by_gcp) }
+
+    before do
+      create(:cluster, :providing_by_gcp)
+    end
+
+    it { is_expected.to contain_exactly(cluster) }
+  end
+
   describe 'validation' do
     subject { cluster.valid? }
 
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 53a4e545ff69b162f3a68fb0ed2fc78f0da4d0ce..add481b80965369eb7550e2c8efbb87ea5e121df 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -252,7 +252,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
         stub_kubeclient_pods(status: 500)
       end
 
-      it { expect { subject }.to raise_error(KubeException) }
+      it { expect { subject }.to raise_error(Kubeclient::HttpError) }
     end
 
     context 'when kubernetes responds with 404s' do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 959383ff0b7c4ba7bac567efda6e05d9355a3030..4e6b037a720a2804a9c233efe4a223493c8ca9ec 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -450,6 +450,11 @@ eos
       it "returns nil if the path doesn't exists" do
         expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
       end
+
+      it 'is nil if the path is nil or empty' do
+        expect(commit.uri_type(nil)).to be_nil
+        expect(commit.uri_type("")).to be_nil
+      end
     end
 
     context 'when Gitaly commit_tree_entry feature is enabled' do
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index b7ed8be69fc64eac0bd4887f5ebe1cb0a8d17df3..2ed29052dc1d2b3a4bd977ad9a81d1e4ca4f87a7 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -368,9 +368,7 @@ describe CommitStatus do
       'rspec:windows 0 : / 1' => 'rspec:windows',
       'rspec:windows 0 : / 1 name' => 'rspec:windows name',
       '0 1 name ruby' => 'name ruby',
-      '0 :/ 1 name ruby' => 'name ruby',
-      'golang test 1.8' => 'golang test',
-      '1.9 golang test' => 'golang test'
+      '0 :/ 1 name ruby' => 'name ruby'
     }
 
     tests.each do |name, group_name|
@@ -535,4 +533,36 @@ describe CommitStatus do
       end
     end
   end
+
+  describe '#enqueue' do
+    let!(:current_time) { Time.new(2018, 4, 5, 14, 0, 0) }
+
+    before do
+      allow(Time).to receive(:now).and_return(current_time)
+    end
+
+    shared_examples 'commit status enqueued' do
+      it 'sets queued_at value when enqueued' do
+        expect { commit_status.enqueue }.to change { commit_status.reload.queued_at }.from(nil).to(current_time)
+      end
+    end
+
+    context 'when initial state is :created' do
+      let(:commit_status) { create(:commit_status, :created) }
+
+      it_behaves_like 'commit status enqueued'
+    end
+
+    context 'when initial state is :skipped' do
+      let(:commit_status) { create(:commit_status, :skipped) }
+
+      it_behaves_like 'commit status enqueued'
+    end
+
+    context 'when initial state is :manual' do
+      let(:commit_status) { create(:commit_status, :manual) }
+
+      it_behaves_like 'commit status enqueued'
+    end
+  end
 end
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index 04f3cecae00d2b18dc1b2824d9001763a439b8eb..8e88bb81162f9f0b946bdc60e5984b306c810963 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -37,33 +37,51 @@ describe Compare do
     end
   end
 
-  describe '#base_commit' do
-    let(:base_commit) { Commit.new(another_sample_commit, project) }
+  describe '#base_commit_sha' do
+    it 'returns @base_sha if it is present' do
+      expect(project).not_to receive(:merge_base_commit)
 
-    it 'returns project merge base commit' do
-      expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit)
+      sha = double
+      service = described_class.new(raw_compare, project, base_sha: sha)
 
-      expect(subject.base_commit).to eq(base_commit)
+      expect(service.base_commit_sha).to eq(sha)
+    end
+
+    it 'fetches merge base SHA from repo when @base_sha is nil' do
+      expect(project).to receive(:merge_base_commit)
+        .with(start_commit.id, head_commit.id)
+        .once
+        .and_call_original
+
+      expect(subject.base_commit_sha)
+        .to eq(project.repository.merge_base(start_commit.id, head_commit.id))
+    end
+
+    it 'is memoized on first call' do
+      expect(project).to receive(:merge_base_commit)
+        .with(start_commit.id, head_commit.id)
+        .once
+        .and_call_original
+
+      3.times { subject.base_commit_sha }
     end
 
     it 'returns nil if there is no start_commit' do
       expect(subject).to receive(:start_commit).and_return(nil)
 
-      expect(subject.base_commit).to eq(nil)
+      expect(subject.base_commit_sha).to eq(nil)
     end
 
     it 'returns nil if there is no head commit' do
       expect(subject).to receive(:head_commit).and_return(nil)
 
-      expect(subject.base_commit).to eq(nil)
+      expect(subject.base_commit_sha).to eq(nil)
     end
   end
 
   describe '#diff_refs' do
-    it 'uses base_commit sha as base_sha' do
-      expect(subject).to receive(:base_commit).at_least(:once).and_call_original
-
-      expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id)
+    it 'uses base_commit_sha sha as base_sha' do
+      expect(subject.diff_refs.base_sha).to eq(subject.base_commit_sha)
     end
 
     it 'uses start_commit sha as start_sha' do
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 34f923d3f0ce805428d1ebb4c7b813374eff773e..a980cff28fb18cb62054d803b22d8444c09730ae 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -46,6 +46,31 @@ describe Awardable do
     end
   end
 
+  describe '#user_can_award?' do
+    let(:user) { create(:user) }
+
+    before do
+      issue.project.add_guest(user)
+    end
+
+    it 'does not allow upvoting or downvoting your own issue' do
+      issue.update!(author: user)
+
+      expect(issue.user_can_award?(user, AwardEmoji::DOWNVOTE_NAME)).to be_falsy
+      expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy
+    end
+
+    it 'is truthy when the user is allowed to award emoji' do
+      expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_truthy
+    end
+
+    it 'is falsy when the project is archived' do
+      issue.project.update!(archived: true)
+
+      expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy
+    end
+  end
+
   describe "#toggle_award_emoji" do
     it "adds an emoji if it isn't awarded yet" do
       expect { issue.toggle_award_emoji("thumbsup", award_emoji.user) }.to change { AwardEmoji.count }.by(1)
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 3c7f578975b3e2b2ac4459a9ca1d0d2602fecbe5..b3797c1fb46057b265c1c82cb32fdcf71c4e3960 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -72,7 +72,7 @@ describe CacheMarkdownField do
   let(:updated_markdown) { '`Bar`' }
   let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' }
 
-  let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: CacheMarkdownField::CACHE_VERSION) }
+  let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
 
   describe '.attributes' do
     it 'excludes cache attributes' do
@@ -89,17 +89,24 @@ describe CacheMarkdownField do
     it { expect(thing.foo).to eq(markdown) }
     it { expect(thing.foo_html).to eq(html) }
     it { expect(thing.foo_html_changed?).not_to be_truthy }
-    it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION) }
+    it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
   end
 
   context 'a changed markdown field' do
-    before do
-      thing.foo = updated_markdown
-      thing.save
+    shared_examples 'with cache version' do |cache_version|
+      let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
+
+      before do
+        thing.foo = updated_markdown
+        thing.save
+      end
+
+      it { expect(thing.foo_html).to eq(updated_html) }
+      it { expect(thing.cached_markdown_version).to eq(cache_version) }
     end
 
-    it { expect(thing.foo_html).to eq(updated_html) }
-    it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION) }
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION
   end
 
   context 'when a markdown field is set repeatedly to an empty string' do
@@ -123,15 +130,22 @@ describe CacheMarkdownField do
   end
 
   context 'a non-markdown field changed' do
-    before do
-      thing.bar = 'OK'
-      thing.save
+    shared_examples 'with cache version' do |cache_version|
+      let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
+
+      before do
+        thing.bar = 'OK'
+        thing.save
+      end
+
+      it { expect(thing.bar).to eq('OK') }
+      it { expect(thing.foo).to eq(markdown) }
+      it { expect(thing.foo_html).to eq(html) }
+      it { expect(thing.cached_markdown_version).to eq(cache_version) }
     end
 
-    it { expect(thing.bar).to eq('OK') }
-    it { expect(thing.foo).to eq(markdown) }
-    it { expect(thing.foo_html).to eq(html) }
-    it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION) }
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION
   end
 
   context 'version is out of date' do
@@ -142,59 +156,85 @@ describe CacheMarkdownField do
     end
 
     it { expect(thing.foo_html).to eq(updated_html) }
-    it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION) }
+    it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) }
   end
 
   describe '#cached_html_up_to_date?' do
-    subject { thing.cached_html_up_to_date?(:foo) }
+    shared_examples 'with cache version' do |cache_version|
+      let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
 
-    it 'returns false when the version is absent' do
-      thing.cached_markdown_version = nil
+      subject { thing.cached_html_up_to_date?(:foo) }
 
-      is_expected.to be_falsy
-    end
+      it 'returns false when the version is absent' do
+        thing.cached_markdown_version = nil
 
-    it 'returns false when the version is too early' do
-      thing.cached_markdown_version -= 1
+        is_expected.to be_falsy
+      end
 
-      is_expected.to be_falsy
-    end
+      it 'returns false when the version is too early' do
+        thing.cached_markdown_version -= 1
 
-    it 'returns false when the version is too late' do
-      thing.cached_markdown_version += 1
+        is_expected.to be_falsy
+      end
 
-      is_expected.to be_falsy
-    end
+      it 'returns false when the version is too late' do
+        thing.cached_markdown_version += 1
 
-    it 'returns true when the version is just right' do
-      thing.cached_markdown_version = CacheMarkdownField::CACHE_VERSION
+        is_expected.to be_falsy
+      end
 
-      is_expected.to be_truthy
-    end
+      it 'returns true when the version is just right' do
+        thing.cached_markdown_version = cache_version
 
-    it 'returns false if markdown has been changed but html has not' do
-      thing.foo = updated_html
+        is_expected.to be_truthy
+      end
 
-      is_expected.to be_falsy
-    end
+      it 'returns false if markdown has been changed but html has not' do
+        thing.foo = updated_html
 
-    it 'returns true if markdown has not been changed but html has' do
-      thing.foo_html = updated_html
+        is_expected.to be_falsy
+      end
+
+      it 'returns true if markdown has not been changed but html has' do
+        thing.foo_html = updated_html
 
-      is_expected.to be_truthy
+        is_expected.to be_truthy
+      end
+
+      it 'returns true if markdown and html have both been changed' do
+        thing.foo = updated_markdown
+        thing.foo_html = updated_html
+
+        is_expected.to be_truthy
+      end
+
+      it 'returns false if the markdown field is set but the html is not' do
+        thing.foo_html = nil
+
+        is_expected.to be_falsy
+      end
     end
 
-    it 'returns true if markdown and html have both been changed' do
-      thing.foo = updated_markdown
-      thing.foo_html = updated_html
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION
+  end
+
+  describe '#latest_cached_markdown_version' do
+    subject { thing.latest_cached_markdown_version }
 
-      is_expected.to be_truthy
+    it 'returns redcarpet version' do
+      thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - 1
+      is_expected.to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
     end
 
-    it 'returns false if the markdown field is set but the html is not' do
-      thing.foo_html = nil
+    it 'returns commonmark version' do
+      thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START + 1
+      is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
+    end
 
-      is_expected.to be_falsy
+    it 'returns default version when version is nil' do
+      thing.cached_markdown_version = nil
+      is_expected.to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
     end
   end
 
@@ -221,37 +261,44 @@ describe CacheMarkdownField do
       thing.cached_markdown_version = nil
       thing.refresh_markdown_cache
 
-      expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION)
+      expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
     end
   end
 
   describe '#refresh_markdown_cache!' do
-    before do
-      thing.foo = updated_markdown
-    end
+    shared_examples 'with cache version' do |cache_version|
+      let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
 
-    it 'fills all html fields' do
-      thing.refresh_markdown_cache!
+      before do
+        thing.foo = updated_markdown
+      end
 
-      expect(thing.foo_html).to eq(updated_html)
-      expect(thing.foo_html_changed?).to be_truthy
-      expect(thing.baz_html_changed?).to be_truthy
-    end
+      it 'fills all html fields' do
+        thing.refresh_markdown_cache!
 
-    it 'skips saving if not persisted' do
-      expect(thing).to receive(:persisted?).and_return(false)
-      expect(thing).not_to receive(:update_columns)
+        expect(thing.foo_html).to eq(updated_html)
+        expect(thing.foo_html_changed?).to be_truthy
+        expect(thing.baz_html_changed?).to be_truthy
+      end
 
-      thing.refresh_markdown_cache!
-    end
+      it 'skips saving if not persisted' do
+        expect(thing).to receive(:persisted?).and_return(false)
+        expect(thing).not_to receive(:update_columns)
 
-    it 'saves the changes using #update_columns' do
-      expect(thing).to receive(:persisted?).and_return(true)
-      expect(thing).to receive(:update_columns)
-        .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => CacheMarkdownField::CACHE_VERSION)
+        thing.refresh_markdown_cache!
+      end
 
-      thing.refresh_markdown_cache!
+      it 'saves the changes using #update_columns' do
+        expect(thing).to receive(:persisted?).and_return(true)
+        expect(thing).to receive(:update_columns)
+          .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version)
+
+        thing.refresh_markdown_cache!
+      end
     end
+
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION
+    it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION
   end
 
   describe '#banzai_render_context' do
@@ -299,7 +346,7 @@ describe CacheMarkdownField do
 
         expect(thing.foo_html).to eq(updated_html)
         expect(thing.baz_html).to eq(updated_html)
-        expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION)
+        expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
       end
     end
 
@@ -319,7 +366,7 @@ describe CacheMarkdownField do
 
         expect(thing.foo_html).to eq(updated_html)
         expect(thing.baz_html).to eq(updated_html)
-        expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION)
+        expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
       end
     end
   end
diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8847623f705b953fecb1d313f1297a0a4d9d28ff
--- /dev/null
+++ b/spec/models/concerns/chronic_duration_attribute_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+shared_examples 'ChronicDurationAttribute reader' do
+  it 'contains dynamically created reader method' do
+    expect(subject.class).to be_public_method_defined(virtual_field)
+  end
+
+  it 'outputs chronic duration formatted value' do
+    subject.send("#{source_field}=", 120)
+
+    expect(subject.send(virtual_field)).to eq('2m')
+  end
+
+  context 'when value is set to nil' do
+    it 'outputs nil' do
+      subject.send("#{source_field}=", nil)
+
+      expect(subject.send(virtual_field)).to be_nil
+    end
+  end
+end
+
+shared_examples 'ChronicDurationAttribute writer' do
+  it 'contains dynamically created writer method' do
+    expect(subject.class).to be_public_method_defined("#{virtual_field}=")
+  end
+
+  before do
+    subject.send("#{virtual_field}=", '10m')
+  end
+
+  it 'parses chronic duration input' do
+    expect(subject.send(source_field)).to eq(600)
+  end
+
+  it 'passes validation' do
+    expect(subject.valid?).to be_truthy
+  end
+
+  context 'when negative input is used' do
+    before do
+      subject.send("#{source_field}=", 3600)
+    end
+
+    it "doesn't raise exception" do
+      expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error(ChronicDuration::DurationParseError)
+    end
+
+    it "doesn't change value" do
+      expect { subject.send("#{virtual_field}=", '-10m') }.not_to change { subject.send(source_field) }
+    end
+
+    it "doesn't pass validation" do
+      subject.send("#{virtual_field}=", '-10m')
+
+      expect(subject.valid?).to be_falsey
+      expect(subject.errors&.messages).to include(virtual_field => ['is not a correct duration'])
+    end
+  end
+
+  context 'when empty input is used' do
+    before do
+      subject.send("#{virtual_field}=", '')
+    end
+
+    it 'writes default value' do
+      expect(subject.send(source_field)).to eq(default_value)
+    end
+
+    it 'passes validation' do
+      expect(subject.valid?).to be_truthy
+    end
+  end
+
+  context 'when nil input is used' do
+    before do
+      subject.send("#{virtual_field}=", nil)
+    end
+
+    it 'writes default value' do
+      expect(subject.send(source_field)).to eq(default_value)
+    end
+
+    it 'passes validation' do
+      expect(subject.valid?).to be_truthy
+    end
+
+    it "doesn't raise exception" do
+      expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError)
+    end
+  end
+end
+
+describe 'ChronicDurationAttribute' do
+  context 'when default value is not set' do
+    let(:source_field) {:maximum_timeout}
+    let(:virtual_field) {:maximum_timeout_human_readable}
+    let(:default_value) { nil }
+
+    subject { create(:ci_runner) }
+
+    it_behaves_like 'ChronicDurationAttribute reader'
+    it_behaves_like 'ChronicDurationAttribute writer'
+  end
+
+  context 'when default value is set' do
+    let(:source_field) {:build_timeout}
+    let(:virtual_field) {:build_timeout_human_readable}
+    let(:default_value) { 3600 }
+
+    subject { create(:project) }
+
+    it_behaves_like 'ChronicDurationAttribute reader'
+    it_behaves_like 'ChronicDurationAttribute writer'
+  end
+end
+
+describe 'ChronicDurationAttribute - reader' do
+  let(:source_field) {:timeout}
+  let(:virtual_field) {:timeout_human_readable}
+
+  subject { create(:ci_build).ensure_metadata }
+
+  it "doesn't contain dynamically created writer method" do
+    expect(subject.class).not_to be_public_method_defined("#{virtual_field}=")
+  end
+
+  it_behaves_like 'ChronicDurationAttribute reader'
+end
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index c163fb01a8183c84e98b29ba98a7c1a1ac09fae7..28352d8c9616b5947dcddaadf9b0a1720c9a474e 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -79,9 +79,24 @@ describe GroupDescendant, :nested_groups do
         expect(described_class.build_hierarchy(groups)).to eq(expected_hierarchy)
       end
 
+      it 'tracks the exception when a parent was not preloaded' do
+        expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
+
+        expect { GroupDescendant.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
+      end
+
+      it 'recovers if a parent was not reloaded by querying for the parent' do
+        expected_hierarchy = { parent => { subgroup => subsub_group } }
+
+        # this does not raise in production, so stubbing it here.
+        allow(Gitlab::Sentry).to receive(:track_exception)
+
+        expect(GroupDescendant.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
+      end
+
       it 'raises an error if not all elements were preloaded' do
         expect { described_class.build_hierarchy([subsub_group]) }
-          .to raise_error('parent was not preloaded')
+          .to raise_error(/was not preloaded/)
       end
     end
   end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 4b217df2e8f27b5fed50b269511964d4401d728d..05693f067e121a5206f92452bf259b6a216f560c 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -34,7 +34,7 @@ describe Issuable do
     subject { build(:issue) }
 
     before do
-      allow(subject).to receive(:set_iid).and_return(false)
+      allow(InternalId).to receive(:generate_next).and_return(nil)
     end
 
     it { is_expected.to validate_presence_of(:project) }
@@ -176,7 +176,7 @@ describe Issuable do
     end
   end
 
-  describe "#sort" do
+  describe "#sort_by_attribute" do
     let(:project) { create(:project) }
 
     context "by milestone due date" do
@@ -193,12 +193,12 @@ describe Issuable do
       let!(:issue3) { create(:issue, project: project) }
 
       it "sorts desc" do
-        issues = project.issues.sort('milestone_due_desc')
+        issues = project.issues.sort_by_attribute('milestone_due_desc')
         expect(issues).to match_array([issue2, issue1, issue, issue3])
       end
 
       it "sorts asc" do
-        issues = project.issues.sort('milestone_due_asc')
+        issues = project.issues.sort_by_attribute('milestone_due_asc')
         expect(issues).to match_array([issue1, issue2, issue, issue3])
       end
     end
@@ -210,7 +210,7 @@ describe Issuable do
 
       it 'has no duplicates across pages' do
         sorted_issue_ids = 1.upto(10).map do |i|
-          project.issues.sort('milestone_due_desc').page(i).per(1).first.id
+          project.issues.sort_by_attribute('milestone_due_desc').page(i).per(1).first.id
         end
 
         expect(sorted_issue_ids).to eq(sorted_issue_ids.uniq)
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f4b9c57e71a3e76ed360d65b98b7914b728981df
--- /dev/null
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -0,0 +1,121 @@
+require 'spec_helper'
+
+describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
+  include PrometheusHelpers
+  include ReactiveCachingHelpers
+
+  class TestClass
+    include PrometheusAdapter
+  end
+
+  let(:project) { create(:prometheus_project) }
+  let(:service) { project.prometheus_service }
+
+  let(:described_class) { TestClass }
+  let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery }
+
+  describe '#query' do
+    describe 'environment' do
+      let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+
+      around do |example|
+        Timecop.freeze { example.run }
+      end
+
+      context 'with valid data' do
+        subject { service.query(:environment, environment) }
+
+        before do
+          stub_reactive_cache(service, prometheus_data, environment_query, environment.id)
+        end
+
+        it 'returns reactive data' do
+          is_expected.to eq(prometheus_metrics_data)
+        end
+      end
+    end
+
+    describe 'matched_metrics' do
+      let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricQuery }
+      let(:prometheus_client_wrapper) { double(:prometheus_client_wrapper, label_values: nil) }
+
+      context 'with valid data' do
+        subject { service.query(:matched_metrics) }
+
+        before do
+          allow(service).to receive(:prometheus_client_wrapper).and_return(prometheus_client_wrapper)
+          synchronous_reactive_cache(service)
+        end
+
+        it 'returns reactive data' do
+          expect(subject[:success]).to be_truthy
+          expect(subject[:data]).to eq([])
+        end
+      end
+    end
+
+    describe 'deployment' do
+      let(:deployment) { build_stubbed(:deployment) }
+      let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery }
+
+      around do |example|
+        Timecop.freeze { example.run }
+      end
+
+      context 'with valid data' do
+        subject { service.query(:deployment, deployment) }
+
+        before do
+          stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id)
+        end
+
+        it 'returns reactive data' do
+          expect(subject).to eq(prometheus_metrics_data)
+        end
+      end
+    end
+  end
+
+  describe '#calculate_reactive_cache' do
+    let(:environment) { create(:environment, slug: 'env-slug') }
+    before do
+      service.manual_configuration = true
+      service.active = true
+    end
+
+    subject do
+      service.calculate_reactive_cache(environment_query.name, environment.id)
+    end
+
+    around do |example|
+      Timecop.freeze { example.run }
+    end
+
+    context 'when service is inactive' do
+      before do
+        service.active = false
+      end
+
+      it { is_expected.to be_nil }
+    end
+
+    context 'when Prometheus responds with valid data' do
+      before do
+        stub_all_prometheus_requests(environment.slug)
+      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|
+      context "when Prometheus responds with #{status}" do
+        before do
+          stub_all_prometheus_requests(environment.slug, status: status, body: "QUERY FAILED!")
+        end
+
+        it { is_expected.to eq(success: false, result: %(#{status} - "QUERY FAILED!")) }
+      end
+    end
+  end
+end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 3d7283e216488896133023351d051ab364bbc0e0..41440c6d288d029c70cc687a31e7e427ee188548 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -17,4 +17,25 @@ describe DeployKey, :mailer do
       should_not_email(user)
     end
   end
+
+  describe '#user' do
+    let(:deploy_key) { create(:deploy_key) }
+    let(:user) { create(:user) }
+
+    context 'when user is set' do
+      before do
+        deploy_key.user = user
+      end
+
+      it 'returns the user' do
+        expect(deploy_key.user).to be(user)
+      end
+    end
+
+    context 'when user is not set' do
+      it 'returns the ghost user' do
+        expect(deploy_key.user).to eq(User.ghost)
+      end
+    end
+  end
 end
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..780b200e837db4e67601c1b8e168494668363fd4
--- /dev/null
+++ b/spec/models/deploy_token_spec.rb
@@ -0,0 +1,145 @@
+require 'spec_helper'
+
+describe DeployToken do
+  subject(:deploy_token) { create(:deploy_token) }
+
+  it { is_expected.to have_many :project_deploy_tokens }
+  it { is_expected.to have_many(:projects).through(:project_deploy_tokens) }
+
+  describe '#ensure_token' do
+    it 'should ensure a token' do
+      deploy_token.token = nil
+      deploy_token.save
+
+      expect(deploy_token.token).not_to be_empty
+    end
+  end
+
+  describe '#ensure_at_least_one_scope' do
+    context 'with at least one scope' do
+      it 'should be valid' do
+        is_expected.to be_valid
+      end
+    end
+
+    context 'with no scopes' do
+      it 'should be invalid' do
+        deploy_token = build(:deploy_token, read_repository: false, read_registry: false)
+
+        expect(deploy_token).not_to be_valid
+        expect(deploy_token.errors[:base].first).to eq("Scopes can't be blank")
+      end
+    end
+  end
+
+  describe '#scopes' do
+    context 'with all the scopes' do
+      it 'should return scopes assigned to DeployToken' do
+        expect(deploy_token.scopes).to eq([:read_repository, :read_registry])
+      end
+    end
+
+    context 'with only one scope' do
+      it 'should return scopes assigned to DeployToken' do
+        deploy_token = create(:deploy_token, read_registry: false)
+        expect(deploy_token.scopes).to eq([:read_repository])
+      end
+    end
+  end
+
+  describe '#revoke!' do
+    it 'should update revoke attribute' do
+      deploy_token.revoke!
+      expect(deploy_token.revoked?).to be_truthy
+    end
+  end
+
+  describe "#active?" do
+    context "when it has been revoked" do
+      it 'should return false' do
+        deploy_token.revoke!
+        expect(deploy_token.active?).to be_falsy
+      end
+    end
+
+    context "when it hasn't been revoked" do
+      it 'should return true' do
+        expect(deploy_token.active?).to be_truthy
+      end
+    end
+  end
+
+  describe '#username' do
+    it 'returns a harcoded username' do
+      expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}")
+    end
+  end
+
+  describe '#has_access_to?' do
+    let(:project) { create(:project) }
+
+    subject { deploy_token.has_access_to?(project) }
+
+    context 'when deploy token is active and related to project' do
+      let(:deploy_token) { create(:deploy_token, projects: [project]) }
+
+      it { is_expected.to be_truthy }
+    end
+
+    context 'when deploy token is active but not related to project' do
+      let(:deploy_token) { create(:deploy_token) }
+
+      it { is_expected.to be_falsy }
+    end
+
+    context 'when deploy token is revoked and related to project' do
+      let(:deploy_token) { create(:deploy_token, :revoked, projects: [project]) }
+
+      it { is_expected.to be_falsy }
+    end
+
+    context 'when deploy token is revoked and not related to the project' do
+      let(:deploy_token) { create(:deploy_token, :revoked) }
+
+      it { is_expected.to be_falsy }
+    end
+  end
+
+  describe '#expires_at' do
+    context 'when using Forever.date' do
+      let(:deploy_token) { create(:deploy_token, expires_at: nil) }
+
+      it 'should return nil' do
+        expect(deploy_token.expires_at).to be_nil
+      end
+    end
+
+    context 'when using a personalized date' do
+      let(:expires_at) { Date.today + 5.months }
+      let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
+
+      it 'should return the personalized date' do
+        expect(deploy_token.expires_at).to eq(expires_at)
+      end
+    end
+  end
+
+  describe '#expires_at=' do
+    context 'when passing nil' do
+      let(:deploy_token) { create(:deploy_token, expires_at: nil) }
+
+      it 'should assign Forever.date' do
+        expect(deploy_token.read_attribute(:expires_at)).to eq(Forever.date)
+      end
+    end
+
+    context 'when passign a value' do
+      let(:expires_at) { Date.today + 5.months }
+      let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
+
+      it 'should respect the value' do
+        expect(deploy_token.read_attribute(:expires_at)).to eq(expires_at)
+      end
+    end
+  end
+end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index ba8aa13d5adf37e23f0a38ab8babf1675dfc9fc4..ac30cd27e0c9bc0877d55a288260b0e5cde3f2bf 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -64,6 +64,7 @@ describe Deployment do
 
   describe '#metrics' do
     let(:deployment) { create(:deployment) }
+    let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
 
     subject { deployment.metrics }
 
@@ -76,17 +77,16 @@ describe Deployment do
         {
           success: true,
           metrics: {},
-          last_update: 42,
-          deployment_time: 1494408956
+          last_update: 42
         }
       end
 
       before do
-        allow(deployment.project).to receive_message_chain(:monitoring_service, :deployment_metrics)
-                                       .with(any_args).and_return(simple_metrics)
+        allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+        allow(prometheus_adapter).to receive(:query).with(:deployment, deployment).and_return(simple_metrics)
       end
 
-      it { is_expected.to eq(simple_metrics) }
+      it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) }
     end
   end
 
@@ -109,11 +109,11 @@ describe Deployment do
         }
       end
 
-      let(:prometheus_service) { double('prometheus_service') }
+      let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
 
       before do
-        allow(project).to receive(:prometheus_service).and_return(prometheus_service)
-        allow(prometheus_service).to receive(:additional_deployment_metrics).and_return(simple_metrics)
+        allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+        allow(prometheus_adapter).to receive(:query).with(:additional_metrics_deployment, deployment).and_return(simple_metrics)
       end
 
       it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) }
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 6f24a039998c85f8b34e0988853641de7765b99b..56161bfcc28a7978925c03542ed6a6e3383ced8f 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Environment do
-  set(:project) { create(:project) }
+  let(:project) { create(:project) }
   subject(:environment) { create(:environment, project: project) }
 
   it { is_expected.to belong_to(:project) }
@@ -142,15 +142,15 @@ describe Environment do
     let(:commit)        { project.commit.parent }
 
     it 'returns deployment id for the environment' do
-      expect(environment.first_deployment_for(commit)).to eq deployment1
+      expect(environment.first_deployment_for(commit.id)).to eq deployment1
     end
 
     it 'return nil when no deployment is found' do
-      expect(environment.first_deployment_for(head_commit)).to eq nil
+      expect(environment.first_deployment_for(head_commit.id)).to eq nil
     end
 
     it 'returns a UTF-8 ref' do
-      expect(environment.first_deployment_for(commit).ref).to be_utf8
+      expect(environment.first_deployment_for(commit.id).ref).to be_utf8
     end
   end
 
@@ -368,6 +368,32 @@ describe Environment do
     end
   end
 
+  describe '#deployment_platform' do
+    context 'when there is a deployment platform for environment' do
+      let!(:cluster) do
+        create(:cluster, :provided_by_gcp,
+               environment_scope: '*', projects: [project])
+      end
+
+      it 'finds a deployment platform' do
+        expect(environment.deployment_platform).to eq cluster.platform
+      end
+    end
+
+    context 'when there is no deployment platform for environment' do
+      it 'returns nil' do
+        expect(environment.deployment_platform).to be_nil
+      end
+    end
+
+    it 'checks deployment platforms associated with a project' do
+      expect(project).to receive(:deployment_platform)
+        .with(environment: environment.name)
+
+      environment.deployment_platform
+    end
+  end
+
   describe '#terminals' do
     subject { environment.terminals }
 
@@ -452,8 +478,8 @@ describe Environment do
       end
 
       it 'returns the metrics from the deployment service' do
-        expect(project.monitoring_service)
-          .to receive(:environment_metrics).with(environment)
+        expect(environment.prometheus_adapter)
+          .to receive(:query).with(:environment, environment)
           .and_return(:fake_metrics)
 
         is_expected.to eq(:fake_metrics)
@@ -508,12 +534,12 @@ describe Environment do
 
     context 'when the environment has additional metrics' do
       before do
-        allow(environment).to receive(:has_additional_metrics?).and_return(true)
+        allow(environment).to receive(:has_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)
+        expect(environment.prometheus_adapter).to receive(:query)
+                                                .with(:additional_metrics_environment, environment)
                                                 .and_return(:fake_metrics)
 
         is_expected.to eq(:fake_metrics)
@@ -522,46 +548,13 @@ describe Environment do
 
     context 'when the environment does not have metrics' do
       before do
-        allow(environment).to receive(:has_additional_metrics?).and_return(false)
+        allow(environment).to receive(:has_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
@@ -654,4 +647,12 @@ describe Environment do
       end
     end
   end
+
+  describe '#prometheus_adapter' do
+    it 'calls prometheus adapter service' do
+      expect_any_instance_of(Prometheus::AdapterService).to receive(:prometheus_adapter)
+
+      subject.prometheus_adapter
+    end
+  end
 end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 67f49348acb80e234937c7be991de6eac1527ff8..8ea924100228acc254c8510e4ab1df92f983dc9b 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -49,6 +49,22 @@ describe Event do
         end
       end
     end
+
+    describe 'after_create :track_user_interacted_projects' do
+      let(:event) { build(:push_event, project: project, author: project.owner) }
+
+      it 'passes event to UserInteractedProject.track' do
+        expect(UserInteractedProject).to receive(:available?).and_return(true)
+        expect(UserInteractedProject).to receive(:track).with(event)
+        event.save
+      end
+
+      it 'does not call UserInteractedProject.track if its not yet available' do
+        expect(UserInteractedProject).to receive(:available?).and_return(false)
+        expect(UserInteractedProject).not_to receive(:track)
+        event.save
+      end
+    end
   end
 
   describe "Push event" do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 4f16b73ef38938b1f1f4874c0ca5669f7960e85f..d620943693c879da0759dc048c2fbbd5e480b635 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -18,6 +18,7 @@ describe Group do
     it { is_expected.to have_many(:uploads).dependent(:destroy) }
     it { is_expected.to have_one(:chat_team) }
     it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
+    it { is_expected.to have_many(:badges).class_name('GroupBadge') }
 
     describe '#members & #requesters' do
       let(:requester) { create(:user) }
@@ -239,7 +240,7 @@ describe Group do
 
     it "is false if avatar is html page" do
       group.update_attribute(:avatar, 'uploads/avatar.html')
-      expect(group.avatar_type).to eq(["only images allowed"])
+      expect(group.avatar_type).to eq(["file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff"])
     end
   end
 
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8ef91e8fab5dcb9a6582826de08cd572762b0023
--- /dev/null
+++ b/spec/models/internal_id_spec.rb
@@ -0,0 +1,139 @@
+require 'spec_helper'
+
+describe InternalId do
+  let(:project) { create(:project) }
+  let(:usage) { :issues }
+  let(:issue) { build(:issue, project: project) }
+  let(:scope) { { project: project } }
+  let(:init) { ->(s) { s.project.issues.maximum(:iid) } }
+
+  context 'validations' do
+    it { is_expected.to validate_presence_of(:usage) }
+  end
+
+  describe '.generate_next' do
+    subject { described_class.generate_next(issue, scope, usage, init) }
+
+    context 'in the absence of a record' do
+      it 'creates a record if not yet present' do
+        expect { subject }.to change { described_class.count }.from(0).to(1)
+      end
+
+      it 'stores record attributes' do
+        subject
+
+        described_class.first.tap do |record|
+          expect(record.project).to eq(project)
+          expect(record.usage).to eq(usage.to_s)
+        end
+      end
+
+      context 'with existing issues' do
+        before do
+          rand(1..10).times { create(:issue, project: project) }
+          described_class.delete_all
+        end
+
+        it 'calculates last_value values automatically' do
+          expect(subject).to eq(project.issues.size + 1)
+        end
+      end
+
+      context 'with an InternalId record present and existing issues with a higher internal id' do
+        # This can happen if the old NonatomicInternalId is still in use
+        before do
+          issues = Array.new(rand(1..10)).map { create(:issue, project: project) }
+
+          issue = issues.last
+          issue.iid = issues.map { |i| i.iid }.max + 1
+          issue.save
+        end
+
+        let(:maximum_iid) { project.issues.map { |i| i.iid }.max }
+
+        it 'updates last_value to the maximum internal id present' do
+          subject
+
+          expect(described_class.find_by(project: project, usage: described_class.usages[usage.to_s]).last_value).to eq(maximum_iid + 1)
+        end
+
+        it 'returns next internal id correctly' do
+          expect(subject).to eq(maximum_iid + 1)
+        end
+      end
+
+      context 'with concurrent inserts on table' do
+        it 'looks up the record if it was created concurrently' do
+          args = { **scope, usage: described_class.usages[usage.to_s] }
+          record = double
+          expect(described_class).to receive(:find_by).with(args).and_return(nil)    # first call, record not present
+          expect(described_class).to receive(:find_by).with(args).and_return(record) # second call, record was created by another process
+          expect(described_class).to receive(:create!).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
+          expect(record).to receive(:increment_and_save!)
+
+          subject
+        end
+      end
+    end
+
+    it 'generates a strictly monotone, gapless sequence' do
+      seq = (0..rand(100)).map do
+        described_class.generate_next(issue, scope, usage, init)
+      end
+      normalized = seq.map { |i| i - seq.min }
+
+      expect(normalized).to eq((0..seq.size - 1).to_a)
+    end
+
+    context 'with an insufficient schema version' do
+      before do
+        described_class.reset_column_information
+        expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
+      end
+
+      let(:init) { double('block') }
+
+      it 'calculates next internal ids on the fly' do
+        val = rand(1..100)
+
+        expect(init).to receive(:call).with(issue).and_return(val)
+        expect(subject).to eq(val + 1)
+      end
+    end
+  end
+
+  describe '#increment_and_save!' do
+    let(:id) { create(:internal_id) }
+    let(:maximum_iid) { nil }
+    subject { id.increment_and_save!(maximum_iid) }
+
+    it 'returns incremented iid' do
+      value = id.last_value
+
+      expect(subject).to eq(value + 1)
+    end
+
+    it 'saves the record' do
+      subject
+
+      expect(id.changed?).to be_falsey
+    end
+
+    context 'with last_value=nil' do
+      let(:id) { build(:internal_id, last_value: nil) }
+
+      it 'returns 1' do
+        expect(subject).to eq(1)
+      end
+    end
+
+    context 'with maximum_iid given' do
+      let(:id) { create(:internal_id, last_value: 1) }
+      let(:maximum_iid) { id.last_value + 10 }
+
+      it 'returns maximum_iid instead' do
+        expect(subject).to eq(12)
+      end
+    end
+  end
+end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index feed7968f09bb6a13614513a90281f95405f675b..111542913689c3e9d600a4bc7a5c26b567c4b3b3 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -9,11 +9,17 @@ describe Issue do
   describe 'modules' do
     subject { described_class }
 
-    it { is_expected.to include_module(InternalId) }
     it { is_expected.to include_module(Issuable) }
     it { is_expected.to include_module(Referable) }
     it { is_expected.to include_module(Sortable) }
     it { is_expected.to include_module(Taskable) }
+
+    it_behaves_like 'AtomicInternalId' do
+      let(:internal_id_attribute) { :iid }
+      let(:instance) { build(:issue) }
+      let(:scope_attrs) { { project: instance.project } }
+      let(:usage) { :issues }
+    end
   end
 
   subject { create(:issue) }
diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ba06ff42d873a4dbfdc4cf68f363de48aad59bbd
--- /dev/null
+++ b/spec/models/lfs_object_spec.rb
@@ -0,0 +1,124 @@
+require 'spec_helper'
+
+describe LfsObject do
+  describe '#local_store?' do
+    it 'returns true when file_store is nil' do
+      subject.file_store = nil
+
+      expect(subject.local_store?).to eq true
+    end
+
+    it 'returns true when file_store is equal to LfsObjectUploader::Store::LOCAL' do
+      subject.file_store = LfsObjectUploader::Store::LOCAL
+
+      expect(subject.local_store?).to eq true
+    end
+
+    it 'returns false whe file_store is equal to LfsObjectUploader::Store::REMOTE' do
+      subject.file_store = LfsObjectUploader::Store::REMOTE
+
+      expect(subject.local_store?).to eq false
+    end
+  end
+
+  describe '#schedule_background_upload' do
+    before do
+      stub_lfs_setting(enabled: true)
+    end
+
+    subject { create(:lfs_object, :with_file) }
+
+    context 'when object storage is disabled' do
+      before do
+        stub_lfs_object_storage(enabled: false)
+      end
+
+      it 'does not schedule the migration' do
+        expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+        subject
+      end
+    end
+
+    context 'when object storage is enabled' do
+      context 'when background upload is enabled' do
+        context 'when is licensed' do
+          before do
+            stub_lfs_object_storage(background_upload: true)
+          end
+
+          it 'schedules the model for migration' do
+            expect(ObjectStorage::BackgroundMoveWorker)
+              .to receive(:perform_async)
+              .with('LfsObjectUploader', described_class.name, :file, kind_of(Numeric))
+              .once
+
+            subject
+          end
+
+          it 'schedules the model for migration once' do
+            expect(ObjectStorage::BackgroundMoveWorker)
+              .to receive(:perform_async)
+              .with('LfsObjectUploader', described_class.name, :file, kind_of(Numeric))
+              .once
+
+            lfs_object = create(:lfs_object)
+            lfs_object.file = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png")
+            lfs_object.save!
+          end
+        end
+      end
+
+      context 'when background upload is disabled' do
+        before do
+          stub_lfs_object_storage(background_upload: false)
+        end
+
+        it 'schedules the model for migration' do
+          expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+          subject
+        end
+      end
+    end
+
+    describe 'file is being stored' do
+      let(:lfs_object) { create(:lfs_object, :with_file) }
+
+      context 'when object has nil store' do
+        before do
+          lfs_object.update_column(:file_store, nil)
+          lfs_object.reload
+        end
+
+        it 'is stored locally' do
+          expect(lfs_object.file_store).to be(nil)
+          expect(lfs_object.file).to be_file_storage
+          expect(lfs_object.file.object_store).to eq(ObjectStorage::Store::LOCAL)
+        end
+      end
+
+      context 'when existing object has local store' do
+        it 'is stored locally' do
+          expect(lfs_object.file_store).to be(ObjectStorage::Store::LOCAL)
+          expect(lfs_object.file).to be_file_storage
+          expect(lfs_object.file.object_store).to eq(ObjectStorage::Store::LOCAL)
+        end
+      end
+
+      context 'when direct upload is enabled' do
+        before do
+          stub_lfs_object_storage(direct_upload: true)
+        end
+
+        context 'when file is stored' do
+          it 'is stored remotely' do
+            expect(lfs_object.file_store).to eq(ObjectStorage::Store::REMOTE)
+            expect(lfs_object.file).not_to be_file_storage
+            expect(lfs_object.file.object_store).to eq(ObjectStorage::Store::REMOTE)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 3e46fa363751bc05e7a821c1bdc12f18f1b6a76f..b8b0e63f92e8098211ee47fb5fd91c11fd4b96f0 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -45,14 +45,6 @@ describe ProjectMember do
     let(:project) { owner.project }
     let(:master)  { create(:project_member, project: project) }
 
-    let(:owner_todos)  { (0...2).map { create(:todo, user: owner.user, project: project) } }
-    let(:master_todos) { (0...3).map { create(:todo, user: master.user, project: project) } }
-
-    before do
-      owner_todos
-      master_todos
-    end
-
     it "creates an expired event when left due to expiry" do
       expired = create(:project_member, project: project, expires_at: Time.now - 6.days)
       expired.destroy
@@ -63,21 +55,6 @@ describe ProjectMember do
       master.destroy
       expect(Event.recent.first.action).to eq(Event::LEFT)
     end
-
-    it "destroys itself and delete associated todos" do
-      expect(owner.user.todos.size).to eq(2)
-      expect(master.user.todos.size).to eq(3)
-      expect(Todo.count).to eq(5)
-
-      master_todo_ids = master_todos.map(&:id)
-      master.destroy
-
-      expect(owner.user.todos.size).to eq(2)
-      expect(Todo.count).to eq(2)
-      master_todo_ids.each do |id|
-        expect(Todo.exists?(id)).to eq(false)
-      end
-    end
   end
 
   describe '.import_team' do
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index 7709cf432007a768922bfa3fd629d9277c097e9a..8c01a7ac18f63c5c27493b04adfde6ad5d51b07a 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -36,7 +36,7 @@ describe MergeRequestDiffCommit do
           "committer_email": "dmitriy.zaporozhets@gmail.com",
           "merge_request_diff_id": merge_request_diff_id,
           "relative_order": 0,
-          "sha": sha_attribute.type_cast_for_database('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+          "sha": sha_attribute.serialize("5937ac0a7beb003549fc5fd26fc247adbce4a52e")
         },
         {
           "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
@@ -48,7 +48,7 @@ describe MergeRequestDiffCommit do
           "committer_email": "dmitriy.zaporozhets@gmail.com",
           "merge_request_diff_id": merge_request_diff_id,
           "relative_order": 1,
-          "sha": sha_attribute.type_cast_for_database('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+          "sha": sha_attribute.serialize("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
         }
       ]
     end
@@ -79,7 +79,7 @@ describe MergeRequestDiffCommit do
           "committer_email": "alejorro70@gmail.com",
           "merge_request_diff_id": merge_request_diff_id,
           "relative_order": 0,
-          "sha": sha_attribute.type_cast_for_database('ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69')
+          "sha": sha_attribute.serialize("ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69")
         }]
       end
 
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 243eeddc7a89b8be1044bc2b42877278abf0b406..f73f44ca0adc6045067bbe20335d6c3311445ca1 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -17,7 +17,7 @@ describe MergeRequest do
   describe 'modules' do
     subject { described_class }
 
-    it { is_expected.to include_module(InternalId) }
+    it { is_expected.to include_module(NonatomicInternalId) }
     it { is_expected.to include_module(Issuable) }
     it { is_expected.to include_module(Referable) }
     it { is_expected.to include_module(Sortable) }
@@ -1544,7 +1544,7 @@ describe MergeRequest do
     end
 
     it "executes diff cache service" do
-      expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
+      expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject, an_instance_of(MergeRequestDiff))
 
       subject.reload_diff
     end
@@ -1961,6 +1961,17 @@ describe MergeRequest do
         expect(subject.merge_request_diff_for(merge_request_diff3.head_commit_sha)).to eq(merge_request_diff3)
       end
     end
+
+    it 'runs a single query on the initial call, and none afterwards' do
+      expect { subject.merge_request_diff_for(merge_request_diff1.diff_refs) }
+        .not_to exceed_query_limit(1)
+
+      expect { subject.merge_request_diff_for(merge_request_diff2.diff_refs) }
+        .not_to exceed_query_limit(0)
+
+      expect { subject.merge_request_diff_for(merge_request_diff3.head_commit_sha) }
+        .not_to exceed_query_limit(0)
+    end
   end
 
   describe '#version_params_for' do
@@ -2084,4 +2095,82 @@ describe MergeRequest do
       it_behaves_like 'checking whether a rebase is in progress'
     end
   end
+
+  describe '#allow_maintainer_to_push' do
+    let(:merge_request) do
+      build(:merge_request, source_branch: 'fixes', allow_maintainer_to_push: true)
+    end
+
+    it 'is false when pushing by a maintainer is not possible' do
+      expect(merge_request).to receive(:maintainer_push_possible?) { false }
+
+      expect(merge_request.allow_maintainer_to_push).to be_falsy
+    end
+
+    it 'is true when pushing by a maintainer is possible' do
+      expect(merge_request).to receive(:maintainer_push_possible?) { true }
+
+      expect(merge_request.allow_maintainer_to_push).to be_truthy
+    end
+  end
+
+  describe '#maintainer_push_possible?' do
+    let(:merge_request) do
+      build(:merge_request, source_branch: 'fixes')
+    end
+
+    before do
+      allow(ProtectedBranch).to receive(:protected?) { false }
+    end
+
+    it 'does not allow maintainer to push if the source project is the same as the target' do
+      merge_request.target_project = merge_request.source_project = create(:project, :public)
+
+      expect(merge_request.maintainer_push_possible?).to be_falsy
+    end
+
+    it 'allows maintainer to push when both source and target are public' do
+      merge_request.target_project = build(:project, :public)
+      merge_request.source_project = build(:project, :public)
+
+      expect(merge_request.maintainer_push_possible?).to be_truthy
+    end
+
+    it 'is not available for protected branches' do
+      merge_request.target_project = build(:project, :public)
+      merge_request.source_project = build(:project, :public)
+
+      expect(ProtectedBranch).to receive(:protected?)
+                                   .with(merge_request.source_project, 'fixes')
+                                   .and_return(true)
+
+      expect(merge_request.maintainer_push_possible?).to be_falsy
+    end
+  end
+
+  describe '#can_allow_maintainer_to_push?' do
+    let(:target_project) { create(:project, :public) }
+    let(:source_project) { fork_project(target_project) }
+    let(:merge_request) do
+      create(:merge_request,
+             source_project: source_project,
+             source_branch: 'fixes',
+             target_project: target_project)
+    end
+    let(:user) { create(:user) }
+
+    before do
+      allow(merge_request).to receive(:maintainer_push_possible?) { true }
+    end
+
+    it 'is false if the user does not have push access to the source project' do
+      expect(merge_request.can_allow_maintainer_to_push?(user)).to be_falsy
+    end
+
+    it 'is true when the user has push access to the source project' do
+      source_project.add_developer(user)
+
+      expect(merge_request.can_allow_maintainer_to_push?(user)).to be_truthy
+    end
+  end
 end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 47f4a792e5cd04d020f54045b8ef0e63a10ee050..c7460981a32e2bcd7157460fb3e23863d469cf5c 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -96,7 +96,9 @@ describe Milestone do
         allow(milestone).to receive(:due_date).and_return(Date.today.prev_year)
       end
 
-      it { expect(milestone.expired?).to be_truthy }
+      it 'returns true when due_date is in the past' do
+        expect(milestone.expired?).to be_truthy
+      end
     end
 
     context "not expired" do
@@ -104,17 +106,19 @@ describe Milestone do
         allow(milestone).to receive(:due_date).and_return(Date.today.next_year)
       end
 
-      it { expect(milestone.expired?).to be_falsey }
+      it 'returns false when due_date is in the future' do
+        expect(milestone.expired?).to be_falsey
+      end
     end
   end
 
   describe '#upcoming?' do
-    it 'returns true' do
+    it 'returns true when start_date is in the future' do
       milestone = build(:milestone, start_date: Time.now + 1.month)
       expect(milestone.upcoming?).to be_truthy
     end
 
-    it 'returns false' do
+    it 'returns false when start_date is in the past' do
       milestone = build(:milestone, start_date: Date.today.prev_year)
       expect(milestone.upcoming?).to be_falsey
     end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e626efd054d3ff605e32bfee330f82e48a9ed1fa..62e95a622eb41895cca255368bac8f5f43e658a2 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -204,43 +204,67 @@ describe Namespace do
         expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy
       end
 
-      context 'with subgroups' do
+      context 'with subgroups', :nested_groups do
         let(:parent) { create(:group, name: 'parent', path: 'parent') }
+        let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
         let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
         let!(:project) { create(:project_empty_repo, :legacy_storage, path: 'the-project', namespace: child, skip_disk_validation: true) }
         let(:uploads_dir) { FileUploader.root }
         let(:pages_dir) { File.join(TestEnv.pages_path) }
 
+        def expect_project_directories_at(namespace_path)
+          expected_repository_path = File.join(TestEnv.repos_path, namespace_path, 'the-project.git')
+          expected_upload_path = File.join(uploads_dir, namespace_path, 'the-project')
+          expected_pages_path = File.join(pages_dir, namespace_path, 'the-project')
+
+          expect(File.directory?(expected_repository_path)).to be_truthy
+          expect(File.directory?(expected_upload_path)).to be_truthy
+          expect(File.directory?(expected_pages_path)).to be_truthy
+        end
+
         before do
+          FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project.full_path}.git"))
           FileUtils.mkdir_p(File.join(uploads_dir, project.full_path))
           FileUtils.mkdir_p(File.join(pages_dir, project.full_path))
         end
 
         context 'renaming child' do
           it 'correctly moves the repository, uploads and pages' do
-            expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git')
-            expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project')
-            expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project')
+            child.update!(path: 'renamed')
 
-            child.update_attributes!(path: 'renamed')
-
-            expect(File.directory?(expected_repository_path)).to be(true)
-            expect(File.directory?(expected_upload_path)).to be(true)
-            expect(File.directory?(expected_pages_path)).to be(true)
+            expect_project_directories_at('parent/renamed')
           end
         end
 
         context 'renaming parent' do
           it 'correctly moves the repository, uploads and pages' do
-            expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git')
-            expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project')
-            expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project')
+            parent.update!(path: 'renamed')
+
+            expect_project_directories_at('renamed/child')
+          end
+        end
+
+        context 'moving from one parent to another' do
+          it 'correctly moves the repository, uploads and pages' do
+            child.update!(parent: new_parent)
 
-            parent.update_attributes!(path: 'renamed')
+            expect_project_directories_at('new_parent/child')
+          end
+        end
+
+        context 'moving from having a parent to root' do
+          it 'correctly moves the repository, uploads and pages' do
+            child.update!(parent: nil)
+
+            expect_project_directories_at('child')
+          end
+        end
+
+        context 'moving from root to having a parent' do
+          it 'correctly moves the repository, uploads and pages' do
+            parent.update!(parent: new_parent)
 
-            expect(File.directory?(expected_repository_path)).to be(true)
-            expect(File.directory?(expected_upload_path)).to be(true)
-            expect(File.directory?(expected_pages_path)).to be(true)
+            expect_project_directories_at('new_parent/parent/child')
           end
         end
       end
@@ -281,7 +305,7 @@ describe Namespace do
   end
 
   describe '#rm_dir', 'callback' do
-    let(:repository_storage_path) { Gitlab.config.repositories.storages.default['path'] }
+    let(:repository_storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path }
     let(:path_in_dir) { File.join(repository_storage_path, namespace.full_path) }
     let(:deleted_path) { namespace.full_path.gsub(namespace.path, "#{namespace.full_path}+#{namespace.id}+deleted") }
     let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) }
@@ -525,7 +549,6 @@ describe Namespace do
       end
     end
 
-    # Note: Group transfers are not yet implemented
     context 'when a group is transferred into a root group' do
       context 'when the root group "Share with group lock" is enabled' do
         let(:root_group) { create(:group, share_with_group_lock: true) }
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index c853f707e6d6f285e233af5c5c44bada410a1a6b..6a6c71e6c825a7023d6032236bd180240025473a 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -91,6 +91,23 @@ describe Note do
     it "keeps the commit around" do
       expect(note.project.repository.kept_around?(commit.id)).to be_truthy
     end
+
+    it 'does not generate N+1 queries for participants', :request_store do
+      def retrieve_participants
+        commit.notes_with_associations.map(&:participants).to_a
+      end
+
+      # Project authorization checks are cached, establish a baseline
+      retrieve_participants
+
+      control_count = ActiveRecord::QueryRecorder.new do
+        retrieve_participants
+      end
+
+      create(:note_on_commit, project: note.project, note: 'another note', noteable_id: commit.id)
+
+      expect { retrieve_participants }.not_to exceed_query_limit(control_count)
+    end
   end
 
   describe 'authorization' do
@@ -191,6 +208,21 @@ describe Note do
     end
   end
 
+  describe "confidential?" do
+    it "delegates to noteable" do
+      issue_note = build(:note, :on_issue)
+      confidential_note = build(:note, noteable: create(:issue, confidential: true))
+
+      expect(issue_note.confidential?).to be_falsy
+      expect(confidential_note.confidential?).to be_truthy
+    end
+
+    it "is falsey when noteable can't be confidential" do
+      commit_note = build(:note_on_commit)
+      expect(commit_note.confidential?).to be_falsy
+    end
+  end
+
   describe "cross_reference_not_visible_for?" do
     let(:private_user)    { create(:user) }
     let(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_master(private_user) } }
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 95713d8b85b23344be1a669353039f774f6e43da..4b85c5e8720739ec4a61c59ed4d7e5b6fba126c5 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -18,24 +18,63 @@ describe PagesDomain do
       it { is_expected.to validate_uniqueness_of(:domain).case_insensitive }
     end
 
-    {
-      'my.domain.com'    => true,
-      '123.456.789'      => true,
-      '0x12345.com'      => true,
-      '0123123'          => true,
-      '_foo.com'         => false,
-      'reserved.com'     => false,
-      'a.reserved.com'   => false,
-      nil                => false
-    }.each do |value, validity|
-      context "domain #{value.inspect} validity" do
-        before do
-          allow(Settings.pages).to receive(:host).and_return('reserved.com')
+    describe "hostname" do
+      {
+        'my.domain.com'    => true,
+        '123.456.789'      => true,
+        '0x12345.com'      => true,
+        '0123123'          => true,
+        '_foo.com'         => false,
+        'reserved.com'     => false,
+        'a.reserved.com'   => false,
+        nil                => false
+      }.each do |value, validity|
+        context "domain #{value.inspect} validity" do
+          before do
+            allow(Settings.pages).to receive(:host).and_return('reserved.com')
+          end
+
+          let(:domain) { value }
+
+          it { expect(pages_domain.valid?).to eq(validity) }
+        end
+      end
+    end
+
+    describe "HTTPS-only" do
+      using RSpec::Parameterized::TableSyntax
+
+      let(:domain) { 'my.domain.com' }
+
+      let(:project) do
+        instance_double(Project, pages_https_only?: pages_https_only)
+      end
+
+      let(:pages_domain) do
+        build(:pages_domain, certificate: certificate, key: key).tap do |pd|
+          allow(pd).to receive(:project).and_return(project)
+          pd.valid?
         end
+      end
 
-        let(:domain) { value }
+      where(:pages_https_only, :certificate, :key, :errors_on) do
+        attributes = attributes_for(:pages_domain)
+        cert, key = attributes.fetch_values(:certificate, :key)
+
+        true  | nil  | nil | %i(certificate key)
+        true  | cert | nil | %i(key)
+        true  | nil  | key | %i(certificate key)
+        true  | cert | key | []
+        false | nil  | nil | []
+        false | cert | nil | %i(key)
+        false | nil  | key | %i(key)
+        false | cert | key | []
+      end
 
-        it { expect(pages_domain.valid?).to eq(validity) }
+      with_them do
+        it "is adds the expected errors" do
+          expect(pages_domain.errors.keys).to eq errors_on
+        end
       end
     end
   end
@@ -43,26 +82,26 @@ describe PagesDomain do
   describe 'validate certificate' do
     subject { domain }
 
-    context 'when only certificate is specified' do
-      let(:domain) { build(:pages_domain, :with_certificate) }
+    context 'with matching key' do
+      let(:domain) { build(:pages_domain) }
 
-      it { is_expected.not_to be_valid }
+      it { is_expected.to be_valid }
     end
 
-    context 'when only key is specified' do
-      let(:domain) { build(:pages_domain, :with_key) }
+    context 'when no certificate is specified' do
+      let(:domain) { build(:pages_domain, :without_certificate) }
 
       it { is_expected.not_to be_valid }
     end
 
-    context 'with matching key' do
-      let(:domain) { build(:pages_domain, :with_certificate, :with_key) }
+    context 'when no key is specified' do
+      let(:domain) { build(:pages_domain, :without_key) }
 
-      it { is_expected.to be_valid }
+      it { is_expected.not_to be_valid }
     end
 
     context 'for not matching key' do
-      let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) }
+      let(:domain) { build(:pages_domain, :with_missing_chain) }
 
       it { is_expected.not_to be_valid }
     end
@@ -103,30 +142,26 @@ describe PagesDomain do
   describe '#url' do
     subject { domain.url }
 
-    context 'without the certificate' do
-      let(:domain) { build(:pages_domain, certificate: '') }
+    let(:domain) { build(:pages_domain) }
 
-      it { is_expected.to eq("http://#{domain.domain}") }
-    end
+    it { is_expected.to eq("https://#{domain.domain}") }
 
-    context 'with a certificate' do
-      let(:domain) { build(:pages_domain, :with_certificate) }
+    context 'without the certificate' do
+      let(:domain) { build(:pages_domain, :without_certificate) }
 
-      it { is_expected.to eq("https://#{domain.domain}") }
+      it { is_expected.to eq("http://#{domain.domain}") }
     end
   end
 
   describe '#has_matching_key?' do
     subject { domain.has_matching_key? }
 
-    context 'for matching key' do
-      let(:domain) { build(:pages_domain, :with_certificate, :with_key) }
+    let(:domain) { build(:pages_domain) }
 
-      it { is_expected.to be_truthy }
-    end
+    it { is_expected.to be_truthy }
 
     context 'for invalid key' do
-      let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) }
+      let(:domain) { build(:pages_domain, :with_missing_chain) }
 
       it { is_expected.to be_falsey }
     end
@@ -136,7 +171,7 @@ describe PagesDomain do
     subject { domain.has_intermediates? }
 
     context 'for self signed' do
-      let(:domain) { build(:pages_domain, :with_certificate) }
+      let(:domain) { build(:pages_domain) }
 
       it { is_expected.to be_truthy }
     end
@@ -162,7 +197,7 @@ describe PagesDomain do
     subject { domain.expired? }
 
     context 'for valid' do
-      let(:domain) { build(:pages_domain, :with_certificate) }
+      let(:domain) { build(:pages_domain) }
 
       it { is_expected.to be_falsey }
     end
@@ -175,7 +210,7 @@ describe PagesDomain do
   end
 
   describe '#subject' do
-    let(:domain) { build(:pages_domain, :with_certificate) }
+    let(:domain) { build(:pages_domain) }
 
     subject { domain.subject }
 
@@ -183,7 +218,7 @@ describe PagesDomain do
   end
 
   describe '#certificate_text' do
-    let(:domain) { build(:pages_domain, :with_certificate) }
+    let(:domain) { build(:pages_domain) }
 
     subject { domain.certificate_text }
 
@@ -191,6 +226,18 @@ describe PagesDomain do
     it { is_expected.not_to be_empty }
   end
 
+  describe "#https?" do
+    context "when a certificate is present" do
+      subject { build(:pages_domain) }
+      it { is_expected.to be_https }
+    end
+
+    context "when no certificate is present" do
+      subject { build(:pages_domain, :without_certificate) }
+      it { is_expected.not_to be_https }
+    end
+  end
+
   describe '#update_daemon' do
     it 'runs when the domain is created' do
       domain = build(:pages_domain)
@@ -267,29 +314,30 @@ describe PagesDomain do
       end
 
       context 'TLS configuration' do
-        set(:domain_with_tls) { create(:pages_domain, :with_key, :with_certificate) }
+        set(:domain_without_tls) { create(:pages_domain, :without_certificate, :without_key) }
+        set(:domain) { create(:pages_domain) }
 
-        let(:cert1) { domain_with_tls.certificate }
+        let(:cert1) { domain.certificate }
         let(:cert2) { cert1 + ' ' }
-        let(:key1) { domain_with_tls.key }
+        let(:key1) { domain.key }
         let(:key2) { key1 + ' ' }
 
         it 'updates when added' do
-          expect(domain).to receive(:update_daemon)
+          expect(domain_without_tls).to receive(:update_daemon)
 
-          domain.update!(key: key1, certificate: cert1)
+          domain_without_tls.update!(key: key1, certificate: cert1)
         end
 
         it 'updates when changed' do
-          expect(domain_with_tls).to receive(:update_daemon)
+          expect(domain).to receive(:update_daemon)
 
-          domain_with_tls.update!(key: key2, certificate: cert2)
+          domain.update!(key: key2, certificate: cert2)
         end
 
         it 'updates when removed' do
-          expect(domain_with_tls).to receive(:update_daemon)
+          expect(domain).to receive(:update_daemon)
 
-          domain_with_tls.update!(key: nil, certificate: nil)
+          domain.update!(key: nil, certificate: nil)
         end
       end
     end
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 296b91a771c83841df299d27c8c26044a772deb9..7545c0797e9a2188837f4d268bd8f93c92740e6d 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -36,14 +36,14 @@ describe ProjectAutoDevops do
     end
   end
 
-  describe '#variables' do
+  describe '#predefined_variables' do
     let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
 
     context 'when domain is defined' do
       let(:domain) { 'example.com' }
 
       it 'returns AUTO_DEVOPS_DOMAIN' do
-        expect(auto_devops.variables).to include(domain_variable)
+        expect(auto_devops.predefined_variables).to include(domain_variable)
       end
     end
 
@@ -55,7 +55,7 @@ describe ProjectAutoDevops do
           allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com')
         end
 
-        it { expect(auto_devops.variables).to include(domain_variable) }
+        it { expect(auto_devops.predefined_variables).to include(domain_variable) }
       end
 
       context 'when there is no instance domain specified' do
@@ -63,7 +63,7 @@ describe ProjectAutoDevops do
           allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil)
         end
 
-        it { expect(auto_devops.variables).not_to include(domain_variable) }
+        it { expect(auto_devops.predefined_variables).not_to include(domain_variable) }
       end
     end
 
diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e2e40c2e8f5729b9ac552bfc006d4dfebe47c14
--- /dev/null
+++ b/spec/models/project_deploy_token_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+RSpec.describe ProjectDeployToken, type: :model do
+  let(:project) { create(:project) }
+  let(:deploy_token) { create(:deploy_token) }
+  subject(:project_deploy_token) { create(:project_deploy_token, project: project, deploy_token: deploy_token) }
+
+  it { is_expected.to belong_to :project }
+  it { is_expected.to belong_to :deploy_token }
+
+  it { is_expected.to validate_presence_of :deploy_token }
+  it { is_expected.to validate_presence_of :project }
+  it { is_expected.to validate_uniqueness_of(:deploy_token_id).scoped_to(:project_id) }
+end
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 04440d890aa3c3e644263e64aac5a953c2bb8e04..e66109fd98f950030897f1ea43276ff116a95e5e 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -47,7 +47,7 @@ describe AsanaService do
 
     it 'calls Asana service to create a story' do
       data = create_data_for_commits('Message from commit. related to #123456')
-      expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.name_with_namespace} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}"
+      expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.full_name} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}"
 
       d1 = double('Asana::Task')
       expect(d1).to receive(:add_comment).with(text: expected_message)
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 23db29cb541e97ca5652337cc40026b90d429125..0cd712e2f40296b6451b3584ffacd64ec9df5ff5 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -29,7 +29,7 @@ describe HipchatService do
     let(:user)    { create(:user) }
     let(:project) { create(:project, :repository) }
     let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
-    let(:project_name) { project.name_with_namespace.gsub(/\s/, '') }
+    let(:project_name) { project.full_name.gsub(/\s/, '') }
     let(:token) { 'verySecret' }
     let(:server_url) { 'https://hipchat.example.com'}
     let(:push_sample_data) do
@@ -253,6 +253,21 @@ describe HipchatService do
               "<b>#{title}</b>" \
               "<pre>issue <strong>note</strong></pre>")
         end
+
+        context 'with confidential issue' do
+          before do
+            issue.update!(confidential: true)
+          end
+
+          it 'calls Hipchat API with issue comment' do
+            data = Gitlab::DataBuilder::Note.build(issue_note, user)
+            hipchat.execute(data)
+
+            message = hipchat.send(:create_message, data)
+
+            expect(message).to include("<pre>issue <strong>note</strong></pre>")
+          end
+        end
       end
 
       context 'when snippet comment event triggered' do
@@ -303,7 +318,7 @@ describe HipchatService do
           message = hipchat.__send__(:create_pipeline_message, data)
 
           project_url = project.web_url
-          project_name = project.name_with_namespace.gsub(/\s/, '')
+          project_name = project.full_name.gsub(/\s/, '')
           pipeline_attributes = data[:object_attributes]
           ref = pipeline_attributes[:ref]
           ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 748c366efca758d45b9e57fefa8a035a24b9edb5..54ef0be67ff248558c5e9c8f0aaf2a0afc0a8283 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -166,7 +166,6 @@ describe JiraService do
 
       # Creates comment
       expect(WebMock).to have_requested(:post, @comment_url)
-
       # Creates Remote Link in JIRA issue fields
       expect(WebMock).to have_requested(:post, @remote_link_url).with(
         body: hash_including(
@@ -174,7 +173,7 @@ describe JiraService do
           object: {
             url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}",
             title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.",
-            icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+            icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
             status: { resolved: true }
           }
         )
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 622d8844a72ecb298a81e56fbbc5a896d31b994e..3be023a48c19e5b580594a77797863a5bbd75d87 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -370,7 +370,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
         stub_kubeclient_pods(status: 500)
       end
 
-      it { expect { subject }.to raise_error(KubeException) }
+      it { expect { subject }.to raise_error(Kubeclient::HttpError) }
     end
 
     context 'when kubernetes responds with 404s' do
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 522cf15f3ba10555a03fa15f4cf16f16a759d2b4..05d33cd387429ba7f9725bd4980f1d1ab5c13062 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -9,10 +9,11 @@ describe MattermostSlashCommandsService do
     let(:user) { create(:user) }
 
     before do
-      Mattermost::Session.base_uri("http://mattermost.example.com")
+      session = Mattermost::Session.new(nil)
+      session.base_uri = 'http://mattermost.example.com'
 
       allow_any_instance_of(Mattermost::Client).to receive(:with_session)
-        .and_yield(Mattermost::Session.new(nil))
+        .and_yield(session)
     end
 
     describe '#configure' do
@@ -31,10 +32,10 @@ describe MattermostSlashCommandsService do
               url: 'http://trigger.url',
               icon_url: 'http://icon.url/icon.png',
               auto_complete: true,
-              auto_complete_desc: "Perform common operations on: #{project.name_with_namespace}",
+              auto_complete_desc: "Perform common operations on: #{project.full_name}",
               auto_complete_hint: '[help]',
-              description: "Perform common operations on: #{project.name_with_namespace}",
-              display_name: "GitLab / #{project.name_with_namespace}",
+              description: "Perform common operations on: #{project.full_name}",
+              display_name: "GitLab / #{project.full_name}",
               method: 'P',
               username: 'GitLab'
             }.to_json)
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index 6693e5783a56d30f76eb6d4453828293705f83fa..7afb1b4a8e3e4558a92bbce00e10f7672f371ae0 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -6,7 +6,6 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
 
   let(:project) { create(:prometheus_project) }
   let(:service) { project.prometheus_service }
-  let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery }
 
   describe "Associations" do
     it { is_expected.to belong_to :project }
@@ -55,197 +54,31 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
     end
   end
 
-  describe '#environment_metrics' do
-    let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
-
-    around do |example|
-      Timecop.freeze { example.run }
-    end
-
-    context 'with valid data' do
-      subject { service.environment_metrics(environment) }
-
-      before do
-        stub_reactive_cache(service, prometheus_data, environment_query, environment.id)
-      end
-
-      it 'returns reactive data' do
-        is_expected.to eq(prometheus_metrics_data)
-      end
-    end
-  end
-
-  describe '#matched_metrics' do
-    let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricsQuery }
-    let(:client) { double(:client, label_values: nil) }
-
-    context 'with valid data' do
-      subject { service.matched_metrics }
-
-      before do
-        allow(service).to receive(:client).and_return(client)
-        synchronous_reactive_cache(service)
-      end
-
-      it 'returns reactive data' do
-        expect(subject[:success]).to be_truthy
-        expect(subject[:data]).to eq([])
-      end
-    end
-  end
-
-  describe '#deployment_metrics' do
-    let(:deployment) { build_stubbed(:deployment) }
-    let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery }
-
-    around do |example|
-      Timecop.freeze { example.run }
-    end
-
-    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.environment.id, deployment.id)
-      end
-
-      it 'returns reactive data' do
-        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
-
-  describe '#calculate_reactive_cache' do
-    let(:environment) { create(:environment, slug: 'env-slug') }
-    before do
-      service.manual_configuration = true
-      service.active = true
-    end
-
-    subject do
-      service.calculate_reactive_cache(environment_query.name, environment.id)
-    end
-
-    around do |example|
-      Timecop.freeze { example.run }
-    end
-
-    context 'when service is inactive' do
-      before do
-        service.active = false
-      end
-
-      it { is_expected.to be_nil }
-    end
-
-    context 'when Prometheus responds with valid data' do
-      before do
-        stub_all_prometheus_requests(environment.slug)
-      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|
-      context "when Prometheus responds with #{status}" do
-        before do
-          stub_all_prometheus_requests(environment.slug, status: status, body: "QUERY FAILED!")
-        end
-
-        it { is_expected.to eq(success: false, result: %(#{status} - "QUERY FAILED!")) }
-      end
-    end
-  end
-
-  describe '#client' do
+  describe '#prometheus_client' do
     context 'manual configuration is enabled' do
       let(:api_url) { 'http://some_url' }
+
       before do
+        subject.active = true
         subject.manual_configuration = true
         subject.api_url = api_url
       end
 
-      it 'returns simple rest client from api_url' do
-        expect(subject.client).to be_instance_of(Gitlab::PrometheusClient)
-        expect(subject.client.rest_client.url).to eq(api_url)
+      it 'returns rest client from api_url' do
+        expect(subject.prometheus_client.url).to eq(api_url)
       end
     end
 
     context 'manual configuration is disabled' do
-      let!(:cluster_for_all) { create(:cluster, environment_scope: '*', projects: [project]) }
-      let!(:cluster_for_dev) { create(:cluster, environment_scope: 'dev', projects: [project]) }
-
-      let!(:prometheus_for_dev) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_dev) }
-      let(:proxy_client) { double('proxy_client') }
+      let(:api_url) { 'http://some_url' }
 
       before do
-        service.manual_configuration = false
-      end
-
-      context 'with cluster for all environments with prometheus installed' do
-        let!(:prometheus_for_all) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_all) }
-
-        context 'without environment supplied' do
-          it 'returns client handling all environments' do
-            expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice
-
-            expect(service.client).to be_instance_of(Gitlab::PrometheusClient)
-            expect(service.client.rest_client).to eq(proxy_client)
-          end
-        end
-
-        context 'with dev environment supplied' do
-          let!(:environment) { create(:environment, project: project, name: 'dev') }
-
-          it 'returns dev cluster client' do
-            expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice
-
-            expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient)
-            expect(service.client(environment.id).rest_client).to eq(proxy_client)
-          end
-        end
-
-        context 'with prod environment supplied' do
-          let!(:environment) { create(:environment, project: project, name: 'prod') }
-
-          it 'returns dev cluster client' do
-            expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice
-
-            expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient)
-            expect(service.client(environment.id).rest_client).to eq(proxy_client)
-          end
-        end
+        subject.manual_configuration = false
+        subject.api_url = api_url
       end
 
-      context 'with cluster for all environments without prometheus installed' do
-        context 'without environment supplied' do
-          it 'raises PrometheusClient::Error because cluster was not found' do
-            expect { service.client }.to raise_error(Gitlab::PrometheusClient::Error, /couldn't find cluster with Prometheus installed/)
-          end
-        end
-
-        context 'with dev environment supplied' do
-          let!(:environment) { create(:environment, project: project, name: 'dev') }
-
-          it 'returns dev cluster client' do
-            expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice
-
-            expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient)
-            expect(service.client(environment.id).rest_client).to eq(proxy_client)
-          end
-        end
-
-        context 'with prod environment supplied' do
-          let!(:environment) { create(:environment, project: project, name: 'prod') }
-
-          it 'raises PrometheusClient::Error because cluster was not found' do
-            expect { service.client }.to raise_error(Gitlab::PrometheusClient::Error, /couldn't find cluster with Prometheus installed/)
-          end
-        end
+      it 'no client provided' do
+        expect(subject.prometheus_client).to be_nil
       end
     end
   end
@@ -284,7 +117,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
     end
   end
 
-  describe '#synchronize_service_state! before_save callback' do
+  describe '#synchronize_service_state before_save callback' do
     context 'no clusters with prometheus are installed' do
       context 'when service is inactive' do
         before do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f4faec9e52a7a9ff9be85e5cfb3bfc2e657d14ee..2675c2f52c1b4bbc0c898dc5a2bd12feb337289c 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe Project do
+  include ProjectForksHelper
+
   describe 'associations' do
     it { is_expected.to belong_to(:group) }
     it { is_expected.to belong_to(:namespace) }
@@ -80,7 +82,10 @@ describe Project do
     it { is_expected.to have_many(:members_and_requesters) }
     it { is_expected.to have_many(:clusters) }
     it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
+    it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
     it { is_expected.to have_many(:lfs_file_locks) }
+    it { is_expected.to have_many(:project_deploy_tokens) }
+    it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
 
     context 'after initialized' do
       it "has a project_feature" do
@@ -221,14 +226,14 @@ describe Project do
       project2 = build(:project, import_url: 'http://localhost:9000/t.git')
 
       expect(project2).to be_invalid
-      expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
+      expect(project2.errors[:import_url].first).to include('Requests to localhost are not allowed')
     end
 
     it "does not allow blocked import_url port" do
       project2 = build(:project, import_url: 'http://github.com:25/t.git')
 
       expect(project2).to be_invalid
-      expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
+      expect(project2.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
     end
 
     describe 'project pending deletion' do
@@ -517,6 +522,20 @@ describe Project do
       it 'returns the project\'s last update date if it has no events' do
         expect(project.last_activity_date).to eq(project.updated_at)
       end
+
+      it 'returns the most recent timestamp' do
+        project.update_attributes(updated_at: nil,
+                                  last_activity_at: timestamp,
+                                  last_repository_updated_at: timestamp - 1.hour)
+
+        expect(project.last_activity_date).to eq(timestamp)
+
+        project.update_attributes(updated_at: timestamp,
+                                  last_activity_at: timestamp - 1.hour,
+                                  last_repository_updated_at: nil)
+
+        expect(project.last_activity_date).to eq(timestamp)
+      end
     end
   end
 
@@ -905,7 +924,7 @@ describe Project do
 
     it 'is false if avatar is html page' do
       project.update_attribute(:avatar, 'uploads/avatar.html')
-      expect(project.avatar_type).to eq(['only images allowed'])
+      expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff'])
     end
   end
 
@@ -1084,8 +1103,8 @@ describe Project do
 
     before do
       storages = {
-        'default' => { 'path' => 'tmp/tests/repositories' },
-        'picked'  => { 'path' => 'tmp/tests/repositories' }
+        'default' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories'),
+        'picked'  => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories')
       }
       allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
     end
@@ -1248,6 +1267,34 @@ describe Project do
     end
   end
 
+  describe '#pages_group_url' do
+    let(:group) { create :group, name: group_name }
+    let(:project) { create :project, namespace: group, name: project_name }
+    let(:domain) { 'Example.com' }
+    let(:port) { 1234 }
+
+    subject { project.pages_group_url }
+
+    before do
+      allow(Settings.pages).to receive(:host).and_return(domain)
+      allow(Gitlab.config.pages).to receive(:url).and_return("http://example.com:#{port}")
+    end
+
+    context 'group page' do
+      let(:group_name) { 'Group' }
+      let(:project_name) { 'group.example.com' }
+
+      it { is_expected.to eq("http://group.example.com:#{port}") }
+    end
+
+    context 'project page' do
+      let(:group_name) { 'Group' }
+      let(:project_name) { 'Project' }
+
+      it { is_expected.to eq("http://group.example.com:#{port}") }
+    end
+  end
+
   describe '.search' do
     let(:project) { create(:project, description: 'kitten mittens') }
 
@@ -1361,7 +1408,7 @@ describe Project do
 
     context 'using a regular repository' do
       it 'creates the repository' do
-        expect(shell).to receive(:add_repository)
+        expect(shell).to receive(:create_repository)
           .with(project.repository_storage, project.disk_path)
           .and_return(true)
 
@@ -1371,7 +1418,7 @@ describe Project do
       end
 
       it 'adds an error if the repository could not be created' do
-        expect(shell).to receive(:add_repository)
+        expect(shell).to receive(:create_repository)
           .with(project.repository_storage, project.disk_path)
           .and_return(false)
 
@@ -1385,7 +1432,7 @@ describe Project do
     context 'using a forked repository' do
       it 'does nothing' do
         expect(project).to receive(:forked?).and_return(true)
-        expect(shell).not_to receive(:add_repository)
+        expect(shell).not_to receive(:create_repository)
 
         project.create_repository
       end
@@ -1404,7 +1451,7 @@ describe Project do
       allow(project).to receive(:repository_exists?)
         .and_return(false)
 
-      allow(shell).to receive(:add_repository)
+      allow(shell).to receive(:create_repository)
         .with(project.repository_storage_path, project.disk_path)
         .and_return(true)
 
@@ -1428,7 +1475,7 @@ describe Project do
       allow(project).to receive(:repository_exists?)
         .and_return(false)
 
-      expect(shell).to receive(:add_repository)
+      expect(shell).to receive(:create_repository)
         .with(project.repository_storage, project.disk_path)
         .and_return(true)
 
@@ -1600,7 +1647,7 @@ describe Project do
 
     before do
       allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
-        .with(project.repository_storage_path, project.disk_path, project.import_url)
+        .with(project.repository_storage, project.disk_path, project.import_url)
         .and_return(true)
 
       expect_any_instance_of(Repository).to receive(:after_import)
@@ -1753,10 +1800,7 @@ describe Project 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,
-          forked_from_project.disk_path).and_return(import_jid)
+        expect(RepositoryForkWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
 
         expect(project.add_import_job).to eq(import_jid)
       end
@@ -1980,6 +2024,22 @@ describe Project do
         expect(forked_project.lfs_storage_project).to eq forked_project
       end
     end
+
+    describe '#all_lfs_objects' do
+      let(:lfs_object) { create(:lfs_object) }
+
+      before do
+        project.lfs_objects << lfs_object
+      end
+
+      it 'returns the lfs object for a project' do
+        expect(project.all_lfs_objects).to contain_exactly(lfs_object)
+      end
+
+      it 'returns the lfs object for a fork' do
+        expect(forked_project.all_lfs_objects).to contain_exactly(lfs_object)
+      end
+    end
   end
 
   describe '#pushes_since_gc' do
@@ -2515,7 +2575,7 @@ describe Project do
     end
   end
 
-  describe '#remove_exports' do
+  describe '#remove_export' do
     let(:legacy_project) { create(:project, :legacy_storage, :with_export) }
     let(:project) { create(:project, :with_export) }
 
@@ -2563,6 +2623,23 @@ describe Project do
     end
   end
 
+  describe '#remove_exported_project_file' do
+    let(:project) { create(:project, :with_export) }
+
+    it 'removes the exported project file' do
+      exported_file = project.export_project_path
+
+      expect(File.exist?(exported_file)).to be_truthy
+
+      allow(FileUtils).to receive(:rm_f).and_call_original
+      expect(FileUtils).to receive(:rm_f).with(exported_file).and_call_original
+
+      project.remove_exported_project_file
+
+      expect(File.exist?(exported_file)).to be_falsy
+    end
+  end
+
   describe '#forks_count' do
     it 'returns the number of forks' do
       project = build(:project)
@@ -3168,6 +3245,7 @@ describe Project do
       expect(project).to receive(:update_project_counter_caches)
       expect(project).to receive(:remove_import_jid)
       expect(project).to receive(:after_create_default_branch)
+      expect(project).to receive(:refresh_markdown_cache!)
 
       project.after_import
     end
@@ -3331,4 +3409,180 @@ describe Project do
       end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
     end
   end
+
+  describe '#badges' do
+    let(:project_group) { create(:group) }
+    let(:project) {  create(:project, path: 'avatar', namespace: project_group) }
+
+    before do
+      create_list(:project_badge, 2, project: project)
+      create(:group_badge, group: project_group)
+    end
+
+    it 'returns the project and the project group badges' do
+      create(:group_badge, group: create(:group))
+
+      expect(Badge.count).to eq 4
+      expect(project.badges.count).to eq 3
+    end
+
+    if Group.supports_nested_groups?
+      context 'with nested_groups' do
+        let(:parent_group) { create(:group) }
+
+        before do
+          create_list(:group_badge, 2, group: project_group)
+          project_group.update(parent: parent_group)
+        end
+
+        it 'returns the project and the project nested groups badges' do
+          expect(project.badges.count).to eq 5
+        end
+      end
+    end
+  end
+
+  context 'with cross project merge requests' do
+    let(:user) { create(:user) }
+    let(:target_project) { create(:project, :repository) }
+    let(:project) { fork_project(target_project, nil, repository: true) }
+    let!(:merge_request) do
+      create(
+        :merge_request,
+        target_project: target_project,
+        target_branch: 'target-branch',
+        source_project: project,
+        source_branch: 'awesome-feature-1',
+        allow_maintainer_to_push: true
+      )
+    end
+
+    before do
+      target_project.add_developer(user)
+    end
+
+    describe '#merge_requests_allowing_push_to_user' do
+      it 'returns open merge requests for which the user has developer access to the target project' do
+        expect(project.merge_requests_allowing_push_to_user(user)).to include(merge_request)
+      end
+
+      it 'does not include closed merge requests' do
+        merge_request.close
+
+        expect(project.merge_requests_allowing_push_to_user(user)).to be_empty
+      end
+
+      it 'does not include merge requests for guest users' do
+        guest = create(:user)
+        target_project.add_guest(guest)
+
+        expect(project.merge_requests_allowing_push_to_user(guest)).to be_empty
+      end
+
+      it 'does not include the merge request for other users' do
+        other_user = create(:user)
+
+        expect(project.merge_requests_allowing_push_to_user(other_user)).to be_empty
+      end
+
+      it 'is empty when no user is passed' do
+        expect(project.merge_requests_allowing_push_to_user(nil)).to be_empty
+      end
+    end
+
+    describe '#branch_allows_maintainer_push?' do
+      it 'allows access if the user can merge the merge request' do
+        expect(project.branch_allows_maintainer_push?(user, 'awesome-feature-1'))
+          .to be_truthy
+      end
+
+      it 'does not allow guest users access' do
+        guest = create(:user)
+        target_project.add_guest(guest)
+
+        expect(project.branch_allows_maintainer_push?(guest, 'awesome-feature-1'))
+          .to be_falsy
+      end
+
+      it 'does not allow access to branches for which the merge request was closed' do
+        create(:merge_request, :closed,
+               target_project: target_project,
+               target_branch: 'target-branch',
+               source_project: project,
+               source_branch: 'rejected-feature-1',
+               allow_maintainer_to_push: true)
+
+        expect(project.branch_allows_maintainer_push?(user, 'rejected-feature-1'))
+          .to be_falsy
+      end
+
+      it 'does not allow access if the user cannot merge the merge request' do
+        create(:protected_branch, :masters_can_push, project: target_project, name: 'target-branch')
+
+        expect(project.branch_allows_maintainer_push?(user, 'awesome-feature-1'))
+          .to be_falsy
+      end
+
+      it 'caches the result' do
+        control = ActiveRecord::QueryRecorder.new { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') }
+
+        expect { 3.times { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') } }
+          .not_to exceed_query_limit(control)
+      end
+
+      context 'when the requeststore is active', :request_store do
+        it 'only queries per project across instances' do
+          control = ActiveRecord::QueryRecorder.new { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') }
+
+          expect { 2.times { described_class.find(project.id).branch_allows_maintainer_push?(user, 'awesome-feature-1') } }
+            .not_to exceed_query_limit(control).with_threshold(2)
+        end
+      end
+    end
+  end
+
+  describe "#pages_https_only?" do
+    subject { build(:project) }
+
+    context "when HTTPS pages are disabled" do
+      it { is_expected.not_to be_pages_https_only }
+    end
+
+    context "when HTTPS pages are enabled", :https_pages_enabled do
+      it { is_expected.to be_pages_https_only }
+    end
+  end
+
+  describe "#pages_https_only? validation", :https_pages_enabled do
+    subject(:project) do
+      # set-up dirty object:
+      create(:project, pages_https_only: false).tap do |p|
+        p.pages_https_only = true
+      end
+    end
+
+    context "when no domains are associated" do
+      it { is_expected.to be_valid }
+    end
+
+    context "when domains including keys and certificates are associated" do
+      before do
+        allow(project)
+          .to receive(:pages_domains)
+          .and_return([instance_double(PagesDomain, https?: true)])
+      end
+
+      it { is_expected.to be_valid }
+    end
+
+    context "when domains including no keys or certificates are associated" do
+      before do
+        allow(project)
+          .to receive(:pages_domains)
+          .and_return([instance_double(PagesDomain, https?: false)])
+      end
+
+      it { is_expected.not_to be_valid }
+    end
+  end
 end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 5cff2af4aca6b132d6e9e656b2aaccd690bb8a3a..38a3590ad12d5a963a420e66f4a4aa71a34320ac 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -4,26 +4,6 @@ describe ProjectStatistics do
   let(:project) { create :project }
   let(:statistics) { project.statistics }
 
-  describe 'constants' do
-    describe 'STORAGE_COLUMNS' do
-      it 'is an array of symbols' do
-        expect(described_class::STORAGE_COLUMNS).to be_kind_of Array
-        expect(described_class::STORAGE_COLUMNS.map(&:class).uniq).to eq [Symbol]
-      end
-    end
-
-    describe 'STATISTICS_COLUMNS' do
-      it 'is an array of symbols' do
-        expect(described_class::STATISTICS_COLUMNS).to be_kind_of Array
-        expect(described_class::STATISTICS_COLUMNS.map(&:class).uniq).to eq [Symbol]
-      end
-
-      it 'includes all storage columns' do
-        expect(described_class::STATISTICS_COLUMNS & described_class::STORAGE_COLUMNS).to eq described_class::STORAGE_COLUMNS
-      end
-    end
-  end
-
   describe 'associations' do
     it { is_expected.to belong_to(:project) }
     it { is_expected.to belong_to(:namespace) }
@@ -63,7 +43,6 @@ describe ProjectStatistics do
       allow(statistics).to receive(:update_commit_count)
       allow(statistics).to receive(:update_repository_size)
       allow(statistics).to receive(:update_lfs_objects_size)
-      allow(statistics).to receive(:update_build_artifacts_size)
       allow(statistics).to receive(:update_storage_size)
     end
 
@@ -76,7 +55,6 @@ describe ProjectStatistics do
         expect(statistics).to have_received(:update_commit_count)
         expect(statistics).to have_received(:update_repository_size)
         expect(statistics).to have_received(:update_lfs_objects_size)
-        expect(statistics).to have_received(:update_build_artifacts_size)
       end
     end
 
@@ -89,7 +67,6 @@ describe ProjectStatistics do
         expect(statistics).to have_received(:update_lfs_objects_size)
         expect(statistics).not_to have_received(:update_commit_count)
         expect(statistics).not_to have_received(:update_repository_size)
-        expect(statistics).not_to have_received(:update_build_artifacts_size)
       end
     end
   end
@@ -131,40 +108,6 @@ describe ProjectStatistics do
     end
   end
 
-  describe '#update_build_artifacts_size' do
-    let!(:pipeline) { create(:ci_pipeline, project: project) }
-
-    context 'when new job artifacts are calculated' do
-      let(:ci_build) { create(:ci_build, pipeline: pipeline) }
-
-      before do
-        create(:ci_job_artifact, :archive, project: pipeline.project, job: ci_build)
-      end
-
-      it "stores the size of related build artifacts" do
-        statistics.update_build_artifacts_size
-
-        expect(statistics.build_artifacts_size).to be(106365)
-      end
-
-      it 'calculates related build artifacts by project' do
-        expect(Ci::JobArtifact).to receive(:artifacts_size_for).with(project) { 0 }
-
-        statistics.update_build_artifacts_size
-      end
-    end
-
-    context 'when legacy artifacts are used' do
-      let!(:ci_build) { create(:ci_build, pipeline: pipeline, artifacts_size: 10.megabytes) }
-
-      it "stores the size of related build artifacts" do
-        statistics.update_build_artifacts_size
-
-        expect(statistics.build_artifacts_size).to eq(10.megabytes)
-      end
-    end
-  end
-
   describe '#update_storage_size' do
     it "sums all storage counters" do
       statistics.update!(
@@ -177,4 +120,27 @@ describe ProjectStatistics do
       expect(statistics.storage_size).to eq 5
     end
   end
+
+  describe '.increment_statistic' do
+    it 'increases the statistic by that amount' do
+      expect { described_class.increment_statistic(project.id, :build_artifacts_size, 13) }
+        .to change { statistics.reload.build_artifacts_size }
+        .by(13)
+    end
+
+    context 'when the amount is 0' do
+      it 'does not execute a query' do
+        project
+        expect { described_class.increment_statistic(project.id, :build_artifacts_size, 0) }
+          .not_to exceed_query_limit(0)
+      end
+    end
+
+    context 'when using an invalid column' do
+      it 'raises an error' do
+        expect { described_class.increment_statistic(project.id, :id, 13) }
+          .to raise_error(ArgumentError, "Cannot increment attribute: id")
+      end
+    end
+  end
 end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 1e7671476f19c5e80a6856b9726b0d5a1b565329..4e83f4353cf8540d29c8518f89b4b9c85b8438bb 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -14,13 +14,13 @@ describe ProjectWiki do
   it { is_expected.to delegate_method(:repository_storage_path).to :project }
   it { is_expected.to delegate_method(:hashed_storage?).to :project }
 
-  describe "#path_with_namespace" do
+  describe "#full_path" do
     it "returns the project path with namespace with the .wiki extension" do
-      expect(subject.path_with_namespace).to eq(project.full_path + '.wiki')
+      expect(subject.full_path).to eq(project.full_path + '.wiki')
     end
 
     it 'returns the same value as #full_path' do
-      expect(subject.path_with_namespace).to eq(subject.full_path)
+      expect(subject.full_path).to eq(subject.full_path)
     end
   end
 
@@ -74,7 +74,7 @@ describe ProjectWiki do
       # Create a fresh project which will not have a wiki
       project_wiki = described_class.new(create(:project), user)
       gitlab_shell = double(:gitlab_shell)
-      allow(gitlab_shell).to receive(:add_repository)
+      allow(gitlab_shell).to receive(:create_repository)
       allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell)
 
       expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
@@ -172,11 +172,12 @@ describe ProjectWiki do
 
   describe '#find_file' do
     shared_examples 'finding a wiki file' do
+      let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
+
       before do
-        file = File.open(Rails.root.join('spec', 'fixtures', 'dk.png'))
         subject.wiki # Make sure the wiki repo exists
 
-        BareRepoOperations.new(subject.repository.path_to_repo).commit_file(file, 'image.png')
+        BareRepoOperations.new(subject.repository.path_to_repo).commit_file(image, 'image.png')
       end
 
       it 'returns the latest version of the file if it exists' do
@@ -192,6 +193,13 @@ describe ProjectWiki do
         file = subject.find_file('image.png')
         expect(file).to be_a Gitlab::Git::WikiFile
       end
+
+      it 'returns the whole file' do
+        file = subject.find_file('image.png')
+        image.rewind
+
+        expect(file.raw_data.b).to eq(image.read.b)
+      end
     end
 
     context 'when Gitaly wiki_find_file is enabled' do
@@ -369,7 +377,7 @@ describe ProjectWiki do
   end
 
   def commit_details
-    Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
+    Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "test commit")
   end
 
   def create_page(name, content)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 38653e1830621c05d03b03bc884d565353a85789..e45fe7db1e7a277b1a4c77dcc63f410470a294fd 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -501,28 +501,6 @@ describe Repository do
     end
   end
 
-  describe '#create_hooks' do
-    let(:hook_path) { File.join(repository.path_to_repo, 'hooks') }
-
-    it 'symlinks the global hooks directory' do
-      repository.create_hooks
-
-      expect(File.symlink?(hook_path)).to be true
-      expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
-    end
-
-    it 'replaces existing symlink with the right directory' do
-      FileUtils.mkdir_p(hook_path)
-
-      expect(File.symlink?(hook_path)).to be false
-
-      repository.create_hooks
-
-      expect(File.symlink?(hook_path)).to be true
-      expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
-    end
-  end
-
   describe "#create_dir" do
     it "commits a change that creates a new directory" do
       expect do
@@ -895,7 +873,7 @@ describe Repository do
     end
 
     it 'returns nil when the content is not recognizable' do
-      repository.create_file(user, 'LICENSE', 'Copyright!',
+      repository.create_file(user, 'LICENSE', 'Gitlab B.V.',
         message: 'Add LICENSE', branch_name: 'master')
 
       expect(repository.license_key).to be_nil
@@ -939,7 +917,7 @@ describe Repository do
     end
 
     it 'returns nil when the content is not recognizable' do
-      repository.create_file(user, 'LICENSE', 'Copyright!',
+      repository.create_file(user, 'LICENSE', 'Gitlab B.V.',
         message: 'Add LICENSE', branch_name: 'master')
 
       expect(repository.license).to be_nil
@@ -1004,7 +982,7 @@ describe Repository do
       end
     end
 
-    context 'with Gitaly disabled', :skip_gitaly_mock do
+    context 'with Gitaly disabled', :disable_gitaly do
       context 'when pre hooks were successful' do
         it 'runs without errors' do
           hook = double(trigger: [true, nil])
@@ -1447,7 +1425,6 @@ describe Repository do
     it 'expires the caches for an empty repository' do
       allow(repository).to receive(:empty?).and_return(true)
 
-      expect(cache).to receive(:expire).with(:empty?)
       expect(cache).to receive(:expire).with(:has_visible_content?)
 
       repository.expire_emptiness_caches
@@ -1456,11 +1433,16 @@ describe Repository do
     it 'does not expire the cache for a non-empty repository' do
       allow(repository).to receive(:empty?).and_return(false)
 
-      expect(cache).not_to receive(:expire).with(:empty?)
       expect(cache).not_to receive(:expire).with(:has_visible_content?)
 
       repository.expire_emptiness_caches
     end
+
+    it 'expires the memoized repository cache' do
+      allow(repository.raw_repository).to receive(:expire_has_local_branches_cache).and_call_original
+
+      repository.expire_emptiness_caches
+    end
   end
 
   describe 'skip_merges option' do
@@ -1896,7 +1878,7 @@ describe Repository do
       it_behaves_like 'adding tag'
     end
 
-    context 'when Gitaly operation_user_add_tag feature is disabled', :skip_gitaly_mock do
+    context 'when Gitaly operation_user_add_tag feature is disabled', :disable_gitaly do
       it_behaves_like 'adding tag'
 
       it 'passes commit SHA to pre-receive and update hooks and tag SHA to post-receive hook' do
@@ -1955,7 +1937,7 @@ describe Repository do
       end
     end
 
-    context 'with gitaly disabled', :skip_gitaly_mock do
+    context 'with gitaly disabled', :disable_gitaly do
       it_behaves_like "user deleting a branch"
 
       let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
@@ -2171,15 +2153,6 @@ describe Repository do
     end
   end
 
-  describe '#expire_method_caches' do
-    it 'expires the caches of the given methods' do
-      expect_any_instance_of(RepositoryCache).to receive(:expire).with(:readme)
-      expect_any_instance_of(RepositoryCache).to receive(:expire).with(:gitignore)
-
-      repository.expire_method_caches(%i(readme gitignore))
-    end
-  end
-
   describe '#expire_all_method_caches' do
     it 'expires the caches of all methods' do
       expect(repository).to receive(:expire_method_caches)
@@ -2325,66 +2298,6 @@ describe Repository do
     end
   end
 
-  describe '#cache_method_output', :use_clean_rails_memory_store_caching do
-    let(:fallback) { 10 }
-
-    context 'with a non-existing repository' do
-      let(:project) { create(:project) } # No repository
-
-      subject do
-        repository.cache_method_output(:cats, fallback: fallback) do
-          repository.cats_call_stub
-        end
-      end
-
-      it 'returns the fallback value' do
-        expect(subject).to eq(fallback)
-      end
-
-      it 'avoids calling the original method' do
-        expect(repository).not_to receive(:cats_call_stub)
-
-        subject
-      end
-    end
-
-    context 'with a method throwing a non-existing-repository error' do
-      subject do
-        repository.cache_method_output(:cats, fallback: fallback) do
-          raise Gitlab::Git::Repository::NoRepository
-        end
-      end
-
-      it 'returns the fallback value' do
-        expect(subject).to eq(fallback)
-      end
-
-      it 'does not cache the data' do
-        subject
-
-        expect(repository.instance_variable_defined?(:@cats)).to eq(false)
-        expect(repository.send(:cache).exist?(:cats)).to eq(false)
-      end
-    end
-
-    context 'with an existing repository' do
-      it 'caches the output' do
-        object = double
-
-        expect(object).to receive(:number).once.and_return(10)
-
-        2.times do
-          val = repository.cache_method_output(:cats) { object.number }
-
-          expect(val).to eq(10)
-        end
-
-        expect(repository.send(:cache).exist?(:cats)).to eq(true)
-        expect(repository.instance_variable_get(:@cats)).to eq(10)
-      end
-    end
-  end
-
   describe '#refresh_method_caches' do
     it 'refreshes the caches of the given types' do
       expect(repository).to receive(:expire_method_caches)
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index dfac82b327a70fe846ae536656633bc74093eb98..01238a89a81b217ab5c66ef5fbe80d80881f0f4a 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -16,66 +16,6 @@ describe Route do
     it { is_expected.to validate_presence_of(:source) }
     it { is_expected.to validate_presence_of(:path) }
     it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
-
-    describe '#ensure_permanent_paths' do
-      context 'when the route is not yet persisted' do
-        let(:new_route) { described_class.new(path: 'foo', source: build(:group)) }
-
-        context 'when permanent conflicting redirects exist' do
-          it 'is invalid' do
-            redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
-            redirect.save!(validate: false)
-
-            expect(new_route.valid?).to be_falsey
-            expect(new_route.errors.first[1]).to eq('has been taken before')
-          end
-        end
-
-        context 'when no permanent conflicting redirects exist' do
-          it 'is valid' do
-            expect(new_route.valid?).to be_truthy
-          end
-        end
-      end
-
-      context 'when path has changed' do
-        before do
-          route.path = 'foo'
-        end
-
-        context 'when permanent conflicting redirects exist' do
-          it 'is invalid' do
-            redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
-            redirect.save!(validate: false)
-
-            expect(route.valid?).to be_falsey
-            expect(route.errors.first[1]).to eq('has been taken before')
-          end
-        end
-
-        context 'when no permanent conflicting redirects exist' do
-          it 'is valid' do
-            expect(route.valid?).to be_truthy
-          end
-        end
-      end
-
-      context 'when path has not changed' do
-        context 'when permanent conflicting redirects exist' do
-          it 'is valid' do
-            redirect = build(:redirect_route, :permanent, path: 'git_lab/foo/bar')
-            redirect.save!(validate: false)
-
-            expect(route.valid?).to be_truthy
-          end
-        end
-        context 'when no permanent conflicting redirects exist' do
-          it 'is valid' do
-            expect(route.valid?).to be_truthy
-          end
-        end
-      end
-    end
   end
 
   describe 'callbacks' do
@@ -211,43 +151,31 @@ describe Route do
     end
 
     context 'when the source is a Project' do
-      it 'creates a temporal RedirectRoute' do
+      it 'creates a RedirectRoute' do
         project = create(:project)
         route = project.route
         redirect_route = route.create_redirect('foo')
-        expect(redirect_route.permanent?).to be_falsy
+        expect(redirect_route).not_to be_nil
       end
     end
 
     context 'when the source is not a project' do
-      it 'creates a permanent RedirectRoute' do
-        redirect_route = route.create_redirect('foo', permanent: true)
-        expect(redirect_route.permanent?).to be_truthy
+      it 'creates a RedirectRoute' do
+        redirect_route = route.create_redirect('foo')
+        expect(redirect_route).not_to be_nil
       end
     end
   end
 
   describe '#delete_conflicting_redirects' do
-    context 'with permanent redirect' do
-      it 'does not delete the redirect' do
-        route.create_redirect("#{route.path}/foo", permanent: true)
-
-        expect do
-          route.delete_conflicting_redirects
-        end.not_to change { RedirectRoute.count }
-      end
-    end
-
-    context 'with temporal redirect' do
-      let(:route) { create(:project).route }
+    let(:route) { create(:project).route }
 
-      it 'deletes the redirect' do
-        route.create_redirect("#{route.path}/foo")
+    it 'deletes the redirect' do
+      route.create_redirect("#{route.path}/foo")
 
-        expect do
-          route.delete_conflicting_redirects
-        end.to change { RedirectRoute.count }.by(-1)
-      end
+      expect do
+        route.delete_conflicting_redirects
+      end.to change { RedirectRoute.count }.by(-1)
     end
 
     context 'when a redirect route with the same path exists' do
@@ -289,31 +217,18 @@ describe Route do
   end
 
   describe '#conflicting_redirects' do
+    let(:route) { create(:project).route }
+
     it 'returns an ActiveRecord::Relation' do
       expect(route.conflicting_redirects).to be_an(ActiveRecord::Relation)
     end
 
-    context 'with permanent redirects' do
-      it 'does not return anything' do
-        route.create_redirect("#{route.path}/foo", permanent: true)
-        route.create_redirect("#{route.path}/foo/bar", permanent: true)
-        route.create_redirect("#{route.path}/baz/quz", permanent: true)
+    it 'returns the redirect routes' do
+      redirect1 = route.create_redirect("#{route.path}/foo")
+      redirect2 = route.create_redirect("#{route.path}/foo/bar")
+      redirect3 = route.create_redirect("#{route.path}/baz/quz")
 
-        expect(route.conflicting_redirects).to be_empty
-      end
-    end
-
-    context 'with temporal redirects' do
-      let(:route) { create(:project).route }
-
-      it 'returns the redirect routes' do
-        route = create(:project).route
-        redirect1 = route.create_redirect("#{route.path}/foo")
-        redirect2 = route.create_redirect("#{route.path}/foo/bar")
-        redirect3 = route.create_redirect("#{route.path}/baz/quz")
-
-        expect(route.conflicting_redirects).to match_array([redirect1, redirect2, redirect3])
-      end
+      expect(route.conflicting_redirects).to match_array([redirect1, redirect2, redirect3])
     end
 
     context 'when a redirect route with the same path exists' do
@@ -348,44 +263,6 @@ describe Route do
     end
   end
 
-  describe "#conflicting_redirect_exists?" do
-    context 'when a conflicting redirect exists' do
-      let(:group1) { create(:group, path: 'foo') }
-      let(:group2) { create(:group, path: 'baz') }
-
-      it 'should not be saved' do
-        group1.path = 'bar'
-        group1.save
-
-        group2.path = 'foo'
-
-        expect(group2.save).to be_falsy
-      end
-
-      it 'should return an error on path' do
-        group1.path = 'bar'
-        group1.save
-
-        group2.path = 'foo'
-        group2.valid?
-        expect(group2.errors[:path]).to eq(['has been taken before'])
-      end
-    end
-
-    context 'when a conflicting redirect does not exist' do
-      let(:project1) { create(:project, path: 'foo') }
-      let(:project2) { create(:project, path: 'baz') }
-
-      it 'should be saved' do
-        project1.path = 'bar'
-        project1.save
-
-        project2.path = 'foo'
-        expect(project2.save).to be_truthy
-      end
-    end
-  end
-
   describe '#delete_conflicting_orphaned_routes' do
     context 'when there is a conflicting route' do
       let!(:conflicting_group) { create(:group, path: 'foo') }
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 79f25dc4360117a6369745b4f037d0532b42e3c5..28c908ea4258aa71bbdf8359db4c3b33d253c52f 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -10,6 +10,22 @@ describe Service do
     it { is_expected.to validate_presence_of(:type) }
   end
 
+  describe 'Scopes' do
+    describe '.confidential_note_hooks' do
+      it 'includes services where confidential_note_events is true' do
+        create(:service, active: true, confidential_note_events: true)
+
+        expect(described_class.confidential_note_hooks.count).to eq 1
+      end
+
+      it 'excludes services where confidential_note_events is false' do
+        create(:service, active: true, confidential_note_events: false)
+
+        expect(described_class.confidential_note_hooks.count).to eq 0
+      end
+    end
+  end
+
   describe "Test Button" do
     describe '#can_test?' do
       let(:service) { create(:service, project: project) }
@@ -58,6 +74,21 @@ describe Service do
   end
 
   describe "Template" do
+    describe '.build_from_template' do
+      context 'when template is invalid' do
+        it 'sets service template to inactive when template is invalid' do
+          project = create(:project)
+          template = JiraService.new(template: true, active: true)
+          template.save(validate: false)
+
+          service = described_class.build_from_template(project.id, template)
+
+          expect(service).to be_valid
+          expect(service.active).to be false
+        end
+      end
+    end
+
     describe "for pushover service" do
       let!(:service_template) do
         PushoverService.create(
diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cb4bb3372d484e79915bf75b3a8faa40a8c07108
--- /dev/null
+++ b/spec/models/user_interacted_project_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe UserInteractedProject do
+  describe '.track' do
+    subject { described_class.track(event) }
+    let(:event) { build(:event) }
+
+    Event::ACTIONS.each do |action|
+      context "for all actions (event types)" do
+        let(:event) { build(:event, action: action) }
+        it 'creates a record' do
+          expect { subject }.to change { described_class.count }.from(0).to(1)
+        end
+      end
+    end
+
+    it 'sets project accordingly' do
+      subject
+      expect(described_class.first.project).to eq(event.project)
+    end
+
+    it 'sets user accordingly' do
+      subject
+      expect(described_class.first.user).to eq(event.author)
+    end
+
+    it 'only creates a record once per user/project' do
+      expect do
+        subject
+        described_class.track(event)
+      end.to change { described_class.count }.from(0).to(1)
+    end
+
+    describe 'with an event without a project' do
+      let(:event) { build(:event, project: nil) }
+
+      it 'ignores the event' do
+        expect { subject }.not_to change { described_class.count }
+      end
+    end
+  end
+
+  describe '.available?' do
+    before do
+      described_class.instance_variable_set('@available_flag', nil)
+    end
+
+    it 'checks schema version and properly caches positive result' do
+      expect(ActiveRecord::Migrator).to receive(:current_version).and_return(described_class::REQUIRED_SCHEMA_VERSION - 1 - rand(1000))
+      expect(described_class.available?).to be_falsey
+      expect(ActiveRecord::Migrator).to receive(:current_version).and_return(described_class::REQUIRED_SCHEMA_VERSION + rand(1000))
+      expect(described_class.available?).to be_truthy
+      expect(ActiveRecord::Migrator).not_to receive(:current_version)
+      expect(described_class.available?).to be_truthy # cached response
+    end
+  end
+
+  it { is_expected.to validate_presence_of(:project_id) }
+  it { is_expected.to validate_presence_of(:user_id) }
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 00b5226d874115772b0a1c70028306bc446a2cda..35db7616efb93ab75909c686754327d6869c9c45 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -25,9 +25,8 @@ describe User do
     it { is_expected.to have_many(:group_members) }
     it { is_expected.to have_many(:groups) }
     it { is_expected.to have_many(:keys).dependent(:destroy) }
-    it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
+    it { is_expected.to have_many(:deploy_keys).dependent(:nullify) }
     it { is_expected.to have_many(:events).dependent(:destroy) }
-    it { is_expected.to have_many(:recent_events).class_name('Event') }
     it { is_expected.to have_many(:issues).dependent(:destroy) }
     it { is_expected.to have_many(:notes).dependent(:destroy) }
     it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
@@ -127,23 +126,6 @@ describe User do
         end
       end
 
-      context 'when the username was used by another user before' do
-        let(:username) { 'foo' }
-        let!(:other_user) { create(:user, username: username) }
-
-        before do
-          other_user.username = 'bar'
-          other_user.save!
-        end
-
-        it 'is invalid' do
-          user = build(:user, username: username)
-
-          expect(user).not_to be_valid
-          expect(user.errors.full_messages).to eq(['Username has been taken before'])
-        end
-      end
-
       context 'when the username is in use by another user' do
         let(:username) { 'foo' }
         let!(:other_user) { create(:user, username: username) }
@@ -1223,7 +1205,7 @@ describe User do
     it 'is false if avatar is html page' do
       user.update_attribute(:avatar, 'uploads/avatar.html')
 
-      expect(user.avatar_type).to eq(['only images allowed'])
+      expect(user.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff'])
     end
   end
 
@@ -1469,7 +1451,7 @@ describe User do
     end
   end
 
-  describe '#sort' do
+  describe '#sort_by_attribute' do
     before do
       described_class.delete_all
       @user = create :user, created_at: Date.today, current_sign_in_at: Date.today, name: 'Alpha'
@@ -1478,7 +1460,7 @@ describe User do
     end
 
     context 'when sort by recent_sign_in' do
-      let(:users) { described_class.sort('recent_sign_in') }
+      let(:users) { described_class.sort_by_attribute('recent_sign_in') }
 
       it 'sorts users by recent sign-in time' do
         expect(users.first).to eq(@user)
@@ -1491,7 +1473,7 @@ describe User do
     end
 
     context 'when sort by oldest_sign_in' do
-      let(:users) { described_class.sort('oldest_sign_in') }
+      let(:users) { described_class.sort_by_attribute('oldest_sign_in') }
 
       it 'sorts users by the oldest sign-in time' do
         expect(users.first).to eq(@user1)
@@ -1504,15 +1486,15 @@ describe User do
     end
 
     it 'sorts users in descending order by their creation time' do
-      expect(described_class.sort('created_desc').first).to eq(@user)
+      expect(described_class.sort_by_attribute('created_desc').first).to eq(@user)
     end
 
     it 'sorts users in ascending order by their creation time' do
-      expect(described_class.sort('created_asc').first).to eq(@user2)
+      expect(described_class.sort_by_attribute('created_asc').first).to eq(@user2)
     end
 
     it 'sorts users by id in descending order when nil is passed' do
-      expect(described_class.sort(nil).first).to eq(@user2)
+      expect(described_class.sort_by_attribute(nil).first).to eq(@user2)
     end
   end
 
@@ -1868,6 +1850,21 @@ describe User do
 
       it_behaves_like :member
     end
+
+    context 'with subgroup with different owner for project runner', :nested_groups do
+      let(:group) { create(:group) }
+      let(:another_user) { create(:user) }
+      let(:subgroup) { create(:group, parent: group) }
+      let(:project) { create(:project, group: subgroup) }
+
+      def add_user(access)
+        group.add_user(user, access)
+        group.add_user(another_user, :owner)
+        subgroup.add_user(another_user, :owner)
+      end
+
+      it_behaves_like :member
+    end
   end
 
   describe '#projects_with_reporter_access_limited_to' do
@@ -2089,6 +2086,8 @@ describe User do
 
       expect(ghost).to be_ghost
       expect(ghost).to be_persisted
+      expect(ghost.namespace).not_to be_nil
+      expect(ghost.namespace).to be_persisted
     end
 
     it "does not create a second ghost user if one is already present" do
@@ -2250,6 +2249,20 @@ describe User do
     end
   end
 
+  context '#invalidate_personal_projects_count' do
+    let(:user) { build_stubbed(:user) }
+
+    it 'invalidates cache for personal projects counter' do
+      cache_mock = double
+
+      expect(cache_mock).to receive(:delete).with(['users', user.id, 'personal_projects_count'])
+
+      allow(Rails).to receive(:cache).and_return(cache_mock)
+
+      user.invalidate_personal_projects_count
+    end
+  end
+
   describe '#allow_password_authentication_for_web?' do
     context 'regular user' do
       let(:user) { build(:user) }
@@ -2299,11 +2312,9 @@ describe User do
       user = build(:user)
       projects = double(:projects, count: 1)
 
-      expect(user).to receive(:personal_projects).once.and_return(projects)
+      expect(user).to receive(:personal_projects).and_return(projects)
 
-      2.times do
-        expect(user.personal_projects_count).to eq(1)
-      end
+      expect(user.personal_projects_count).to eq(1)
     end
   end
 
@@ -2700,27 +2711,19 @@ describe User do
     end
   end
 
-  describe "#username_previously_taken?" do
-    let(:user1) { create(:user, username: 'foo') }
+  context 'changing a username' do
+    let(:user) { create(:user, username: 'foo') }
 
-    context 'when the username has been taken before' do
-      before do
-        user1.username = 'bar'
-        user1.save!
-      end
-
-      it 'should raise an ActiveRecord::RecordInvalid exception' do
-        user2 = build(:user, username: 'foo')
-        expect { user2.save! }.to raise_error(ActiveRecord::RecordInvalid, /Username has been taken before/)
-      end
+    it 'creates a redirect route' do
+      expect { user.update!(username: 'bar') }
+        .to change { RedirectRoute.where(path: 'foo').count }.by(1)
     end
 
-    context 'when the username has not been taken before' do
-      it 'should be valid' do
-        expect(RedirectRoute.count).to eq(0)
-        user2 = build(:user, username: 'baz')
-        expect(user2).to be_valid
-      end
+    it 'deletes the redirect when a user with the old username was created' do
+      user.update!(username: 'bar')
+
+      expect { create(:user, username: 'foo') }
+        .to change { RedirectRoute.where(path: 'foo').count }.by(-1)
     end
   end
 end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index b2b7721674c5797efcf92018fd30912ed058385d..90b7e7715a84da0a51051d274dc1fc870225b80b 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -561,7 +561,7 @@ describe WikiPage do
   end
 
   def commit_details
-    Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
+    Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "test commit")
   end
 
   def create_page(name, content)
diff --git a/spec/policies/deploy_token_policy_spec.rb b/spec/policies/deploy_token_policy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eea287d895e6c37f15e6b26d5205b6de6fee4ae5
--- /dev/null
+++ b/spec/policies/deploy_token_policy_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe DeployTokenPolicy do
+  let(:current_user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:deploy_token) { create(:deploy_token, projects: [project]) }
+
+  subject { described_class.new(current_user, deploy_token) }
+
+  describe 'creating a deploy key' do
+    context 'when user is master' do
+      before do
+        project.add_master(current_user)
+      end
+
+      it { is_expected.to be_allowed(:create_deploy_token) }
+    end
+
+    context 'when user is not master' do
+      before do
+        project.add_developer(current_user)
+      end
+
+      it { is_expected.to be_disallowed(:create_deploy_token) }
+    end
+  end
+
+  describe 'updating a deploy key' do
+    context 'when user is master' do
+      before do
+        project.add_master(current_user)
+      end
+
+      it { is_expected.to be_allowed(:update_deploy_token) }
+    end
+
+    context 'when user is not master' do
+      before do
+        project.add_developer(current_user)
+      end
+
+      it { is_expected.to be_disallowed(:update_deploy_token) }
+    end
+  end
+end
diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb
index 58d36a2c84e064524679e2774f241bac068abddf..e8096358f7df2e21e60dcde7db5a755e048db051 100644
--- a/spec/policies/note_policy_spec.rb
+++ b/spec/policies/note_policy_spec.rb
@@ -18,7 +18,6 @@ describe NotePolicy, mdoels: true do
     context 'when the project is public' do
       context 'when the note author is not a project member' do
         it 'can edit a note' do
-          expect(policies).to be_allowed(:update_note)
           expect(policies).to be_allowed(:admin_note)
           expect(policies).to be_allowed(:resolve_note)
           expect(policies).to be_allowed(:read_note)
@@ -29,7 +28,6 @@ describe NotePolicy, mdoels: true do
         it 'can edit note' do
           policies = policies(create(:project_snippet, project: project))
 
-          expect(policies).to be_allowed(:update_note)
           expect(policies).to be_allowed(:admin_note)
           expect(policies).to be_allowed(:resolve_note)
           expect(policies).to be_allowed(:read_note)
@@ -47,7 +45,6 @@ describe NotePolicy, mdoels: true do
           end
 
           it 'can edit a note' do
-            expect(policies).to be_allowed(:update_note)
             expect(policies).to be_allowed(:admin_note)
             expect(policies).to be_allowed(:resolve_note)
             expect(policies).to be_allowed(:read_note)
@@ -56,7 +53,6 @@ describe NotePolicy, mdoels: true do
 
         context 'when the note author is not a project member' do
           it 'can not edit a note' do
-            expect(policies).to be_disallowed(:update_note)
             expect(policies).to be_disallowed(:admin_note)
             expect(policies).to be_disallowed(:resolve_note)
           end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 50bb0899ebae5f76c83ce7f241e2ccd46e0317ab..3809692b37357e0dfb2e285d394a7c0744ef8ab5 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -27,6 +27,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_allowed(:read_personal_snippet)
         is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -37,6 +38,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_allowed(:read_personal_snippet)
         is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -47,6 +49,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_allowed(:read_personal_snippet)
         is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(:award_emoji)
         is_expected.to be_allowed(*author_permissions)
       end
     end
@@ -61,6 +64,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
         is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -71,6 +75,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_allowed(:read_personal_snippet)
         is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -81,6 +86,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
         is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -91,6 +97,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_allowed(:read_personal_snippet)
         is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(:award_emoji)
         is_expected.to be_allowed(*author_permissions)
       end
     end
@@ -105,6 +112,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
         is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -115,6 +123,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
         is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -125,6 +134,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
         is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
     end
@@ -135,6 +145,7 @@ describe PersonalSnippetPolicy do
       it do
         is_expected.to be_allowed(:read_personal_snippet)
         is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(:award_emoji)
         is_expected.to be_allowed(*author_permissions)
       end
     end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 129344f105f85a487c6100e7f0f4a8d408a74345..8b9c4ac0b4bd31bb247662693a7cc65e51ae1886 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -11,10 +11,11 @@ describe ProjectPolicy do
 
   let(:base_guest_permissions) do
     %i[
-      read_project read_board read_list read_wiki read_issue read_label
-      read_milestone read_project_snippet read_project_member
-      read_note create_project create_issue create_note
-      upload_file
+      read_project read_board read_list read_wiki read_issue
+      read_project_for_iids read_issue_iid read_merge_request_iid read_label
+      read_milestone read_project_snippet read_project_member read_note
+      create_project create_issue create_note upload_file create_merge_request_in
+      award_emoji
     ]
   end
 
@@ -35,7 +36,7 @@ describe ProjectPolicy do
     %i[
       admin_milestone admin_merge_request update_merge_request create_commit_status
       update_commit_status create_build update_build create_pipeline
-      update_pipeline create_merge_request create_wiki push_code
+      update_pipeline create_merge_request_from create_wiki push_code
       resolve_note create_container_image update_container_image
       create_environment create_deployment
     ]
@@ -43,7 +44,7 @@ describe ProjectPolicy do
 
   let(:base_master_permissions) do
     %i[
-      delete_protected_branch update_project_snippet update_environment
+      push_to_delete_protected_branch update_project_snippet update_environment
       update_deployment admin_project_snippet
       admin_project_member admin_note admin_wiki admin_project
       admin_commit_status admin_build admin_container_image
@@ -120,7 +121,7 @@ describe ProjectPolicy do
         project.issues_enabled = false
         project.save!
 
-        expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue
+        expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue
       end
     end
 
@@ -131,7 +132,60 @@ describe ProjectPolicy do
         project.issues_enabled = false
         project.save!
 
-        expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue
+        expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue
+      end
+    end
+  end
+
+  context 'merge requests feature' do
+    subject { described_class.new(owner, project) }
+
+    it 'disallows all permissions when the feature is disabled' do
+      project.project_feature.update(merge_requests_access_level: ProjectFeature::DISABLED)
+
+      mr_permissions = [:create_merge_request_from, :read_merge_request,
+                        :update_merge_request, :admin_merge_request,
+                        :create_merge_request_in]
+
+      expect_disallowed(*mr_permissions)
+    end
+  end
+
+  shared_examples 'archived project policies' do
+    let(:feature_write_abilities) do
+      described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
+        described_class.create_update_admin_destroy(feature)
+      end
+    end
+
+    let(:other_write_abilities) do
+      %i[
+        create_merge_request_in
+        create_merge_request_from
+        push_to_delete_protected_branch
+        push_code
+        request_access
+        upload_file
+        resolve_note
+        award_emoji
+      ]
+    end
+
+    context 'when the project is archived' do
+      before do
+        project.archived = true
+      end
+
+      it 'disables write actions on all relevant project features' do
+        expect_disallowed(*feature_write_abilities)
+      end
+
+      it 'disables some other important write actions' do
+        expect_disallowed(*other_write_abilities)
+      end
+
+      it 'does not disable other other abilities' do
+        expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
       end
     end
   end
@@ -141,8 +195,8 @@ describe ProjectPolicy do
       context 'when a project has pending invites' do
         let(:group) { create(:group, :public) }
         let(:project) { create(:project, :public, namespace: group) }
-        let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
-        let(:anonymous_permissions) { guest_permissions - user_permissions }
+        let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
+        let(:anonymous_permissions) { guest_permissions - user_permissions  }
 
         subject { described_class.new(nil, project) }
 
@@ -154,6 +208,10 @@ describe ProjectPolicy do
           expect_allowed(*anonymous_permissions)
           expect_disallowed(*user_permissions)
         end
+
+        it_behaves_like 'archived project policies' do
+          let(:regular_abilities) { anonymous_permissions }
+        end
       end
     end
 
@@ -184,6 +242,10 @@ describe ProjectPolicy do
         expect_disallowed(*owner_permissions)
       end
 
+      it_behaves_like 'archived project policies' do
+        let(:regular_abilities) { guest_permissions }
+      end
+
       context 'public builds enabled' do
         it do
           expect_allowed(*guest_permissions)
@@ -224,12 +286,15 @@ describe ProjectPolicy do
       it do
         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
+
+      it_behaves_like 'archived project policies' do
+        let(:regular_abilities) { reporter_permissions }
+      end
     end
   end
 
@@ -247,6 +312,10 @@ describe ProjectPolicy do
         expect_disallowed(*master_permissions)
         expect_disallowed(*owner_permissions)
       end
+
+      it_behaves_like 'archived project policies' do
+        let(:regular_abilities) { developer_permissions }
+      end
     end
   end
 
@@ -264,6 +333,10 @@ describe ProjectPolicy do
         expect_allowed(*master_permissions)
         expect_disallowed(*owner_permissions)
       end
+
+      it_behaves_like 'archived project policies' do
+        let(:regular_abilities) { master_permissions }
+      end
     end
   end
 
@@ -281,6 +354,10 @@ describe ProjectPolicy do
         expect_allowed(*master_permissions)
         expect_allowed(*owner_permissions)
       end
+
+      it_behaves_like 'archived project policies' do
+        let(:regular_abilities) { owner_permissions }
+      end
     end
   end
 
@@ -298,6 +375,10 @@ describe ProjectPolicy do
         expect_allowed(*master_permissions)
         expect_allowed(*owner_permissions)
       end
+
+      it_behaves_like 'archived project policies' do
+        let(:regular_abilities) { owner_permissions }
+      end
     end
   end
 
@@ -308,4 +389,41 @@ describe ProjectPolicy do
   it_behaves_like 'project policies as master'
   it_behaves_like 'project policies as owner'
   it_behaves_like 'project policies as admin'
+
+  context 'when a public project has merge requests allowing access' do
+    include ProjectForksHelper
+    let(:user) { create(:user) }
+    let(:target_project) { create(:project, :public) }
+    let(:project) { fork_project(target_project) }
+    let!(:merge_request) do
+      create(
+        :merge_request,
+        target_project: target_project,
+        source_project: project,
+        allow_maintainer_to_push: true
+      )
+    end
+    let(:maintainer_abilities) do
+      %w(create_build update_build create_pipeline update_pipeline)
+    end
+
+    subject { described_class.new(user, project) }
+
+    it 'does not allow pushing code' do
+      expect_disallowed(*maintainer_abilities)
+    end
+
+    it 'allows pushing if the user is a member with push access to the target project' do
+      target_project.add_developer(user)
+
+      expect_allowed(*maintainer_abilities)
+    end
+
+    it 'dissallows abilities to a maintainer if the merge request was closed' do
+      target_project.add_developer(user)
+      merge_request.close!
+
+      expect_disallowed(*maintainer_abilities)
+    end
+  end
 end
diff --git a/spec/policies/protected_branch_policy_spec.rb b/spec/policies/protected_branch_policy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b39de42d721ff8481727b0d090fab422fbaccd32
--- /dev/null
+++ b/spec/policies/protected_branch_policy_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe ProtectedBranchPolicy do
+  let(:user) { create(:user) }
+  let(:name) { 'feature' }
+  let(:protected_branch) { create(:protected_branch, name: name) }
+  let(:project) { protected_branch.project }
+
+  subject { described_class.new(user, protected_branch) }
+
+  it 'branches can be updated via project masters' do
+    project.add_master(user)
+
+    is_expected.to be_allowed(:update_protected_branch)
+  end
+
+  it "branches can't be updated by guests" do
+    project.add_guest(user)
+
+    is_expected.to be_disallowed(:update_protected_branch)
+  end
+end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 1a8001be6ab2a93c216a19702c21aeb69d75f121..4bc005df2fc5ec456c55838fd63fb331b2c42300 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -72,13 +72,44 @@ describe Ci::BuildPresenter do
       end
     end
 
-    context 'when build is not auto-canceled' do
-      before do
-        expect(build).to receive(:auto_canceled?).and_return(false)
+    context 'when build failed' do
+      let(:build) { create(:ci_build, :failed, pipeline: pipeline) }
+
+      it 'returns the reason of failure' do
+        status_title = presenter.status_title
+
+        expect(status_title).to eq('Failed <br> (unknown failure)')
+      end
+    end
+
+    context 'when build has failed && retried' do
+      let(:build) { create(:ci_build, :failed, :retried, pipeline: pipeline) }
+
+      it 'does not include retried title' do
+        status_title = presenter.status_title
+
+        expect(status_title).not_to include('(retried)')
+        expect(status_title).to eq('Failed <br> (unknown failure)')
       end
+    end
+
+    context 'when build has failed and is allowed to' do
+      let(:build) { create(:ci_build, :failed, :allowed_to_fail, pipeline: pipeline) }
 
-      it 'does not have a status title' do
-        expect(presenter.status_title).to be_nil
+      it 'returns the reason of failure' do
+        status_title = presenter.status_title
+
+        expect(status_title).to eq('Failed <br> (unknown failure)')
+      end
+    end
+
+    context 'For any other build' do
+      let(:build) { create(:ci_build, :success, pipeline: pipeline) }
+
+      it 'returns the status' do
+        tooltip_description = presenter.status_title
+
+        expect(tooltip_description).to eq('Success')
       end
     end
   end
@@ -134,4 +165,91 @@ describe Ci::BuildPresenter do
       end
     end
   end
+
+  describe '#tooltip_message' do
+    context 'When build has failed' do
+      let(:build) { create(:ci_build, :script_failure, pipeline: pipeline) }
+
+      it 'returns the reason of failure' do
+        tooltip = subject.tooltip_message
+
+        expect(tooltip).to eq("#{build.name} - failed <br> (script failure)")
+      end
+    end
+
+    context 'When build has failed and retried' do
+      let(:build) { create(:ci_build, :script_failure, :retried, pipeline: pipeline) }
+
+      it 'should include the reason of failure and the retried title' do
+        tooltip = subject.tooltip_message
+
+        expect(tooltip).to eq("#{build.name} - failed <br> (script failure) (retried)")
+      end
+    end
+
+    context 'When build has failed and is allowed to' do
+      let(:build) { create(:ci_build, :script_failure, :allowed_to_fail, pipeline: pipeline) }
+
+      it 'should include the reason of failure' do
+        tooltip = subject.tooltip_message
+
+        expect(tooltip).to eq("#{build.name} - failed <br> (script failure) (allowed to fail)")
+      end
+    end
+
+    context 'For any other build (no retried)' do
+      let(:build) { create(:ci_build, :success, pipeline: pipeline) }
+
+      it 'should include build name and status' do
+        tooltip = subject.tooltip_message
+
+        expect(tooltip).to eq("#{build.name} - passed")
+      end
+    end
+
+    context 'For any other build (retried)' do
+      let(:build) { create(:ci_build, :success, :retried, pipeline: pipeline) }
+
+      it 'should include build name and status' do
+        tooltip = subject.tooltip_message
+
+        expect(tooltip).to eq("#{build.name} - passed (retried)")
+      end
+    end
+  end
+
+  describe '#callout_failure_message' do
+    let(:build) { create(:ci_build, :failed, :script_failure) }
+
+    it 'returns a verbose failure reason' do
+      description = subject.callout_failure_message
+      expect(description).to eq('There has been a script failure. Check the job log for more information')
+    end
+  end
+
+  describe '#recoverable?' do
+    let(:build) { create(:ci_build, :failed, :script_failure) }
+
+    context 'when is a script or missing dependency failure' do
+      let(:failure_reasons) { %w(script_failure missing_dependency_failure) }
+
+      it 'should return false' do
+        failure_reasons.each do |failure_reason|
+          build.update_attribute(:failure_reason, failure_reason)
+          expect(presenter.recoverable?).to be_falsy
+        end
+      end
+    end
+
+    context 'when is any other failure type' do
+      let(:failure_reasons) { %w(unknown_failure api_failure stuck_or_timeout_failure runner_system_failure) }
+
+      it 'should return true' do
+        failure_reasons.each do |failure_reason|
+          build.update_attribute(:failure_reason, failure_reason)
+          expect(presenter.recoverable?).to be_truthy
+        end
+      end
+    end
+  end
 end
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index f8c93d91ec5c1e4370ef2033ac01a43cb1f42349..55962f345d4d0f9a6b5a10264e6211b47a6425ab 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -339,7 +339,7 @@ describe ProjectPresenter do
 
         it 'returns link to clusters page if more than one exists' do
           project.add_master(user)
-          create(:cluster, projects: [project])
+          create(:cluster, :production_environment, projects: [project])
           create(:cluster, projects: [project])
 
           expect(presenter.kubernetes_cluster_anchor_data).to eq(OpenStruct.new(enabled: true,
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae64a9ca16253b37d822aca4dd10146889d97819
--- /dev/null
+++ b/spec/requests/api/badges_spec.rb
@@ -0,0 +1,367 @@
+require 'spec_helper'
+
+describe API::Badges do
+  let(:master) { create(:user, username: 'master_user') }
+  let(:developer) { create(:user) }
+  let(:access_requester) { create(:user) }
+  let(:stranger) { create(:user) }
+  let(:project_group) { create(:group) }
+  let(:project) { setup_project }
+  let!(:group) { setup_group }
+
+  shared_context 'source helpers' do
+    def get_source(source_type)
+      source_type == 'project' ? project : group
+    end
+  end
+
+  shared_examples 'GET /:sources/:id/badges' do |source_type|
+    include_context 'source helpers'
+
+    let(:source) { get_source(source_type) }
+
+    context "with :sources == #{source_type.pluralize}" do
+      it_behaves_like 'a 404 response when source is private' do
+        let(:route) { get api("/#{source_type.pluralize}/#{source.id}/badges", stranger) }
+      end
+
+      %i[master developer access_requester stranger].each do |type|
+        context "when authenticated as a #{type}" do
+          it 'returns 200' do
+            user = public_send(type)
+            badges_count = source_type == 'project' ? 3 : 2
+
+            get api("/#{source_type.pluralize}/#{source.id}/badges", user)
+
+            expect(response).to have_gitlab_http_status(200)
+            expect(response).to include_pagination_headers
+            expect(json_response).to be_an Array
+            expect(json_response.size).to eq(badges_count)
+          end
+        end
+      end
+
+      it 'avoids N+1 queries' do
+        # Establish baseline
+        get api("/#{source_type.pluralize}/#{source.id}/badges", master)
+
+        control = ActiveRecord::QueryRecorder.new do
+          get api("/#{source_type.pluralize}/#{source.id}/badges", master)
+        end
+
+        project.add_developer(create(:user))
+
+        expect do
+          get api("/#{source_type.pluralize}/#{source.id}/badges", master)
+        end.not_to exceed_query_limit(control)
+      end
+    end
+  end
+
+  shared_examples 'GET /:sources/:id/badges/:badge_id' do |source_type|
+    include_context 'source helpers'
+
+    let(:source) { get_source(source_type) }
+
+    context "with :sources == #{source_type.pluralize}" do
+      it_behaves_like 'a 404 response when source is private' do
+        let(:route) { get api("/#{source_type.pluralize}/#{source.id}/badges/#{developer.id}", stranger) }
+      end
+
+      context 'when authenticated as a non-member' do
+        %i[master developer access_requester stranger].each do |type|
+          let(:badge) { source.badges.first }
+
+          context "as a #{type}" do
+            it 'returns 200' do
+              user = public_send(type)
+
+              get api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user)
+
+              expect(response).to have_gitlab_http_status(200)
+              expect(json_response['id']).to eq(badge.id)
+              expect(json_response['link_url']).to eq(badge.link_url)
+              expect(json_response['rendered_link_url']).to eq(badge.rendered_link_url)
+              expect(json_response['image_url']).to eq(badge.image_url)
+              expect(json_response['rendered_image_url']).to eq(badge.rendered_image_url)
+              expect(json_response['kind']).to eq source_type
+            end
+          end
+        end
+      end
+    end
+  end
+
+  shared_examples 'POST /:sources/:id/badges' do |source_type|
+    include_context 'source helpers'
+
+    let(:source) { get_source(source_type) }
+    let(:example_url) { 'http://www.example.com' }
+    let(:example_url2) { 'http://www.example1.com' }
+
+    context "with :sources == #{source_type.pluralize}" do
+      it_behaves_like 'a 404 response when source is private' do
+        let(:route) do
+          post api("/#{source_type.pluralize}/#{source.id}/badges", stranger),
+               link_url: example_url, image_url: example_url2
+        end
+      end
+
+      context 'when authenticated as a non-member or member with insufficient rights' do
+        %i[access_requester stranger developer].each do |type|
+          context "as a #{type}" do
+            it 'returns 403' do
+              user = public_send(type)
+
+              post api("/#{source_type.pluralize}/#{source.id}/badges", user),
+                   link_url: example_url, image_url: example_url2
+
+              expect(response).to have_gitlab_http_status(403)
+            end
+          end
+        end
+      end
+
+      context 'when authenticated as a master/owner' do
+        it 'creates a new badge' do
+          expect do
+            post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+                link_url: example_url, image_url: example_url2
+
+            expect(response).to have_gitlab_http_status(201)
+          end.to change { source.badges.count }.by(1)
+
+          expect(json_response['link_url']).to eq(example_url)
+          expect(json_response['image_url']).to eq(example_url2)
+          expect(json_response['kind']).to eq source_type
+        end
+      end
+
+      it 'returns 400 when link_url is not given' do
+        post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+             link_url: example_url
+
+        expect(response).to have_gitlab_http_status(400)
+      end
+
+      it 'returns 400 when image_url is not given' do
+        post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+             image_url: example_url2
+
+        expect(response).to have_gitlab_http_status(400)
+      end
+
+      it 'returns 400 when link_url or image_url is not valid' do
+        post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+             link_url: 'whatever', image_url: 'whatever'
+
+        expect(response).to have_gitlab_http_status(400)
+      end
+    end
+  end
+
+  shared_examples 'PUT /:sources/:id/badges/:badge_id' do |source_type|
+    include_context 'source helpers'
+
+    let(:source) { get_source(source_type) }
+
+    context "with :sources == #{source_type.pluralize}" do
+      let(:badge) { source.badges.first }
+      let(:example_url) { 'http://www.example.com' }
+      let(:example_url2) { 'http://www.example1.com' }
+
+      it_behaves_like 'a 404 response when source is private' do
+        let(:route) do
+          put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger),
+              link_url: example_url
+        end
+      end
+
+      context 'when authenticated as a non-member or member with insufficient rights' do
+        %i[access_requester stranger developer].each do |type|
+          context "as a #{type}" do
+            it 'returns 403' do
+              user = public_send(type)
+
+              put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user),
+                  link_url: example_url
+
+              expect(response).to have_gitlab_http_status(403)
+            end
+          end
+        end
+      end
+
+      context 'when authenticated as a master/owner' do
+        it 'updates the member' do
+          put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master),
+              link_url: example_url, image_url: example_url2
+
+          expect(response).to have_gitlab_http_status(200)
+          expect(json_response['link_url']).to eq(example_url)
+          expect(json_response['image_url']).to eq(example_url2)
+          expect(json_response['kind']).to eq source_type
+        end
+      end
+
+      it 'returns 400 when link_url or image_url is not valid' do
+        put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master),
+            link_url: 'whatever', image_url: 'whatever'
+
+        expect(response).to have_gitlab_http_status(400)
+      end
+    end
+  end
+
+  shared_examples 'DELETE /:sources/:id/badges/:badge_id' do |source_type|
+    include_context 'source helpers'
+
+    let(:source) { get_source(source_type) }
+
+    context "with :sources == #{source_type.pluralize}" do
+      let(:badge) { source.badges.first }
+
+      it_behaves_like 'a 404 response when source is private' do
+        let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger) }
+      end
+
+      context 'when authenticated as a non-member or member with insufficient rights' do
+        %i[access_requester developer stranger].each do |type|
+          context "as a #{type}" do
+            it 'returns 403' do
+              user = public_send(type)
+
+              delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user)
+
+              expect(response).to have_gitlab_http_status(403)
+            end
+          end
+        end
+      end
+
+      context 'when authenticated as a master/owner' do
+        it 'deletes the badge' do
+          expect do
+            delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master)
+
+            expect(response).to have_gitlab_http_status(204)
+          end.to change { source.badges.count }.by(-1)
+        end
+
+        it_behaves_like '412 response' do
+          let(:request) { api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master) }
+        end
+      end
+
+      it 'returns 404 if badge does not exist' do
+        delete api("/#{source_type.pluralize}/#{source.id}/badges/123", master)
+
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+  end
+
+  shared_examples 'GET /:sources/:id/badges/render' do |source_type|
+    include_context 'source helpers'
+
+    let(:source) { get_source(source_type) }
+    let(:example_url) { 'http://www.example.com' }
+    let(:example_url2) { 'http://www.example1.com' }
+
+    context "with :sources == #{source_type.pluralize}" do
+      it_behaves_like 'a 404 response when source is private' do
+        let(:route) do
+          get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", stranger)
+        end
+      end
+
+      context 'when authenticated as a non-member or member with insufficient rights' do
+        %i[access_requester stranger developer].each do |type|
+          context "as a #{type}" do
+            it 'returns 403' do
+              user = public_send(type)
+
+              get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", user)
+
+              expect(response).to have_gitlab_http_status(403)
+            end
+          end
+        end
+      end
+
+      context 'when authenticated as a master/owner' do
+        it 'gets the rendered badge values' do
+          get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", master)
+
+          expect(response).to have_gitlab_http_status(200)
+
+          expect(json_response.keys).to contain_exactly('link_url', 'rendered_link_url', 'image_url', 'rendered_image_url')
+          expect(json_response['link_url']).to eq(example_url)
+          expect(json_response['image_url']).to eq(example_url2)
+          expect(json_response['rendered_link_url']).to eq(example_url)
+          expect(json_response['rendered_image_url']).to eq(example_url2)
+        end
+      end
+
+      it 'returns 400 when link_url is not given' do
+        get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}", master)
+
+        expect(response).to have_gitlab_http_status(400)
+      end
+
+      it 'returns 400 when image_url is not given' do
+        get api("/#{source_type.pluralize}/#{source.id}/badges/render?image_url=#{example_url}", master)
+
+        expect(response).to have_gitlab_http_status(400)
+      end
+
+      it 'returns 400 when link_url or image_url is not valid' do
+        get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=whatever&image_url=whatever", master)
+
+        expect(response).to have_gitlab_http_status(400)
+      end
+    end
+  end
+
+  context 'when deleting a badge' do
+    context 'and the source is a project' do
+      it 'cannot delete badges owned by the project group' do
+        delete api("/projects/#{project.id}/badges/#{project_group.badges.first.id}", master)
+
+        expect(response).to have_gitlab_http_status(403)
+      end
+    end
+  end
+
+  describe 'Endpoints' do
+    %w(project group).each do |source_type|
+      it_behaves_like 'GET /:sources/:id/badges', source_type
+      it_behaves_like 'GET /:sources/:id/badges/:badge_id', source_type
+      it_behaves_like 'GET /:sources/:id/badges/render', source_type
+      it_behaves_like 'POST /:sources/:id/badges', source_type
+      it_behaves_like 'PUT /:sources/:id/badges/:badge_id', source_type
+      it_behaves_like 'DELETE /:sources/:id/badges/:badge_id', source_type
+    end
+  end
+
+  def setup_project
+    create(:project, :public, :access_requestable, creator_id: master.id, namespace: project_group) do |project|
+      project.add_developer(developer)
+      project.add_master(master)
+      project.request_access(access_requester)
+      project.project_badges << build(:project_badge, project: project)
+      project.project_badges << build(:project_badge, project: project)
+      project_group.badges << build(:group_badge, group: group)
+    end
+  end
+
+  def setup_group
+    create(:group, :public, :access_requestable) do |group|
+      group.add_developer(developer)
+      group.add_owner(master)
+      group.request_access(access_requester)
+      group.badges << build(:group_badge, group: group)
+      group.badges << build(:group_badge, group: group)
+    end
+  end
+end
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index c6c10025f7f2f03750370ed101b7e6bb397aaedf..92b614b087ea6ad7163a10fb623ddd2d7c326f09 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -48,5 +48,36 @@ describe API::Boards do
       expect(json_response['label']['name']).to eq(group_label.title)
       expect(json_response['position']).to eq(3)
     end
+
+    it 'creates a new board list for ancestor group labels' do
+      group = create(:group)
+      sub_group = create(:group, parent: group)
+      group_label = create(:group_label, group: group)
+      board_parent.update(group: sub_group)
+      group.add_developer(user)
+      sub_group.add_developer(user)
+
+      post api(url, user), label_id: group_label.id
+
+      expect(response).to have_gitlab_http_status(201)
+      expect(json_response['label']['name']).to eq(group_label.title)
+    end
+  end
+
+  describe "POST /groups/:id/boards/lists", :nested_groups do
+    set(:group) { create(:group) }
+    set(:board_parent) { create(:group, parent: group ) }
+    let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }
+    set(:board) { create(:board, group: board_parent) }
+
+    it 'creates a new board list for ancestor group labels' do
+      group.add_developer(user)
+      group_label = create(:group_label, group: group)
+
+      post api(url, user), label_id: group_label.id
+
+      expect(response).to have_gitlab_http_status(201)
+      expect(json_response['label']['name']).to eq(group_label.title)
+    end
   end
 end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index e433597f58b06a0bc12c2f2b3c81602b1cdb826a..64f51d9843d8bb53aa1a6012c5f1ecbcb0ececa7 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -39,6 +39,27 @@ describe API::Branches do
       end
     end
 
+    context 'when search parameter is passed' do
+      context 'and branch exists' do
+        it 'returns correct branches' do
+          get api(route, user), per_page: 100, search: branch_name
+
+          searched_branch_names = json_response.map { |branch| branch['name'] }
+          project_branch_names = project.repository.branch_names.grep(/#{branch_name}/)
+
+          expect(searched_branch_names).to match_array(project_branch_names)
+        end
+      end
+
+      context 'and branch does not exist' do
+        it 'returns an empty array' do
+          get api(route, user), per_page: 100, search: 'no_such_branch_name_entropy_of_jabadabadu'
+
+          expect(json_response).to eq []
+        end
+      end
+    end
+
     context 'when unauthenticated', 'and project is public' do
       before do
         project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 852f67db9582ee59e8176985825b194b88509c95..8ad19e3f0f5686e2397536f30c36e406fa0adc2b 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -1141,4 +1141,33 @@ describe API::Commits do
       end
     end
   end
+
+  describe 'GET /projects/:id/repository/commits/:sha/merge_requests' do
+    let!(:project) { create(:project, :repository, :private) }
+    let!(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') }
+    let(:commit) { merged_mr.merge_request_diff.commits.last }
+
+    it 'returns the correct merge request' do
+      get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", user)
+
+      expect(response).to have_gitlab_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response.length).to eq(1)
+      expect(json_response[0]['id']).to eq(merged_mr.id)
+    end
+
+    it 'returns 403 for an unauthorized user' do
+      project.add_guest(user)
+
+      get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", user)
+
+      expect(response).to have_gitlab_http_status(403)
+    end
+
+    it 'responds 404 when the commit does not exist' do
+      get api("/projects/#{project.id}/repository/commits/a7d26f00c35b/merge_requests", user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+  end
 end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 0772b3f2e6462dccf6a378ddb8d14ef875024086..ae9c0e9c3047b93c5a0598050e1312d5b3cc0973 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -91,6 +91,10 @@ describe API::DeployKeys do
       expect do
         post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
       end.to change { project.deploy_keys.count }.by(1)
+
+      new_key = project.deploy_keys.last
+      expect(new_key.key).to eq(key_attrs[:key])
+      expect(new_key.user).to eq(admin)
     end
 
     it 'returns an existing ssh key when attempting to add a duplicate' do
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4a44b219a6728beb4027ec25d10a8d64784ee3be
--- /dev/null
+++ b/spec/requests/api/discussions_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe API::Discussions do
+  let(:user) { create(:user) }
+  let!(:project) { create(:project, :public, namespace: user.namespace) }
+  let(:private_user)    { create(:user) }
+
+  before do
+    project.add_reporter(user)
+  end
+
+  context "when noteable is an Issue" do
+    let!(:issue) { create(:issue, project: project, author: user) }
+    let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) }
+
+    it_behaves_like "discussions API", 'projects', 'issues', 'iid' do
+      let(:parent) { project }
+      let(:noteable) { issue }
+      let(:note) { issue_note }
+    end
+  end
+
+  context "when noteable is a Snippet" do
+    let!(:snippet) { create(:project_snippet, project: project, author: user) }
+    let!(:snippet_note) { create(:discussion_note_on_snippet, noteable: snippet, project: project, author: user) }
+
+    it_behaves_like "discussions API", 'projects', 'snippets', 'id' do
+      let(:parent) { project }
+      let(:noteable) { snippet }
+      let(:note) { snippet_note }
+    end
+  end
+end
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 267058d98eec214736d2e93a56479bc9ed753217..c5354c2d639d38314030f3e063e5ca1f77391a01 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
 describe API::Features do
-  let(:user)  { create(:user) }
-  let(:admin) { create(:admin) }
+  set(:user)  { create(:user) }
+  set(:admin) { create(:admin) }
 
   before do
     Flipper.unregister_groups
@@ -249,4 +249,43 @@ describe API::Features do
       end
     end
   end
+
+  describe 'DELETE /feature/:name' do
+    let(:feature_name) { 'my_feature' }
+
+    context 'when the user has no access' do
+      it 'returns a 401 for anonymous users' do
+        delete api("/features/#{feature_name}")
+
+        expect(response).to have_gitlab_http_status(401)
+      end
+
+      it 'returns a 403 for users' do
+        delete api("/features/#{feature_name}", user)
+
+        expect(response).to have_gitlab_http_status(403)
+      end
+    end
+
+    context 'when the user has access' do
+      it 'returns 204 when the value is not set' do
+        delete api("/features/#{feature_name}", admin)
+
+        expect(response).to have_gitlab_http_status(204)
+      end
+
+      context 'when the gate value was set' do
+        before do
+          Feature.get(feature_name).enable
+        end
+
+        it 'deletes an enabled feature' do
+          delete api("/features/#{feature_name}", admin)
+
+          expect(response).to have_gitlab_http_status(204)
+          expect(Feature.get(feature_name)).not_to be_enabled
+        end
+      end
+    end
+  end
 end
diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..894c94688ba4a56a854cb4569d89d97d70ed8125
--- /dev/null
+++ b/spec/requests/api/group_boards_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe API::GroupBoards do
+  set(:user) { create(:user) }
+  set(:non_member) { create(:user) }
+  set(:guest) { create(:user) }
+  set(:admin) { create(:user, :admin) }
+  set(:board_parent) { create(:group, :public) }
+
+  before do
+    board_parent.add_owner(user)
+  end
+
+  set(:project) { create(:project, :public, namespace: board_parent ) }
+
+  set(:dev_label) do
+    create(:group_label, title: 'Development', color: '#FFAABB', group: board_parent)
+  end
+
+  set(:test_label) do
+    create(:group_label, title: 'Testing', color: '#FFAACC', group: board_parent)
+  end
+
+  set(:ux_label) do
+    create(:group_label, title: 'UX', color: '#FF0000', group: board_parent)
+  end
+
+  set(:dev_list) do
+    create(:list, label: dev_label, position: 1)
+  end
+
+  set(:test_list) do
+    create(:list, label: test_label, position: 2)
+  end
+
+  set(:milestone) { create(:milestone, group: board_parent) }
+  set(:board_label) { create(:group_label, group: board_parent) }
+
+  set(:board) { create(:board, group: board_parent, lists: [dev_list, test_list]) }
+
+  it_behaves_like 'group and project boards', "/groups/:id/boards", false
+
+  describe 'POST /groups/:id/boards/lists' do
+    let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }
+
+    it 'does not create lists for child project labels' do
+      project_label = create(:label, project: project)
+
+      post api(url, user), label_id: project_label.id
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+  end
+end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 827f4c043249c0351faec25d74579c82cacd2121..db8c5f963d64aa908e0248d0769694fc1b4522ba 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -251,44 +251,23 @@ describe API::Internal do
       end
 
       context 'with env passed as a JSON' do
-        context 'when relative path envs are not set' do
-          it 'sets env in RequestStore' do
-            expect(Gitlab::Git::Env).to receive(:set).with({
-              'GIT_OBJECT_DIRECTORY' => 'foo',
-              'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
-            })
-
-            push(key, project.wiki, env: {
-              GIT_OBJECT_DIRECTORY: 'foo',
-              GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar'
-            }.to_json)
+        let(:gl_repository) { project.gl_repository(is_wiki: true) }
 
-            expect(response).to have_gitlab_http_status(200)
-          end
-        end
+        it 'sets env in RequestStore' do
+          obj_dir_relative = './objects'
+          alt_obj_dirs_relative = ['./alt-objects-1', './alt-objects-2']
 
-        context 'when relative path envs are set' do
-          it 'sets env in RequestStore' do
-            obj_dir_relative = './objects'
-            alt_obj_dirs_relative = ['./alt-objects-1', './alt-objects-2']
-            repo_path = project.wiki.repository.path_to_repo
-
-            expect(Gitlab::Git::Env).to receive(:set).with({
-              'GIT_OBJECT_DIRECTORY' => File.join(repo_path, obj_dir_relative),
-              'GIT_ALTERNATE_OBJECT_DIRECTORIES' => alt_obj_dirs_relative.map { |d| File.join(repo_path, d) },
-              'GIT_OBJECT_DIRECTORY_RELATIVE' => obj_dir_relative,
-              'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => alt_obj_dirs_relative
-            })
-
-            push(key, project.wiki, env: {
-              GIT_OBJECT_DIRECTORY: 'foo',
-              GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar',
-              GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
-              GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
-            }.to_json)
+          expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, {
+            'GIT_OBJECT_DIRECTORY_RELATIVE' => obj_dir_relative,
+            'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => alt_obj_dirs_relative
+          })
 
-            expect(response).to have_gitlab_http_status(200)
-          end
+          push(key, project.wiki, env: {
+            GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
+            GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
+          }.to_json)
+
+          expect(response).to have_gitlab_http_status(200)
         end
       end
 
@@ -335,21 +314,8 @@ describe API::Internal do
       end
 
       context "git push" do
-        context "gitaly disabled", :disable_gitaly do
-          it "has the correct payload" do
-            push(key, project)
-
-            expect(response).to have_gitlab_http_status(200)
-            expect(json_response["status"]).to be_truthy
-            expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
-            expect(json_response["gl_repository"]).to eq("project-#{project.id}")
-            expect(json_response["gitaly"]).to be_nil
-            expect(user).not_to have_an_activity_record
-          end
-        end
-
-        context "gitaly enabled" do
-          it "has the correct payload" do
+        context 'project as namespace/project' do
+          it do
             push(key, project)
 
             expect(response).to have_gitlab_http_status(200)
@@ -365,17 +331,6 @@ describe API::Internal do
             expect(user).not_to have_an_activity_record
           end
         end
-
-        context 'project as namespace/project' do
-          it do
-            push(key, project)
-
-            expect(response).to have_gitlab_http_status(200)
-            expect(json_response["status"]).to be_truthy
-            expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
-            expect(json_response["gl_repository"]).to eq("project-#{project.id}")
-          end
-        end
       end
     end
 
@@ -471,6 +426,12 @@ describe API::Internal do
 
           expect(response).to have_gitlab_http_status(200)
           expect(json_response["status"]).to be_truthy
+          expect(json_response["gitaly"]).not_to be_nil
+          expect(json_response["gitaly"]["repository"]).not_to be_nil
+          expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
+          expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
+          expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
+          expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
         end
       end
 
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index d1569e5d650de5d93ba289747ef36d04cee61f14..90f9c4ad214a46e888ea1d030dec322beb4de892 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -163,6 +163,42 @@ describe API::Issues do
         expect(first_issue['id']).to eq(issue.id)
       end
 
+      context 'filtering before a specific date' do
+        let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) }
+
+        it 'returns issues created before a specific date' do
+          get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
+
+          expect(json_response.size).to eq(1)
+          expect(first_issue['id']).to eq(issue2.id)
+        end
+
+        it 'returns issues updated before a specific date' do
+          get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
+
+          expect(json_response.size).to eq(1)
+          expect(first_issue['id']).to eq(issue2.id)
+        end
+      end
+
+      context 'filtering after a specific date' do
+        let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+
+        it 'returns issues created after a specific date' do
+          get api("/issues?created_after=#{issue2.created_at}", user)
+
+          expect(json_response.size).to eq(1)
+          expect(first_issue['id']).to eq(issue2.id)
+        end
+
+        it 'returns issues updated after a specific date' do
+          get api("/issues?updated_after=#{issue2.updated_at}", user)
+
+          expect(json_response.size).to eq(1)
+          expect(first_issue['id']).to eq(issue2.id)
+        end
+      end
+
       it 'returns an array of labeled issues' do
         get api("/issues", user), labels: label.title
 
@@ -348,6 +384,30 @@ describe API::Issues do
     end
     let(:base_url) { "/groups/#{group.id}/issues" }
 
+    context 'when group has subgroups', :nested_groups do
+      let(:subgroup_1) { create(:group, parent: group) }
+      let(:subgroup_2) { create(:group, parent: subgroup_1) }
+
+      let(:subgroup_1_project) { create(:project, namespace: subgroup_1) }
+      let(:subgroup_2_project) { create(:project, namespace: subgroup_2) }
+
+      let!(:issue_1) { create(:issue, project: subgroup_1_project) }
+      let!(:issue_2) { create(:issue, project: subgroup_2_project) }
+
+      before do
+        group.add_developer(user)
+      end
+
+      it 'also returns subgroups projects issues' do
+        get api(base_url, user)
+
+        issue_ids = json_response.map { |issue| issue['id'] }
+
+        expect_paginated_array_response(size: 5)
+        expect(issue_ids).to include(issue_1.id, issue_2.id)
+      end
+    end
+
     it 'returns all group issues (including opened and closed)' do
       get api(base_url, admin)
 
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 6192bbd4abb84dc8083d1f7c0e7712b7e7cc8559..3ffdfdc0e9a9e562b4609107418f56fed78ec7ef 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe API::Jobs do
+  include HttpIOHelpers
+
   set(:project) do
     create(:project, :repository, public_builds: false)
   end
@@ -112,6 +114,7 @@ describe API::Jobs do
     let(:query) { Hash.new }
 
     before do
+      job
       get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
     end
 
@@ -335,10 +338,55 @@ describe API::Jobs do
           end
         end
 
+        context 'when artifacts are stored remotely' do
+          let(:proxy_download) { false }
+
+          before do
+            stub_artifacts_object_storage(proxy_download: proxy_download)
+          end
+
+          let(:job) { create(:ci_build, pipeline: pipeline) }
+          let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
+
+          before do
+            job.reload
+
+            get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+          end
+
+          context 'when proxy download is enabled' do
+            let(:proxy_download) { true }
+
+            it 'responds with the workhorse send-url' do
+              expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
+            end
+          end
+
+          context 'when proxy download is disabled' do
+            it 'returns location redirect' do
+              expect(response).to have_gitlab_http_status(302)
+            end
+          end
+
+          context 'authorized user' do
+            it 'returns the file remote URL' do
+              expect(response).to redirect_to(artifact.file.url)
+            end
+          end
+
+          context 'unauthorized user' do
+            let(:api_user) { nil }
+
+            it 'does not return specific job artifacts' do
+              expect(response).to have_gitlab_http_status(404)
+            end
+          end
+        end
+
         it 'does not return job artifacts if not uploaded' do
           get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
 
-          expect(response).to have_gitlab_http_status(404)
+          expect(response).to have_gitlab_http_status(:not_found)
         end
       end
     end
@@ -349,6 +397,7 @@ describe API::Jobs do
     let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
 
     before do
+      stub_artifacts_object_storage
       job.success
     end
 
@@ -412,9 +461,24 @@ describe API::Jobs do
                 "attachment; filename=#{job.artifacts_file.filename}" }
           end
 
-          it { expect(response).to have_gitlab_http_status(200) }
+          it { expect(response).to have_http_status(:ok) }
           it { expect(response.headers).to include(download_headers) }
         end
+
+        context 'when artifacts are stored remotely' do
+          let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
+          let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
+
+          before do
+            job.reload
+
+            get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+          end
+
+          it 'returns location redirect' do
+            expect(response).to have_http_status(:found)
+          end
+        end
       end
 
       context 'with regular branch' do
@@ -451,6 +515,22 @@ describe API::Jobs do
     end
 
     context 'authorized user' do
+      context 'when trace is in ObjectStorage' do
+        let!(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+        before do
+          stub_remote_trace_206
+          allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
+          allow_any_instance_of(JobArtifactUploader).to receive(:url) { remote_trace_url }
+          allow_any_instance_of(JobArtifactUploader).to receive(:size) { remote_trace_size }
+        end
+
+        it 'returns specific job trace' do
+          expect(response).to have_gitlab_http_status(200)
+          expect(response.body).to eq(job.trace.raw)
+        end
+      end
+
       context 'when trace is artifact' do
         let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
 
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e8eb01f6c3264deb23b7c05beb78c6a5d47e84d7..f64623d70180423dba321ec8c8817b2435595261 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -172,6 +172,42 @@ describe API::MergeRequests do
         end
       end
 
+      it 'returns merge requests created before a specific date' do
+        merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: Date.new(2000, 1, 1))
+
+        get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user)
+
+        expect(json_response.size).to eq(1)
+        expect(json_response.first['id']).to eq(merge_request2.id)
+      end
+
+      it 'returns merge requests created after a specific date' do
+        merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: 1.week.from_now)
+
+        get api("/merge_requests?created_after=#{merge_request2.created_at}", user)
+
+        expect(json_response.size).to eq(1)
+        expect(json_response.first['id']).to eq(merge_request2.id)
+      end
+
+      it 'returns merge requests updated before a specific date' do
+        merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: Date.new(2000, 1, 1))
+
+        get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user)
+
+        expect(json_response.size).to eq(1)
+        expect(json_response.first['id']).to eq(merge_request2.id)
+      end
+
+      it 'returns merge requests updated after a specific date' do
+        merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: 1.week.from_now)
+
+        get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user)
+
+        expect(json_response.size).to eq(1)
+        expect(json_response.first['id']).to eq(merge_request2.id)
+      end
+
       context 'search params' do
         before do
           merge_request.update(title: 'Search title', description: 'Search description')
@@ -580,6 +616,25 @@ describe API::MergeRequests do
         expect(json_response['changes_count']).to eq('5+')
       end
     end
+
+    context 'for forked projects' do
+      let(:user2) { create(:user) }
+      let(:project) { create(:project, :public, :repository) }
+      let(:forked_project) { fork_project(project, user2, repository: true) }
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: forked_project,
+               target_project: project,
+               source_branch: 'fixes',
+               allow_maintainer_to_push: true)
+      end
+
+      it 'includes the `allow_maintainer_to_push` field' do
+        get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
+
+        expect(json_response['allow_maintainer_to_push']).to be_truthy
+      end
+    end
   end
 
   describe 'GET /projects/:id/merge_requests/:merge_request_iid/participants' do
@@ -779,6 +834,7 @@ describe API::MergeRequests do
 
     context 'forked projects' do
       let!(:user2) { create(:user) }
+      let(:project) { create(:project, :public, :repository) }
       let!(:forked_project) { fork_project(project, user2, repository: true) }
       let!(:unrelated_project) { create(:project,  namespace: create(:user).namespace, creator_id: user2.id) }
 
@@ -805,7 +861,7 @@ describe API::MergeRequests do
         expect(json_response['title']).to eq('Test merge_request')
       end
 
-      it 'returns 422 when target project has disabled merge requests' do
+      it 'returns 403 when target project has disabled merge requests' do
         project.project_feature.update(merge_requests_access_level: 0)
 
         post api("/projects/#{forked_project.id}/merge_requests", user2),
@@ -815,7 +871,7 @@ describe API::MergeRequests do
              author: user2,
              target_project_id: project.id
 
-        expect(response).to have_gitlab_http_status(422)
+        expect(response).to have_gitlab_http_status(403)
       end
 
       it "returns 400 when source_branch is missing" do
@@ -836,6 +892,14 @@ describe API::MergeRequests do
         expect(response).to have_gitlab_http_status(400)
       end
 
+      it 'allows setting `allow_maintainer_to_push`' do
+        post api("/projects/#{forked_project.id}/merge_requests", user2),
+          title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
+          author: user2, target_project_id: project.id, allow_maintainer_to_push: true
+        expect(response).to have_gitlab_http_status(201)
+        expect(json_response['allow_maintainer_to_push']).to be_truthy
+      end
+
       context 'when target_branch and target_project_id is specified' do
         let(:params) do
           { title: 'Test merge_request',
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 981c9c27325972b5d6d2e0e7919b9820d73d847d..dd568c24c72c6b9a90c7b300ba2a6ef9a727ee0b 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -3,117 +3,86 @@ require 'spec_helper'
 describe API::Notes do
   let(:user) { create(:user) }
   let!(:project) { create(:project, :public, namespace: user.namespace) }
-  let!(:issue) { create(:issue, project: project, author: user) }
-  let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
-  let!(:snippet) { create(:project_snippet, project: project, author: user) }
-  let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
-  let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
-  let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
-
-  # For testing the cross-reference of a private issue in a public issue
   let(:private_user)    { create(:user) }
-  let(:private_project) do
-    create(:project, namespace: private_user.namespace)
-    .tap { |p| p.add_master(private_user) }
-  end
-  let(:private_issue)    { create(:issue, project: private_project) }
-
-  let(:ext_proj)  { create(:project, :public) }
-  let(:ext_issue) { create(:issue, project: ext_proj) }
-
-  let!(:cross_reference_note) do
-    create :note,
-    noteable: ext_issue, project: ext_proj,
-    note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
-    system: true
-  end
 
   before do
     project.add_reporter(user)
   end
 
-  describe "GET /projects/:id/noteable/:noteable_id/notes" do
-    context "when noteable is an Issue" do
-      context 'sorting' do
-        before do
-          create_list(:note, 3, noteable: issue, project: project, author: user)
-        end
-
-        it 'sorts by created_at in descending order by default' do
-          get api("/projects/#{project.id}/issues/#{issue.iid}/notes", user)
-
-          response_dates = json_response.map { |noteable| noteable['created_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort.reverse)
-        end
-
-        it 'sorts by ascending order when requested' do
-          get api("/projects/#{project.id}/issues/#{issue.iid}/notes?sort=asc", user)
-
-          response_dates = json_response.map { |noteable| noteable['created_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort)
-        end
-
-        it 'sorts by updated_at in descending order when requested' do
-          get api("/projects/#{project.id}/issues/#{issue.iid}/notes?order_by=updated_at", user)
-
-          response_dates = json_response.map { |noteable| noteable['updated_at'] }
+  context "when noteable is an Issue" do
+    let!(:issue) { create(:issue, project: project, author: user) }
+    let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
 
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort.reverse)
-        end
+    it_behaves_like "noteable API", 'projects', 'issues', 'iid' do
+      let(:parent) { project }
+      let(:noteable) { issue }
+      let(:note) { issue_note }
+    end
 
-        it 'sorts by updated_at in ascending order when requested' do
-          get api("/projects/#{project.id}/issues/#{issue.iid}/notes??order_by=updated_at&sort=asc", user)
+    context 'when user does not have access to create noteable' do
+      let(:private_issue) { create(:issue, project: create(:project, :private)) }
 
-          response_dates = json_response.map { |noteable| noteable['updated_at'] }
+      ##
+      # We are posting to project user has access to, but we use issue id
+      # from a different project, see #15577
+      #
+      before do
+        post api("/projects/#{private_issue.project.id}/issues/#{private_issue.iid}/notes", user),
+             body: 'Hi!'
+      end
 
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort)
-        end
+      it 'responds with resource not found error' do
+        expect(response.status).to eq 404
       end
 
-      it "returns an array of issue notes" do
-        get api("/projects/#{project.id}/issues/#{issue.iid}/notes", user)
+      it 'does not create new note' do
+        expect(private_issue.notes.reload).to be_empty
+      end
+    end
 
-        expect(response).to have_gitlab_http_status(200)
-        expect(response).to include_pagination_headers
-        expect(json_response).to be_an Array
-        expect(json_response.first['body']).to eq(issue_note.note)
+    context "when referencing other project" do
+      # For testing the cross-reference of a private issue in a public project
+      let(:private_project) do
+        create(:project, namespace: private_user.namespace)
+        .tap { |p| p.add_master(private_user) }
       end
+      let(:private_issue)    { create(:issue, project: private_project) }
 
-      it "returns a 404 error when issue id not found" do
-        get api("/projects/#{project.id}/issues/12345/notes", user)
+      let(:ext_proj)  { create(:project, :public) }
+      let(:ext_issue) { create(:issue, project: ext_proj) }
 
-        expect(response).to have_gitlab_http_status(404)
+      let!(:cross_reference_note) do
+        create :note,
+        noteable: ext_issue, project: ext_proj,
+        note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
+        system: true
       end
 
-      context "and current user cannot view the notes" do
-        it "returns an empty array" do
-          get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user)
-
-          expect(response).to have_gitlab_http_status(200)
-          expect(response).to include_pagination_headers
-          expect(json_response).to be_an Array
-          expect(json_response).to be_empty
-        end
+      describe "GET /projects/:id/noteable/:noteable_id/notes" do
+        context "current user cannot view the notes" do
+          it "returns an empty array" do
+            get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user)
 
-        context "and issue is confidential" do
-          before do
-            ext_issue.update_attributes(confidential: true)
+            expect(response).to have_gitlab_http_status(200)
+            expect(response).to include_pagination_headers
+            expect(json_response).to be_an Array
+            expect(json_response).to be_empty
           end
 
-          it "returns 404" do
-            get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user)
+          context "issue is confidential" do
+            before do
+              ext_issue.update_attributes(confidential: true)
+            end
 
-            expect(response).to have_gitlab_http_status(404)
+            it "returns 404" do
+              get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user)
+
+              expect(response).to have_gitlab_http_status(404)
+            end
           end
         end
 
-        context "and current user can view the note" do
+        context "current user can view the note" do
           it "returns an empty array" do
             get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", private_user)
 
@@ -124,172 +93,29 @@ describe API::Notes do
           end
         end
       end
-    end
-
-    context "when noteable is a Snippet" do
-      context 'sorting' do
-        before do
-          create_list(:note, 3, noteable: snippet, project: project, author: user)
-        end
-
-        it 'sorts by created_at in descending order by default' do
-          get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
-
-          response_dates = json_response.map { |noteable| noteable['created_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort.reverse)
-        end
-
-        it 'sorts by ascending order when requested' do
-          get api("/projects/#{project.id}/snippets/#{snippet.id}/notes?sort=asc", user)
-
-          response_dates = json_response.map { |noteable| noteable['created_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort)
-        end
-
-        it 'sorts by updated_at in descending order when requested' do
-          get api("/projects/#{project.id}/snippets/#{snippet.id}/notes?order_by=updated_at", user)
-
-          response_dates = json_response.map { |noteable| noteable['updated_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort.reverse)
-        end
 
-        it 'sorts by updated_at in ascending order when requested' do
-          get api("/projects/#{project.id}/snippets/#{snippet.id}/notes??order_by=updated_at&sort=asc", user)
+      describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do
+        context "current user cannot view the notes" do
+          it "returns a 404 error" do
+            get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", user)
 
-          response_dates = json_response.map { |noteable| noteable['updated_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort)
-        end
-      end
-      it "returns an array of snippet notes" do
-        get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
-
-        expect(response).to have_gitlab_http_status(200)
-        expect(response).to include_pagination_headers
-        expect(json_response).to be_an Array
-        expect(json_response.first['body']).to eq(snippet_note.note)
-      end
-
-      it "returns a 404 error when snippet id not found" do
-        get api("/projects/#{project.id}/snippets/42/notes", user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it "returns 404 when not authorized" do
-        get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-    end
-
-    context "when noteable is a Merge Request" do
-      context 'sorting' do
-        before do
-          create_list(:note, 3, noteable: merge_request, project: project, author: user)
-        end
-
-        it 'sorts by created_at in descending order by default' do
-          get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user)
-
-          response_dates = json_response.map { |noteable| noteable['created_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort.reverse)
-        end
-
-        it 'sorts by ascending order when requested' do
-          get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes?sort=asc", user)
-
-          response_dates = json_response.map { |noteable| noteable['created_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort)
-        end
-
-        it 'sorts by updated_at in descending order when requested' do
-          get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes?order_by=updated_at", user)
-
-          response_dates = json_response.map { |noteable| noteable['updated_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort.reverse)
-        end
-
-        it 'sorts by updated_at in ascending order when requested' do
-          get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes??order_by=updated_at&sort=asc", user)
-
-          response_dates = json_response.map { |noteable| noteable['updated_at'] }
-
-          expect(json_response.length).to eq(4)
-          expect(response_dates).to eq(response_dates.sort)
-        end
-      end
-      it "returns an array of merge_requests notes" do
-        get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user)
-
-        expect(response).to have_gitlab_http_status(200)
-        expect(response).to include_pagination_headers
-        expect(json_response).to be_an Array
-        expect(json_response.first['body']).to eq(merge_request_note.note)
-      end
-
-      it "returns a 404 error if merge request id not found" do
-        get api("/projects/#{project.id}/merge_requests/4444/notes", user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it "returns 404 when not authorized" do
-        get api("/projects/#{project.id}/merge_requests/4444/notes", private_user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-    end
-  end
-
-  describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do
-    context "when noteable is an Issue" do
-      it "returns an issue note by id" do
-        get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", user)
-
-        expect(response).to have_gitlab_http_status(200)
-        expect(json_response['body']).to eq(issue_note.note)
-      end
-
-      it "returns a 404 error if issue note not found" do
-        get api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      context "and current user cannot view the note" do
-        it "returns a 404 error" do
-          get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", user)
-
-          expect(response).to have_gitlab_http_status(404)
-        end
-
-        context "when issue is confidential" do
-          before do
-            issue.update_attributes(confidential: true)
+            expect(response).to have_gitlab_http_status(404)
           end
 
-          it "returns 404" do
-            get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", private_user)
+          context "when issue is confidential" do
+            before do
+              issue.update_attributes(confidential: true)
+            end
 
-            expect(response).to have_gitlab_http_status(404)
+            it "returns 404" do
+              get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", private_user)
+
+              expect(response).to have_gitlab_http_status(404)
+            end
           end
         end
 
-        context "and current user can view the note" do
+        context "current user can view the note" do
           it "returns an issue note by id" do
             get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", private_user)
 
@@ -299,132 +125,27 @@ describe API::Notes do
         end
       end
     end
-
-    context "when noteable is a Snippet" do
-      it "returns a snippet note by id" do
-        get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user)
-
-        expect(response).to have_gitlab_http_status(200)
-        expect(json_response['body']).to eq(snippet_note.note)
-      end
-
-      it "returns a 404 error if snippet note not found" do
-        get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-    end
   end
 
-  describe "POST /projects/:id/noteable/:noteable_id/notes" do
-    context "when noteable is an Issue" do
-      it "creates a new issue note" do
-        post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'hi!'
-
-        expect(response).to have_gitlab_http_status(201)
-        expect(json_response['body']).to eq('hi!')
-        expect(json_response['author']['username']).to eq(user.username)
-      end
-
-      it "returns a 400 bad request error if body not given" do
-        post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user)
-
-        expect(response).to have_gitlab_http_status(400)
-      end
-
-      it "returns a 401 unauthorized error if user not authenticated" do
-        post api("/projects/#{project.id}/issues/#{issue.iid}/notes"), body: 'hi!'
-
-        expect(response).to have_gitlab_http_status(401)
-      end
-
-      context 'when an admin or owner makes the request' do
-        it 'accepts the creation date to be set' do
-          creation_time = 2.weeks.ago
-          post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user),
-            body: 'hi!', created_at: creation_time
-
-          expect(response).to have_gitlab_http_status(201)
-          expect(json_response['body']).to eq('hi!')
-          expect(json_response['author']['username']).to eq(user.username)
-          expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
-        end
-      end
-
-      context 'when the user is posting an award emoji on an issue created by someone else' do
-        let(:issue2) { create(:issue, project: project) }
-
-        it 'creates a new issue note' do
-          post api("/projects/#{project.id}/issues/#{issue2.iid}/notes", user), body: ':+1:'
-
-          expect(response).to have_gitlab_http_status(201)
-          expect(json_response['body']).to eq(':+1:')
-        end
-      end
-
-      context 'when the user is posting an award emoji on his/her own issue' do
-        it 'creates a new issue note' do
-          post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: ':+1:'
-
-          expect(response).to have_gitlab_http_status(201)
-          expect(json_response['body']).to eq(':+1:')
-        end
-      end
-    end
-
-    context "when noteable is a Snippet" do
-      it "creates a new snippet note" do
-        post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!'
+  context "when noteable is a Snippet" do
+    let!(:snippet) { create(:project_snippet, project: project, author: user) }
+    let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
 
-        expect(response).to have_gitlab_http_status(201)
-        expect(json_response['body']).to eq('hi!')
-        expect(json_response['author']['username']).to eq(user.username)
-      end
-
-      it "returns a 400 bad request error if body not given" do
-        post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
-
-        expect(response).to have_gitlab_http_status(400)
-      end
-
-      it "returns a 401 unauthorized error if user not authenticated" do
-        post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
-
-        expect(response).to have_gitlab_http_status(401)
-      end
+    it_behaves_like "noteable API", 'projects', 'snippets', 'id' do
+      let(:parent) { project }
+      let(:noteable) { snippet }
+      let(:note) { snippet_note }
     end
+  end
 
-    context 'when user does not have access to read the noteable' do
-      it 'responds with 404' do
-        project = create(:project, :private) { |p| p.add_guest(user) }
-        issue = create(:issue, :confidential, project: project)
-
-        post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user),
-          body: 'Foo'
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-    end
-
-    context 'when user does not have access to create noteable' do
-      let(:private_issue) { create(:issue, project: create(:project, :private)) }
-
-      ##
-      # We are posting to project user has access to, but we use issue id
-      # from a different project, see #15577
-      #
-      before do
-        post api("/projects/#{private_issue.project.id}/issues/#{private_issue.iid}/notes", user),
-             body: 'Hi!'
-      end
-
-      it 'responds with resource not found error' do
-        expect(response.status).to eq 404
-      end
+  context "when noteable is a Merge Request" do
+    let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
+    let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
 
-      it 'does not create new note' do
-        expect(private_issue.notes.reload).to be_empty
-      end
+    it_behaves_like "noteable API", 'projects', 'merge_requests', 'iid' do
+      let(:parent) { project }
+      let(:noteable) { merge_request }
+      let(:note) { merge_request_note }
     end
 
     context 'when the merge request discussion is locked' do
@@ -461,145 +182,4 @@ describe API::Notes do
       end
     end
   end
-
-  describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
-    it "creates an activity event when an issue note is created" do
-      expect(Event).to receive(:create!)
-
-      post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'hi!'
-    end
-  end
-
-  describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do
-    context 'when noteable is an Issue' do
-      it 'returns modified note' do
-        put api("/projects/#{project.id}/issues/#{issue.iid}/"\
-                  "notes/#{issue_note.id}", user), body: 'Hello!'
-
-        expect(response).to have_gitlab_http_status(200)
-        expect(json_response['body']).to eq('Hello!')
-      end
-
-      it 'returns a 404 error when note id not found' do
-        put api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user),
-                body: 'Hello!'
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it 'returns a 400 bad request error if body not given' do
-        put api("/projects/#{project.id}/issues/#{issue.iid}/"\
-                  "notes/#{issue_note.id}", user)
-
-        expect(response).to have_gitlab_http_status(400)
-      end
-    end
-
-    context 'when noteable is a Snippet' do
-      it 'returns modified note' do
-        put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
-                  "notes/#{snippet_note.id}", user), body: 'Hello!'
-
-        expect(response).to have_gitlab_http_status(200)
-        expect(json_response['body']).to eq('Hello!')
-      end
-
-      it 'returns a 404 error when note id not found' do
-        put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
-                  "notes/12345", user), body: "Hello!"
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-    end
-
-    context 'when noteable is a Merge Request' do
-      it 'returns modified note' do
-        put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/"\
-                  "notes/#{merge_request_note.id}", user), body: 'Hello!'
-
-        expect(response).to have_gitlab_http_status(200)
-        expect(json_response['body']).to eq('Hello!')
-      end
-
-      it 'returns a 404 error when note id not found' do
-        put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/"\
-                  "notes/12345", user), body: "Hello!"
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-    end
-  end
-
-  describe 'DELETE /projects/:id/noteable/:noteable_id/notes/:note_id' do
-    context 'when noteable is an Issue' do
-      it 'deletes a note' do
-        delete api("/projects/#{project.id}/issues/#{issue.iid}/"\
-                   "notes/#{issue_note.id}", user)
-
-        expect(response).to have_gitlab_http_status(204)
-        # Check if note is really deleted
-        delete api("/projects/#{project.id}/issues/#{issue.iid}/"\
-                   "notes/#{issue_note.id}", user)
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it 'returns a 404 error when note id not found' do
-        delete api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it_behaves_like '412 response' do
-        let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", user) }
-      end
-    end
-
-    context 'when noteable is a Snippet' do
-      it 'deletes a note' do
-        delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
-                   "notes/#{snippet_note.id}", user)
-
-        expect(response).to have_gitlab_http_status(204)
-        # Check if note is really deleted
-        delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
-                   "notes/#{snippet_note.id}", user)
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it 'returns a 404 error when note id not found' do
-        delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
-                   "notes/12345", user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it_behaves_like '412 response' do
-        let(:request) { api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) }
-      end
-    end
-
-    context 'when noteable is a Merge Request' do
-      it 'deletes a note' do
-        delete api("/projects/#{project.id}/merge_requests/"\
-                   "#{merge_request.iid}/notes/#{merge_request_note.id}", user)
-
-        expect(response).to have_gitlab_http_status(204)
-        # Check if note is really deleted
-        delete api("/projects/#{project.id}/merge_requests/"\
-                   "#{merge_request.iid}/notes/#{merge_request_note.id}", user)
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it 'returns a 404 error when note id not found' do
-        delete api("/projects/#{project.id}/merge_requests/"\
-                   "#{merge_request.iid}/notes/12345", user)
-
-        expect(response).to have_gitlab_http_status(404)
-      end
-
-      it_behaves_like '412 response' do
-        let(:request) { api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes/#{merge_request_note.id}", user) }
-      end
-    end
-  end
 end
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 025165622b798a740ab9c5765eb0c41595e14408..a9ccbb3266611a3a6c88db5930f88610a595d9e5 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -1,22 +1,22 @@
 require 'rails_helper'
 
 describe API::PagesDomains do
-  set(:project) { create(:project, path: 'my.project') }
+  set(:project) { create(:project, path: 'my.project', pages_https_only: false) }
   set(:user) { create(:user) }
   set(:admin) { create(:admin) }
 
-  set(:pages_domain) { create(:pages_domain, domain: 'www.domain.test', project: project) }
-  set(:pages_domain_secure) { create(:pages_domain, :with_certificate, :with_key, domain: 'ssl.domain.test', project: project) }
-  set(:pages_domain_expired) { create(:pages_domain, :with_expired_certificate, :with_key, domain: 'expired.domain.test', project: project) }
+  set(:pages_domain) { create(:pages_domain, :without_key, :without_certificate, domain: 'www.domain.test', project: project) }
+  set(:pages_domain_secure) { create(:pages_domain, domain: 'ssl.domain.test', project: project) }
+  set(:pages_domain_expired) { create(:pages_domain, :with_expired_certificate, domain: 'expired.domain.test', project: project) }
 
-  let(:pages_domain_params) { build(:pages_domain, domain: 'www.other-domain.test').slice(:domain) }
-  let(:pages_domain_secure_params) { build(:pages_domain, :with_certificate, :with_key, domain: 'ssl.other-domain.test', project: project).slice(:domain, :certificate, :key) }
-  let(:pages_domain_secure_key_missmatch_params) {build(:pages_domain, :with_trusted_chain, :with_key, project: project).slice(:domain, :certificate, :key) }
+  let(:pages_domain_params) { build(:pages_domain, :without_key, :without_certificate, domain: 'www.other-domain.test').slice(:domain) }
+  let(:pages_domain_secure_params) { build(:pages_domain, domain: 'ssl.other-domain.test', project: project).slice(:domain, :certificate, :key) }
+  let(:pages_domain_secure_key_missmatch_params) {build(:pages_domain, :with_trusted_chain, project: project).slice(:domain, :certificate, :key) }
   let(:pages_domain_secure_missing_chain_params) {build(:pages_domain, :with_missing_chain, project: project).slice(:certificate) }
 
   let(:route) { "/projects/#{project.id}/pages/domains" }
   let(:route_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain.domain}" }
-  let(:route_domain_path) { "/projects/#{project.path_with_namespace.gsub('/', '%2F')}/pages/domains/#{pages_domain.domain}" }
+  let(:route_domain_path) { "/projects/#{project.full_path.gsub('/', '%2F')}/pages/domains/#{pages_domain.domain}" }
   let(:route_secure_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_secure.domain}" }
   let(:route_expired_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_expired.domain}" }
   let(:route_vacant_domain) { "/projects/#{project.id}/pages/domains/www.vacant-domain.test" }
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index 7ea2505975684252247579dec999fff52f4a244e..91d4d5d3de9d03b2cb081669373c05d7f30e35ed 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -17,6 +17,17 @@ describe API::PipelineSchedules do
         pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
       end
 
+      def create_pipeline_schedules(count)
+        create_list(:ci_pipeline_schedule, count, project: project)
+          .each do |pipeline_schedule|
+          create(:user).tap do |user|
+            project.add_developer(user)
+            pipeline_schedule.update_attributes(owner: user)
+          end
+          pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+        end
+      end
+
       it 'returns list of pipeline_schedules' do
         get api("/projects/#{project.id}/pipeline_schedules", developer)
 
@@ -26,18 +37,14 @@ describe API::PipelineSchedules do
       end
 
       it 'avoids N + 1 queries' do
+        # We need at least two users to trigger a preload for that relation.
+        create_pipeline_schedules(1)
+
         control_count = ActiveRecord::QueryRecorder.new do
           get api("/projects/#{project.id}/pipeline_schedules", developer)
         end.count
 
-        create_list(:ci_pipeline_schedule, 10, project: project)
-          .each do |pipeline_schedule|
-          create(:user).tap do |user|
-            project.add_developer(user)
-            pipeline_schedule.update_attributes(owner: user)
-          end
-          pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
-        end
+        create_pipeline_schedules(10)
 
         expect do
           get api("/projects/#{project.id}/pipeline_schedules", developer)
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3834d27d0a9b0da56a7e6a7477906ef91cb2d7cb
--- /dev/null
+++ b/spec/requests/api/project_export_spec.rb
@@ -0,0 +1,370 @@
+require 'spec_helper'
+
+describe API::ProjectExport do
+  set(:project) { create(:project) }
+  set(:project_none) { create(:project) }
+  set(:project_started) { create(:project) }
+  set(:project_finished) { create(:project) }
+  set(:project_after_export) { create(:project) }
+  set(:user) { create(:user) }
+  set(:admin) { create(:admin) }
+
+  let(:path) { "/projects/#{project.id}/export" }
+  let(:path_none) { "/projects/#{project_none.id}/export" }
+  let(:path_started) { "/projects/#{project_started.id}/export" }
+  let(:path_finished) { "/projects/#{project_finished.id}/export" }
+  let(:path_after_export) { "/projects/#{project_after_export.id}/export" }
+
+  let(:download_path) { "/projects/#{project.id}/export/download" }
+  let(:download_path_none) { "/projects/#{project_none.id}/export/download" }
+  let(:download_path_started) { "/projects/#{project_started.id}/export/download" }
+  let(:download_path_finished) { "/projects/#{project_finished.id}/export/download" }
+  let(:download_path_export_action) { "/projects/#{project_after_export.id}/export/download" }
+
+  let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
+
+  before do
+    allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+
+    # simulate exporting work directory
+    FileUtils.mkdir_p File.join(project_started.export_path, 'securerandom-hex')
+
+    # simulate exported
+    FileUtils.mkdir_p project_finished.export_path
+    FileUtils.touch File.join(project_finished.export_path, '_export.tar.gz')
+
+    # simulate in after export action
+    FileUtils.mkdir_p project_after_export.export_path
+    FileUtils.touch File.join(project_after_export.export_path, '_export.tar.gz')
+    FileUtils.touch Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy.lock_file_path(project_after_export)
+  end
+
+  after do
+    FileUtils.rm_rf(export_path, secure: true)
+  end
+
+  shared_examples_for 'when project export is disabled' do
+    before do
+      stub_application_setting(project_export_enabled?: false)
+    end
+
+    it_behaves_like '404 response'
+  end
+
+  describe 'GET /projects/:project_id/export' do
+    shared_examples_for 'get project export status not found' do
+      it_behaves_like '404 response' do
+        let(:request) { get api(path, user) }
+      end
+    end
+
+    shared_examples_for 'get project export status denied' do
+      it_behaves_like '403 response' do
+        let(:request) { get api(path, user) }
+      end
+    end
+
+    shared_examples_for 'get project export status ok' do
+      it 'is none' do
+        get api(path_none, user)
+
+        expect(response).to have_gitlab_http_status(200)
+        expect(response).to match_response_schema('public_api/v4/project/export_status')
+        expect(json_response['export_status']).to eq('none')
+      end
+
+      it 'is started' do
+        get api(path_started, user)
+
+        expect(response).to have_gitlab_http_status(200)
+        expect(response).to match_response_schema('public_api/v4/project/export_status')
+        expect(json_response['export_status']).to eq('started')
+      end
+
+      it 'is after_export' do
+        get api(path_after_export, user)
+
+        expect(response).to have_gitlab_http_status(200)
+        expect(response).to match_response_schema('public_api/v4/project/export_status')
+        expect(json_response['export_status']).to eq('after_export_action')
+      end
+
+      it 'is finished' do
+        get api(path_finished, user)
+
+        expect(response).to have_gitlab_http_status(200)
+        expect(response).to match_response_schema('public_api/v4/project/export_status')
+        expect(json_response['export_status']).to eq('finished')
+      end
+    end
+
+    it_behaves_like 'when project export is disabled' do
+      let(:request) { get api(path, admin) }
+    end
+
+    context 'when project export is enabled' do
+      context 'when user is an admin' do
+        let(:user) { admin }
+
+        it_behaves_like 'get project export status ok'
+      end
+
+      context 'when user is a master' do
+        before do
+          project.add_master(user)
+          project_none.add_master(user)
+          project_started.add_master(user)
+          project_finished.add_master(user)
+          project_after_export.add_master(user)
+        end
+
+        it_behaves_like 'get project export status ok'
+      end
+
+      context 'when user is a developer' do
+        before do
+          project.add_developer(user)
+        end
+
+        it_behaves_like 'get project export status denied'
+      end
+
+      context 'when user is a reporter' do
+        before do
+          project.add_reporter(user)
+        end
+
+        it_behaves_like 'get project export status denied'
+      end
+
+      context 'when user is a guest' do
+        before do
+          project.add_guest(user)
+        end
+
+        it_behaves_like 'get project export status denied'
+      end
+
+      context 'when user is not a member' do
+        it_behaves_like 'get project export status not found'
+      end
+    end
+  end
+
+  describe 'GET /projects/:project_id/export/download' do
+    shared_examples_for 'get project export download not found' do
+      it_behaves_like '404 response' do
+        let(:request) { get api(download_path, user) }
+      end
+    end
+
+    shared_examples_for 'get project export download denied' do
+      it_behaves_like '403 response' do
+        let(:request) { get api(download_path, user) }
+      end
+    end
+
+    shared_examples_for 'get project export download' do
+      it_behaves_like '404 response' do
+        let(:request) { get api(download_path_none, user) }
+      end
+
+      it_behaves_like '404 response' do
+        let(:request) { get api(download_path_started, user) }
+      end
+
+      it 'downloads' do
+        get api(download_path_finished, user)
+
+        expect(response).to have_gitlab_http_status(200)
+      end
+    end
+
+    shared_examples_for 'get project export upload after action' do
+      context 'and is uploading' do
+        it 'downloads' do
+          get api(download_path_export_action, user)
+
+          expect(response).to have_gitlab_http_status(200)
+        end
+      end
+
+      context 'when upload complete' do
+        before do
+          FileUtils.rm_rf(project_after_export.export_path)
+        end
+
+        it_behaves_like '404 response' do
+          let(:request) { get api(download_path_export_action, user) }
+        end
+      end
+    end
+
+    shared_examples_for 'get project download by strategy' do
+      context 'when upload strategy set' do
+        it_behaves_like 'get project export upload after action'
+      end
+
+      context 'when download strategy set' do
+        it_behaves_like 'get project export download'
+      end
+    end
+
+    it_behaves_like 'when project export is disabled' do
+      let(:request) { get api(download_path, admin) }
+    end
+
+    context 'when project export is enabled' do
+      context 'when user is an admin' do
+        let(:user) { admin }
+
+        it_behaves_like 'get project download by strategy'
+      end
+
+      context 'when user is a master' do
+        before do
+          project.add_master(user)
+          project_none.add_master(user)
+          project_started.add_master(user)
+          project_finished.add_master(user)
+          project_after_export.add_master(user)
+        end
+
+        it_behaves_like 'get project download by strategy'
+      end
+
+      context 'when user is a developer' do
+        before do
+          project.add_developer(user)
+        end
+
+        it_behaves_like 'get project export download denied'
+      end
+
+      context 'when user is a reporter' do
+        before do
+          project.add_reporter(user)
+        end
+
+        it_behaves_like 'get project export download denied'
+      end
+
+      context 'when user is a guest' do
+        before do
+          project.add_guest(user)
+        end
+
+        it_behaves_like 'get project export download denied'
+      end
+
+      context 'when user is not a member' do
+        it_behaves_like 'get project export download not found'
+      end
+    end
+  end
+
+  describe 'POST /projects/:project_id/export' do
+    shared_examples_for 'post project export start not found' do
+      it_behaves_like '404 response' do
+        let(:request) { post api(path, user) }
+      end
+    end
+
+    shared_examples_for 'post project export start denied' do
+      it_behaves_like '403 response' do
+        let(:request) { post api(path, user) }
+      end
+    end
+
+    shared_examples_for 'post project export start' do
+      context 'with upload strategy' do
+        context 'when params invalid' do
+          it_behaves_like '400 response' do
+            let(:request) { post(api(path, user), 'upload[url]' => 'whatever') }
+          end
+        end
+
+        it 'starts' do
+          allow_any_instance_of(Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy).to receive(:send_file)
+
+          post(api(path, user), 'upload[url]' => 'http://gitlab.com')
+
+          expect(response).to have_gitlab_http_status(202)
+        end
+      end
+
+      context 'with download strategy' do
+        it 'starts' do
+          expect_any_instance_of(Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy).not_to receive(:send_file)
+
+          post api(path, user)
+
+          expect(response).to have_gitlab_http_status(202)
+        end
+      end
+    end
+
+    it_behaves_like 'when project export is disabled' do
+      let(:request) { post api(path, admin) }
+    end
+
+    context 'when project export is enabled' do
+      context 'when user is an admin' do
+        let(:user) { admin }
+
+        it_behaves_like 'post project export start'
+      end
+
+      context 'when user is a master' do
+        before do
+          project.add_master(user)
+          project_none.add_master(user)
+          project_started.add_master(user)
+          project_finished.add_master(user)
+          project_after_export.add_master(user)
+        end
+
+        it_behaves_like 'post project export start'
+      end
+
+      context 'when user is a developer' do
+        before do
+          project.add_developer(user)
+        end
+
+        it_behaves_like 'post project export start denied'
+      end
+
+      context 'when user is a reporter' do
+        before do
+          project.add_reporter(user)
+        end
+
+        it_behaves_like 'post project export start denied'
+      end
+
+      context 'when user is a guest' do
+        before do
+          project.add_guest(user)
+        end
+
+        it_behaves_like 'post project export start denied'
+      end
+
+      context 'when user is not a member' do
+        it_behaves_like 'post project export start not found'
+      end
+
+      context 'when overriding description' do
+        it 'starts' do
+          params = { description: "Foo" }
+
+          expect_any_instance_of(Projects::ImportExport::ExportService).to receive(:execute)
+          post api(path, project.owner), params
+
+          expect(response).to have_gitlab_http_status(202)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 392cad667be37a429c18016113e7564dc485aecc..12a183fed1ef0d80a21e3895cab5e4b7536aad60 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -33,6 +33,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
         expect(json_response.first['merge_requests_events']).to eq(true)
         expect(json_response.first['tag_push_events']).to eq(true)
         expect(json_response.first['note_events']).to eq(true)
+        expect(json_response.first['confidential_note_events']).to eq(true)
         expect(json_response.first['job_events']).to eq(true)
         expect(json_response.first['pipeline_events']).to eq(true)
         expect(json_response.first['wiki_page_events']).to eq(true)
@@ -62,6 +63,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
         expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
         expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
         expect(json_response['note_events']).to eq(hook.note_events)
+        expect(json_response['confidential_note_events']).to eq(hook.confidential_note_events)
         expect(json_response['job_events']).to eq(hook.job_events)
         expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
         expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
@@ -104,6 +106,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
       expect(json_response['merge_requests_events']).to eq(false)
       expect(json_response['tag_push_events']).to eq(false)
       expect(json_response['note_events']).to eq(false)
+      expect(json_response['confidential_note_events']).to eq(nil)
       expect(json_response['job_events']).to eq(true)
       expect(json_response['pipeline_events']).to eq(false)
       expect(json_response['wiki_page_events']).to eq(true)
@@ -152,6 +155,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
       expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
       expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
       expect(json_response['note_events']).to eq(hook.note_events)
+      expect(json_response['confidential_note_events']).to eq(hook.confidential_note_events)
       expect(json_response['job_events']).to eq(hook.job_events)
       expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
       expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 987f6e269712c9a781b0a0bd9b1bf9d7ae0a4d73..f68057a92a18e88b7858e854e9a6487841679c97 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -40,7 +40,7 @@ describe API::ProjectImport do
       expect(response).to have_gitlab_http_status(201)
     end
 
-    it 'schedules an import at the user namespace level' do
+    it 'does not shedule an import for a nampespace that does not exist' do
       expect_any_instance_of(Project).not_to receive(:import_schedule)
       expect(::Projects::CreateService).not_to receive(:new)
 
@@ -71,6 +71,72 @@ describe API::ProjectImport do
       expect(json_response['error']).to eq('file is invalid')
     end
 
+    it 'stores params that can be overridden' do
+      stub_import(namespace)
+      override_params = { 'description' => 'Hello world' }
+
+      post api('/projects/import', user),
+           path: 'test-import',
+           file: fixture_file_upload(file),
+           namespace: namespace.id,
+           override_params: override_params
+      import_project = Project.find(json_response['id'])
+
+      expect(import_project.import_data.data['override_params']).to eq(override_params)
+    end
+
+    it 'does not store params that are not allowed' do
+      stub_import(namespace)
+      override_params = { 'not_allowed' => 'Hello world' }
+
+      post api('/projects/import', user),
+           path: 'test-import',
+           file: fixture_file_upload(file),
+           namespace: namespace.id,
+           override_params: override_params
+      import_project = Project.find(json_response['id'])
+
+      expect(import_project.import_data.data['override_params']).to be_empty
+    end
+
+    it 'correctly overrides params during the import' do
+      override_params = { 'description' => 'Hello world' }
+
+      Sidekiq::Testing.inline! do
+        post api('/projects/import', user),
+             path: 'test-import',
+             file: fixture_file_upload(file),
+             namespace: namespace.id,
+             override_params: override_params
+      end
+      import_project = Project.find(json_response['id'])
+
+      expect(import_project.description).to eq('Hello world')
+    end
+
+    context 'when target path already exists in namespace' do
+      let(:existing_project) { create(:project, namespace: user.namespace) }
+
+      it 'does not schedule an import' do
+        expect_any_instance_of(Project).not_to receive(:import_schedule)
+
+        post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file)
+
+        expect(response).to have_gitlab_http_status(400)
+        expect(json_response['message']).to eq('Name has already been taken')
+      end
+
+      context 'when param overwrite is true' do
+        it 'schedules an import' do
+          stub_import(user.namespace)
+
+          post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file), overwrite: true
+
+          expect(response).to have_gitlab_http_status(201)
+        end
+      end
+    end
+
     def stub_import(namespace)
       expect_any_instance_of(Project).to receive(:import_schedule)
       expect(::Projects::CreateService).to receive(:new).with(user, hash_including(namespace_id: namespace.id)).and_call_original
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index cee93f6ed14b48f31d20b6e0b2e3f3f9a52f3338..17272cb00e57f539b71b183fc0329314887998d4 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1,6 +1,18 @@
 # -*- coding: utf-8 -*-
 require 'spec_helper'
 
+shared_examples 'languages and percentages JSON response' do
+  let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
+
+  it 'returns expected language values' do
+    get api("/projects/#{project.id}/languages", user)
+
+    expect(response).to have_gitlab_http_status(:ok)
+    expect(json_response).to eq(expected_languages)
+    expect(json_response.count).to be > 1
+  end
+end
+
 describe API::Projects do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
@@ -452,7 +464,8 @@ describe API::Projects do
         only_allow_merge_if_pipeline_succeeds: false,
         request_access_enabled: true,
         only_allow_merge_if_all_discussions_are_resolved: false,
-        ci_config_path: 'a/custom/path'
+        ci_config_path: 'a/custom/path',
+        merge_method: 'ff'
       })
 
       post api('/projects', user), project
@@ -569,6 +582,22 @@ describe API::Projects do
       expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
     end
 
+    it 'sets the merge method of a project to rebase merge' do
+      project = attributes_for(:project, merge_method: 'rebase_merge')
+
+      post api('/projects', user), project
+
+      expect(json_response['merge_method']).to eq('rebase_merge')
+    end
+
+    it 'rejects invalid values for merge_method' do
+      project = attributes_for(:project, merge_method: 'totally_not_valid_method')
+
+      post api('/projects', user), project
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+
     it 'ignores import_url when it is nil' do
       project = attributes_for(:project, import_url: nil)
 
@@ -823,6 +852,7 @@ describe API::Projects do
         expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
         expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
         expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
+        expect(json_response['merge_method']).to eq(project.merge_method.to_s)
       end
 
       it 'returns a project by path name' do
@@ -1474,6 +1504,26 @@ describe API::Projects do
           expect(json_response[k.to_s]).to eq(v)
         end
       end
+
+      it 'updates merge_method' do
+        project_param = { merge_method: 'ff' }
+
+        put api("/projects/#{project3.id}", user), project_param
+
+        expect(response).to have_gitlab_http_status(200)
+
+        project_param.each_pair do |k, v|
+          expect(json_response[k.to_s]).to eq(v)
+        end
+      end
+
+      it 'rejects to update merge_method when merge_method is invalid' do
+        project_param = { merge_method: 'invalid' }
+
+        put api("/projects/#{project3.id}", user), project_param
+
+        expect(response).to have_gitlab_http_status(400)
+      end
     end
 
     context 'when authenticated as project master' do
@@ -1491,6 +1541,7 @@ describe API::Projects do
                           wiki_enabled: true,
                           snippets_enabled: true,
                           merge_requests_enabled: true,
+                          merge_method: 'ff',
                           description: 'new description' }
 
         put api("/projects/#{project3.id}", user4), project_param
@@ -1655,6 +1706,42 @@ describe API::Projects do
     end
   end
 
+  describe 'GET /projects/:id/languages' do
+    context 'with an authorized user' do
+      it_behaves_like 'languages and percentages JSON response' do
+        let(:project) { project3 }
+      end
+
+      it 'returns not_found(404) for not existing project' do
+        get api("/projects/9999999999/languages", user)
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+
+    context 'with not authorized user' do
+      it 'returns not_found for existing but unauthorized project' do
+        get api("/projects/#{project3.id}/languages", user3)
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+
+    context 'without user' do
+      let(:project_public) { create(:project, :public, :repository) }
+
+      it_behaves_like 'languages and percentages JSON response' do
+        let(:project) { project_public }
+      end
+
+      it 'returns not_found for existing but unauthorized project' do
+        get api("/projects/#{project3.id}/languages", nil)
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+  end
+
   describe 'DELETE /projects/:id' do
     context 'when authenticated as user' do
       it 'removes project' do
@@ -1718,6 +1805,12 @@ describe API::Projects do
       group
     end
 
+    let(:group3) do
+      group = create(:group, name: 'group3_name', parent: group2)
+      group.add_owner(user2)
+      group
+    end
+
     before do
       project.add_reporter(user2)
     end
@@ -1813,6 +1906,15 @@ describe API::Projects do
         expect(json_response['namespace']['name']).to eq(group2.name)
       end
 
+      it 'forks to owned subgroup' do
+        full_path = "#{group2.path}/#{group3.path}"
+        post api("/projects/#{project.id}/fork", user2), namespace: full_path
+
+        expect(response).to have_gitlab_http_status(201)
+        expect(json_response['namespace']['name']).to eq(group3.name)
+        expect(json_response['namespace']['full_path']).to eq(full_path)
+      end
+
       it 'fails to fork to not owned group' do
         post api("/projects/#{project.id}/fork", user2), namespace: group.name
 
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 1d23e023bb678e8d6034e264d4ed352f04929eeb..576fde4661593ca9763441069fd2183641825158 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -193,6 +193,19 @@ describe API::ProtectedBranches do
           expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER)
         end
       end
+
+      context 'when a policy restricts rule deletion' do
+        before do
+          policy = instance_double(ProtectedBranchPolicy, can?: false)
+          expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+        end
+
+        it "prevents deletion of the protected branch rule" do
+          post post_endpoint, name: branch_name
+
+          expect(response).to have_gitlab_http_status(403)
+        end
+      end
     end
 
     context 'when authenticated as a guest' do
@@ -209,18 +222,20 @@ describe API::ProtectedBranches do
   end
 
   describe "DELETE /projects/:id/protected_branches/unprotect/:branch" do
+    let(:delete_endpoint) { api("/projects/#{project.id}/protected_branches/#{branch_name}", user) }
+
     before do
       project.add_master(user)
     end
 
     it "unprotects a single branch" do
-      delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user)
+      delete delete_endpoint
 
       expect(response).to have_gitlab_http_status(204)
     end
 
     it_behaves_like '412 response' do
-      let(:request) { api("/projects/#{project.id}/protected_branches/#{branch_name}", user) }
+      let(:request) { delete_endpoint }
     end
 
     it "returns 404 if branch does not exist" do
@@ -229,11 +244,24 @@ describe API::ProtectedBranches do
       expect(response).to have_gitlab_http_status(404)
     end
 
+    context 'when a policy restricts rule deletion' do
+      before do
+        policy = instance_double(ProtectedBranchPolicy, can?: false)
+        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+      end
+
+      it "prevents deletion of the protected branch rule" do
+        delete delete_endpoint
+
+        expect(response).to have_gitlab_http_status(403)
+      end
+    end
+
     context 'when branch has a wildcard in its name' do
       let(:protected_name) { 'feature*' }
 
       it "unprotects a wildcard branch" do
-        delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user)
+        delete delete_endpoint
 
         expect(response).to have_gitlab_http_status(204)
       end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 741800ff61d9a19921a68ec552f1d32bbaaa60de..9e6d69e387489c524b0b12e80159105a2c4c9de4 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -427,5 +427,20 @@ describe API::Repositories do
         let(:request) { get api(route, guest) }
       end
     end
+
+    # Regression: https://gitlab.com/gitlab-org/gitlab-ce/issues/45363
+    describe 'Links header contains working URLs when no `order_by` nor `sort` is given' do
+      let(:project) { create(:project, :public, :repository) }
+      let(:current_user) { nil }
+
+      it 'returns `Link` header that includes URLs with default value for `order_by` & `sort`' do
+        get api(route, current_user)
+
+        first_link_url = response.headers['Link'].split(';').first
+
+        expect(first_link_url).to include('order_by=commits')
+        expect(first_link_url).to include('sort=asc')
+      end
+    end
   end
 end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 72cafac3f90717bc632f989f40c731d4b1dbe5ee..17c7a511857555c75e388fbc4b8a78c20689618f 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -109,6 +109,26 @@ describe API::Runner do
         end
       end
 
+      context 'when maximum job timeout is specified' do
+        it 'creates runner' do
+          post api('/runners'), token: registration_token,
+                                maximum_timeout: 9000
+
+          expect(response).to have_gitlab_http_status 201
+          expect(Ci::Runner.first.maximum_timeout).to eq(9000)
+        end
+
+        context 'when maximum job timeout is empty' do
+          it 'creates runner' do
+            post api('/runners'), token: registration_token,
+                                  maximum_timeout: ''
+
+            expect(response).to have_gitlab_http_status 201
+            expect(Ci::Runner.first.maximum_timeout).to be_nil
+          end
+        end
+      end
+
       %w(name version revision platform architecture).each do |param|
         context "when info parameter '#{param}' info is present" do
           let(:value) { "#{param}_value" }
@@ -200,7 +220,7 @@ describe API::Runner do
     let(:project) { create(:project, shared_runners_enabled: false) }
     let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') }
     let(:runner) { create(:ci_runner) }
-    let!(:job) do
+    let(:job) do
       create(:ci_build, :artifacts, :extended_options,
              pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, commands: "ls\ndate")
     end
@@ -215,6 +235,7 @@ describe API::Runner do
       let(:user_agent) { 'gitlab-runner 9.0.0 (9-0-stable; go1.7.4; linux/amd64)' }
 
       before do
+        job
         stub_container_registry_config(enabled: false)
       end
 
@@ -339,12 +360,12 @@ describe API::Runner do
           let(:expected_steps) do
             [{ 'name' => 'script',
                'script' => %w(ls date),
-               'timeout' => job.timeout,
+               'timeout' => job.metadata_timeout,
                'when' => 'on_success',
                'allow_failure' => false },
              { 'name' => 'after_script',
                'script' => %w(ls date),
-               'timeout' => job.timeout,
+               'timeout' => job.metadata_timeout,
                'when' => 'always',
                'allow_failure' => true }]
           end
@@ -385,7 +406,7 @@ describe API::Runner do
             expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' })
             expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
                                                        'alias' => nil, 'command' => nil },
-                                                     { 'name' => 'docker:dind', 'entrypoint' => '/bin/sh',
+                                                     { 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
                                                        'alias' => 'docker', 'command' => 'sleep 30' }])
             expect(json_response['steps']).to eq(expected_steps)
             expect(json_response['artifacts']).to eq(expected_artifacts)
@@ -647,6 +668,41 @@ describe API::Runner do
               end
             end
           end
+
+          describe 'timeout support' do
+            context 'when project specifies job timeout' do
+              let(:project) { create(:project, shared_runners_enabled: false, build_timeout: 1234) }
+
+              it 'contains info about timeout taken from project' do
+                request_job
+
+                expect(response).to have_gitlab_http_status(201)
+                expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
+              end
+
+              context 'when runner specifies lower timeout' do
+                let(:runner) { create(:ci_runner, maximum_timeout: 1000) }
+
+                it 'contains info about timeout overridden by runner' do
+                  request_job
+
+                  expect(response).to have_gitlab_http_status(201)
+                  expect(json_response['runner_info']).to include({ 'timeout' => 1000 })
+                end
+              end
+
+              context 'when runner specifies bigger timeout' do
+                let(:runner) { create(:ci_runner, maximum_timeout: 2000) }
+
+                it 'contains info about timeout not overridden by runner' do
+                  request_job
+
+                  expect(response).to have_gitlab_http_status(201)
+                  expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
+                end
+              end
+            end
+          end
         end
 
         def request_job(token = runner.token, **params)
@@ -698,10 +754,10 @@ describe API::Runner do
         end
       end
 
-      context 'when tace is given' do
+      context 'when trace is given' do
         it 'creates a trace artifact' do
           allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do
-            CreateTraceArtifactWorker.new.perform(job.id)
+            ArchiveTraceWorker.new.perform(job.id)
           end
 
           update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
@@ -888,17 +944,59 @@ describe API::Runner do
       let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
 
       before do
+        stub_artifacts_object_storage
         job.run!
       end
 
       describe 'POST /api/v4/jobs/:id/artifacts/authorize' do
         context 'when using token as parameter' do
-          it 'authorizes posting artifacts to running job' do
-            authorize_artifacts_with_token_in_params
+          context 'posting artifacts to running job' do
+            subject do
+              authorize_artifacts_with_token_in_params
+            end
 
-            expect(response).to have_gitlab_http_status(200)
-            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
-            expect(json_response['TempPath']).not_to be_nil
+            shared_examples 'authorizes local file' do
+              it 'succeeds' do
+                subject
+
+                expect(response).to have_gitlab_http_status(200)
+                expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+                expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path)
+                expect(json_response['RemoteObject']).to be_nil
+              end
+            end
+
+            context 'when using local storage' do
+              it_behaves_like 'authorizes local file'
+            end
+
+            context 'when using remote storage' do
+              context 'when direct upload is enabled' do
+                before do
+                  stub_artifacts_object_storage(enabled: true, direct_upload: true)
+                end
+
+                it 'succeeds' do
+                  subject
+
+                  expect(response).to have_gitlab_http_status(200)
+                  expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+                  expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path)
+                  expect(json_response['RemoteObject']).to have_key('ID')
+                  expect(json_response['RemoteObject']).to have_key('GetURL')
+                  expect(json_response['RemoteObject']).to have_key('StoreURL')
+                  expect(json_response['RemoteObject']).to have_key('DeleteURL')
+                end
+              end
+
+              context 'when direct upload is disabled' do
+                before do
+                  stub_artifacts_object_storage(enabled: true, direct_upload: false)
+                end
+
+                it_behaves_like 'authorizes local file'
+              end
+            end
           end
 
           it 'fails to post too large artifact' do
@@ -994,20 +1092,45 @@ describe API::Runner do
               end
             end
 
-            context 'when uses regular file post' do
-              before do
-                upload_artifacts(file_upload, headers_with_token, false)
+            context 'when uses accelerated file post' do
+              context 'for file stored locally' do
+                before do
+                  upload_artifacts(file_upload, headers_with_token)
+                end
+
+                it_behaves_like 'successful artifacts upload'
               end
 
-              it_behaves_like 'successful artifacts upload'
-            end
+              context 'for file stored remotelly' do
+                let!(:fog_connection) do
+                  stub_artifacts_object_storage(direct_upload: true)
+                end
 
-            context 'when uses accelerated file post' do
-              before do
-                upload_artifacts(file_upload, headers_with_token, true)
-              end
+                before do
+                  fog_connection.directories.get('artifacts').files.create(
+                    key: 'tmp/upload/12312300',
+                    body: 'content'
+                  )
+
+                  upload_artifacts(file_upload, headers_with_token,
+                    { 'file.remote_id' => remote_id })
+                end
 
-              it_behaves_like 'successful artifacts upload'
+                context 'when valid remote_id is used' do
+                  let(:remote_id) { '12312300' }
+
+                  it_behaves_like 'successful artifacts upload'
+                end
+
+                context 'when invalid remote_id is used' do
+                  let(:remote_id) { 'invalid id' }
+
+                  it 'responds with bad request' do
+                    expect(response).to have_gitlab_http_status(500)
+                    expect(json_response['message']).to eq("Missing file")
+                  end
+                end
+              end
             end
 
             context 'when using runners token' do
@@ -1100,11 +1223,15 @@ describe API::Runner do
 
           context 'posts artifacts file and metadata file' do
             let!(:artifacts) { file_upload }
+            let!(:artifacts_sha256) { Digest::SHA256.file(artifacts.path).hexdigest }
             let!(:metadata) { file_upload2 }
+            let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest }
 
             let(:stored_artifacts_file) { job.reload.artifacts_file.file }
             let(:stored_metadata_file) { job.reload.artifacts_metadata.file }
             let(:stored_artifacts_size) { job.reload.artifacts_size }
+            let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
+            let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 }
 
             before do
               post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token)
@@ -1114,8 +1241,10 @@ describe API::Runner do
               let(:post_data) do
                 { 'file.path' => artifacts.path,
                   'file.name' => artifacts.original_filename,
+                  'file.sha256' => artifacts_sha256,
                   'metadata.path' => metadata.path,
-                  'metadata.name' => metadata.original_filename }
+                  'metadata.name' => metadata.original_filename,
+                  'metadata.sha256' => metadata_sha256 }
               end
 
               it 'stores artifacts and artifacts metadata' do
@@ -1123,6 +1252,8 @@ describe API::Runner do
                 expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
                 expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
                 expect(stored_artifacts_size).to eq(72821)
+                expect(stored_artifacts_sha256).to eq(artifacts_sha256)
+                expect(stored_metadata_sha256).to eq(metadata_sha256)
               end
             end
 
@@ -1143,15 +1274,19 @@ describe API::Runner do
         end
 
         context 'when artifacts are being stored outside of tmp path' do
+          let(:new_tmpdir) { Dir.mktmpdir }
+
           before do
+            # init before overwriting tmp dir
+            file_upload
+
             # by configuring this path we allow to pass file from @tmpdir only
             # but all temporary files are stored in system tmp directory
-            @tmpdir = Dir.mktmpdir
-            allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return(@tmpdir)
+            allow(Dir).to receive(:tmpdir).and_return(new_tmpdir)
           end
 
           after do
-            FileUtils.remove_entry @tmpdir
+            FileUtils.remove_entry(new_tmpdir)
           end
 
           it' "fails to post artifacts for outside of tmp path"' do
@@ -1161,12 +1296,11 @@ describe API::Runner do
           end
         end
 
-        def upload_artifacts(file, headers = {}, accelerated = true)
-          params = if accelerated
-                     { 'file.path' => file.path, 'file.name' => file.original_filename }
-                   else
-                     { 'file' => file }
-                   end
+        def upload_artifacts(file, headers = {}, params = {})
+          params = params.merge({
+            'file.path' => file.path,
+            'file.name' => file.original_filename
+          })
 
           post api("/jobs/#{job.id}/artifacts"), params, headers
         end
@@ -1175,27 +1309,67 @@ describe API::Runner do
       describe 'GET /api/v4/jobs/:id/artifacts' do
         let(:token) { job.token }
 
-        before do
-          download_artifact
-        end
-
         context 'when job has artifacts' do
-          let(:job) { create(:ci_build, :artifacts) }
-          let(:download_headers) do
-            { 'Content-Transfer-Encoding' => 'binary',
-              'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
+          let(:job) { create(:ci_build) }
+          let(:store) { JobArtifactUploader::Store::LOCAL }
+
+          before do
+            create(:ci_job_artifact, :archive, file_store: store, job: job)
           end
 
           context 'when using job token' do
-            it 'download artifacts' do
-              expect(response).to have_gitlab_http_status(200)
-              expect(response.headers).to include download_headers
+            context 'when artifacts are stored locally' do
+              let(:download_headers) do
+                { 'Content-Transfer-Encoding' => 'binary',
+                  'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
+              end
+
+              before do
+                download_artifact
+              end
+
+              it 'download artifacts' do
+                expect(response).to have_http_status(200)
+                expect(response.headers).to include download_headers
+              end
+            end
+
+            context 'when artifacts are stored remotely' do
+              let(:store) { JobArtifactUploader::Store::REMOTE }
+              let!(:job) { create(:ci_build) }
+
+              context 'when proxy download is being used' do
+                before do
+                  download_artifact(direct_download: false)
+                end
+
+                it 'uses workhorse send-url' do
+                  expect(response).to have_gitlab_http_status(200)
+                  expect(response.headers).to include(
+                    'Gitlab-Workhorse-Send-Data' => /send-url:/)
+                end
+              end
+
+              context 'when direct download is being used' do
+                before do
+                  download_artifact(direct_download: true)
+                end
+
+                it 'receive redirect for downloading artifacts' do
+                  expect(response).to have_gitlab_http_status(302)
+                  expect(response.headers).to include('Location')
+                end
+              end
             end
           end
 
           context 'when using runnners token' do
             let(:token) { job.project.runners_token }
 
+            before do
+              download_artifact
+            end
+
             it 'responds with forbidden' do
               expect(response).to have_gitlab_http_status(403)
             end
@@ -1204,12 +1378,16 @@ describe API::Runner do
 
         context 'when job does not has artifacts' do
           it 'responds with not found' do
+            download_artifact
+
             expect(response).to have_gitlab_http_status(404)
           end
         end
 
         def download_artifact(params = {}, request_headers = headers)
           params = params.merge(token: token)
+          job.reload
+
           get api("/jobs/#{job.id}/artifacts"), params, request_headers
         end
       end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index ec5cad4f4fd66a50e9f16f66e42cc74462daf8fb..d30f0cf36e2a0486a8d0fb388cd30941960235e7 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -123,6 +123,7 @@ describe API::Runners do
 
           expect(response).to have_gitlab_http_status(200)
           expect(json_response['description']).to eq(shared_runner.description)
+          expect(json_response['maximum_timeout']).to be_nil
         end
       end
 
@@ -192,7 +193,8 @@ describe API::Runners do
                                                  tag_list: ['ruby2.1', 'pgsql', 'mysql'],
                                                  run_untagged: 'false',
                                                  locked: 'true',
-                                                 access_level: 'ref_protected')
+                                                 access_level: 'ref_protected',
+                                                 maximum_timeout: 1234)
           shared_runner.reload
 
           expect(response).to have_gitlab_http_status(200)
@@ -204,6 +206,7 @@ describe API::Runners do
           expect(shared_runner.ref_protected?).to be_truthy
           expect(shared_runner.ensure_runner_queue_value)
             .not_to eq(runner_queue_value)
+          expect(shared_runner.maximum_timeout).to eq(1234)
         end
       end
 
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 9052a18c60b49b0b80831e3f6c67492b3cf6dc28..f8d5258a8d912f93db9eb2684f0c7eff09e3e458 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -99,10 +99,10 @@ describe API::Search do
     end
   end
 
-  describe "GET /groups/:id/-/search" do
+  describe "GET /groups/:id/search" do
     context 'when user is not authenticated' do
       it 'returns 401 error' do
-        get api("/groups/#{group.id}/-/search"), scope: 'projects', search: 'awesome'
+        get api("/groups/#{group.id}/search"), scope: 'projects', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(401)
       end
@@ -110,7 +110,7 @@ describe API::Search do
 
     context 'when scope is not supported' do
       it 'returns 400 error' do
-        get api("/groups/#{group.id}/-/search", user), scope: 'unsupported', search: 'awesome'
+        get api("/groups/#{group.id}/search", user), scope: 'unsupported', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(400)
       end
@@ -118,7 +118,7 @@ describe API::Search do
 
     context 'when scope is missing' do
       it 'returns 400 error' do
-        get api("/groups/#{group.id}/-/search", user), search: 'awesome'
+        get api("/groups/#{group.id}/search", user), search: 'awesome'
 
         expect(response).to have_gitlab_http_status(400)
       end
@@ -126,7 +126,7 @@ describe API::Search do
 
     context 'when group does not exist' do
       it 'returns 404 error' do
-        get api('/groups/9999/-/search', user), scope: 'issues', search: 'awesome'
+        get api('/groups/9999/search', user), scope: 'issues', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(404)
       end
@@ -136,7 +136,7 @@ describe API::Search do
       it 'returns 404 error' do
         private_group = create(:group, :private)
 
-        get api("/groups/#{private_group.id}/-/search", user), scope: 'issues', search: 'awesome'
+        get api("/groups/#{private_group.id}/search", user), scope: 'issues', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(404)
       end
@@ -145,7 +145,7 @@ describe API::Search do
     context 'with correct params' do
       context 'for projects scope' do
         before do
-          get api("/groups/#{group.id}/-/search", user), scope: 'projects', search: 'awesome'
+          get api("/groups/#{group.id}/search", user), scope: 'projects', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/projects'
@@ -155,7 +155,7 @@ describe API::Search do
         before do
           create(:issue, project: project, title: 'awesome issue')
 
-          get api("/groups/#{group.id}/-/search", user), scope: 'issues', search: 'awesome'
+          get api("/groups/#{group.id}/search", user), scope: 'issues', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
@@ -165,7 +165,7 @@ describe API::Search do
         before do
           create(:merge_request, source_project: repo_project, title: 'awesome mr')
 
-          get api("/groups/#{group.id}/-/search", user), scope: 'merge_requests', search: 'awesome'
+          get api("/groups/#{group.id}/search", user), scope: 'merge_requests', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
@@ -175,7 +175,7 @@ describe API::Search do
         before do
           create(:milestone, project: project, title: 'awesome milestone')
 
-          get api("/groups/#{group.id}/-/search", user), scope: 'milestones', search: 'awesome'
+          get api("/groups/#{group.id}/search", user), scope: 'milestones', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
@@ -187,7 +187,7 @@ describe API::Search do
           create(:milestone, project: project, title: 'awesome milestone')
           create(:milestone, project: another_project, title: 'awesome milestone other project')
 
-          get api("/groups/#{CGI.escape(group.full_path)}/-/search", user), scope: 'milestones', search: 'awesome'
+          get api("/groups/#{CGI.escape(group.full_path)}/search", user), scope: 'milestones', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
@@ -198,7 +198,7 @@ describe API::Search do
   describe "GET /projects/:id/search" do
     context 'when user is not authenticated' do
       it 'returns 401 error' do
-        get api("/projects/#{project.id}/-/search"), scope: 'issues', search: 'awesome'
+        get api("/projects/#{project.id}/search"), scope: 'issues', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(401)
       end
@@ -206,7 +206,7 @@ describe API::Search do
 
     context 'when scope is not supported' do
       it 'returns 400 error' do
-        get api("/projects/#{project.id}/-/search", user), scope: 'unsupported', search: 'awesome'
+        get api("/projects/#{project.id}/search", user), scope: 'unsupported', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(400)
       end
@@ -214,7 +214,7 @@ describe API::Search do
 
     context 'when scope is missing' do
       it 'returns 400 error' do
-        get api("/projects/#{project.id}/-/search", user), search: 'awesome'
+        get api("/projects/#{project.id}/search", user), search: 'awesome'
 
         expect(response).to have_gitlab_http_status(400)
       end
@@ -222,7 +222,7 @@ describe API::Search do
 
     context 'when project does not exist' do
       it 'returns 404 error' do
-        get api('/projects/9999/-/search', user), scope: 'issues', search: 'awesome'
+        get api('/projects/9999/search', user), scope: 'issues', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(404)
       end
@@ -232,7 +232,7 @@ describe API::Search do
       it 'returns 404 error' do
         project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
 
-        get api("/projects/#{project.id}/-/search", user), scope: 'issues', search: 'awesome'
+        get api("/projects/#{project.id}/search", user), scope: 'issues', search: 'awesome'
 
         expect(response).to have_gitlab_http_status(404)
       end
@@ -243,7 +243,7 @@ describe API::Search do
         before do
           create(:issue, project: project, title: 'awesome issue')
 
-          get api("/projects/#{project.id}/-/search", user), scope: 'issues', search: 'awesome'
+          get api("/projects/#{project.id}/search", user), scope: 'issues', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
@@ -253,7 +253,7 @@ describe API::Search do
         before do
           create(:merge_request, source_project: repo_project, title: 'awesome mr')
 
-          get api("/projects/#{repo_project.id}/-/search", user), scope: 'merge_requests', search: 'awesome'
+          get api("/projects/#{repo_project.id}/search", user), scope: 'merge_requests', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
@@ -263,7 +263,7 @@ describe API::Search do
         before do
           create(:milestone, project: project, title: 'awesome milestone')
 
-          get api("/projects/#{project.id}/-/search", user), scope: 'milestones', search: 'awesome'
+          get api("/projects/#{project.id}/search", user), scope: 'milestones', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
@@ -273,7 +273,7 @@ describe API::Search do
         before do
           create(:note_on_merge_request, project: project, note: 'awesome note')
 
-          get api("/projects/#{project.id}/-/search", user), scope: 'notes', search: 'awesome'
+          get api("/projects/#{project.id}/search", user), scope: 'notes', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/notes'
@@ -284,7 +284,7 @@ describe API::Search do
           wiki = create(:project_wiki, project: project)
           create(:wiki_page, wiki: wiki, attrs: { title: 'home', content: "Awesome page" })
 
-          get api("/projects/#{project.id}/-/search", user), scope: 'wiki_blobs', search: 'awesome'
+          get api("/projects/#{project.id}/search", user), scope: 'wiki_blobs', search: 'awesome'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/blobs'
@@ -292,7 +292,7 @@ describe API::Search do
 
       context 'for commits scope' do
         before do
-          get api("/projects/#{repo_project.id}/-/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
+          get api("/projects/#{repo_project.id}/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details'
@@ -300,7 +300,7 @@ describe API::Search do
 
       context 'for commits scope with project path as id' do
         before do
-          get api("/projects/#{CGI.escape(repo_project.full_path)}/-/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
+          get api("/projects/#{CGI.escape(repo_project.full_path)}/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details'
@@ -308,7 +308,7 @@ describe API::Search do
 
       context 'for blobs scope' do
         before do
-          get api("/projects/#{repo_project.id}/-/search", user), scope: 'blobs', search: 'monitors'
+          get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'monitors'
         end
 
         it_behaves_like 'response is correct', schema: 'public_api/v4/blobs', size: 2
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index de1619f33c1d733e5b0ddece6c23817911c1cb7e..6bb53fdc98d705378df9fdec6ade1c3669e3dd80 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -65,7 +65,7 @@ describe API::Templates do
       expect(json_response['description']).to include('A short and simple permissive license with conditions')
       expect(json_response['conditions']).to eq(%w[include-copyright])
       expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use])
-      expect(json_response['limitations']).to eq(%w[no-liability])
+      expect(json_response['limitations']).to eq(%w[liability warranty])
       expect(json_response['content']).to include('MIT License')
     end
   end
diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb
index 79041c6a79217e6d9e01f4d2795dbfaee1520557..00f067889a03de3f0df12fb370d46ead71ba6ffc 100644
--- a/spec/requests/api/v3/builds_spec.rb
+++ b/spec/requests/api/v3/builds_spec.rb
@@ -216,6 +216,7 @@ describe API::V3::Builds do
 
   describe 'GET /projects/:id/builds/:build_id/artifacts' do
     before do
+      stub_artifacts_object_storage
       get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
     end
 
@@ -230,13 +231,24 @@ describe API::V3::Builds do
           end
 
           it 'returns specific job artifacts' do
-            expect(response).to have_gitlab_http_status(200)
+            expect(response).to have_http_status(200)
             expect(response.headers).to include(download_headers)
             expect(response.body).to match_file(build.artifacts_file.file.file)
           end
         end
       end
 
+      context 'when artifacts are stored remotely' do
+        let(:build) { create(:ci_build, pipeline: pipeline) }
+        let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) }
+
+        it 'returns location redirect' do
+          get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
+
+          expect(response).to have_gitlab_http_status(302)
+        end
+      end
+
       context 'unauthorized user' do
         let(:api_user) { nil }
 
@@ -256,6 +268,7 @@ describe API::V3::Builds do
     let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
 
     before do
+      stub_artifacts_object_storage
       build.success
     end
 
@@ -318,9 +331,24 @@ describe API::V3::Builds do
                 "attachment; filename=#{build.artifacts_file.filename}" }
           end
 
-          it { expect(response).to have_gitlab_http_status(200) }
+          it { expect(response).to have_http_status(200) }
           it { expect(response.headers).to include(download_headers) }
         end
+
+        context 'when artifacts are stored remotely' do
+          let(:build) { create(:ci_build, pipeline: pipeline) }
+          let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) }
+
+          before do
+            build.reload
+
+            get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
+          end
+
+          it 'returns location redirect' do
+            expect(response).to have_http_status(302)
+          end
+        end
       end
 
       context 'with regular branch' do
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index 6b748369f0dfd95472f67127474e87cd6510fa35..be70cb24dcebfe54e36960e11e03e9349d03a099 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -340,7 +340,7 @@ describe API::MergeRequests do
         expect(json_response['title']).to eq('Test merge_request')
       end
 
-      it "returns 422 when target project has disabled merge requests" do
+      it "returns 403 when target project has disabled merge requests" do
         project.project_feature.update(merge_requests_access_level: 0)
 
         post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
@@ -350,7 +350,7 @@ describe API::MergeRequests do
              author: user2,
              target_project_id: project.id
 
-        expect(response).to have_gitlab_http_status(422)
+        expect(response).to have_gitlab_http_status(403)
       end
 
       it "returns 400 when source_branch is missing" do
diff --git a/spec/requests/api/v3/templates_spec.rb b/spec/requests/api/v3/templates_spec.rb
index 38a8994eb79bcea9c9528259acf98be543452f10..1a637f3cf9620459bd33c110c32f2684547f97a8 100644
--- a/spec/requests/api/v3/templates_spec.rb
+++ b/spec/requests/api/v3/templates_spec.rb
@@ -57,7 +57,7 @@ describe API::V3::Templates do
       expect(json_response['description']).to include('A short and simple permissive license with conditions')
       expect(json_response['conditions']).to eq(%w[include-copyright])
       expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use])
-      expect(json_response['limitations']).to eq(%w[no-liability])
+      expect(json_response['limitations']).to eq(%w[liability warranty])
       expect(json_response['content']).to include('MIT License')
     end
   end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 0467e0251b3f12c3b59dcddf36b1dfdb8b90394a..494db30e8e0996a0304514e31a3d281f4bdedd4d 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -163,7 +163,7 @@ describe 'Git HTTP requests' do
             download(path) do |response|
               json_body = ActiveSupport::JSON.decode(response.body)
 
-              expect(json_body['RepoPath']).to include(wiki.repository.disk_path)
+              expect(json_body['Repository']['relative_path']).to eq(wiki.repository.relative_path)
             end
           end
         end
@@ -344,20 +344,11 @@ describe 'Git HTTP requests' do
         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:
-
-                git remote set-url origin #{project.http_url_to_repo} and try again.
-            MSG
-          end
-
-          it 'downloads get status 404 with "project was moved" message' do
+          it 'downloads get status 200 for redirects' do
             clone_get(path, {})
-            expect(response).to have_gitlab_http_status(:not_found)
-            expect(response.body).to match(project_moved_message)
+
+            expect(response).to have_gitlab_http_status(:ok)
           end
         end
       end
@@ -559,20 +550,19 @@ describe 'Git HTTP requests' do
 
                     Please update your Git remote:
 
-                      git remote set-url origin #{project.http_url_to_repo} 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
+                it 'downloads get status 200' do
                   clone_get(path, env)
-                  expect(response).to have_gitlab_http_status(:not_found)
-                  expect(response.body).to match(project_moved_message)
+
+                  expect(response).to have_gitlab_http_status(:ok)
                 end
 
                 it 'uploads get status 404 with "project was moved" message' do
                   upload(path, env) do |response|
-                    expect(response).to have_gitlab_http_status(:not_found)
-                    expect(response.body).to match(project_moved_message)
+                    expect(response).to have_gitlab_http_status(:ok)
                   end
                 end
               end
@@ -795,9 +785,9 @@ describe 'Git HTTP requests' do
     let(:path) { 'doesnt/exist.git' }
 
     before do
-      allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
-      allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)
-      allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
+      allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).and_return(true)
+      allow_any_instance_of(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)
+      allow_any_instance_of(Gitlab::Auth::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
     end
 
     it_behaves_like 'pulls require Basic HTTP Authentication'
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 971b45c411d249bf75944919aad68f64b9441e13..f80abb06fca31b828fc80dafd03d1f7477fc413d 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -191,10 +191,12 @@ describe 'Git LFS API and storage' do
   describe 'when fetching lfs object' do
     let(:project) { create(:project) }
     let(:update_permissions) { }
+    let(:before_get) { }
 
     before do
       enable_lfs
       update_permissions
+      before_get
       get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
     end
 
@@ -239,6 +241,38 @@ describe 'Git LFS API and storage' do
             end
 
             it_behaves_like 'responds with a file'
+
+            context 'when LFS uses object storage' do
+              context 'when proxy download is enabled' do
+                let(:before_get) do
+                  stub_lfs_object_storage(proxy_download: true)
+                  lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+                end
+
+                it 'responds with redirect' do
+                  expect(response).to have_gitlab_http_status(200)
+                end
+
+                it 'responds with the workhorse send-url' do
+                  expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
+                end
+              end
+
+              context 'when proxy download is disabled' do
+                let(:before_get) do
+                  stub_lfs_object_storage(proxy_download: false)
+                  lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+                end
+
+                it 'responds with redirect' do
+                  expect(response).to have_gitlab_http_status(302)
+                end
+
+                it 'responds with the file location' do
+                  expect(response.location).to include(lfs_object.reload.file.path)
+                end
+              end
+            end
           end
         end
 
@@ -945,22 +979,61 @@ describe 'Git LFS API and storage' do
           end
 
           context 'and request is sent by gitlab-workhorse to authorize the request' do
-            before do
-              put_authorize
+            shared_examples 'a valid response' do
+              before do
+                put_authorize
+              end
+
+              it 'responds with status 200' do
+                expect(response).to have_gitlab_http_status(200)
+              end
+
+              it 'uses the gitlab-workhorse content type' do
+                expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+              end
             end
 
-            it 'responds with status 200' do
-              expect(response).to have_gitlab_http_status(200)
+            shared_examples 'a local file' do
+              it_behaves_like 'a valid response' do
+                it 'responds with status 200, location of lfs store and object details' do
+                  expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
+                  expect(json_response['RemoteObject']).to be_nil
+                  expect(json_response['LfsOid']).to eq(sample_oid)
+                  expect(json_response['LfsSize']).to eq(sample_size)
+                end
+              end
             end
 
-            it 'uses the gitlab-workhorse content type' do
-              expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+            context 'when using local storage' do
+              it_behaves_like 'a local file'
             end
 
-            it 'responds with status 200, location of lfs store and object details' do
-              expect(json_response['StoreLFSPath']).to eq(LfsObjectUploader.workhorse_upload_path)
-              expect(json_response['LfsOid']).to eq(sample_oid)
-              expect(json_response['LfsSize']).to eq(sample_size)
+            context 'when using remote storage' do
+              context 'when direct upload is enabled' do
+                before do
+                  stub_lfs_object_storage(enabled: true, direct_upload: true)
+                end
+
+                it_behaves_like 'a valid response' do
+                  it 'responds with status 200, location of lfs remote store and object details' do
+                    expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
+                    expect(json_response['RemoteObject']).to have_key('ID')
+                    expect(json_response['RemoteObject']).to have_key('GetURL')
+                    expect(json_response['RemoteObject']).to have_key('StoreURL')
+                    expect(json_response['RemoteObject']).to have_key('DeleteURL')
+                    expect(json_response['LfsOid']).to eq(sample_oid)
+                    expect(json_response['LfsSize']).to eq(sample_size)
+                  end
+                end
+              end
+
+              context 'when direct upload is disabled' do
+                before do
+                  stub_lfs_object_storage(enabled: true, direct_upload: false)
+                end
+
+                it_behaves_like 'a local file'
+              end
             end
           end
 
@@ -978,14 +1051,98 @@ describe 'Git LFS API and storage' do
             end
           end
 
+          context 'and workhorse requests upload finalize for a new lfs object' do
+            before do
+              lfs_object.destroy
+            end
+
+            context 'with object storage disabled' do
+              it "doesn't attempt to migrate file to object storage" do
+                expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+                put_finalize(with_tempfile: true)
+              end
+            end
+
+            context 'with object storage enabled' do
+              context 'and direct upload enabled' do
+                let!(:fog_connection) do
+                  stub_lfs_object_storage(direct_upload: true)
+                end
+
+                ['123123', '../../123123'].each do |remote_id|
+                  context "with invalid remote_id: #{remote_id}" do
+                    subject do
+                      put_finalize(with_tempfile: true, args: {
+                        'file.remote_id' => remote_id
+                      })
+                    end
+
+                    it 'responds with status 403' do
+                      subject
+
+                      expect(response).to have_gitlab_http_status(403)
+                    end
+                  end
+                end
+
+                context 'with valid remote_id' do
+                  before do
+                    fog_connection.directories.get('lfs-objects').files.create(
+                      key: 'tmp/upload/12312300',
+                      body: 'content'
+                    )
+                  end
+
+                  subject do
+                    put_finalize(with_tempfile: true, args: {
+                      'file.remote_id' => '12312300',
+                      'file.name' => 'name'
+                    })
+                  end
+
+                  it 'responds with status 200' do
+                    subject
+
+                    expect(response).to have_gitlab_http_status(200)
+                  end
+
+                  it 'schedules migration of file to object storage' do
+                    subject
+
+                    expect(LfsObject.last.projects).to include(project)
+                  end
+
+                  it 'have valid file' do
+                    subject
+
+                    expect(LfsObject.last.file_store).to eq(ObjectStorage::Store::REMOTE)
+                    expect(LfsObject.last.file).to be_exists
+                  end
+                end
+              end
+
+              context 'and background upload enabled' do
+                before do
+                  stub_lfs_object_storage(background_upload: true)
+                end
+
+                it 'schedules migration of file to object storage' do
+                  expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('LfsObjectUploader', 'LfsObject', :file, kind_of(Numeric))
+
+                  put_finalize(with_tempfile: true)
+                end
+              end
+            end
+          end
+
           context 'invalid tempfiles' do
-            it 'rejects slashes in the tempfile name (path traversal' do
-              put_finalize('foo/bar')
-              expect(response).to have_gitlab_http_status(403)
+            before do
+              lfs_object.destroy
             end
 
-            it 'rejects tempfile names that do not start with the oid' do
-              put_finalize("foo#{sample_oid}")
+            it 'rejects slashes in the tempfile name (path traversal)' do
+              put_finalize('../bar', with_tempfile: true)
               expect(response).to have_gitlab_http_status(403)
             end
           end
@@ -1075,7 +1232,7 @@ describe 'Git LFS API and storage' do
             end
 
             it 'with location of lfs store and object details' do
-              expect(json_response['StoreLFSPath']).to eq(LfsObjectUploader.workhorse_upload_path)
+              expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
               expect(json_response['LfsOid']).to eq(sample_oid)
               expect(json_response['LfsSize']).to eq(sample_size)
             end
@@ -1177,9 +1334,25 @@ describe 'Git LFS API and storage' do
       put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, authorize_headers
     end
 
-    def put_finalize(lfs_tmp = lfs_tmp_file)
-      put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
-          headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp).compact
+    def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, args: {})
+      upload_path = LfsObjectUploader.workhorse_local_upload_path
+      file_path = upload_path + '/' + lfs_tmp if lfs_tmp
+
+      if with_tempfile
+        FileUtils.mkdir_p(upload_path)
+        FileUtils.touch(file_path)
+      end
+
+      extra_args = {
+        'file.path' => file_path,
+        'file.name' => File.basename(file_path)
+      }
+
+      put_finalize_with_args(args.merge(extra_args).compact)
+    end
+
+    def put_finalize_with_args(args)
+      put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", args, headers
     end
 
     def lfs_tmp_file
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index eef860821e50c72c58291ac4d15ea26972b5e0f4..bcc3e3a267868e672254fefb43a4bc5443e8b530 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -23,7 +23,7 @@ describe 'cycle analytics events' do
     it 'lists the issue events' do
       get project_cycle_analytics_issue_path(project, format: :json)
 
-      first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
+      first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
 
       expect(json_response['events']).not_to be_empty
       expect(json_response['events'].first['iid']).to eq(first_issue_iid)
@@ -32,7 +32,7 @@ describe 'cycle analytics events' do
     it 'lists the plan events' do
       get project_cycle_analytics_plan_path(project, format: :json)
 
-      first_mr_short_sha = project.merge_requests.sort(:created_asc).first.commits.first.short_id
+      first_mr_short_sha = project.merge_requests.sort_by_attribute(:created_asc).first.commits.first.short_id
 
       expect(json_response['events']).not_to be_empty
       expect(json_response['events'].first['short_sha']).to eq(first_mr_short_sha)
@@ -43,7 +43,7 @@ describe 'cycle analytics events' do
 
       expect(json_response['events']).not_to be_empty
 
-      first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s
+      first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
 
       expect(json_response['events'].first['iid']).to eq(first_mr_iid)
     end
@@ -58,7 +58,7 @@ describe 'cycle analytics events' do
     it 'lists the review events' do
       get project_cycle_analytics_review_path(project, format: :json)
 
-      first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s
+      first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
 
       expect(json_response['events']).not_to be_empty
       expect(json_response['events'].first['iid']).to eq(first_mr_iid)
@@ -74,7 +74,7 @@ describe 'cycle analytics events' do
     it 'lists the production events' do
       get project_cycle_analytics_production_path(project, format: :json)
 
-      first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
+      first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
 
       expect(json_response['events']).not_to be_empty
       expect(json_response['events'].first['iid']).to eq(first_issue_iid)
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index fb1281a6b428af8333d5f0de0eb693269911f9d0..e1b4e6180922738c2d8eac0d13ccd6cd45534b9d 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -164,20 +164,36 @@ describe 'project routing' do
   #  archive_project_repository GET    /:project_id/repository/archive(.:format)  projects/repositories#archive
   #     edit_project_repository GET    /:project_id/repository/edit(.:format)     projects/repositories#edit
   describe Projects::RepositoriesController, 'routing' do
-    it 'to #archive' do
-      expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'master')
-    end
-
     it 'to #archive format:zip' do
-      expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', ref: 'master')
+      expect(get('/gitlab/gitlabhq/-/archive/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master/archive')
     end
 
     it 'to #archive format:tar.bz2' do
-      expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', ref: 'master')
+      expect(get('/gitlab/gitlabhq/-/archive/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master/archive')
     end
 
     it 'to #archive with "/" in route' do
-      expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'improve/awesome')
+      expect(get('/gitlab/gitlabhq/-/archive/improve/awesome/gitlabhq-improve-awesome.tar.gz')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.gz', id: 'improve/awesome/gitlabhq-improve-awesome')
+    end
+
+    it 'to #archive_alternative' do
+      expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', append_sha: true)
+    end
+
+    it 'to #archive_deprecated' do
+      expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', append_sha: true)
+    end
+
+    it 'to #archive_deprecated format:zip' do
+      expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master', append_sha: true)
+    end
+
+    it 'to #archive_deprecated format:tar.bz2' do
+      expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master', append_sha: true)
+    end
+
+    it 'to #archive_deprecated with "/" in route' do
+      expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'improve/awesome', append_sha: true)
     end
   end
 
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 56d025f0176eeb05282147e7dacb9ae898f98ecc..9345671a1a7fe4c4755093f990a1b2e935aaf63b 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
 # user_calendar_activities   GET    /u/:username/calendar_activities(.:format)
 describe UsersController, "routing" do
   it "to #show" do
-    allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true)
+    allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
 
     expect(get("/User")).to route_to('users#show', username: 'User')
   end
@@ -210,7 +210,7 @@ describe Profiles::KeysController, "routing" do
 
   # get all the ssh-keys of a user
   it "to #get_keys" do
-    allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true)
+    allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
 
     expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo')
   end
diff --git a/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac7b1575ec0f458370479368220e7169d5c691f1
--- /dev/null
+++ b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/avoid_break_from_strong_memoize'
+
+describe RuboCop::Cop::AvoidBreakFromStrongMemoize do
+  include CopHelper
+
+  subject(:cop) { described_class.new }
+
+  it 'flags violation for break inside strong_memoize' do
+    expect_offense(<<~RUBY)
+      strong_memoize(:result) do
+        break if something
+        ^^^^^ Do not use break inside strong_memoize, use next instead.
+
+        do_an_heavy_calculation
+      end
+    RUBY
+  end
+
+  it 'flags violation for break inside strong_memoize nested blocks' do
+    expect_offense(<<~RUBY)
+      strong_memoize do
+        items.each do |item|
+          break item
+          ^^^^^^^^^^ Do not use break inside strong_memoize, use next instead.
+        end
+      end
+    RUBY
+  end
+
+  it "doesn't flag violation for next inside strong_memoize" do
+    expect_no_offenses(<<~RUBY)
+      strong_memoize(:result) do
+        next if something
+
+        do_an_heavy_calculation
+      end
+    RUBY
+  end
+
+  it "doesn't flag violation for break inside blocks" do
+    expect_no_offenses(<<~RUBY)
+      call do
+        break if something
+
+        do_an_heavy_calculation
+      end
+    RUBY
+  end
+
+  it "doesn't call add_offense twice for nested blocks" do
+    source = <<~RUBY
+      call do
+        strong_memoize(:result) do
+          break if something
+
+          do_an_heavy_calculation
+        end
+      end
+    RUBY
+    expect_any_instance_of(described_class).to receive(:add_offense).once
+
+    inspect_source(source)
+  end
+
+  it "doesn't check when block is empty" do
+    expect_no_offenses(<<~RUBY)
+      strong_memoize(:result) do
+      end
+    RUBY
+  end
+end
diff --git a/spec/rubocop/cop/avoid_return_from_blocks_spec.rb b/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a5c280a7adc403e7c9d41220bb7fcf9250b2406d
--- /dev/null
+++ b/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
@@ -0,0 +1,127 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/avoid_return_from_blocks'
+
+describe RuboCop::Cop::AvoidReturnFromBlocks do
+  include CopHelper
+
+  subject(:cop) { described_class.new }
+
+  it 'flags violation for return inside a block' do
+    expect_offense(<<~RUBY)
+      call do
+        do_something
+        return if something_else
+        ^^^^^^ Do not return from a block, use next or break instead.
+      end
+    RUBY
+  end
+
+  it "doesn't call add_offense twice for nested blocks" do
+    source = <<~RUBY
+      call do
+        call do
+          something
+          return if something_else
+        end
+      end
+    RUBY
+    expect_any_instance_of(described_class).to receive(:add_offense).once
+
+    inspect_source(source)
+  end
+
+  it 'flags violation for return inside included > def > block' do
+    expect_offense(<<~RUBY)
+      included do
+        def a_method
+          return if something
+
+          call do
+            return if something_else
+            ^^^^^^ Do not return from a block, use next or break instead.
+          end
+        end
+      end
+    RUBY
+  end
+
+  shared_examples 'examples with whitelisted method' do |whitelisted_method|
+    it "doesn't flag violation for return inside #{whitelisted_method}" do
+      expect_no_offenses(<<~RUBY)
+        items.#{whitelisted_method} do |item|
+          do_something
+          return if something_else
+        end
+      RUBY
+    end
+  end
+
+  %i[each each_filename times loop].each do |whitelisted_method|
+    it_behaves_like 'examples with whitelisted method', whitelisted_method
+  end
+
+  shared_examples 'examples with def methods' do |def_method|
+    it "doesn't flag violation for return inside #{def_method}" do
+      expect_no_offenses(<<~RUBY)
+        helpers do
+          #{def_method} do
+            return if something
+
+            do_something_more
+          end
+        end
+      RUBY
+    end
+  end
+
+  %i[define_method lambda].each do |def_method|
+    it_behaves_like 'examples with def methods', def_method
+  end
+
+  it "doesn't flag violation for return inside a lambda" do
+    expect_no_offenses(<<~RUBY)
+      lambda do
+        do_something
+        return if something_else
+      end
+    RUBY
+  end
+
+  it "doesn't flag violation for return used inside a method definition" do
+    expect_no_offenses(<<~RUBY)
+      describe Klass do
+        def a_method
+          do_something
+          return if something_else
+        end
+      end
+    RUBY
+  end
+
+  it "doesn't flag violation for next inside a block" do
+    expect_no_offenses(<<~RUBY)
+      call do
+        do_something
+        next if something_else
+      end
+    RUBY
+  end
+
+  it "doesn't flag violation for break inside a block" do
+    expect_no_offenses(<<~RUBY)
+      call do
+        do_something
+        break if something_else
+      end
+    RUBY
+  end
+
+  it "doesn't check when block is empty" do
+    expect_no_offenses(<<~RUBY)
+      call do
+      end
+    RUBY
+  end
+end
diff --git a/spec/rubocop/cop/gitlab/httparty_spec.rb b/spec/rubocop/cop/gitlab/httparty_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..510839a21d7aee6b75842bffde7ce54bdfdded78
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/httparty_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/httparty'
+
+describe RuboCop::Cop::Gitlab::HTTParty do # rubocop:disable RSpec/FilePath
+  include CopHelper
+
+  subject(:cop) { described_class.new }
+
+  shared_examples('registering include offense') do |options|
+    let(:offending_lines) { options[:offending_lines] }
+
+    it 'registers an offense when the class includes HTTParty' do
+      inspect_source(source)
+
+      aggregate_failures do
+        expect(cop.offenses.size).to eq(offending_lines.size)
+        expect(cop.offenses.map(&:line)).to eq(offending_lines)
+      end
+    end
+  end
+
+  shared_examples('registering call offense') do |options|
+    let(:offending_lines) { options[:offending_lines] }
+
+    it 'registers an offense when the class calls HTTParty' do
+      inspect_source(source)
+
+      aggregate_failures do
+        expect(cop.offenses.size).to eq(offending_lines.size)
+        expect(cop.offenses.map(&:line)).to eq(offending_lines)
+      end
+    end
+  end
+
+  context 'when source is a regular module' do
+    it_behaves_like 'registering include offense', offending_lines: [2] do
+      let(:source) do
+        <<~RUBY
+          module M
+            include HTTParty
+          end
+        RUBY
+      end
+    end
+  end
+
+  context 'when source is a regular class' do
+    it_behaves_like 'registering include offense', offending_lines: [2] do
+      let(:source) do
+        <<~RUBY
+          class Foo
+            include HTTParty
+          end
+        RUBY
+      end
+    end
+  end
+
+  context 'when HTTParty is called' do
+    it_behaves_like 'registering call offense', offending_lines: [3] do
+      let(:source) do
+        <<~RUBY
+          class Foo
+            def bar
+              HTTParty.get('http://example.com')
+            end
+          end
+        RUBY
+      end
+    end
+  end
+end
diff --git a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2763f2bda21bc7bed6e73a2cb9f8efaa10ffdcd7
--- /dev/null
+++ b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/rspec/factories_in_migration_specs'
+
+describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
+  include CopHelper
+
+  let(:source_file) { 'spec/migrations/foo_spec.rb' }
+
+  subject(:cop) { described_class.new }
+
+  shared_examples 'an offensive factory call' do |namespace|
+    %i[build build_list create create_list].each do |forbidden_method|
+      namespaced_forbidden_method = "#{namespace}#{forbidden_method}(:user)"
+
+      it "registers an offense for #{namespaced_forbidden_method}" do
+        expect_offense(<<-RUBY)
+        describe 'foo' do
+          let(:user) { #{namespaced_forbidden_method} }
+                       #{'^' * namespaced_forbidden_method.size} Don't use FactoryBot.#{forbidden_method} in migration specs, use `table` instead.
+        end
+        RUBY
+      end
+    end
+  end
+
+  context 'in a migration spec file' do
+    before do
+      allow(cop).to receive(:in_migration_spec?).and_return(true)
+    end
+
+    it_behaves_like 'an offensive factory call', ''
+    it_behaves_like 'an offensive factory call', 'FactoryBot.'
+  end
+
+  context 'outside of a migration spec file' do
+    it "does not register an offense" do
+      expect_no_offenses(<<-RUBY)
+        describe 'foo' do
+          let(:user) { create(:user) }
+        end
+      RUBY
+    end
+  end
+end
diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb
index 9673b11c2a284fc4db87f5fdd31ca4c76730f43a..98cd15e248b9ac1516aa5da2c25850a39d176d6a 100644
--- a/spec/serializers/build_serializer_spec.rb
+++ b/spec/serializers/build_serializer_spec.rb
@@ -28,15 +28,31 @@ describe BuildSerializer do
   end
 
   describe '#represent_status' do
-    context 'when represents only status' do
-      let(:resource) { create(:ci_build) }
+    context 'for a failed build' do
+      let(:resource) { create(:ci_build, :failed) }
+      let(:status) { resource.detailed_status(double('user')) }
+
+      subject { serializer.represent_status(resource) }
+
+      it 'serializes only status' do
+        expect(subject[:text]).to eq(status.text)
+        expect(subject[:label]).to eq('failed')
+        expect(subject[:tooltip]).to eq('failed <br> (unknown failure)')
+        expect(subject[:icon]).to eq(status.icon)
+        expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico")
+      end
+    end
+
+    context 'for any other type of build' do
+      let(:resource) { create(:ci_build, :success) }
       let(:status) { resource.detailed_status(double('user')) }
 
       subject { serializer.represent_status(resource) }
 
       it 'serializes only status' do
         expect(subject[:text]).to eq(status.text)
-        expect(subject[:label]).to eq(status.label)
+        expect(subject[:label]).to eq('passed')
+        expect(subject[:tooltip]).to eq('passed')
         expect(subject[:icon]).to eq(status.icon)
         expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico")
       end
diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb
index 7ee8e38af1c81a69c1615959df7388e3ed30e94b..7e19e74ca00dcc03a36c940d64e706c62883b1fd 100644
--- a/spec/serializers/discussion_entity_spec.rb
+++ b/spec/serializers/discussion_entity_spec.rb
@@ -6,7 +6,7 @@ describe DiscussionEntity do
   let(:user) { create(:user) }
   let(:note) { create(:discussion_note_on_merge_request) }
   let(:discussion) { note.discussion }
-  let(:request) { double('request') }
+  let(:request) { double('request', note_entity: ProjectNoteEntity) }
   let(:controller) { double('controller') }
   let(:entity) { described_class.new(discussion, request: request, context: controller) }
 
diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb
index b9cc2f6483133c4f054cfc5a1c48f10646e94a84..36da8d33a4460429017dd7cc91201760049a32ed 100644
--- a/spec/serializers/entity_date_helper_spec.rb
+++ b/spec/serializers/entity_date_helper_spec.rb
@@ -32,6 +32,7 @@ describe EntityDateHelper do
   end
 
   it 'converts 86560 seconds' do
+    Rails.logger.debug date_helper_class.inspect
     expect(date_helper_class.distance_of_time_as_hash(86560)).to eq(days: 1, mins: 2, seconds: 40)
   end
 
@@ -42,4 +43,58 @@ describe EntityDateHelper do
   it 'converts 986760 seconds' do
     expect(date_helper_class.distance_of_time_as_hash(986760)).to eq(days: 11, hours: 10, mins: 6)
   end
+
+  describe '#remaining_days_in_words' do
+    around do |example|
+      Timecop.freeze(Time.utc(2017, 3, 17)) { example.run }
+    end
+
+    context 'when less than 31 days remaining' do
+      let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 12.days.from_now.utc)) }
+
+      it 'returns days remaining' do
+        expect(milestone_remaining).to eq("<strong>12</strong> days remaining")
+      end
+    end
+
+    context 'when less than 1 year and more than 30 days remaining' do
+      let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.months.from_now.utc)) }
+
+      it 'returns months remaining' do
+        expect(milestone_remaining).to eq("<strong>2</strong> months remaining")
+      end
+    end
+
+    context 'when more than 1 year remaining' do
+      let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: (1.year.from_now + 2.days).utc)) }
+
+      it 'returns years remaining' do
+        expect(milestone_remaining).to eq("<strong>1</strong> year remaining")
+      end
+    end
+
+    context 'when milestone is expired' do
+      let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.days.ago.utc)) }
+
+      it 'returns "Past due"' do
+        expect(milestone_remaining).to eq("<strong>Past due</strong>")
+      end
+    end
+
+    context 'when milestone has start_date in the future' do
+      let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.from_now.utc)) }
+
+      it 'returns "Upcoming"' do
+        expect(milestone_remaining).to eq("<strong>Upcoming</strong>")
+      end
+    end
+
+    context 'when milestone has start_date in the past' do
+      let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.ago.utc)) }
+
+      it 'returns days elapsed' do
+        expect(milestone_remaining).to eq("<strong>2</strong> days elapsed")
+      end
+    end
+  end
 end
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index 026360e91a342589695f64eca522cf35c56fd153..c90396ebb28c17ef5b5a81781b00193efafdcd44 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -38,7 +38,7 @@ describe JobEntity do
 
   it 'contains details' do
     expect(subject).to include :status
-    expect(subject[:status]).to include :icon, :favicon, :text, :label
+    expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
   end
 
   context 'when job is retryable' do
@@ -126,7 +126,72 @@ describe JobEntity do
 
     it 'contains details' do
       expect(subject).to include :status
-      expect(subject[:status]).to include :icon, :favicon, :text, :label
+      expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
+    end
+  end
+
+  context 'when job failed' do
+    let(:job) { create(:ci_build, :script_failure) }
+
+    it 'contains details' do
+      expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
+    end
+
+    it 'states that it failed' do
+      expect(subject[:status][:label]).to eq('failed')
+    end
+
+    it 'should indicate the failure reason on tooltip' do
+      expect(subject[:status][:tooltip]).to eq('failed <br> (script failure)')
+    end
+
+    it 'should include a callout message with a verbose output' do
+      expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information')
+    end
+
+    it 'should state that it is not recoverable' do
+      expect(subject[:recoverable]).to be_falsy
+    end
+  end
+
+  context 'when job is allowed to fail' do
+    let(:job) { create(:ci_build, :allowed_to_fail, :script_failure) }
+
+    it 'contains details' do
+      expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
+    end
+
+    it 'states that it failed' do
+      expect(subject[:status][:label]).to eq('failed (allowed to fail)')
+    end
+
+    it 'should indicate the failure reason on tooltip' do
+      expect(subject[:status][:tooltip]).to eq('failed <br> (script failure) (allowed to fail)')
+    end
+
+    it 'should include a callout message with a verbose output' do
+      expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information')
+    end
+
+    it 'should state that it is not recoverable' do
+      expect(subject[:recoverable]).to be_falsy
+    end
+  end
+
+  context 'when job failed and is recoverable' do
+    let(:job) { create(:ci_build, :api_failure) }
+
+    it 'should state it is recoverable' do
+      expect(subject[:recoverable]).to be_truthy
+    end
+  end
+
+  context 'when job passed' do
+    let(:job) { create(:ci_build, :success) }
+
+    it 'should not include callout message or recoverable keys' do
+      expect(subject).not_to include('callout_message')
+      expect(subject).not_to include('recoverable')
     end
   end
 end
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 80a271ba7fbc317b7e92cd51e79053a529f2cd02..d2072198d83d833db8de1b3630f48dc2a0bca66c 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -147,9 +147,9 @@ describe MergeRequestWidgetEntity do
       allow(resource).to receive(:diff_head_sha) { 'sha' }
     end
 
-    context 'when no diff head commit' do
+    context 'when diff head commit is empty' do
       it 'returns nil' do
-        allow(resource).to receive(:diff_head_commit) { nil }
+        allow(resource).to receive(:diff_head_sha) { '' }
 
         expect(subject[:diff_head_sha]).to be_nil
       end
@@ -157,8 +157,6 @@ describe MergeRequestWidgetEntity do
 
     context 'when diff head commit present' do
       it 'returns diff head commit short id' do
-        allow(resource).to receive(:diff_head_commit) { double }
-
         expect(subject[:diff_head_sha]).to eq('sha')
       end
     end
diff --git a/spec/serializers/note_entity_spec.rb b/spec/serializers/note_entity_spec.rb
index 51a8587ace9615883059bf862507a6cd12743274..13cda781cda82ff2cf97af7497f1efc7cb33a57f 100644
--- a/spec/serializers/note_entity_spec.rb
+++ b/spec/serializers/note_entity_spec.rb
@@ -10,53 +10,5 @@ describe NoteEntity do
   let(:user) { create(:user) }
   subject { entity.as_json }
 
-  context 'basic note' do
-    it 'exposes correct elements' do
-      expect(subject).to include(:type, :author, :human_access, :note, :note_html, :current_user,
-        :discussion_id, :emoji_awardable, :award_emoji, :toggle_award_path, :report_abuse_path, :path, :attachment)
-    end
-
-    it 'does not expose elements for specific notes cases' do
-      expect(subject).not_to include(:last_edited_by, :last_edited_at, :system_note_icon_name)
-    end
-
-    it 'exposes author correctly' do
-      expect(subject[:author]).to include(:id, :name, :username, :state, :avatar_url, :path)
-    end
-
-    it 'does not expose web_url for author' do
-      expect(subject[:author]).not_to include(:web_url)
-    end
-  end
-
-  context 'when note was edited' do
-    before do
-      note.update(updated_at: 1.minute.from_now, updated_by: user)
-    end
-
-    it 'exposes last_edited_at and last_edited_by elements' do
-      expect(subject).to include(:last_edited_at, :last_edited_by)
-    end
-  end
-
-  context 'when note is a system note' do
-    before do
-      note.update(system: true)
-    end
-
-    it 'exposes system_note_icon_name element' do
-      expect(subject).to include(:system_note_icon_name)
-    end
-  end
-
-  context 'when note is part of resolvable discussion' do
-    before do
-      allow(note).to receive(:part_of_discussion?).and_return(true)
-      allow(note).to receive(:resolvable?).and_return(true)
-    end
-
-    it 'exposes paths to resolve note' do
-      expect(subject).to include(:resolve_path, :resolve_with_issue_path)
-    end
-  end
+  it_behaves_like 'note entity'
 end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 248552d1858ff91a7df1b204f091728c088498d3..2473c561f4b4d28366f043bc09f950b5b53ba782 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -30,7 +30,7 @@ describe PipelineEntity do
         expect(subject).to include :details
         expect(subject[:details])
           .to include :duration, :finished_at
-        expect(subject[:details][:status]).to include :icon, :favicon, :text, :label
+        expect(subject[:details][:status]).to include :icon, :favicon, :text, :label, :tooltip
       end
 
       it 'contains flags' do
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index c38795ad1a138ddc3a2c5fc04bc22bb491dda2e8..f51c11b141fca73a3c1c75adef15beb9613d13dd 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -117,6 +117,7 @@ describe PipelineSerializer do
       shared_examples 'no N+1 queries' do
         it 'verifies number of queries', :request_store do
           recorded = ActiveRecord::QueryRecorder.new { subject }
+
           expect(recorded.count).to be_within(1).of(36)
           expect(recorded.cached_count).to eq(0)
         end
diff --git a/spec/serializers/project_note_entity_spec.rb b/spec/serializers/project_note_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dafd1cf603e417383ef23b80ed0b7bab9e5af624
--- /dev/null
+++ b/spec/serializers/project_note_entity_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe ProjectNoteEntity do
+  include Gitlab::Routing
+
+  let(:request) { double('request', current_user: user, noteable: note.noteable) }
+
+  let(:entity) { described_class.new(note, request: request) }
+  let(:note) { create(:note) }
+  let(:user) { create(:user) }
+  subject { entity.as_json }
+
+  it_behaves_like 'note entity'
+
+  it 'exposes project-specific elements' do
+    expect(subject).to include(:human_access, :toggle_award_path, :path)
+  end
+
+  context 'when note is part of resolvable discussion' do
+    before do
+      allow(note).to receive(:part_of_discussion?).and_return(true)
+      allow(note).to receive(:resolvable?).and_return(true)
+    end
+
+    it 'exposes paths to resolve note' do
+      expect(subject).to include(:resolve_path, :resolve_with_issue_path)
+    end
+  end
+end
diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb
index 40e303f7b899323f795fe46e42cc23f19f496804..2034c7891effdf078887c4e6ff55ff4d93f2c42a 100644
--- a/spec/serializers/stage_entity_spec.rb
+++ b/spec/serializers/stage_entity_spec.rb
@@ -26,7 +26,7 @@ describe StageEntity do
     end
 
     it 'contains detailed status' do
-      expect(subject[:status]).to include :text, :label, :group, :icon
+      expect(subject[:status]).to include :text, :label, :group, :icon, :tooltip
       expect(subject[:status][:label]).to eq 'passed'
     end
 
diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb
index 16431ed4188d2b96ca545118a6498205e333d3aa..559475e571cb3ac2e79f6e8191ed48e062558c7a 100644
--- a/spec/serializers/status_entity_spec.rb
+++ b/spec/serializers/status_entity_spec.rb
@@ -16,7 +16,7 @@ describe StatusEntity do
     subject { entity.as_json }
 
     it 'contains status details' do
-      expect(subject).to include :text, :icon, :favicon, :label, :group
+      expect(subject).to include :text, :icon, :favicon, :label, :group, :tooltip
       expect(subject).to include :has_details, :details_path
       expect(subject[:favicon]).to match_asset_path('/assets/ci_favicons/favicon_status_success.ico')
     end
@@ -25,5 +25,10 @@ describe StatusEntity do
       allow(Rails.env).to receive(:development?) { true }
       expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/dev/favicon_status_success.ico')
     end
+
+    it 'contains a canary namespaced favicon if canary env' do
+      stub_env('CANARY', 'true')
+      expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/canary/favicon_status_success.ico')
+    end
   end
 end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 9128280eb5a5604a8a87ed101dc00f6229969add..da8e660c16ba46599c45974ec8b128b660d976a6 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -172,7 +172,7 @@ describe Auth::ContainerRegistryAuthenticationService do
         end
 
         let(:current_params) do
-          { scope: "repository:#{project.path_with_namespace}:*" }
+          { scope: "repository:#{project.full_path}:*" }
         end
 
         it_behaves_like 'an inaccessible'
@@ -200,7 +200,7 @@ describe Auth::ContainerRegistryAuthenticationService do
         end
 
         let(:current_params) do
-          { scope: "repository:#{project.path_with_namespace}:*" }
+          { scope: "repository:#{project.full_path}:*" }
         end
 
         it_behaves_like 'an inaccessible'
@@ -239,7 +239,7 @@ describe Auth::ContainerRegistryAuthenticationService do
         end
 
         let(:current_params) do
-          { scope: "repository:#{project.path_with_namespace}:*" }
+          { scope: "repository:#{project.full_path}:*" }
         end
 
         it_behaves_like 'an inaccessible'
@@ -270,7 +270,7 @@ describe Auth::ContainerRegistryAuthenticationService do
 
       context 'disallow anyone to delete images' do
         let(:current_params) do
-          { scope: "repository:#{project.path_with_namespace}:*" }
+          { scope: "repository:#{project.full_path}:*" }
         end
 
         it_behaves_like 'an inaccessible'
@@ -311,7 +311,7 @@ describe Auth::ContainerRegistryAuthenticationService do
 
         context 'disallow anyone to delete images' do
           let(:current_params) do
-            { scope: "repository:#{project.path_with_namespace}:*" }
+            { scope: "repository:#{project.full_path}:*" }
           end
 
           it_behaves_like 'an inaccessible'
@@ -323,7 +323,7 @@ describe Auth::ContainerRegistryAuthenticationService do
         context 'disallow anyone to pull or push images' do
           let(:current_user) { create(:user, external: true) }
           let(:current_params) do
-            { scope: "repository:#{project.path_with_namespace}:pull,push" }
+            { scope: "repository:#{project.full_path}:pull,push" }
           end
 
           it_behaves_like 'an inaccessible'
@@ -333,7 +333,7 @@ describe Auth::ContainerRegistryAuthenticationService do
         context 'disallow anyone to delete images' do
           let(:current_user) { create(:user, external: true) }
           let(:current_params) do
-            { scope: "repository:#{project.path_with_namespace}:*" }
+            { scope: "repository:#{project.full_path}:*" }
           end
 
           it_behaves_like 'an inaccessible'
@@ -359,7 +359,7 @@ describe Auth::ContainerRegistryAuthenticationService do
 
     context 'allow to delete images' do
       let(:current_params) do
-        { scope: "repository:#{current_project.path_with_namespace}:*" }
+        { scope: "repository:#{current_project.full_path}:*" }
       end
 
       it_behaves_like 'a deletable' do
@@ -398,7 +398,7 @@ describe Auth::ContainerRegistryAuthenticationService do
 
     context 'disallow to delete images' do
       let(:current_params) do
-        { scope: "repository:#{current_project.path_with_namespace}:*" }
+        { scope: "repository:#{current_project.full_path}:*" }
       end
 
       it_behaves_like 'an inaccessible' do
@@ -585,4 +585,140 @@ describe Auth::ContainerRegistryAuthenticationService do
       it_behaves_like 'not a container repository factory'
     end
   end
+
+  context 'for deploy tokens' do
+    let(:current_params) do
+      { scope: "repository:#{project.full_path}:pull" }
+    end
+
+    context 'when deploy token has read_registry as a scope' do
+      let(:current_user) { create(:deploy_token, projects: [project]) }
+
+      context 'for public project' do
+        let(:project) { create(:project, :public) }
+
+        context 'when pulling' do
+          it_behaves_like 'a pullable'
+        end
+
+        context 'when pushing' do
+          let(:current_params) do
+            { scope: "repository:#{project.full_path}:push" }
+          end
+
+          it_behaves_like 'an inaccessible'
+        end
+      end
+
+      context 'for internal project' do
+        let(:project) { create(:project, :internal) }
+
+        context 'when pulling' do
+          it_behaves_like 'a pullable'
+        end
+
+        context 'when pushing' do
+          let(:current_params) do
+            { scope: "repository:#{project.full_path}:push" }
+          end
+
+          it_behaves_like 'an inaccessible'
+        end
+      end
+
+      context 'for private project' do
+        let(:project) { create(:project, :private) }
+
+        context 'when pulling' do
+          it_behaves_like 'a pullable'
+        end
+
+        context 'when pushing' do
+          let(:current_params) do
+            { scope: "repository:#{project.full_path}:push" }
+          end
+
+          it_behaves_like 'an inaccessible'
+        end
+      end
+    end
+
+    context 'when deploy token does not have read_registry scope' do
+      let(:current_user) { create(:deploy_token, projects: [project], read_registry: false) }
+
+      context 'for public project' do
+        let(:project) { create(:project, :public) }
+
+        context 'when pulling' do
+          it_behaves_like 'a pullable'
+        end
+      end
+
+      context 'for internal project' do
+        let(:project) { create(:project, :internal) }
+
+        context 'when pulling' do
+          it_behaves_like 'an inaccessible'
+        end
+      end
+
+      context 'for private project' do
+        let(:project) { create(:project, :internal) }
+
+        context 'when pulling' do
+          it_behaves_like 'an inaccessible'
+        end
+      end
+    end
+
+    context 'when deploy token is not related to the project' do
+      let(:current_user) { create(:deploy_token, read_registry: false) }
+
+      context 'for public project' do
+        let(:project) { create(:project, :public) }
+
+        context 'when pulling' do
+          it_behaves_like 'a pullable'
+        end
+      end
+
+      context 'for internal project' do
+        let(:project) { create(:project, :internal) }
+
+        context 'when pulling' do
+          it_behaves_like 'an inaccessible'
+        end
+      end
+
+      context 'for private project' do
+        let(:project) { create(:project, :internal) }
+
+        context 'when pulling' do
+          it_behaves_like 'an inaccessible'
+        end
+      end
+    end
+
+    context 'when deploy token has been revoked' do
+      let(:current_user) { create(:deploy_token, :revoked, projects: [project]) }
+
+      context 'for public project' do
+        let(:project) { create(:project, :public) }
+
+        it_behaves_like 'a pullable'
+      end
+
+      context 'for internal project' do
+        let(:project) { create(:project, :internal) }
+
+        it_behaves_like 'an inaccessible'
+      end
+
+      context 'for private project' do
+        let(:project) { create(:project, :internal) }
+
+        it_behaves_like 'an inaccessible'
+      end
+    end
+  end
 end
diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb
index db51a524e797699c213af1e9ede247774098d87d..a715261cd6c01bd2d903ed67059ad79d22ed1a35 100644
--- a/spec/services/boards/create_service_spec.rb
+++ b/spec/services/boards/create_service_spec.rb
@@ -2,34 +2,20 @@ require 'spec_helper'
 
 describe Boards::CreateService do
   describe '#execute' do
-    let(:project) { create(:project) }
+    context 'when board parent is a project' do
+      let(:parent) { create(:project) }
 
-    subject(:service) { described_class.new(project, double) }
+      subject(:service) { described_class.new(parent, double) }
 
-    context 'when project does not have a board' do
-      it 'creates a new board' do
-        expect { service.execute }.to change(Board, :count).by(1)
-      end
-
-      it 'creates the default lists' do
-        board = service.execute
-
-        expect(board.lists.size).to eq 2
-        expect(board.lists.first).to be_backlog
-        expect(board.lists.last).to be_closed
-      end
+      it_behaves_like 'boards create service'
     end
 
-    context 'when project has a board' do
-      before do
-        create(:board, project: project)
-      end
+    context 'when board parent is a group' do
+      let(:parent) { create(:group) }
 
-      it 'does not create a new board' do
-        expect(service).to receive(:can_create_board?) { false }
+      subject(:service) { described_class.new(parent, double) }
 
-        expect { service.execute }.not_to change(project.boards, :count)
-      end
+      it_behaves_like 'boards create service'
     end
   end
 end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index ff5733b7064a2671007e76e4bf252edb3955377e..27a7bf0e605c24d3595eada4f8321df861d61fef 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -2,97 +2,117 @@ require 'spec_helper'
 
 describe Boards::Issues::ListService do
   describe '#execute' do
-    let(:user)    { create(:user) }
-    let(:project) { create(:project) }
-    let(:board)   { create(:board, project: project) }
-
-    let(:bug) { create(:label, project: project, name: 'Bug') }
-    let(:development) { create(:label, project: project, name: 'Development') }
-    let(:testing)  { create(:label, project: project, name: 'Testing') }
-    let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
-    let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
-    let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
-
-    let!(:backlog) { create(:backlog_list, board: board) }
-    let!(:list1)   { create(:list, board: board, label: development, position: 0) }
-    let!(:list2)   { create(:list, board: board, label: testing, position: 1) }
-    let!(:closed)  { create(:closed_list, board: board) }
-
-    let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) }
-    let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) }
-    let!(:reopened_issue1) { create(:issue, :opened, project: project) }
-
-    let!(:list1_issue1) { create(:labeled_issue, project: project, labels: [p2, development]) }
-    let!(:list1_issue2) { create(:labeled_issue, project: project, labels: [development]) }
-    let!(:list1_issue3) { create(:labeled_issue, project: project, labels: [development, p1]) }
-    let!(:list2_issue1) { create(:labeled_issue, project: project, labels: [testing]) }
-
-    let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
-    let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) }
-    let!(:closed_issue3) { create(:issue, :closed, project: project) }
-    let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) }
-    let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development]) }
-
-    before do
-      project.add_developer(user)
-    end
-
-    it 'delegates search to IssuesFinder' do
-      params = { board_id: board.id, id: list1.id }
-
-      expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original
+    context 'when parent is a project' do
+      let(:user)    { create(:user) }
+      let(:project) { create(:project) }
+      let(:board)   { create(:board, project: project) }
+
+      let(:m1) { create(:milestone, project: project) }
+      let(:m2) { create(:milestone, project: project) }
+
+      let(:bug) { create(:label, project: project, name: 'Bug') }
+      let(:development) { create(:label, project: project, name: 'Development') }
+      let(:testing)  { create(:label, project: project, name: 'Testing') }
+      let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
+      let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
+      let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
+
+      let!(:backlog) { create(:backlog_list, board: board) }
+      let!(:list1)   { create(:list, board: board, label: development, position: 0) }
+      let!(:list2)   { create(:list, board: board, label: testing, position: 1) }
+      let!(:closed)  { create(:closed_list, board: board) }
+
+      let!(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
+      let!(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2]) }
+      let!(:reopened_issue1) { create(:issue, :opened, project: project, title: 'Issue 3' ) }
+
+      let!(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, development]) }
+      let!(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) }
+      let!(:list1_issue3) { create(:labeled_issue, project: project, milestone: m1, labels: [development, p1]) }
+      let!(:list2_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [testing]) }
+
+      let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
+      let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) }
+      let!(:closed_issue3) { create(:issue, :closed, project: project) }
+      let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) }
+      let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development]) }
+
+      let(:parent) { project }
+
+      before do
+        project.add_developer(user)
+      end
 
-      described_class.new(project, user, params).execute
+      it_behaves_like 'issues list service'
     end
 
-    context 'issues are ordered by priority' do
-      it 'returns opened issues when list_id is missing' do
-        params = { board_id: board.id }
+    context 'when parent is a group' do
+      let(:user)    { create(:user) }
+      let(:project) { create(:project, :empty_repo, namespace: group) }
+      let(:project1) { create(:project, :empty_repo, namespace: group) }
 
-        issues = described_class.new(project, user, params).execute
+      let(:m1) { create(:milestone, group: group) }
+      let(:m2) { create(:milestone, group: group) }
 
-        expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
-      end
+      let(:bug) { create(:group_label, group: group, name: 'Bug') }
+      let(:development) { create(:group_label, group: group, name: 'Development') }
+      let(:testing)  { create(:group_label, group: group, name: 'Testing') }
 
-      it 'returns opened issues when listing issues from Backlog' do
-        params = { board_id: board.id, id: backlog.id }
+      let(:p1) { create(:group_label, title: 'P1', group: group) }
+      let(:p2) { create(:group_label, title: 'P2', group: group) }
+      let(:p3) { create(:group_label, title: 'P3', group: group) }
 
-        issues = described_class.new(project, user, params).execute
+      let(:p1_project) { create(:label, title: 'P1_project', project: project, priority: 1) }
+      let(:p2_project) { create(:label, title: 'P2_project', project: project, priority: 2) }
+      let(:p3_project) { create(:label, title: 'P3_project', project: project, priority: 3) }
 
-        expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
-      end
+      let(:p1_project1) { create(:label, title: 'P1_project1', project: project1, priority: 1) }
+      let(:p2_project1) { create(:label, title: 'P2_project1', project: project1, priority: 2) }
+      let(:p3_project1) { create(:label, title: 'P3_project1', project: project1, priority: 3) }
 
-      it 'returns closed issues when listing issues from Closed' do
-        params = { board_id: board.id, id: closed.id }
+      let!(:backlog) { create(:backlog_list, board: board) }
+      let!(:list1)   { create(:list, board: board, label: development, position: 0) }
+      let!(:list2)   { create(:list, board: board, label: testing, position: 1) }
+      let!(:closed)  { create(:closed_list, board: board) }
 
-        issues = described_class.new(project, user, params).execute
+      let!(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
+      let!(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2, p2_project]) }
+      let!(:reopened_issue1) { create(:issue, state: 'opened', project: project, title: 'Issue 3', closed_at: Time.now ) }
 
-        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
-        params = { board_id: board.id, id: list1.id }
+      let!(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, p2_project, development]) }
+      let!(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) }
+      let!(:list1_issue3) { create(:labeled_issue, project: project1, milestone: m1, labels: [development, p1, p1_project1]) }
+      let!(:list2_issue1) { create(:labeled_issue, project: project1, milestone: m1, labels: [testing]) }
 
-        issues = described_class.new(project, user, params).execute
+      let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
+      let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3, p3_project]) }
+      let!(:closed_issue3) { create(:issue, :closed, project: project1) }
+      let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1, p1_project1]) }
+      let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development]) }
 
-        expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2]
+      before do
+        group.add_developer(user)
       end
-    end
 
-    context 'with list that does not belong to the board' do
-      it 'raises an error' do
-        list = create(:list)
-        service = described_class.new(project, user, board_id: board.id, id: list.id)
+      context 'and group has no parent' do
+        let(:parent) { group }
+        let(:group) { create(:group) }
+        let(:board) { create(:board, group: group) }
 
-        expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
+        it_behaves_like 'issues list service'
       end
-    end
 
-    context 'with invalid list id' do
-      it 'raises an error' do
-        service = described_class.new(project, user, board_id: board.id, id: nil)
+      context 'and group is an ancestor', :nested_groups do
+        let(:parent) { create(:group) }
+        let(:group) { create(:group, parent: parent) }
+        let!(:backlog) { create(:backlog_list, board: board) }
+        let(:board) { create(:board, group: parent) }
+
+        before do
+          parent.add_developer(user)
+        end
 
-        expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
+        it_behaves_like 'issues list service'
       end
     end
   end
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 280e411683e97cba78d8d09fac82b87e2b94537f..dd0ad5f11bd5b0ac66fe44fa43d22ca1fd4a7288 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -2,108 +2,53 @@ require 'spec_helper'
 
 describe Boards::Issues::MoveService do
   describe '#execute' do
-    let(:user)    { create(:user) }
-    let(:project) { create(:project) }
-    let(:board1)  { create(:board, project: project) }
-
-    let(:bug) { create(:label, project: project, name: 'Bug') }
-    let(:development) { create(:label, project: project, name: 'Development') }
-    let(:testing)  { create(:label, project: project, name: 'Testing') }
-
-    let!(:list1)   { create(:list, board: board1, label: development, position: 0) }
-    let!(:list2)   { create(:list, board: board1, label: testing, position: 1) }
-    let!(:closed)  { create(:closed_list, board: board1) }
-
-    before do
-      project.add_developer(user)
-    end
-
-    context 'when moving an issue between lists' do
-      let(:issue)  { create(:labeled_issue, project: project, labels: [bug, development]) }
-      let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
-
-      it 'delegates the label changes to Issues::UpdateService' do
-        expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once
-
-        described_class.new(project, user, params).execute(issue)
-      end
-
-      it 'removes the label from the list it came from and adds the label of the list it goes to' do
-        described_class.new(project, user, params).execute(issue)
-
-        expect(issue.reload.labels).to contain_exactly(bug, testing)
-      end
-    end
-
-    context 'when moving to closed' do
+    context 'when parent is a project' do
+      let(:user) { create(:user) }
+      let(:project) { create(:project) }
+      let(:board1) { create(:board, project: project) }
       let(:board2) { create(:board, project: project) }
+
+      let(:bug) { create(:label, project: project, name: 'Bug') }
+      let(:development) { create(:label, project: project, name: 'Development') }
+      let(:testing)  { create(:label, project: project, name: 'Testing') }
       let(:regression) { create(:label, project: project, name: 'Regression') }
-      let!(:list3) { create(:list, board: board2, label: regression, position: 1) }
 
-      let(:issue)  { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
-      let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
+      let!(:list1)   { create(:list, board: board1, label: development, position: 0) }
+      let!(:list2)   { create(:list, board: board1, label: testing, position: 1) }
+      let!(:closed)  { create(:closed_list, board: board1) }
 
-      it 'delegates the close proceedings to Issues::CloseService' do
-        expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
+      let(:parent) { project }
 
-        described_class.new(project, user, params).execute(issue)
+      before do
+        parent.add_developer(user)
       end
 
-      it 'removes all list-labels from project boards and close the issue' do
-        described_class.new(project, user, params).execute(issue)
-        issue.reload
-
-        expect(issue.labels).to contain_exactly(bug)
-        expect(issue).to be_closed
-      end
+      it_behaves_like 'issues move service'
     end
 
-    context 'when moving from closed' do
-      let(:issue)  { create(:labeled_issue, :closed, project: project, labels: [bug]) }
-      let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
-
-      it 'delegates the re-open proceedings to Issues::ReopenService' do
-        expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
-
-        described_class.new(project, user, params).execute(issue)
-      end
-
-      it 'adds the label of the list it goes to and reopen the issue' do
-        described_class.new(project, user, params).execute(issue)
-        issue.reload
-
-        expect(issue.labels).to contain_exactly(bug, testing)
-        expect(issue).to be_opened
-      end
-    end
+    context 'when parent is a group' do
+      let(:user) { create(:user) }
+      let(:group) { create(:group) }
+      let(:project) { create(:project, namespace: group) }
+      let(:board1) { create(:board, group: group) }
+      let(:board2) { create(:board, group: group) }
 
-    context 'when moving to same list' do
-      let(:issue)   { create(:labeled_issue, project: project, labels: [bug, development]) }
-      let(:issue1)  { create(:labeled_issue, project: project, labels: [bug, development]) }
-      let(:issue2)  { create(:labeled_issue, project: project, labels: [bug, development]) }
-      let(:params)  { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
+      let(:bug) { create(:group_label, group: group, name: 'Bug') }
+      let(:development) { create(:group_label, group: group, name: 'Development') }
+      let(:testing)  { create(:group_label, group: group, name: 'Testing') }
+      let(:regression) { create(:group_label, group: group, name: 'Regression') }
 
-      it 'returns false' do
-        expect(described_class.new(project, user, params).execute(issue)).to eq false
-      end
+      let!(:list1)   { create(:list, board: board1, label: development, position: 0) }
+      let!(:list2)   { create(:list, board: board1, label: testing, position: 1) }
+      let!(:closed)  { create(:closed_list, board: board1) }
 
-      it 'keeps issues labels' do
-        described_class.new(project, user, params).execute(issue)
+      let(:parent) { group }
 
-        expect(issue.reload.labels).to contain_exactly(bug, development)
+      before do
+        parent.add_developer(user)
       end
 
-      it 'sorts issues' do
-        [issue, issue1, issue2].each do |issue|
-          issue.move_to_end && issue.save!
-        end
-
-        params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
-
-        described_class.new(project, user, params).execute(issue)
-
-        expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
-      end
+      it_behaves_like 'issues move service', true
     end
   end
 end
diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb
index 1d0be99fb35239417196f28dc1c8ff0741fe6fc8..7518e9e9b75afc9dcd484caf2300e0afbe1b5b63 100644
--- a/spec/services/boards/list_service_spec.rb
+++ b/spec/services/boards/list_service_spec.rb
@@ -2,36 +2,20 @@ require 'spec_helper'
 
 describe Boards::ListService do
   describe '#execute' do
-    let(:project) { create(:project) }
+    context 'when board parent is a project' do
+      let(:parent) { create(:project) }
 
-    subject(:service) { described_class.new(project, double) }
+      subject(:service) { described_class.new(parent, double) }
 
-    context 'when project does not have a board' do
-      it 'creates a new project board' do
-        expect { service.execute }.to change(project.boards, :count).by(1)
-      end
-
-      it 'delegates the project board creation to Boards::CreateService' do
-        expect_any_instance_of(Boards::CreateService).to receive(:execute).once
-
-        service.execute
-      end
+      it_behaves_like 'boards list service'
     end
 
-    context 'when project has a board' do
-      before do
-        create(:board, project: project)
-      end
-
-      it 'does not create a new board' do
-        expect { service.execute }.not_to change(project.boards, :count)
-      end
-    end
+    context 'when board parent is a group' do
+      let(:parent) { create(:group) }
 
-    it 'returns project boards' do
-      board = create(:board, project: project)
+      subject(:service) { described_class.new(parent, double) }
 
-      expect(service.execute).to match_array [board]
+      it_behaves_like 'boards list service'
     end
   end
 end
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index d5322e1bb2140131ba67ab5df87baf2c1544e726..7d3f5f86deba56ac6f9b6062c44991b1be9267e0 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -2,62 +2,77 @@ require 'spec_helper'
 
 describe Boards::Lists::CreateService do
   describe '#execute' do
-    let(:project) { create(:project) }
-    let(:board)   { create(:board, project: project) }
-    let(:user)    { create(:user) }
-    let(:label)   { create(:label, project: project, name: 'in-progress') }
+    shared_examples 'creating board lists' do
+      let(:user)    { create(:user) }
 
-    subject(:service) { described_class.new(project, user, label_id: label.id) }
+      subject(:service) { described_class.new(parent, user, label_id: label.id) }
 
-    before do
-      project.add_developer(user)
-    end
+      before do
+        parent.add_developer(user)
+      end
 
-    context 'when board lists is empty' do
-      it 'creates a new list at beginning of the list' do
-        list = service.execute(board)
+      context 'when board lists is empty' do
+        it 'creates a new list at beginning of the list' do
+          list = service.execute(board)
 
-        expect(list.position).to eq 0
+          expect(list.position).to eq 0
+        end
       end
-    end
 
-    context 'when board lists has the done list' do
-      it 'creates a new list at beginning of the list' do
-        list = service.execute(board)
+      context 'when board lists has the done list' do
+        it 'creates a new list at beginning of the list' do
+          list = service.execute(board)
 
-        expect(list.position).to eq 0
+          expect(list.position).to eq 0
+        end
       end
-    end
 
-    context 'when board lists has labels lists' do
-      it 'creates a new list at end of the lists' do
-        create(:list, board: board, position: 0)
-        create(:list, board: board, position: 1)
+      context 'when board lists has labels lists' do
+        it 'creates a new list at end of the lists' do
+          create(:list, board: board, position: 0)
+          create(:list, board: board, position: 1)
 
-        list = service.execute(board)
+          list = service.execute(board)
 
-        expect(list.position).to eq 2
+          expect(list.position).to eq 2
+        end
       end
-    end
 
-    context 'when board lists has label and done lists' do
-      it 'creates a new list at end of the label lists' do
-        list1 = create(:list, board: board, position: 0)
+      context 'when board lists has label and done lists' do
+        it 'creates a new list at end of the label lists' do
+          list1 = create(:list, board: board, position: 0)
 
-        list2 = service.execute(board)
+          list2 = service.execute(board)
 
-        expect(list1.reload.position).to eq 0
-        expect(list2.reload.position).to eq 1
+          expect(list1.reload.position).to eq 0
+          expect(list2.reload.position).to eq 1
+        end
       end
-    end
 
-    context 'when provided label does not belongs to the project' do
-      it 'raises an error' do
-        label = create(:label, name: 'in-development')
-        service = described_class.new(project, user, label_id: label.id)
+      context 'when provided label does not belongs to the parent' do
+        it 'raises an error' do
+          label = create(:label, name: 'in-development')
+          service = described_class.new(parent, user, label_id: label.id)
 
-        expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound)
+          expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound)
+        end
       end
     end
+
+    context 'when board parent is a project' do
+      let(:parent) { create(:project) }
+      let(:board) { create(:board, project: parent) }
+      let(:label) { create(:label, project: parent, name: 'in-progress') }
+
+      it_behaves_like 'creating board lists'
+    end
+
+    context 'when board parent is a group' do
+      let(:parent) { create(:group) }
+      let(:board) { create(:board, group: parent) }
+      let(:label) { create(:group_label, group: parent, name: 'in-progress') }
+
+      it_behaves_like 'creating board lists'
+    end
   end
 end
diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb
index bd98625b44f885c75263ece90fd5f6af0abd263d..3c4eb6b3fc52bbd5dea7e48ffce76e7d37486e82 100644
--- a/spec/services/boards/lists/destroy_service_spec.rb
+++ b/spec/services/boards/lists/destroy_service_spec.rb
@@ -2,37 +2,24 @@ require 'spec_helper'
 
 describe Boards::Lists::DestroyService do
   describe '#execute' do
-    let(:project) { create(:project) }
-    let(:board)   { create(:board, project: project) }
-    let(:user)    { create(:user) }
+    context 'when board parent is a project' do
+      let(:project) { create(:project) }
+      let(:board)   { create(:board, project: project) }
+      let(:user)    { create(:user) }
 
-    context 'when list type is label' do
-      it 'removes list from board' do
-        list = create(:list, board: board)
-        service = described_class.new(project, user)
+      let(:parent) { project }
 
-        expect { service.execute(list) }.to change(board.lists, :count).by(-1)
-      end
-
-      it 'decrements position of higher lists' do
-        development = create(:list, board: board, position: 0)
-        review      = create(:list, board: board, position: 1)
-        staging     = create(:list, board: board, position: 2)
-        closed      = board.closed_list
-
-        described_class.new(project, user).execute(development)
-
-        expect(review.reload.position).to eq 0
-        expect(staging.reload.position).to eq 1
-        expect(closed.reload.position).to be_nil
-      end
+      it_behaves_like 'lists destroy service'
     end
 
-    it 'does not remove list from board when list type is closed' do
-      list = board.closed_list
-      service = described_class.new(project, user)
+    context 'when board parent is a group' do
+      let(:group) { create(:group) }
+      let(:board)   { create(:board, group: group) }
+      let(:user)    { create(:user) }
+
+      let(:parent) { group }
 
-      expect { service.execute(list) }.not_to change(board.lists, :count)
+      it_behaves_like 'lists destroy service'
     end
   end
 end
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index b189857e4f465746f1760fcf02dcb212bcbfe32f..24e04eed642805964f9cae449f26bbae651a43b1 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -1,33 +1,25 @@
 require 'spec_helper'
 
 describe Boards::Lists::ListService do
-  let(:project) { create(:project) }
-  let(:board) { create(:board, project: project) }
-  let(:label) { create(:label, project: project) }
-  let!(:list) { create(:list, board: board, label: label) }
-  let(:service) { described_class.new(project, double) }
-
   describe '#execute' do
-    context 'when the board has a backlog list' do
-      let!(:backlog_list) { create(:backlog_list, board: board) }
-
-      it 'does not create a backlog list' do
-        expect { service.execute(board) }.not_to change(board.lists, :count)
-      end
+    context 'when board parent is a project' do
+      let(:project) { create(:project) }
+      let(:board) { create(:board, project: project) }
+      let(:label) { create(:label, project: project) }
+      let!(:list) { create(:list, board: board, label: label) }
+      let(:service) { described_class.new(project, double) }
 
-      it "returns board's lists" do
-        expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
-      end
+      it_behaves_like 'lists list service'
     end
 
-    context 'when the board does not have a backlog list' do
-      it 'creates a backlog list' do
-        expect { service.execute(board) }.to change(board.lists, :count).by(1)
-      end
+    context 'when board parent is a group' do
+      let(:group) { create(:group) }
+      let(:board) { create(:board, group: group) }
+      let(:label) { create(:group_label, group: group) }
+      let!(:list) { create(:list, board: board, label: label) }
+      let(:service) { described_class.new(group, double) }
 
-      it "returns board's lists" do
-        expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
-      end
+      it_behaves_like 'lists list service'
     end
   end
 end
diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb
index a9d218bad495d2d38bc2f0a90e39ae27d28a3ceb..16dfb2ae6af94b8cb0e70d83c1a5498b76df1f21 100644
--- a/spec/services/boards/lists/move_service_spec.rb
+++ b/spec/services/boards/lists/move_service_spec.rb
@@ -2,100 +2,24 @@ require 'spec_helper'
 
 describe Boards::Lists::MoveService do
   describe '#execute' do
-    let(:project) { create(:project) }
-    let(:board)   { create(:board, project: project) }
-    let(:user)    { create(:user) }
+    context 'when board parent is a project' do
+      let(:project) { create(:project) }
+      let(:board)   { create(:board, project: project) }
+      let(:user)    { create(:user) }
 
-    let!(:planning)    { create(:list, board: board, position: 0) }
-    let!(:development) { create(:list, board: board, position: 1) }
-    let!(:review)      { create(:list, board: board, position: 2) }
-    let!(:staging)     { create(:list, board: board, position: 3) }
-    let!(:closed)      { create(:closed_list, board: board) }
+      let(:parent) { project }
 
-    context 'when list type is set to label' do
-      it 'keeps position of lists when new position is nil' do
-        service = described_class.new(project, user, position: nil)
-
-        service.execute(planning)
-
-        expect(current_list_positions).to eq [0, 1, 2, 3]
-      end
-
-      it 'keeps position of lists when new positon is equal to old position' do
-        service = described_class.new(project, user, position: planning.position)
-
-        service.execute(planning)
-
-        expect(current_list_positions).to eq [0, 1, 2, 3]
-      end
-
-      it 'keeps position of lists when new positon is negative' do
-        service = described_class.new(project, user, position: -1)
-
-        service.execute(planning)
-
-        expect(current_list_positions).to eq [0, 1, 2, 3]
-      end
-
-      it 'keeps position of lists when new positon is equal to number of labels lists' do
-        service = described_class.new(project, user, position: board.lists.label.size)
-
-        service.execute(planning)
-
-        expect(current_list_positions).to eq [0, 1, 2, 3]
-      end
-
-      it 'keeps position of lists when new positon is greater than number of labels lists' do
-        service = described_class.new(project, user, position: board.lists.label.size + 1)
-
-        service.execute(planning)
-
-        expect(current_list_positions).to eq [0, 1, 2, 3]
-      end
-
-      it 'increments position of intermediate lists when new positon is equal to first position' do
-        service = described_class.new(project, user, position: 0)
-
-        service.execute(staging)
-
-        expect(current_list_positions).to eq [1, 2, 3, 0]
-      end
-
-      it 'decrements position of intermediate lists when new positon is equal to last position' do
-        service = described_class.new(project, user, position: board.lists.label.last.position)
-
-        service.execute(planning)
-
-        expect(current_list_positions).to eq [3, 0, 1, 2]
-      end
-
-      it 'decrements position of intermediate lists when new position is greater than old position' do
-        service = described_class.new(project, user, position: 2)
-
-        service.execute(planning)
-
-        expect(current_list_positions).to eq [2, 0, 1, 3]
-      end
-
-      it 'increments position of intermediate lists when new position is lower than old position' do
-        service = described_class.new(project, user, position: 1)
-
-        service.execute(staging)
-
-        expect(current_list_positions).to eq [0, 2, 3, 1]
-      end
+      it_behaves_like 'lists move service'
     end
 
-    it 'keeps position of lists when list type is closed' do
-      service = described_class.new(project, user, position: 2)
+    context 'when board parent is a group' do
+      let(:group) { create(:group) }
+      let(:board)   { create(:board, group: group) }
+      let(:user)    { create(:user) }
 
-      service.execute(closed)
+      let(:parent) { group }
 
-      expect(current_list_positions).to eq [0, 1, 2, 3]
+      it_behaves_like 'lists move service'
     end
   end
-
-  def current_list_positions
-    [planning, development, review, staging].map { |list| list.reload.position }
-  end
 end
diff --git a/spec/services/ci/create_trace_artifact_service_spec.rb b/spec/services/ci/create_trace_artifact_service_spec.rb
deleted file mode 100644
index 8c5e8e438c73b4c723eec9c5193dc0e8801852a6..0000000000000000000000000000000000000000
--- a/spec/services/ci/create_trace_artifact_service_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'spec_helper'
-
-describe Ci::CreateTraceArtifactService do
-  describe '#execute' do
-    subject { described_class.new(nil, nil).execute(job) }
-
-    context 'when the job does not have trace artifact' do
-      context 'when the job has a trace file' do
-        let!(:job) { create(:ci_build, :trace_live) }
-        let!(:legacy_path) { job.trace.read { |stream| return stream.path } }
-        let!(:legacy_checksum) { Digest::SHA256.file(legacy_path).hexdigest }
-        let(:new_path) { job.job_artifacts_trace.file.path }
-        let(:new_checksum) { Digest::SHA256.file(new_path).hexdigest }
-
-        it { expect(File.exist?(legacy_path)).to be_truthy }
-
-        it 'creates trace artifact' do
-          expect { subject }.to change { Ci::JobArtifact.count }.by(1)
-
-          expect(File.exist?(legacy_path)).to be_falsy
-          expect(File.exist?(new_path)).to be_truthy
-          expect(new_checksum).to eq(legacy_checksum)
-          expect(job.job_artifacts_trace.file.exists?).to be_truthy
-          expect(job.job_artifacts_trace.file.filename).to eq('job.log')
-        end
-
-        context 'when failed to create trace artifact record' do
-          before do
-            # When ActiveRecord error happens
-            allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false)
-            allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages)
-              .and_return("Error")
-
-            subject rescue nil
-
-            job.reload
-          end
-
-          it 'keeps legacy trace and removes trace artifact' do
-            expect(File.exist?(legacy_path)).to be_truthy
-            expect(job.job_artifacts_trace).to be_nil
-          end
-        end
-      end
-
-      context 'when the job does not have a trace file' do
-        let!(:job) { create(:ci_build) }
-
-        it 'does not create trace artifact' do
-          expect { subject }.not_to change { Ci::JobArtifact.count }
-        end
-      end
-    end
-
-    context 'when the job has already had trace artifact' do
-      let!(:job) { create(:ci_build, :trace_artifact) }
-
-      it 'does not create trace artifact' do
-        expect { subject }.not_to change { Ci::JobArtifact.count }
-      end
-    end
-  end
-end
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 0ce41e7c7eeac2829a0b6971677914bf0300db0c..feb5120bc68c56f1725cde00dba6fdcff9d82d04 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -9,6 +9,8 @@ describe Ci::ProcessPipelineService, '#execute' do
   end
 
   before do
+    stub_ci_pipeline_to_return_yaml_file
+
     stub_not_protect_default_branch
 
     project.add_developer(user)
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 97a563c1ce11e941bd970d9d5fbc4d2da1435638..8a537e83d5f2ad7cc53149b574f24dca3dca5fa6 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -370,10 +370,111 @@ module Ci
           it_behaves_like 'validation is not active'
         end
       end
+    end
+
+    describe '#register_success' do
+      let!(:current_time) { Time.new(2018, 4, 5, 14, 0, 0) }
+      let!(:attempt_counter) { double('Gitlab::Metrics::NullMetric') }
+      let!(:job_queue_duration_seconds) { double('Gitlab::Metrics::NullMetric') }
+
+      before do
+        allow(Time).to receive(:now).and_return(current_time)
+
+        # Stub defaults for any metrics other than the ones we're testing
+        allow(Gitlab::Metrics).to receive(:counter)
+                                    .with(any_args)
+                                    .and_return(Gitlab::Metrics::NullMetric.instance)
+        allow(Gitlab::Metrics).to receive(:histogram)
+                                    .with(any_args)
+                                    .and_return(Gitlab::Metrics::NullMetric.instance)
+
+        # Stub tested metrics
+        allow(Gitlab::Metrics).to receive(:counter)
+                                    .with(:job_register_attempts_total, anything)
+                                    .and_return(attempt_counter)
+        allow(Gitlab::Metrics).to receive(:histogram)
+                                    .with(:job_queue_duration_seconds, anything, anything, anything)
+                                    .and_return(job_queue_duration_seconds)
+
+        project.update(shared_runners_enabled: true)
+        pending_job.update(created_at: current_time - 3600, queued_at: current_time - 1800)
+      end
+
+      shared_examples 'attempt counter collector' do
+        it 'increments attempt counter' do
+          allow(job_queue_duration_seconds).to receive(:observe)
+          expect(attempt_counter).to receive(:increment)
+
+          execute(runner)
+        end
+      end
+
+      shared_examples 'jobs queueing time histogram collector' do
+        it 'counts job queuing time histogram with expected labels' do
+          allow(attempt_counter).to receive(:increment)
+          expect(job_queue_duration_seconds).to receive(:observe)
+            .with({ shared_runner: expected_shared_runner,
+                    jobs_running_for_project: expected_jobs_running_for_project_first_job }, 1800)
+
+          execute(runner)
+        end
+
+        context 'when project already has running jobs' do
+          let!(:build2) { create( :ci_build, :running, pipeline: pipeline, runner: shared_runner) }
+          let!(:build3) { create( :ci_build, :running, pipeline: pipeline, runner: shared_runner) }
+
+          it 'counts job queuing time histogram with expected labels' do
+            allow(attempt_counter).to receive(:increment)
+            expect(job_queue_duration_seconds).to receive(:observe)
+              .with({ shared_runner: expected_shared_runner,
+                      jobs_running_for_project: expected_jobs_running_for_project_third_job }, 1800)
+
+            execute(runner)
+          end
+        end
+      end
 
-      def execute(runner)
-        described_class.new(runner).execute.build
+      shared_examples 'metrics collector' do
+        it_behaves_like 'attempt counter collector'
+        it_behaves_like 'jobs queueing time histogram collector'
       end
+
+      context 'when shared runner is used' do
+        let(:runner) { shared_runner }
+        let(:expected_shared_runner) { true }
+        let(:expected_jobs_running_for_project_first_job) { 0 }
+        let(:expected_jobs_running_for_project_third_job) { 2 }
+
+        it_behaves_like 'metrics collector'
+
+        context 'when pending job with queued_at=nil is used' do
+          before do
+            pending_job.update(queued_at: nil)
+          end
+
+          it_behaves_like 'attempt counter collector'
+
+          it "doesn't count job queuing time histogram" do
+            allow(attempt_counter).to receive(:increment)
+            expect(job_queue_duration_seconds).not_to receive(:observe)
+
+            execute(runner)
+          end
+        end
+      end
+
+      context 'when specific runner is used' do
+        let(:runner) { specific_runner }
+        let(:expected_shared_runner) { false }
+        let(:expected_jobs_running_for_project_first_job) { '+Inf' }
+        let(:expected_jobs_running_for_project_third_job) { '+Inf' }
+
+        it_behaves_like 'metrics collector'
+      end
+    end
+
+    def execute(runner)
+      described_class.new(runner).execute.build
     end
   end
 end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index db9c216d3f40886a6857638c437e18de39dafcc4..8de0bdf92e2877bacb98a8911c50e82a312faeb1 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -28,7 +28,9 @@ describe Ci::RetryBuildService do
     %i[type lock_version target_url base_tags trace_sections
        commit_id deployments erased_by_id last_deployment project_id
        runner_id tag_taggings taggings tags trigger_request_id
-       user_id auto_canceled_by_id retried failure_reason].freeze
+       user_id auto_canceled_by_id retried failure_reason
+       artifacts_file_store artifacts_metadata_store
+       metadata].freeze
 
   shared_examples 'build duplication' do
     let(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index ad175226e92b472f85b162f28e27627a8452692c..93199964a0ea1f8a85ed4cd62a48dfe9e13aa93d 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -34,7 +34,7 @@ describe Clusters::Applications::InstallService do
 
     context 'when k8s cluster communication fails' do
       before do
-        error = KubeException.new(500, 'system failure', nil)
+        error = Kubeclient::HttpError.new(500, 'system failure', nil)
         expect(helm_client).to receive(:install).with(install_command).and_raise(error)
       end
 
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index e2e64659dfac6642f459d98a722b0d9b6ec86cd9..1c2f9c5cf43a7a45c486a613f4a1e7e750efb0af 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -82,7 +82,7 @@ describe Clusters::CreateService do
 
     context 'when project has a cluster' do
       include_context 'valid params'
-      let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+      let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
 
       it 'does not create a cluster' do
         expect(ClusterProvisionWorker).not_to receive(:perform_async)
diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3a2bbf1ecd19be81f1a487d708276da5b9dc9f3c
--- /dev/null
+++ b/spec/services/deploy_tokens/create_service_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe DeployTokens::CreateService do
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+  let(:deploy_token_params) { attributes_for(:deploy_token) }
+
+  describe '#execute' do
+    subject { described_class.new(project, user, deploy_token_params).execute }
+
+    context 'when the deploy token is valid' do
+      it 'should create a new DeployToken' do
+        expect { subject }.to change { DeployToken.count }.by(1)
+      end
+
+      it 'should create a new ProjectDeployToken' do
+        expect { subject }.to change { ProjectDeployToken.count }.by(1)
+      end
+
+      it 'returns a DeployToken' do
+        expect(subject).to be_an_instance_of DeployToken
+      end
+    end
+
+    context 'when expires at date is not passed' do
+      let(:deploy_token_params) { attributes_for(:deploy_token, expires_at: '') }
+
+      it 'should set Forever.date' do
+        expect(subject.read_attribute(:expires_at)).to eq(Forever.date)
+      end
+    end
+
+    context 'when the deploy token is invalid' do
+      let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) }
+
+      it 'should not create a new DeployToken' do
+        expect { subject }.not_to change { DeployToken.count }
+      end
+
+      it 'should not create a new ProjectDeployToken' do
+        expect { subject }.not_to change { ProjectDeployToken.count }
+      end
+    end
+  end
+end
diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb
index b4a4a44d07b06e12a2f7cd2627393e7dd5ada9a4..075cb45e46c82edc50a74d420e7c57c2c7753a7f 100644
--- a/spec/services/events/render_service_spec.rb
+++ b/spec/services/events/render_service_spec.rb
@@ -9,9 +9,7 @@ describe Events::RenderService do
     context 'when the request format is atom' do
       it 'renders the note inside events' do
         expect(Banzai::ObjectRenderer).to receive(:new)
-          .with(event.project, user,
-                only_path: false,
-                xhtml: true)
+          .with(user: user, redaction_context: { only_path: false, xhtml: true })
           .and_call_original
 
         expect_any_instance_of(Banzai::ObjectRenderer)
@@ -24,7 +22,7 @@ describe Events::RenderService do
     context 'when the request format is not atom' do
       it 'renders the note inside events' do
         expect(Banzai::ObjectRenderer).to receive(:new)
-          .with(event.project, user, {})
+          .with(user: user, redaction_context: {})
           .and_call_original
 
         expect_any_instance_of(Banzai::ObjectRenderer)
diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb
index 030263b1502763277e39f13828f725633c4e2cff..abe99b9e794cdb6c4180c3420060f64d23e8dfbc 100644
--- a/spec/services/files/create_service_spec.rb
+++ b/spec/services/files/create_service_spec.rb
@@ -43,7 +43,7 @@ describe Files::CreateService do
 
           blob = repository.blob_at('lfs', file_path)
 
-          expect(blob.data).not_to start_with('version https://git-lfs.github.com/spec/v1')
+          expect(blob.data).not_to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
           expect(blob.data).to eq(file_content)
         end
       end
@@ -58,7 +58,7 @@ describe Files::CreateService do
 
           blob = repository.blob_at('lfs', file_path)
 
-          expect(blob.data).to start_with('version https://git-lfs.github.com/spec/v1')
+          expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
         end
 
         it "creates an LfsObject with the file's content" do
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index b9971776b33f3fb0de415b97ca93a0cdf6489e2d..59984c10990225fa495466956d4eb2f938b52447 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -4,28 +4,30 @@ describe Files::MultiService do
   subject { described_class.new(project, user, commit_params) }
 
   let(:project) { create(:project, :repository) }
+  let(:repository) { project.repository }
   let(:user) { create(:user) }
   let(:branch_name) { project.default_branch }
   let(:original_file_path) { 'files/ruby/popen.rb' }
   let(:new_file_path) { 'files/ruby/popen.rb' }
+  let(:file_content) { 'New content' }
   let(:action) { 'update' }
 
   let!(:original_commit_id) do
     Gitlab::Git::Commit.last_for_path(project.repository, branch_name, original_file_path).sha
   end
 
-  let(:actions) do
-    [
-      {
-        action: action,
-        file_path: new_file_path,
-        previous_path: original_file_path,
-        content: 'New content',
-        last_commit_id: original_commit_id
-      }
-    ]
+  let(:default_action) do
+    {
+      action: action,
+      file_path: new_file_path,
+      previous_path: original_file_path,
+      content: file_content,
+      last_commit_id: original_commit_id
+    }
   end
 
+  let(:actions) { [default_action] }
+
   let(:commit_params) do
     {
       commit_message: "Update File",
@@ -110,6 +112,56 @@ describe Files::MultiService do
       end
     end
 
+    context 'when creating a file matching an LFS filter' do
+      let(:action) { 'create' }
+      let(:branch_name) { 'lfs' }
+      let(:new_file_path) { 'test_file.lfs' }
+
+      before do
+        allow(project).to receive(:lfs_enabled?).and_return(true)
+      end
+
+      it 'creates an LFS pointer' do
+        subject.execute
+
+        blob = repository.blob_at('lfs', new_file_path)
+
+        expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+      end
+
+      it "creates an LfsObject with the file's content" do
+        subject.execute
+
+        expect(LfsObject.last.file.read).to eq file_content
+      end
+
+      context 'with base64 encoded content' do
+        let(:raw_file_content) { 'Raw content' }
+        let(:file_content) { Base64.encode64(raw_file_content) }
+        let(:actions) { [default_action.merge(encoding: 'base64')] }
+
+        it 'creates an LFS pointer' do
+          subject.execute
+
+          blob = repository.blob_at('lfs', new_file_path)
+
+          expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+        end
+
+        it "creates an LfsObject with the file's content" do
+          subject.execute
+
+          expect(LfsObject.last.file.read).to eq raw_file_content
+        end
+      end
+
+      it 'links the LfsObject to the project' do
+        expect do
+          subject.execute
+        end.to change { project.lfs_objects.count }.by(1)
+      end
+    end
+
     context 'when file status validation is skipped' do
       let(:action) { 'create' }
       let(:new_file_path) { 'files/ruby/new_file.rb' }
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index e1c873f8c1eb2571cf483173ad501a3f96315904..999677cfaaa6592c1e1e4aacd56368408868ca0f 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -222,8 +222,8 @@ describe Groups::TransferService, :postgresql do
           expect(new_parent_group.children.first).to eq(group)
         end
 
-        it 'should create a permanent redirect for the group' do
-          expect(group.redirect_routes.permanent.count).to eq(1)
+        it 'should create a redirect for the group' do
+          expect(group.redirect_routes.count).to eq(1)
         end
       end
 
@@ -243,10 +243,10 @@ describe Groups::TransferService, :postgresql do
           end
         end
 
-        it 'should create permanent redirects for the subgroups' do
-          expect(group.redirect_routes.permanent.count).to eq(1)
-          expect(subgroup1.redirect_routes.permanent.count).to eq(1)
-          expect(subgroup2.redirect_routes.permanent.count).to eq(1)
+        it 'should create redirects for the subgroups' do
+          expect(group.redirect_routes.count).to eq(1)
+          expect(subgroup1.redirect_routes.count).to eq(1)
+          expect(subgroup2.redirect_routes.count).to eq(1)
         end
 
         context 'when the new parent has a higher visibility than the children' do
@@ -287,9 +287,9 @@ describe Groups::TransferService, :postgresql do
         end
 
         it 'should create permanent redirects for the projects' do
-          expect(group.redirect_routes.permanent.count).to eq(1)
-          expect(project1.redirect_routes.permanent.count).to eq(1)
-          expect(project2.redirect_routes.permanent.count).to eq(1)
+          expect(group.redirect_routes.count).to eq(1)
+          expect(project1.redirect_routes.count).to eq(1)
+          expect(project2.redirect_routes.count).to eq(1)
         end
 
         context 'when the new parent has a higher visibility than the projects' do
@@ -338,12 +338,12 @@ describe Groups::TransferService, :postgresql do
           end
         end
 
-        it 'should create permanent redirect for the subgroups and projects' do
-          expect(group.redirect_routes.permanent.count).to eq(1)
-          expect(subgroup1.redirect_routes.permanent.count).to eq(1)
-          expect(subgroup2.redirect_routes.permanent.count).to eq(1)
-          expect(project1.redirect_routes.permanent.count).to eq(1)
-          expect(project2.redirect_routes.permanent.count).to eq(1)
+        it 'should create redirect for the subgroups and projects' do
+          expect(group.redirect_routes.count).to eq(1)
+          expect(subgroup1.redirect_routes.count).to eq(1)
+          expect(subgroup2.redirect_routes.count).to eq(1)
+          expect(project1.redirect_routes.count).to eq(1)
+          expect(project2.redirect_routes.count).to eq(1)
         end
       end
 
@@ -380,12 +380,12 @@ describe Groups::TransferService, :postgresql do
           end
         end
 
-        it 'should create permanent redirect for the subgroups and projects' do
-          expect(group.redirect_routes.permanent.count).to eq(1)
-          expect(project1.redirect_routes.permanent.count).to eq(1)
-          expect(subgroup1.redirect_routes.permanent.count).to eq(1)
-          expect(nested_subgroup.redirect_routes.permanent.count).to eq(1)
-          expect(nested_project.redirect_routes.permanent.count).to eq(1)
+        it 'should create redirect for the subgroups and projects' do
+          expect(group.redirect_routes.count).to eq(1)
+          expect(project1.redirect_routes.count).to eq(1)
+          expect(subgroup1.redirect_routes.count).to eq(1)
+          expect(nested_subgroup.redirect_routes.count).to eq(1)
+          expect(nested_project.redirect_routes.count).to eq(1)
         end
       end
 
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index 0a3647a814f714f57a659a0f23d8c0aac8cdd062..8ccbba7fa58195bfc8e7036259c4cfb114db91c1 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -8,7 +8,7 @@ describe Issuable::DestroyService do
 
   describe '#execute' do
     context 'when issuable is an issue' do
-      let!(:issue) { create(:issue, project: project, author: user) }
+      let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
 
       it 'destroys the issue' do
         expect { service.execute(issue) }.to change { project.issues.count }.by(-1)
@@ -26,10 +26,15 @@ describe Issuable::DestroyService do
         expect { service.execute(issue) }
           .to change { user.todos_pending_count }.from(1).to(0)
       end
+
+      it 'invalidates the issues count cache for the assignees' do
+        expect_any_instance_of(User).to receive(:invalidate_cache_counts).once
+        service.execute(issue)
+      end
     end
 
     context 'when issuable is a merge request' do
-      let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user) }
+      let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignee: user) }
 
       it 'destroys the merge request' do
         expect { service.execute(merge_request) }.to change { project.merge_requests.count }.by(-1)
@@ -41,6 +46,11 @@ describe Issuable::DestroyService do
         service.execute(merge_request)
       end
 
+      it 'invalidates the merge request caches for the MR assignee' do
+        expect_any_instance_of(User).to receive(:invalidate_cache_counts).once
+        service.execute(merge_request)
+      end
+
       it 'updates the todo caches for users with todos on the merge request' do
         create(:todo, target: merge_request, user: user, author: user, project: project)
 
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 47c1ebbeb812c1409664daf8ecb58dc9a4738b6f..7ae49c0689689bb989f9117310e8f2f63e53b839 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -67,6 +67,10 @@ describe Issues::CloseService do
         expect(issue).to be_closed
       end
 
+      it 'records closed user' do
+        expect(issue.closed_by_id).to be(user.id)
+      end
+
       it 'sends email to user2 about assign of new issue' do
         email = ActionMailer::Base.deliveries.last
         expect(email.to.first).to eq(user2.email)
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index c148a98569b2783ff748c064977c67317a006cea..a9aee9e100fa9757b50568de56091c7e013e7ed4 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -6,7 +6,7 @@ describe Issues::MoveService do
   let(:title) { 'Some issue' }
   let(:description) { 'Some issue description' }
   let(:old_project) { create(:project) }
-  let(:new_project) { create(:project, group: create(:group)) }
+  let(:new_project) { create(:project) }
   let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') }
 
   let(:old_issue) do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 41237dd71609cf24639d61f5dbc89813a7d256c4..23b1134b5a31cb66a205117942bfbc73d5d8b479 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -97,6 +97,39 @@ describe Issues::UpdateService, :mailer do
         expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
       end
 
+      context 'when moving issue between issues from different projects', :nested_groups do
+        let(:group) { create(:group) }
+        let(:subgroup) { create(:group, parent: group) }
+
+        let(:project_1) { create(:project, namespace: group) }
+        let(:project_2) { create(:project, namespace: group) }
+        let(:project_3) { create(:project, namespace: subgroup) }
+
+        let(:issue_1) { create(:issue, project: project_1) }
+        let(:issue_2) { create(:issue, project: project_2) }
+        let(:issue_3) { create(:issue, project: project_3) }
+
+        before do
+          group.add_developer(user)
+        end
+
+        it 'sorts issues as specified by parameters' do
+          # Moving all issues to end here like the last example won't work since
+          # all projects only have the same issue count
+          # so their relative_position will be the same.
+          issue_1.move_to_end
+          issue_2.move_after(issue_1)
+          issue_3.move_after(issue_2)
+          [issue_1, issue_2, issue_3].map(&:save)
+
+          opts[:move_between_ids] = [issue_1.id, issue_2.id]
+          opts[:board_group_id] = group.id
+
+          described_class.new(issue_3.project, user, opts).execute(issue_3)
+          expect(issue_2.relative_position).to be_between(issue_1.relative_position, issue_2.relative_position)
+        end
+      end
+
       context 'when current user cannot admin issues in the project' do
         let(:guest) { create(:user) }
         before do
diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb
index ae819c011decf23d70a4c490d7c2a33a455d94b3..80bac590a1188b76e2563ed03a3a52252f600efc 100644
--- a/spec/services/labels/transfer_service_spec.rb
+++ b/spec/services/labels/transfer_service_spec.rb
@@ -8,6 +8,7 @@ describe Labels::TransferService do
     let(:group_3) { create(:group) }
     let(:project_1) { create(:project, namespace: group_2) }
     let(:project_2) { create(:project, namespace: group_3) }
+    let(:project_3) { create(:project, namespace: group_1) }
 
     let(:group_label_1) { create(:group_label, group: group_1, name: 'Group Label 1') }
     let(:group_label_2) { create(:group_label, group: group_1, name: 'Group Label 2') }
@@ -23,6 +24,7 @@ describe Labels::TransferService do
       create(:labeled_issue, project: project_1, labels: [group_label_4])
       create(:labeled_issue, project: project_1, labels: [project_label_1])
       create(:labeled_issue, project: project_2, labels: [group_label_5])
+      create(:labeled_issue, project: project_3, labels: [group_label_1])
       create(:labeled_merge_request, source_project: project_1, labels: [group_label_1, group_label_2])
       create(:labeled_merge_request, source_project: project_2, labels: [group_label_5])
     end
@@ -52,5 +54,13 @@ describe Labels::TransferService do
 
       expect(project_1.labels.where(title: group_label_4.title)).to be_empty
     end
+
+    it 'updates only label links in the given project' do
+      service.execute
+
+      targets = LabelLink.where(label_id: group_label_1.id).map(&:target)
+
+      expect(targets).to eq(project_3.issues)
+    end
   end
 end
diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e8938338cb7cf0d1e6baeccd38f1f6a41cad6a89
--- /dev/null
+++ b/spec/services/lfs/file_transformer_spec.rb
@@ -0,0 +1,97 @@
+require "spec_helper"
+
+describe Lfs::FileTransformer do
+  let(:project) { create(:project, :repository) }
+  let(:repository) { project.repository }
+  let(:file_content) { 'Test file content' }
+  let(:branch_name) { 'lfs' }
+  let(:file_path) { 'test_file.lfs' }
+
+  subject { described_class.new(project, branch_name) }
+
+  describe '#new_file' do
+    context 'with lfs disabled' do
+      it 'skips gitattributes check' do
+        expect(repository.raw).not_to receive(:blob_at)
+
+        subject.new_file(file_path, file_content)
+      end
+
+      it 'returns untransformed content' do
+        result = subject.new_file(file_path, file_content)
+
+        expect(result.content).to eq(file_content)
+      end
+
+      it 'returns untransformed encoding' do
+        result = subject.new_file(file_path, file_content, encoding: 'base64')
+
+        expect(result.encoding).to eq('base64')
+      end
+    end
+
+    context 'with lfs enabled' do
+      before do
+        allow(project).to receive(:lfs_enabled?).and_return(true)
+      end
+
+      it 'reuses cached gitattributes' do
+        second_file = 'another_file.lfs'
+
+        expect(repository.raw).to receive(:blob_at).with(branch_name, '.gitattributes').once
+
+        subject.new_file(file_path, file_content)
+        subject.new_file(second_file, file_content)
+      end
+
+      it "creates an LfsObject with the file's content" do
+        subject.new_file(file_path, file_content)
+
+        expect(LfsObject.last.file.read).to eq file_content
+      end
+
+      it 'returns an LFS pointer' do
+        result = subject.new_file(file_path, file_content)
+
+        expect(result.content).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+      end
+
+      it 'returns LFS pointer encoding as text' do
+        result = subject.new_file(file_path, file_content, encoding: 'base64')
+
+        expect(result.encoding).to eq('text')
+      end
+
+      context "when doesn't use LFS" do
+        let(:file_path) { 'other.filetype' }
+
+        it "doesn't create LFS pointers" do
+          new_content = subject.new_file(file_path, file_content).content
+
+          expect(new_content).not_to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+          expect(new_content).to eq(file_content)
+        end
+      end
+
+      it 'links LfsObjects to project' do
+        expect do
+          subject.new_file(file_path, file_content)
+        end.to change { project.lfs_objects.count }.by(1)
+      end
+
+      context 'when LfsObject already exists' do
+        let(:lfs_pointer) { Gitlab::Git::LfsPointerFile.new(file_content) }
+
+        before do
+          create(:lfs_object, oid: lfs_pointer.sha256, size: lfs_pointer.size)
+        end
+
+        it 'links LfsObjects to project' do
+          expect do
+            subject.new_file(file_path, file_content)
+          end.to change { project.lfs_objects.count }.by(1)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 10c264a90c51a1cc547b922855e22f1ec8186880..36b6e5a701e73307fbbe78fd7853db3f2a789e38 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -19,32 +19,11 @@ describe Members::DestroyService do
     end
   end
 
-  def number_of_assigned_issuables(user)
-    Issue.assigned_to(user).count + MergeRequest.assigned_to(user).count
-  end
-
   shared_examples 'a service destroying a member' do
     it 'destroys the member' do
       expect { described_class.new(current_user).execute(member, opts) }.to change { member.source.members_and_requesters.count }.by(-1)
     end
 
-    it 'unassigns issues and merge requests' do
-      if member.invite?
-        expect { described_class.new(current_user).execute(member, opts) }
-          .not_to change { number_of_assigned_issuables(member_user) }
-      else
-        create :issue, assignees: [member_user]
-        issue = create :issue, project: group_project, assignees: [member_user]
-        merge_request = create :merge_request, target_project: group_project, source_project: group_project, assignee: member_user
-
-        expect { described_class.new(current_user).execute(member, opts) }
-          .to change { number_of_assigned_issuables(member_user) }.from(3).to(1)
-
-        expect(issue.reload.assignee_ids).to be_empty
-        expect(merge_request.reload.assignee_id).to be_nil
-      end
-    end
-
     it 'destroys member notification_settings' do
       if member_user.notification_settings.any?
         expect { described_class.new(current_user).execute(member, opts) }
@@ -56,6 +35,29 @@ describe Members::DestroyService do
     end
   end
 
+  shared_examples 'a service destroying a member with access' do
+    it_behaves_like 'a service destroying a member'
+
+    it 'invalidates cached counts for todos and assigned issues and merge requests', :aggregate_failures do
+      create(:issue, project: group_project, assignees: [member_user])
+      create(:merge_request, source_project: group_project, assignee: member_user)
+      create(:todo, :pending, project: group_project, user: member_user)
+      create(:todo, :done, project: group_project, user: member_user)
+
+      expect(member_user.assigned_open_merge_requests_count).to be(1)
+      expect(member_user.assigned_open_issues_count).to be(1)
+      expect(member_user.todos_pending_count).to be(1)
+      expect(member_user.todos_done_count).to be(1)
+
+      described_class.new(current_user).execute(member, opts)
+
+      expect(member_user.assigned_open_merge_requests_count).to be(0)
+      expect(member_user.assigned_open_issues_count).to be(0)
+      expect(member_user.todos_pending_count).to be(0)
+      expect(member_user.todos_done_count).to be(0)
+    end
+  end
+
   shared_examples 'a service destroying an access requester' do
     it_behaves_like 'a service destroying a member'
 
@@ -74,29 +76,39 @@ describe Members::DestroyService do
     end
   end
 
-  context 'with a member' do
+  context 'with a member with access' do
     before do
-      group_project.add_developer(member_user)
-      group.add_developer(member_user)
+      group_project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+      group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
     end
 
     context 'when current user cannot destroy the given member' do
-      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+      context 'with a project member' do
         let(:member) { group_project.members.find_by(user_id: member_user.id) }
-      end
 
-      it_behaves_like 'a service destroying a member' do
-        let(:opts) { { skip_authorization: true } }
-        let(:member) { group_project.members.find_by(user_id: member_user.id) }
-      end
+        before do
+          group_project.add_developer(member_user)
+        end
 
-      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
-        let(:member) { group.members.find_by(user_id: member_user.id) }
+        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+
+        it_behaves_like 'a service destroying a member with access' do
+          let(:opts) { { skip_authorization: true } }
+        end
       end
 
-      it_behaves_like 'a service destroying a member' do
-        let(:opts) { { skip_authorization: true } }
+      context 'with a group member' do
         let(:member) { group.members.find_by(user_id: member_user.id) }
+
+        before do
+          group.add_developer(member_user)
+        end
+
+        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+
+        it_behaves_like 'a service destroying a member with access' do
+          let(:opts) { { skip_authorization: true } }
+        end
       end
     end
 
@@ -106,12 +118,24 @@ describe Members::DestroyService do
         group.add_owner(current_user)
       end
 
-      it_behaves_like 'a service destroying a member' do
+      context 'with a project member' do
         let(:member) { group_project.members.find_by(user_id: member_user.id) }
+
+        before do
+          group_project.add_developer(member_user)
+        end
+
+        it_behaves_like 'a service destroying a member with access'
       end
 
-      it_behaves_like 'a service destroying a member' do
+      context 'with a group member' do
         let(:member) { group.members.find_by(user_id: member_user.id) }
+
+        before do
+          group.add_developer(member_user)
+        end
+
+        it_behaves_like 'a service destroying a member with access'
       end
     end
   end
diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb
index 6cadcd438c39cab09dbc368874003e76146cd67e..837b8a56d12bf7bf79c7c20acd117ca96488c255 100644
--- a/spec/services/merge_requests/conflicts/list_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/list_service_spec.rb
@@ -77,6 +77,14 @@ describe MergeRequests::Conflicts::ListService do
       expect(service.can_be_resolved_in_ui?).to be_falsey
     end
 
+    it 'returns a falsey value when the MR has a missing revision after a force push' do
+      merge_request = create_merge_request('conflict-resolvable')
+      service = conflicts_service(merge_request)
+      allow(merge_request).to receive_message_chain(:target_branch_head, :raw, :id).and_return(Gitlab::Git::BLANK_SHA)
+
+      expect(service.can_be_resolved_in_ui?).to be_falsey
+    end
+
     context 'with gitaly disabled', :skip_gitaly_mock do
       it 'returns a falsey value when the MR has a missing ref after a force push' do
         merge_request = create_merge_request('conflict-resolvable')
@@ -85,6 +93,14 @@ describe MergeRequests::Conflicts::ListService do
 
         expect(service.can_be_resolved_in_ui?).to be_falsey
       end
+
+      it 'returns a falsey value when the MR has a missing revision after a force push' do
+        merge_request = create_merge_request('conflict-resolvable')
+        service = conflicts_service(merge_request)
+        allow(merge_request).to receive_message_chain(:target_branch_head, :raw, :id).and_return(Gitlab::Git::BLANK_SHA)
+
+        expect(service.can_be_resolved_in_ui?).to be_falsey
+      end
     end
   end
 end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 5d226f34d2dcdba81ad1d63ddb94ce3541ddc9ef..736a50b2c15e4c3285c76a9ba82869f621770d43 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe MergeRequests::CreateService do
+  include ProjectForksHelper
+
   let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:assignee) { create(:user) }
@@ -28,6 +30,7 @@ describe MergeRequests::CreateService do
 
       it 'creates an MR' do
         expect(merge_request).to be_valid
+        expect(merge_request.work_in_progress?).to be(false)
         expect(merge_request.title).to eq('Awesome merge_request')
         expect(merge_request.assignee).to be_nil
         expect(merge_request.merge_params['force_remove_source_branch']).to eq('1')
@@ -62,6 +65,40 @@ describe MergeRequests::CreateService do
         expect(Event.where(attributes).count).to eq(1)
       end
 
+      describe 'when marked with /wip' do
+        context 'in title and in description' do
+          let(:opts) do
+            {
+              title: 'WIP: Awesome merge_request',
+              description: "well this is not done yet\n/wip",
+              source_branch: 'feature',
+              target_branch: 'master',
+              assignee: assignee
+            }
+          end
+
+          it 'sets MR to WIP' do
+            expect(merge_request.work_in_progress?).to be(true)
+          end
+        end
+
+        context 'in description only' do
+          let(:opts) do
+            {
+              title: 'Awesome merge_request',
+              description: "well this is not done yet\n/wip",
+              source_branch: 'feature',
+              target_branch: 'master',
+              assignee: assignee
+            }
+          end
+
+          it 'sets MR to WIP' do
+            expect(merge_request.work_in_progress?).to be(true)
+          end
+        end
+      end
+
       context 'when merge request is assigned to someone' do
         let(:opts) do
           {
@@ -265,7 +302,7 @@ describe MergeRequests::CreateService do
     end
 
     context 'when source and target projects are different' do
-      let(:target_project) { create(:project) }
+      let(:target_project) { fork_project(project, nil, repository: true) }
 
       let(:opts) do
         {
@@ -299,6 +336,26 @@ describe MergeRequests::CreateService do
             .to raise_error Gitlab::Access::AccessDeniedError
         end
       end
+
+      context 'when the user has access to both projects' do
+        before do
+          target_project.add_developer(user)
+          project.add_developer(user)
+        end
+
+        it 'creates the merge request' do
+          merge_request = described_class.new(project, user, opts).execute
+
+          expect(merge_request).to be_persisted
+        end
+
+        it 'does not create the merge request when the target project is archived' do
+          target_project.update!(archived: true)
+
+          expect { described_class.new(project, user, opts).execute }
+            .to raise_error Gitlab::Access::AccessDeniedError
+        end
+      end
     end
 
     context 'when user sets source project id' do
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
index bb46e1dd9abdc5abc1222037c10c51be0db6b687..57b6165cfb0a161dcf4fbc9927802d9b1f17a37e 100644
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -1,19 +1,39 @@
 require 'spec_helper'
 
-describe MergeRequests::MergeRequestDiffCacheService do
+describe MergeRequests::MergeRequestDiffCacheService, :use_clean_rails_memory_store_caching do
   let(:subject) { described_class.new }
+  let(:merge_request) { create(:merge_request) }
 
   describe '#execute' do
-    it 'retrieves the diff files to cache the highlighted result' do
-      merge_request = create(:merge_request)
-      cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options]
-
-      expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
-      expect(Rails.cache).to receive(:write).with(cache_key, anything)
+    before do
       allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true)
       allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true)
+    end
+
+    it 'retrieves the diff files to cache the highlighted result' do
+      new_diff = merge_request.merge_request_diff
+      cache_key = new_diff.diffs.cache_key
+
+      expect(Rails.cache).to receive(:read).with(cache_key).and_call_original
+      expect(Rails.cache).to receive(:write).with(cache_key, anything, anything).and_call_original
+
+      subject.execute(merge_request, new_diff)
+    end
+
+    it 'clears the cache for older diffs on the merge request' do
+      old_diff = merge_request.merge_request_diff
+      old_cache_key = old_diff.diffs.cache_key
+
+      subject.execute(merge_request, old_diff)
+
+      new_diff = merge_request.create_merge_request_diff
+      new_cache_key = new_diff.diffs.cache_key
+
+      expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
+      expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
+      expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
 
-      subject.execute(merge_request)
+      subject.execute(merge_request, new_diff)
     end
   end
 end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 903aa0a5078a420cd4ba568ffa8915dac38a8512..2536c6e2514214ef95135caf443f665785c48df9 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -24,6 +24,14 @@ describe MergeRequests::RefreshService do
                               merge_when_pipeline_succeeds: true,
                               merge_user: @user)
 
+      @another_merge_request = create(:merge_request,
+                                      source_project: @project,
+                                      source_branch: 'master',
+                                      target_branch: 'test',
+                                      target_project: @project,
+                                      merge_when_pipeline_succeeds: true,
+                                      merge_user: @user)
+
       @fork_merge_request = create(:merge_request,
                                    source_project: @fork_project,
                                    source_branch: 'master',
@@ -52,9 +60,11 @@ describe MergeRequests::RefreshService do
 
     context 'push to origin repo source branch' do
       let(:refresh_service) { service.new(@project, @user) }
+      let(:notification_service) { spy('notification_service') }
 
       before do
         allow(refresh_service).to receive(:execute_hooks)
+        allow(NotificationService).to receive(:new) { notification_service }
       end
 
       it 'executes hooks with update action' do
@@ -64,6 +74,11 @@ describe MergeRequests::RefreshService do
         expect(refresh_service).to have_received(:execute_hooks)
           .with(@merge_request, 'update', old_rev: @oldrev)
 
+        expect(notification_service).to have_received(:push_to_merge_request)
+          .with(@merge_request, @user, new_commits: anything, existing_commits: anything)
+        expect(notification_service).to have_received(:push_to_merge_request)
+          .with(@another_merge_request, @user, new_commits: anything, existing_commits: anything)
+
         expect(@merge_request.notes).not_to be_empty
         expect(@merge_request).to be_open
         expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
@@ -119,11 +134,13 @@ describe MergeRequests::RefreshService do
 
     context 'push to origin repo source branch when an MR was reopened' do
       let(:refresh_service) { service.new(@project, @user) }
+      let(:notification_service) { spy('notification_service') }
 
       before do
         @merge_request.update(state: :reopened)
 
         allow(refresh_service).to receive(:execute_hooks)
+        allow(NotificationService).to receive(:new) { notification_service }
         refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
         reload_mrs
       end
@@ -131,6 +148,10 @@ describe MergeRequests::RefreshService do
       it 'executes hooks with update action' do
         expect(refresh_service).to have_received(:execute_hooks)
           .with(@merge_request, 'update', old_rev: @oldrev)
+        expect(notification_service).to have_received(:push_to_merge_request)
+          .with(@merge_request, @user, new_commits: anything, existing_commits: anything)
+        expect(notification_service).to have_received(:push_to_merge_request)
+          .with(@another_merge_request, @user, new_commits: anything, existing_commits: anything)
 
         expect(@merge_request.notes).not_to be_empty
         expect(@merge_request).to be_open
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index c31259239ee9892f89a21ca2aa002b7c1ce842f7..5279ea6164e2c07959a86151cbb7c53abbf2030b 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe MergeRequests::UpdateService, :mailer do
+  include ProjectForksHelper
+
   let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
@@ -538,5 +540,40 @@ describe MergeRequests::UpdateService, :mailer do
       let(:open_issuable) { merge_request }
       let(:closed_issuable) { create(:closed_merge_request, source_project: project) }
     end
+
+    context 'setting `allow_maintainer_to_push`' do
+      let(:target_project) { create(:project, :public) }
+      let(:source_project) { fork_project(target_project) }
+      let(:user) { create(:user) }
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: source_project,
+               source_branch: 'fixes',
+               target_project: target_project)
+      end
+
+      before do
+        allow(ProtectedBranch).to receive(:protected?).with(source_project, 'fixes') { false }
+      end
+
+      it 'does not allow a maintainer of the target project to set `allow_maintainer_to_push`' do
+        target_project.add_developer(user)
+
+        update_merge_request(allow_maintainer_to_push: true, title: 'Updated title')
+
+        expect(merge_request.title).to eq('Updated title')
+        expect(merge_request.allow_maintainer_to_push).to be_falsy
+      end
+
+      it 'is allowed by a user that can push to the source and can update the merge request' do
+        merge_request.update!(assignee: user)
+        source_project.add_developer(user)
+
+        update_merge_request(allow_maintainer_to_push: true, title: 'Updated title')
+
+        expect(merge_request.title).to eq('Updated title')
+        expect(merge_request.allow_maintainer_to_push).to be_truthy
+      end
+    end
   end
 end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 0ae26e871549e2ebcbf87aafc65905211672c30c..f5cff66de6d6c27b11d591b153578d524ece7ecb 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -57,32 +57,55 @@ describe Notes::CreateService do
       end
     end
 
-    describe 'note with commands' do
-      describe '/close, /label, /assign & /milestone' do
-        let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) }
+    context 'note with commands' do
+      context 'as a user who can update the target' do
+        context '/close, /label, /assign & /milestone' do
+          let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) }
 
-        it 'saves the note and does not alter the note text' do
-          expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original
+          it 'saves the note and does not alter the note text' do
+            expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original
 
-          note = described_class.new(project, user, opts.merge(note: note_text)).execute
+            note = described_class.new(project, user, opts.merge(note: note_text)).execute
 
-          expect(note.note).to eq "HELLO\nWORLD"
+            expect(note.note).to eq "HELLO\nWORLD"
+          end
+        end
+
+        context '/merge with sha option' do
+          let(:note_text) { %(HELLO\n/merge\nWORLD) }
+          let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') }
+
+          it 'saves the note and exectues merge command' do
+            note = described_class.new(project, user, params).execute
+
+            expect(note.note).to eq "HELLO\nWORLD"
+          end
         end
       end
 
-      describe '/merge with sha option' do
-        let(:note_text) { %(HELLO\n/merge\nWORLD) }
-        let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') }
+      context 'as a user who cannot update the target' do
+        let(:note_text) { "HELLO\n/todo\n/assign #{user.to_reference}\nWORLD" }
+        let(:note) { described_class.new(project, user, opts.merge(note: note_text)).execute }
 
-        it 'saves the note and exectues merge command' do
-          note = described_class.new(project, user, params).execute
+        before do
+          project.team.find_member(user.id).update!(access_level: Gitlab::Access::GUEST)
+        end
+
+        it 'applies commands the user can execute' do
+          expect { note }.to change { user.todos_pending_count }.from(0).to(1)
+        end
+
+        it 'does not apply commands the user cannot execute' do
+          expect { note }.not_to change { issue.assignees }
+        end
 
+        it 'saves the note' do
           expect(note.note).to eq "HELLO\nWORLD"
         end
       end
     end
 
-    describe 'personal snippet note' do
+    context 'personal snippet note' do
       subject { described_class.new(nil, user, params).execute }
 
       let(:snippet) { create(:personal_snippet) }
@@ -103,7 +126,7 @@ describe Notes::CreateService do
       end
     end
 
-    describe 'note with emoji only' do
+    context 'note with emoji only' do
       it 'creates regular note' do
         opts = {
           note: ':smile: ',
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index 6ef5e93cb2005f2f723480f3ccfb024ce39ba478..4e2ab919f0fc2fc2f59198aad9a9a325fd38fd64 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -23,5 +23,23 @@ describe Notes::PostProcessService do
 
       described_class.new(@note).execute
     end
+
+    context 'with a confidential issue' do
+      let(:issue) { create(:issue, :confidential, project: project) }
+
+      it "doesn't call note hooks/services" do
+        expect(project).not_to receive(:execute_hooks).with(anything, :note_hooks)
+        expect(project).not_to receive(:execute_services).with(anything, :note_hooks)
+
+        described_class.new(@note).execute
+      end
+
+      it "calls confidential-note hooks/services" do
+        expect(project).to receive(:execute_hooks).with(anything, :confidential_note_hooks)
+        expect(project).to receive(:execute_services).with(anything, :confidential_note_hooks)
+
+        described_class.new(@note).execute
+      end
+    end
   end
 end
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index 5eafe56c99de8f987a386b6546d4d5e697c08b3f..b1e218821d2005d2ef0a30ce2dabe09ac02bdb76 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -165,31 +165,17 @@ describe Notes::QuickActionsService do
 
     let(:note) { create(:note_on_issue, project: project) }
 
-    context 'with no current_user' do
-      it 'returns false' do
-        expect(described_class.supported?(note, nil)).to be_falsy
-      end
-    end
-
-    context 'when current_user cannot update the noteable' do
-      it 'returns false' do
-        user = create(:user)
-
-        expect(described_class.supported?(note, user)).to be_falsy
-      end
-    end
-
-    context 'when current_user can update the noteable' do
+    context 'with a note on an issue' do
       it 'returns true' do
-        expect(described_class.supported?(note, master)).to be_truthy
+        expect(described_class.supported?(note)).to be_truthy
       end
+    end
 
-      context 'with a note on a commit' do
-        let(:note) { create(:note_on_commit, project: project) }
+    context 'with a note on a commit' do
+      let(:note) { create(:note_on_commit, project: project) }
 
-        it 'returns false' do
-          expect(described_class.supported?(note, nil)).to be_falsy
-        end
+      it 'returns false' do
+        expect(described_class.supported?(note)).to be_falsy
       end
     end
   end
@@ -201,7 +187,7 @@ describe Notes::QuickActionsService do
       service = described_class.new(project, master)
       note = create(:note_on_issue, project: project)
 
-      expect(described_class).to receive(:supported?).with(note, master)
+      expect(described_class).to receive(:supported?).with(note)
 
       service.supported?(note)
     end
diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb
index faac498037fc8b22d50b22654a96d0ec56b9c055..f771620bc0d90e6e732def9a6b9a603d84c6cc45 100644
--- a/spec/services/notes/render_service_spec.rb
+++ b/spec/services/notes/render_service_spec.rb
@@ -4,23 +4,28 @@ describe Notes::RenderService do
   describe '#execute' do
     it 'renders a Note' do
       note = double(:note)
-      project = double(:project)
       wiki = double(:wiki)
       user = double(:user)
 
-      expect(Banzai::ObjectRenderer).to receive(:new)
-        .with(project, user,
-             requested_path: 'foo',
-             project_wiki: wiki,
-             ref: 'bar',
-             only_path: nil,
-             xhtml: false)
+      expect(Banzai::ObjectRenderer)
+        .to receive(:new)
+        .with(
+          user: user,
+          redaction_context: {
+              requested_path: 'foo',
+              project_wiki: wiki,
+              ref: 'bar',
+              only_path: nil,
+              xhtml: false
+          }
+        )
         .and_call_original
 
       expect_any_instance_of(Banzai::ObjectRenderer)
-        .to receive(:render).with([note], :note)
+        .to receive(:render)
+        .with([note], :note)
 
-      described_class.new(user).execute([note], project,
+      described_class.new(user).execute([note],
                                         requested_path: 'foo',
                                         project_wiki: wiki,
                                         ref: 'bar',
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 62fdf8700904880b550d107b2ee3ff611426be6b..55bbe9544918b8da262ff717995fb7d3c529e92f 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -34,6 +34,12 @@ describe NotificationService, :mailer do
       should_not_email_anyone
     end
 
+    it 'emails new mentions despite being unsubscribed' do
+      send_notifications(@unsubscribed_mentioned)
+
+      should_only_email(@unsubscribed_mentioned)
+    end
+
     it 'sends the proper notification reason header' do
       send_notifications(@u_watcher)
       should_only_email(@u_watcher)
@@ -122,7 +128,7 @@ describe NotificationService, :mailer do
       let(:project) { create(:project, :private) }
       let(:issue) { create(:issue, project: project, assignees: [assignee]) }
       let(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
-      let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @outsider also') }
+      let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @unsubscribed_mentioned and @outsider also') }
 
       before do
         build_team(note.project)
@@ -150,7 +156,7 @@ describe NotificationService, :mailer do
           add_users_with_subscription(note.project, issue)
           reset_delivered_emails!
 
-          expect(SentNotification).to receive(:record).with(issue, any_args).exactly(9).times
+          expect(SentNotification).to receive(:record).with(issue, any_args).exactly(10).times
 
           notification.new_note(note)
 
@@ -163,6 +169,7 @@ describe NotificationService, :mailer do
           should_email(@watcher_and_subscriber)
           should_email(@subscribed_participant)
           should_email(@u_custom_off)
+          should_email(@unsubscribed_mentioned)
           should_not_email(@u_guest_custom)
           should_not_email(@u_guest_watcher)
           should_not_email(note.author)
@@ -279,6 +286,7 @@ describe NotificationService, :mailer do
       before do
         build_team(note.project)
         note.project.add_master(note.author)
+        add_users_with_subscription(note.project, issue)
         reset_delivered_emails!
       end
 
@@ -286,6 +294,9 @@ describe NotificationService, :mailer do
         it 'notifies the team members' do
           notification.new_note(note)
 
+          # Make sure @unsubscribed_mentioned is part of the team
+          expect(note.project.team.members).to include(@unsubscribed_mentioned)
+
           # Notify all team members
           note.project.team.members.each do |member|
             # User with disabled notification should not be notified
@@ -486,7 +497,7 @@ describe NotificationService, :mailer do
     let(:group) { create(:group) }
     let(:project) { create(:project, :public, namespace: group) }
     let(:another_project) { create(:project, :public, namespace: group) }
-    let(:issue) { create :issue, project: project, assignees: [assignee], description: 'cc @participant' }
+    let(:issue) { create :issue, project: project, assignees: [assignee], description: 'cc @participant @unsubscribed_mentioned' }
 
     before do
       build_team(issue.project)
@@ -510,6 +521,7 @@ describe NotificationService, :mailer do
         should_email(@u_participant_mentioned)
         should_email(@g_global_watcher)
         should_email(@g_watcher)
+        should_email(@unsubscribed_mentioned)
         should_not_email(@u_mentioned)
         should_not_email(@u_participating)
         should_not_email(@u_disabled)
@@ -921,6 +933,46 @@ describe NotificationService, :mailer do
         let(:notification_trigger) { notification.issue_moved(issue, new_issue, @u_disabled) }
       end
     end
+
+    describe '#issue_due' do
+      before do
+        issue.update!(due_date: Date.today)
+
+        update_custom_notification(:issue_due, @u_guest_custom, resource: project)
+        update_custom_notification(:issue_due, @u_custom_global)
+      end
+
+      it 'sends email to issue notification recipients, excluding watchers' do
+        notification.issue_due(issue)
+
+        should_email(issue.assignees.first)
+        should_email(issue.author)
+        should_email(@u_guest_custom)
+        should_email(@u_custom_global)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_email(@watcher_and_subscriber)
+        should_not_email(@u_watcher)
+        should_not_email(@u_guest_watcher)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
+        should_not_email(@u_lazy_participant)
+      end
+
+      it 'sends the email from the author' do
+        notification.issue_due(issue)
+        email = find_email_for(@subscriber)
+
+        expect(email.header[:from].display_names).to eq([issue.author.name])
+      end
+
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { issue }
+        let(:notification_trigger) { notification.issue_due(issue) }
+      end
+    end
   end
 
   describe 'Merge Requests' do
@@ -1078,6 +1130,36 @@ describe NotificationService, :mailer do
       end
     end
 
+    describe '#push_to_merge_request' do
+      before do
+        update_custom_notification(:push_to_merge_request, @u_guest_custom, resource: project)
+        update_custom_notification(:push_to_merge_request, @u_custom_global)
+      end
+
+      it do
+        notification.push_to_merge_request(merge_request, @u_disabled)
+
+        should_email(merge_request.assignee)
+        should_email(@u_guest_custom)
+        should_email(@u_custom_global)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_email(@watcher_and_subscriber)
+        should_not_email(@u_watcher)
+        should_not_email(@u_guest_watcher)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
+        should_not_email(@u_lazy_participant)
+      end
+
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { merge_request }
+        let(:notification_trigger) { notification.push_to_merge_request(merge_request, @u_disabled) }
+      end
+    end
+
     describe '#relabel_merge_request' do
       let(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1', merge_requests: [merge_request]) }
       let(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') }
@@ -1823,6 +1905,7 @@ describe NotificationService, :mailer do
   def add_users_with_subscription(project, issuable)
     @subscriber = create :user
     @unsubscriber = create :user
+    @unsubscribed_mentioned = create :user, username: 'unsubscribed_mentioned'
     @subscribed_participant = create_global_setting_for(create(:user, username: 'subscribed_participant'), :participating)
     @watcher_and_subscriber = create_global_setting_for(create(:user), :watch)
 
@@ -1830,7 +1913,9 @@ describe NotificationService, :mailer do
     project.add_master(@subscriber)
     project.add_master(@unsubscriber)
     project.add_master(@watcher_and_subscriber)
+    project.add_master(@unsubscribed_mentioned)
 
+    issuable.subscriptions.create(user: @unsubscribed_mentioned, project: project, subscribed: false)
     issuable.subscriptions.create(user: @subscriber, project: project, subscribed: true)
     issuable.subscriptions.create(user: @subscribed_participant, project: project, subscribed: true)
     issuable.subscriptions.create(user: @unsubscriber, project: project, subscribed: false)
diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb
index 609d678caea02a27885ae27175dc7c7e19cec85a..d40e6f1449de0a7635a9c7779810aa695896a887 100644
--- a/spec/services/projects/create_from_template_service_spec.rb
+++ b/spec/services/projects/create_from_template_service_spec.rb
@@ -7,7 +7,7 @@ describe Projects::CreateFromTemplateService do
         path: user.to_param,
         template_name: 'rails',
         description: 'project description',
-        visibility_level: Gitlab::VisibilityLevel::PRIVATE
+        visibility_level: Gitlab::VisibilityLevel::PUBLIC
     }
   end
 
@@ -24,7 +24,23 @@ describe Projects::CreateFromTemplateService do
 
     expect(project).to be_saved
     expect(project.scheduled?).to be(true)
-    expect(project.description).to match('project description')
-    expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+  end
+
+  context 'the result project' do
+    before do
+      Sidekiq::Testing.inline! do
+        @project = subject.execute
+      end
+
+      @project.reload
+    end
+
+    it 'overrides template description' do
+      expect(@project.description).to match('project description')
+    end
+
+    it 'overrides template visibility_level' do
+      expect(@project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+    end
   end
 end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 9a44dfde41baeb951e8973f23ecb87bf239d3053..e35f0f6337a24cbc2888c2f875584779efb9cd4d 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -28,6 +28,14 @@ describe Projects::CreateService, '#execute' do
     end
   end
 
+  describe 'after create actions' do
+    it 'invalidate personal_projects_count caches' do
+      expect(user).to receive(:invalidate_personal_projects_count)
+
+      create_project(user, opts)
+    end
+  end
+
   context "admin creates project with other user's namespace_id" do
     it 'sets the correct permissions' do
       admin = create(:admin)
@@ -70,6 +78,16 @@ describe Projects::CreateService, '#execute' do
       opts[:default_branch] = 'master'
       expect(create_project(user, opts)).to eq(nil)
     end
+
+    it 'sets invalid service as inactive' do
+      create(:service, type: 'JiraService', project: nil, template: true, active: true)
+
+      project = create_project(user, opts)
+      service = project.services.first
+
+      expect(project).to be_persisted
+      expect(service.active).to be false
+    end
   end
 
   context 'wiki_enabled creates repository directory' do
@@ -153,7 +171,7 @@ describe Projects::CreateService, '#execute' do
 
     context 'when another repository already exists on disk' do
       let(:repository_storage) { 'default' }
-      let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
+      let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
 
       let(:opts) do
         {
@@ -164,7 +182,7 @@ describe Projects::CreateService, '#execute' do
 
       context 'with legacy storage' do
         before do
-          gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
+          gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
         end
 
         after do
@@ -200,7 +218,7 @@ describe Projects::CreateService, '#execute' do
         end
 
         before do
-          gitlab_shell.add_repository(repository_storage, hashed_path)
+          gitlab_shell.create_repository(repository_storage, hashed_path)
         end
 
         after do
@@ -232,14 +250,15 @@ describe Projects::CreateService, '#execute' do
   end
 
   context 'when a bad service template is created' do
-    it 'reports an error in the imported project' do
+    it 'sets service to be inactive' do
       opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-ce'
       create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
 
       project = create_project(user, opts)
+      service = project.services.first
 
-      expect(project.errors.full_messages_for(:base).first).to match(/Unable to save project. Error: Unable to save DroneCiService/)
-      expect(project.services.count).to eq 0
+      expect(project).to be_persisted
+      expect(service.active).to be false
     end
   end
 
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 0bec2054f501cef7f7969b6a9907cbdc2a97b9f2..a66e3c5e995959c1f7f5459e44c0707aef7457e6 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -66,6 +66,12 @@ describe Projects::DestroyService do
     end
 
     it_behaves_like 'deleting the project'
+
+    it 'invalidates personal_project_count cache' do
+      expect(user).to receive(:invalidate_personal_projects_count)
+
+      destroy_project(project, user)
+    end
   end
 
   context 'Sidekiq fake' do
@@ -242,6 +248,28 @@ describe Projects::DestroyService do
     end
   end
 
+  context '#attempt_restore_repositories' do
+    let(:path) { project.disk_path + '.git' }
+
+    before do
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
+
+      # Dont run sidekiq to check if renamed repository exists
+      Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
+
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_falsey
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy
+    end
+
+    it 'restores the repositories' do
+      Sidekiq::Testing.fake! { described_class.new(project, user).attempt_repositories_rollback }
+
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy
+      expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
+    end
+  end
+
   def destroy_project(project, user, params = {})
     if async
       Projects::DestroyService.new(project, user, params).async_execute
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 409d5de8d4353d6c4d4b6eb74d8f254001d12655..0f7c46367d09671a04280cf45c8734bef191cfea 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -105,10 +105,10 @@ describe Projects::ForkService do
 
       context 'repository already exists' do
         let(:repository_storage) { 'default' }
-        let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
+        let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
 
         before do
-          gitlab_shell.add_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
+          gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
         end
 
         after do
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
index 6b8f9619bc4ee4b969e9ab00fc232ea4698edfe8..ee1a886f5d626d6618564deb31df4be7d69a5e01 100644
--- a/spec/services/projects/gitlab_projects_import_service_spec.rb
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -2,8 +2,11 @@ require 'spec_helper'
 
 describe Projects::GitlabProjectsImportService do
   set(:namespace) { create(:namespace) }
+  let(:path) { 'test-path' }
   let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
-  subject { described_class.new(namespace.owner, { namespace_id: namespace.id, path: path, file: file }) }
+  let(:overwrite) { false }
+  let(:import_params) { { namespace_id: namespace.id, path: path, file: file, overwrite: overwrite } }
+  subject { described_class.new(namespace.owner, import_params) }
 
   describe '#execute' do
     context 'with an invalid path' do
@@ -18,8 +21,6 @@ describe Projects::GitlabProjectsImportService do
     end
 
     context 'with a valid path' do
-      let(:path) { 'test-path' }
-
       it 'creates a project' do
         project = subject.execute
 
@@ -27,5 +28,38 @@ describe Projects::GitlabProjectsImportService do
         expect(project).to be_valid
       end
     end
+
+    context 'override params' do
+      it 'stores them as import data when passed' do
+        project = described_class
+                    .new(namespace.owner, import_params, description: 'Hello')
+                    .execute
+
+        expect(project.import_data.data['override_params']['description']).to eq('Hello')
+      end
+    end
+
+    context 'when there is a project with the same path' do
+      let(:existing_project) { create(:project, namespace: namespace) }
+      let(:path) { existing_project.path}
+
+      it 'does not create the project' do
+        project = subject.execute
+
+        expect(project).to be_invalid
+        expect(project).not_to be_persisted
+      end
+
+      context 'when overwrite param is set' do
+        let(:overwrite) { true }
+
+        it 'creates a project in a temporary full_path' do
+          project = subject.execute
+
+          expect(project).to be_valid
+          expect(project).to be_persisted
+        end
+      end
+    end
   end
 end
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f9e5530bc9d4d418321a267cebdf85b055f706ef
--- /dev/null
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -0,0 +1,128 @@
+require 'spec_helper'
+
+describe Projects::ImportExport::ExportService do
+  describe '#execute' do
+    let!(:user) { create(:user) }
+    let(:project) { create(:project) }
+    let(:shared) { project.import_export_shared }
+    let(:service) { described_class.new(project, user) }
+    let!(:after_export_strategy) { Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy.new }
+
+    it 'saves the version' do
+      expect(Gitlab::ImportExport::VersionSaver).to receive(:new).and_call_original
+
+      service.execute
+    end
+
+    it 'saves the avatar' do
+      expect(Gitlab::ImportExport::AvatarSaver).to receive(:new).and_call_original
+
+      service.execute
+    end
+
+    it 'saves the models' do
+      expect(Gitlab::ImportExport::ProjectTreeSaver).to receive(:new).and_call_original
+
+      service.execute
+    end
+
+    it 'saves the uploads' do
+      expect(Gitlab::ImportExport::UploadsSaver).to receive(:new).and_call_original
+
+      service.execute
+    end
+
+    it 'saves the repo' do
+      # once for the normal repo, once for the wiki
+      expect(Gitlab::ImportExport::RepoSaver).to receive(:new).twice.and_call_original
+
+      service.execute
+    end
+
+    it 'saves the lfs objects' do
+      expect(Gitlab::ImportExport::LfsSaver).to receive(:new).and_call_original
+
+      service.execute
+    end
+
+    it 'saves the wiki repo' do
+      expect(Gitlab::ImportExport::WikiRepoSaver).to receive(:new).and_call_original
+
+      service.execute
+    end
+
+    context 'when all saver services succeed' do
+      before do
+        allow(service).to receive(:save_services).and_return(true)
+      end
+
+      it 'saves the project in the file system' do
+        expect(Gitlab::ImportExport::Saver).to receive(:save).with(project: project, shared: shared)
+
+        service.execute
+      end
+
+      it 'calls the after export strategy' do
+        expect(after_export_strategy).to receive(:execute)
+
+        service.execute(after_export_strategy)
+      end
+
+      context 'when after export strategy fails' do
+        before do
+          allow(after_export_strategy).to receive(:execute).and_return(false)
+        end
+
+        after do
+          service.execute(after_export_strategy)
+        end
+
+        it 'removes the remaining exported data' do
+          allow(shared).to receive(:export_path).and_return('whatever')
+          allow(FileUtils).to receive(:rm_rf)
+
+          expect(FileUtils).to receive(:rm_rf).with(shared.export_path)
+        end
+
+        it 'notifies the user' do
+          expect_any_instance_of(NotificationService).to receive(:project_not_exported)
+        end
+
+        it 'notifies logger' do
+          allow(Rails.logger).to receive(:error)
+
+          expect(Rails.logger).to receive(:error)
+        end
+      end
+    end
+
+    context 'when saver services fail' do
+      before do
+        allow(service).to receive(:save_services).and_return(false)
+      end
+
+      after do
+        expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
+      end
+
+      it 'removes the remaining exported data' do
+        allow(shared).to receive(:export_path).and_return('whatever')
+        allow(FileUtils).to receive(:rm_rf)
+
+        expect(FileUtils).to receive(:rm_rf).with(shared.export_path)
+      end
+
+      it 'notifies the user' do
+        expect_any_instance_of(NotificationService).to receive(:project_not_exported)
+      end
+
+      it 'notifies logger' do
+        expect(Rails.logger).to receive(:error)
+      end
+
+      it 'the after export strategy is not called' do
+        expect(service).not_to receive(:execute_after_export_action)
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index bf7facaec99fda214341314a3fed0096d7c3c516..30c89ebd82159adc22e4a1e41174b57d0de21ebb 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -156,7 +156,7 @@ describe Projects::ImportService do
         result = described_class.new(project, user).execute
 
         expect(result[:status]).to eq :error
-        expect(result[:message]).to end_with 'Blocked import URL.'
+        expect(result[:message]).to include('Requests to localhost are not allowed')
       end
 
       it 'fails with port 25' do
@@ -165,7 +165,7 @@ describe Projects::ImportService do
         result = described_class.new(project, user).execute
 
         expect(result[:status]).to eq :error
-        expect(result[:message]).to end_with 'Blocked import URL.'
+        expect(result[:message]).to include('Only allowed ports are 22, 80, 443')
       end
     end
 
diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a820ebd91f48eb5c32019d0a6ee6373290ea29d6
--- /dev/null
+++ b/spec/services/projects/move_access_service_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+
+describe Projects::MoveAccessService do
+  let(:user) { create(:user) }
+  let(:group) { create(:group) }
+  let(:project_with_access) { create(:project, namespace: user.namespace) }
+  let(:master_user) { create(:user) }
+  let(:reporter_user) { create(:user) }
+  let(:developer_user) { create(:user) }
+  let(:master_group) { create(:group) }
+  let(:reporter_group) { create(:group) }
+  let(:developer_group) { create(:group) }
+
+  before do
+    project_with_access.add_master(master_user)
+    project_with_access.add_developer(developer_user)
+    project_with_access.add_reporter(reporter_user)
+    project_with_access.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+    project_with_access.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+    project_with_access.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
+  end
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    shared_examples 'move the accesses' do
+      it do
+        expect(project_with_access.project_members.count).to eq 4
+        expect(project_with_access.project_group_links.count).to eq 3
+        expect(project_with_access.authorized_users.count).to eq 4
+
+        subject.execute(project_with_access)
+
+        expect(project_with_access.project_members.count).to eq 0
+        expect(project_with_access.project_group_links.count).to eq 0
+        expect(project_with_access.authorized_users.count).to eq 1
+        expect(target_project.project_members.count).to eq 4
+        expect(target_project.project_group_links.count).to eq 3
+        expect(target_project.authorized_users.count).to eq 4
+      end
+
+      it 'rollbacks if an exception is raised' do
+        allow(subject).to receive(:success).and_raise(StandardError)
+
+        expect { subject.execute(project_with_groups) }.to raise_error(StandardError)
+
+        expect(project_with_access.project_members.count).to eq 4
+        expect(project_with_access.project_group_links.count).to eq 3
+        expect(project_with_access.authorized_users.count).to eq 4
+      end
+    end
+
+    context 'when both projects are in the same namespace' do
+      let(:target_project) { create(:project, namespace: user.namespace) }
+
+      it 'does not refresh project owner authorized projects' do
+        allow(project_with_access).to receive(:namespace).and_return(user.namespace)
+        expect(project_with_access.namespace).not_to receive(:refresh_project_authorizations)
+        expect(target_project.namespace).not_to receive(:refresh_project_authorizations)
+
+        subject.execute(project_with_access)
+      end
+
+      it_behaves_like 'move the accesses'
+    end
+
+    context 'when projects are in different namespaces' do
+      let(:target_project) { create(:project, namespace: group) }
+
+      before do
+        group.add_owner(user)
+      end
+
+      it 'refreshes both project owner authorized projects' do
+        allow(project_with_access).to receive(:namespace).and_return(user.namespace)
+        expect(user.namespace).to receive(:refresh_project_authorizations).once
+        expect(group).to receive(:refresh_project_authorizations).once
+
+        subject.execute(project_with_access)
+      end
+
+      it_behaves_like 'move the accesses'
+    end
+
+    context 'when remove_remaining_elements is false' do
+      let(:target_project) { create(:project, namespace: user.namespace) }
+      let(:options) { { remove_remaining_elements: false } }
+
+      it 'does not remove remaining memberships' do
+        target_project.add_master(master_user)
+
+        subject.execute(project_with_access, options)
+
+        expect(project_with_access.project_members.count).not_to eq 0
+      end
+
+      it 'does not remove remaining group links' do
+        target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+
+        subject.execute(project_with_access, options)
+
+        expect(project_with_access.project_group_links.count).not_to eq 0
+      end
+
+      it 'does not remove remaining authorizations' do
+        target_project.add_developer(developer_user)
+
+        subject.execute(project_with_access, options)
+
+        expect(project_with_access.project_authorizations.count).not_to eq 0
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c548edf39a8529f497548a55521c518ed8008afe
--- /dev/null
+++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Projects::MoveDeployKeysProjectsService do
+  let!(:user) { create(:user) }
+  let!(:project_with_deploy_keys) { create(:project, namespace: user.namespace) }
+  let!(:target_project) { create(:project, namespace: user.namespace) }
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    before do
+      create_list(:deploy_keys_project, 2, project: project_with_deploy_keys)
+    end
+
+    it 'moves the user\'s deploy keys from one project to another' do
+      expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
+      expect(target_project.deploy_keys_projects.count).to eq 0
+
+      subject.execute(project_with_deploy_keys)
+
+      expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0
+      expect(target_project.deploy_keys_projects.count).to eq 2
+    end
+
+    it 'does not link existent deploy_keys in the current project' do
+      target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
+
+      expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
+      expect(target_project.deploy_keys_projects.count).to eq 1
+
+      subject.execute(project_with_deploy_keys)
+
+      expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0
+      expect(target_project.deploy_keys_projects.count).to eq 2
+    end
+
+    it 'rollbacks changes if transaction fails' do
+      allow(subject).to receive(:success).and_raise(StandardError)
+
+      expect { subject.execute(project_with_deploy_keys) }.to raise_error(StandardError)
+
+      expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
+      expect(target_project.deploy_keys_projects.count).to eq 0
+    end
+
+    context 'when remove_remaining_elements is false' do
+      let(:options) { { remove_remaining_elements: false } }
+
+      it 'does not remove remaining deploy keys projects' do
+        target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
+
+        subject.execute(project_with_deploy_keys, options)
+
+        expect(project_with_deploy_keys.deploy_keys_projects.count).not_to eq 0
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f4a5a7f9fc2dc18aad891e0bd51b4a4229560c05
--- /dev/null
+++ b/spec/services/projects/move_forks_service_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+describe Projects::MoveForksService do
+  include ProjectForksHelper
+
+  let!(:user) { create(:user) }
+  let!(:project_with_forks) { create(:project, namespace: user.namespace) }
+  let!(:target_project) { create(:project, namespace: user.namespace) }
+  let!(:lvl1_forked_project_1) { fork_project(project_with_forks, user) }
+  let!(:lvl1_forked_project_2) { fork_project(project_with_forks, user) }
+  let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) }
+  let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) }
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    context 'when moving a root forked project' do
+      it 'moves the descendant forks' do
+        expect(project_with_forks.forks.count).to eq 2
+        expect(target_project.forks.count).to eq 0
+
+        subject.execute(project_with_forks)
+
+        expect(project_with_forks.forks.count).to eq 0
+        expect(target_project.forks.count).to eq 2
+        expect(lvl1_forked_project_1.forked_from_project).to eq target_project
+        expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq target_project
+        expect(lvl1_forked_project_2.forked_from_project).to eq target_project
+        expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq target_project
+      end
+
+      it 'updates the fork network' do
+        expect(project_with_forks.fork_network.root_project).to eq project_with_forks
+        expect(project_with_forks.fork_network.fork_network_members.map(&:project)).to include project_with_forks
+
+        subject.execute(project_with_forks)
+
+        expect(target_project.reload.fork_network.root_project).to eq target_project
+        expect(target_project.fork_network.fork_network_members.map(&:project)).not_to include project_with_forks
+      end
+    end
+
+    context 'when moving a intermediate forked project' do
+      it 'moves the descendant forks' do
+        expect(lvl1_forked_project_1.forks.count).to eq 2
+        expect(target_project.forks.count).to eq 0
+
+        subject.execute(lvl1_forked_project_1)
+
+        expect(lvl1_forked_project_1.forks.count).to eq 0
+        expect(target_project.forks.count).to eq 2
+        expect(lvl2_forked_project_1_1.forked_from_project).to eq target_project
+        expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq target_project
+        expect(lvl2_forked_project_1_2.forked_from_project).to eq target_project
+        expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq target_project
+      end
+
+      it 'moves the ascendant fork' do
+        subject.execute(lvl1_forked_project_1)
+
+        expect(target_project.forked_from_project).to eq project_with_forks
+        expect(target_project.fork_network_member.forked_from_project).to eq project_with_forks
+      end
+
+      it 'does not update fork network' do
+        subject.execute(lvl1_forked_project_1)
+
+        expect(target_project.reload.fork_network.root_project).to eq project_with_forks
+      end
+    end
+
+    context 'when moving a leaf forked project' do
+      it 'moves the ascendant fork' do
+        subject.execute(lvl2_forked_project_1_1)
+
+        expect(target_project.forked_from_project).to eq lvl1_forked_project_1
+        expect(target_project.fork_network_member.forked_from_project).to eq lvl1_forked_project_1
+      end
+
+      it 'does not update fork network' do
+        subject.execute(lvl2_forked_project_1_1)
+
+        expect(target_project.reload.fork_network.root_project).to eq project_with_forks
+      end
+    end
+
+    it 'rollbacks changes if transaction fails' do
+      allow(subject).to receive(:success).and_raise(StandardError)
+
+      expect { subject.execute(project_with_forks) }.to raise_error(StandardError)
+
+      expect(project_with_forks.forks.count).to eq 2
+      expect(target_project.forks.count).to eq 0
+    end
+  end
+end
diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..517a24a982a5a74ed35d2cf949a2f92bfc48be17
--- /dev/null
+++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Projects::MoveLfsObjectsProjectsService do
+  let!(:user) { create(:user) }
+  let!(:project_with_lfs_objects) { create(:project, namespace: user.namespace) }
+  let!(:target_project) { create(:project, namespace: user.namespace) }
+
+  subject { described_class.new(target_project, user) }
+
+  before do
+    create_list(:lfs_objects_project, 3, project: project_with_lfs_objects)
+  end
+
+  describe '#execute' do
+    it 'links the lfs objects from existent in source project' do
+      expect(target_project.lfs_objects.count).to eq 0
+
+      subject.execute(project_with_lfs_objects)
+
+      expect(project_with_lfs_objects.reload.lfs_objects.count).to eq 0
+      expect(target_project.reload.lfs_objects.count).to eq 3
+    end
+
+    it 'does not link existent lfs_object in the current project' do
+      target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
+
+      expect(target_project.lfs_objects.count).to eq 2
+
+      subject.execute(project_with_lfs_objects)
+
+      expect(target_project.lfs_objects.count).to eq 3
+    end
+
+    it 'rollbacks changes if transaction fails' do
+      allow(subject).to receive(:success).and_raise(StandardError)
+
+      expect { subject.execute(project_with_lfs_objects) }.to raise_error(StandardError)
+
+      expect(project_with_lfs_objects.lfs_objects.count).to eq 3
+      expect(target_project.lfs_objects.count).to eq 0
+    end
+
+    context 'when remove_remaining_elements is false' do
+      let(:options) { { remove_remaining_elements: false } }
+
+      it 'does not remove remaining lfs objects' do
+        target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
+
+        subject.execute(project_with_lfs_objects, options)
+
+        expect(project_with_lfs_objects.lfs_objects.count).not_to eq 0
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..24d69eef86acf4e2b9f641e9fce789347f25d0eb
--- /dev/null
+++ b/spec/services/projects/move_notification_settings_service_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Projects::MoveNotificationSettingsService do
+  let(:user) { create(:user) }
+  let(:project_with_notifications) { create(:project, namespace: user.namespace) }
+  let(:target_project) { create(:project, namespace: user.namespace) }
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    context 'with notification settings' do
+      before do
+        create_list(:notification_setting, 2, source: project_with_notifications)
+      end
+
+      it 'moves the user\'s notification settings from one project to another' do
+        expect(project_with_notifications.notification_settings.count).to eq 3
+        expect(target_project.notification_settings.count).to eq 1
+
+        subject.execute(project_with_notifications)
+
+        expect(project_with_notifications.notification_settings.count).to eq 0
+        expect(target_project.notification_settings.count).to eq 3
+      end
+
+      it 'rollbacks changes if transaction fails' do
+        allow(subject).to receive(:success).and_raise(StandardError)
+
+        expect { subject.execute(project_with_notifications) }.to raise_error(StandardError)
+
+        expect(project_with_notifications.notification_settings.count).to eq 3
+        expect(target_project.notification_settings.count).to eq 1
+      end
+    end
+
+    it 'does not move existent notification settings in the current project' do
+      expect(project_with_notifications.notification_settings.count).to eq 1
+      expect(target_project.notification_settings.count).to eq 1
+      expect(user.notification_settings.count).to eq 2
+
+      subject.execute(project_with_notifications)
+
+      expect(user.notification_settings.count).to eq 1
+    end
+
+    context 'when remove_remaining_elements is false' do
+      let(:options) { { remove_remaining_elements: false } }
+
+      it 'does not remove remaining notification settings' do
+        subject.execute(project_with_notifications, options)
+
+        expect(project_with_notifications.notification_settings.count).not_to eq 0
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f7262b9b88756c7999820ad656faefb717144395
--- /dev/null
+++ b/spec/services/projects/move_project_authorizations_service_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Projects::MoveProjectAuthorizationsService do
+  let!(:user) { create(:user) }
+  let(:project_with_users) { create(:project, namespace: user.namespace) }
+  let(:target_project) { create(:project, namespace: user.namespace) }
+  let(:master_user) { create(:user) }
+  let(:reporter_user) { create(:user) }
+  let(:developer_user) { create(:user) }
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    before do
+      project_with_users.add_master(master_user)
+      project_with_users.add_developer(developer_user)
+      project_with_users.add_reporter(reporter_user)
+    end
+
+    it 'moves the authorizations from one project to another' do
+      expect(project_with_users.authorized_users.count).to eq 4
+      expect(target_project.authorized_users.count).to eq 1
+
+      subject.execute(project_with_users)
+
+      expect(project_with_users.authorized_users.count).to eq 0
+      expect(target_project.authorized_users.count).to eq 4
+    end
+
+    it 'does not move existent authorizations to the current project' do
+      target_project.add_master(developer_user)
+      target_project.add_developer(reporter_user)
+
+      expect(project_with_users.authorized_users.count).to eq 4
+      expect(target_project.authorized_users.count).to eq 3
+
+      subject.execute(project_with_users)
+
+      expect(project_with_users.authorized_users.count).to eq 0
+      expect(target_project.authorized_users.count).to eq 4
+    end
+
+    context 'when remove_remaining_elements is false' do
+      let(:options) { { remove_remaining_elements: false } }
+
+      it 'does not remove remaining project authorizations' do
+        target_project.add_master(developer_user)
+        target_project.add_developer(reporter_user)
+
+        subject.execute(project_with_users, options)
+
+        expect(project_with_users.project_authorizations.count).not_to eq 0
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e3d06e6d3d73b8512a04504358c63b9714c6848f
--- /dev/null
+++ b/spec/services/projects/move_project_group_links_service_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Projects::MoveProjectGroupLinksService do
+  let!(:user) { create(:user) }
+  let(:project_with_groups) { create(:project, namespace: user.namespace) }
+  let(:target_project) { create(:project, namespace: user.namespace) }
+  let(:master_group) { create(:group) }
+  let(:reporter_group) { create(:group) }
+  let(:developer_group) { create(:group) }
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    before do
+      project_with_groups.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+      project_with_groups.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+      project_with_groups.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
+    end
+
+    it 'moves the group links from one project to another' do
+      expect(project_with_groups.project_group_links.count).to eq 3
+      expect(target_project.project_group_links.count).to eq 0
+
+      subject.execute(project_with_groups)
+
+      expect(project_with_groups.project_group_links.count).to eq 0
+      expect(target_project.project_group_links.count).to eq 3
+    end
+
+    it 'does not move existent group links in the current project' do
+      target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+      target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+
+      expect(project_with_groups.project_group_links.count).to eq 3
+      expect(target_project.project_group_links.count).to eq 2
+
+      subject.execute(project_with_groups)
+
+      expect(project_with_groups.project_group_links.count).to eq 0
+      expect(target_project.project_group_links.count).to eq 3
+    end
+
+    it 'rollbacks changes if transaction fails' do
+      allow(subject).to receive(:success).and_raise(StandardError)
+
+      expect { subject.execute(project_with_groups) }.to raise_error(StandardError)
+
+      expect(project_with_groups.project_group_links.count).to eq 3
+      expect(target_project.project_group_links.count).to eq 0
+    end
+
+    context 'when remove_remaining_elements is false' do
+      let(:options) { { remove_remaining_elements: false } }
+
+      it 'does not remove remaining project group links' do
+        target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+        target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+
+        subject.execute(project_with_groups, options)
+
+        expect(project_with_groups.project_group_links.count).not_to eq 0
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9c9a2d2fde1e5cfdb4efa5eb0aa5c01e9cd028dd
--- /dev/null
+++ b/spec/services/projects/move_project_members_service_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Projects::MoveProjectMembersService do
+  let!(:user) { create(:user) }
+  let(:project_with_users) { create(:project, namespace: user.namespace) }
+  let(:target_project) { create(:project, namespace: user.namespace) }
+  let(:master_user) { create(:user) }
+  let(:reporter_user) { create(:user) }
+  let(:developer_user) { create(:user) }
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    before do
+      project_with_users.add_master(master_user)
+      project_with_users.add_developer(developer_user)
+      project_with_users.add_reporter(reporter_user)
+    end
+
+    it 'moves the members from one project to another' do
+      expect(project_with_users.project_members.count).to eq 4
+      expect(target_project.project_members.count).to eq 1
+
+      subject.execute(project_with_users)
+
+      expect(project_with_users.project_members.count).to eq 0
+      expect(target_project.project_members.count).to eq 4
+    end
+
+    it 'does not move existent members to the current project' do
+      target_project.add_master(developer_user)
+      target_project.add_developer(reporter_user)
+
+      expect(project_with_users.project_members.count).to eq 4
+      expect(target_project.project_members.count).to eq 3
+
+      subject.execute(project_with_users)
+
+      expect(project_with_users.project_members.count).to eq 0
+      expect(target_project.project_members.count).to eq 4
+    end
+
+    it 'rollbacks changes if transaction fails' do
+      allow(subject).to receive(:success).and_raise(StandardError)
+
+      expect { subject.execute(project_with_users) }.to raise_error(StandardError)
+
+      expect(project_with_users.project_members.count).to eq 4
+      expect(target_project.project_members.count).to eq 1
+    end
+
+    context 'when remove_remaining_elements is false' do
+      let(:options) { { remove_remaining_elements: false } }
+
+      it 'does not remove remaining project members' do
+        target_project.add_master(developer_user)
+        target_project.add_developer(reporter_user)
+
+        subject.execute(project_with_users, options)
+
+        expect(project_with_users.project_members.count).not_to eq 0
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e0545c5a21b4ad5a4d4fb0c7fb980adf8c14ab38
--- /dev/null
+++ b/spec/services/projects/move_users_star_projects_service_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Projects::MoveUsersStarProjectsService do
+  let!(:user) { create(:user) }
+  let!(:project_with_stars) { create(:project, namespace: user.namespace) }
+  let!(:target_project) { create(:project, namespace: user.namespace) }
+
+  subject { described_class.new(target_project, user) }
+
+  describe '#execute' do
+    before do
+      create_list(:users_star_project, 2, project: project_with_stars)
+    end
+
+    it 'moves the user\'s stars from one project to another' do
+      expect(project_with_stars.users_star_projects.count).to eq 2
+      expect(project_with_stars.star_count).to eq 2
+      expect(target_project.users_star_projects.count).to eq 0
+      expect(target_project.star_count).to eq 0
+
+      subject.execute(project_with_stars)
+      project_with_stars.reload
+      target_project.reload
+
+      expect(project_with_stars.users_star_projects.count).to eq 0
+      expect(project_with_stars.star_count).to eq 0
+      expect(target_project.users_star_projects.count).to eq 2
+      expect(target_project.star_count).to eq 2
+    end
+
+    it 'rollbacks changes if transaction fails' do
+      allow(subject).to receive(:success).and_raise(StandardError)
+
+      expect { subject.execute(project_with_stars) }.to raise_error(StandardError)
+
+      expect(project_with_stars.users_star_projects.count).to eq 2
+      expect(project_with_stars.star_count).to eq 2
+      expect(target_project.users_star_projects.count).to eq 0
+      expect(target_project.star_count).to eq 0
+    end
+  end
+end
diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..252c61f42245177c7398db6ee179848d6a5d1e1d
--- /dev/null
+++ b/spec/services/projects/overwrite_project_service_spec.rb
@@ -0,0 +1,198 @@
+require 'spec_helper'
+
+describe Projects::OverwriteProjectService do
+  include ProjectForksHelper
+
+  let(:user) { create(:user) }
+  let(:project_from) { create(:project, namespace: user.namespace) }
+  let(:project_to) { create(:project, namespace: user.namespace) }
+  let!(:lvl1_forked_project_1) { fork_project(project_from, user) }
+  let!(:lvl1_forked_project_2) { fork_project(project_from, user) }
+  let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) }
+  let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) }
+
+  subject { described_class.new(project_to, user) }
+
+  before do
+    allow(project_to).to receive(:import_data).and_return(double(data: { 'original_path' => project_from.path }))
+  end
+
+  describe '#execute' do
+    shared_examples 'overwrite actions' do
+      it 'moves deploy keys' do
+        deploy_keys_count = project_from.deploy_keys_projects.count
+
+        subject.execute(project_from)
+
+        expect(project_to.deploy_keys_projects.count).to eq deploy_keys_count
+      end
+
+      it 'moves notification settings' do
+        notification_count = project_from.notification_settings.count
+
+        subject.execute(project_from)
+
+        expect(project_to.notification_settings.count).to eq notification_count
+      end
+
+      it 'moves users stars' do
+        stars_count = project_from.users_star_projects.count
+
+        subject.execute(project_from)
+        project_to.reload
+
+        expect(project_to.users_star_projects.count).to eq stars_count
+        expect(project_to.star_count).to eq stars_count
+      end
+
+      it 'moves project group links' do
+        group_links_count = project_from.project_group_links.count
+
+        subject.execute(project_from)
+
+        expect(project_to.project_group_links.count).to eq group_links_count
+      end
+
+      it 'moves memberships and authorizations' do
+        members_count = project_from.project_members.count
+        project_authorizations = project_from.project_authorizations.count
+
+        subject.execute(project_from)
+
+        expect(project_to.project_members.count).to eq members_count
+        expect(project_to.project_authorizations.count).to eq project_authorizations
+      end
+
+      context 'moves lfs objects relationships' do
+        before do
+          create_list(:lfs_objects_project, 3, project: project_from)
+        end
+
+        it do
+          lfs_objects_count = project_from.lfs_objects.count
+
+          subject.execute(project_from)
+
+          expect(project_to.lfs_objects.count).to eq lfs_objects_count
+        end
+      end
+
+      it 'removes the original project' do
+        subject.execute(project_from)
+
+        expect { Project.find(project_from.id) }.to raise_error(ActiveRecord::RecordNotFound)
+      end
+
+      it 'renames the project' do
+        subject.execute(project_from)
+
+        expect(project_to.full_path).to eq project_from.full_path
+      end
+    end
+
+    context 'when project does not have any relation' do
+      it_behaves_like 'overwrite actions'
+    end
+
+    context 'when project with elements' do
+      it_behaves_like 'overwrite actions' do
+        let(:master_user) { create(:user) }
+        let(:reporter_user) { create(:user) }
+        let(:developer_user) { create(:user) }
+        let(:master_group) { create(:group) }
+        let(:reporter_group) { create(:group) }
+        let(:developer_group) { create(:group) }
+
+        before do
+          create_list(:deploy_keys_project, 2, project: project_from)
+          create_list(:notification_setting, 2, source: project_from)
+          create_list(:users_star_project, 2, project: project_from)
+          project_from.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+          project_from.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+          project_from.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
+          project_from.add_master(master_user)
+          project_from.add_developer(developer_user)
+          project_from.add_reporter(reporter_user)
+        end
+      end
+    end
+
+    context 'forks' do
+      context 'when moving a root forked project' do
+        it 'moves the descendant forks' do
+          expect(project_from.forks.count).to eq 2
+          expect(project_to.forks.count).to eq 0
+
+          subject.execute(project_from)
+
+          expect(project_from.forks.count).to eq 0
+          expect(project_to.forks.count).to eq 2
+          expect(lvl1_forked_project_1.forked_from_project).to eq project_to
+          expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq project_to
+          expect(lvl1_forked_project_2.forked_from_project).to eq project_to
+          expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq project_to
+        end
+
+        it 'updates the fork network' do
+          expect(project_from.fork_network.root_project).to eq project_from
+          expect(project_from.fork_network.fork_network_members.map(&:project)).to include project_from
+
+          subject.execute(project_from)
+
+          expect(project_to.reload.fork_network.root_project).to eq project_to
+          expect(project_to.fork_network.fork_network_members.map(&:project)).not_to include project_from
+        end
+      end
+      context 'when moving a intermediate forked project' do
+        let(:project_to) { create(:project, namespace: lvl1_forked_project_1.namespace) }
+
+        it 'moves the descendant forks' do
+          expect(lvl1_forked_project_1.forks.count).to eq 2
+          expect(project_to.forks.count).to eq 0
+
+          subject.execute(lvl1_forked_project_1)
+
+          expect(lvl1_forked_project_1.forks.count).to eq 0
+          expect(project_to.forks.count).to eq 2
+          expect(lvl2_forked_project_1_1.forked_from_project).to eq project_to
+          expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq project_to
+          expect(lvl2_forked_project_1_2.forked_from_project).to eq project_to
+          expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq project_to
+        end
+
+        it 'moves the ascendant fork' do
+          subject.execute(lvl1_forked_project_1)
+
+          expect(project_to.reload.forked_from_project).to eq project_from
+          expect(project_to.fork_network_member.forked_from_project).to eq project_from
+        end
+
+        it 'does not update fork network' do
+          subject.execute(lvl1_forked_project_1)
+
+          expect(project_to.reload.fork_network.root_project).to eq project_from
+        end
+      end
+    end
+
+    context 'if an exception is raised' do
+      it 'rollbacks changes' do
+        updated_at = project_from.updated_at
+
+        allow(subject).to receive(:rename_project).and_raise(StandardError)
+
+        expect { subject.execute(project_from) }.to raise_error(StandardError)
+        expect(Project.find(project_from.id)).not_to be_nil
+        expect(project_from.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0)
+      end
+
+      it 'tries to restore the original project repositories' do
+        allow(subject).to receive(:rename_project).and_raise(StandardError)
+
+        expect(subject).to receive(:attempt_restore_repositories).with(project_from)
+
+        expect { subject.execute(project_from) }.to raise_error(StandardError)
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index ae0e22e3dc0c171cc4443662346c01ad57fdfe44..ff9b2372a358b0b57f9cd7e1d9f7e143a1ce1e74 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -37,6 +37,12 @@ describe Projects::TransferService do
       transfer_project(project, user, group)
     end
 
+    it 'invalidates the user\'s personal_project_count cache' do
+      expect(user).to receive(:invalidate_personal_projects_count)
+
+      transfer_project(project, user, group)
+    end
+
     it 'executes system hooks' do
       transfer_project(project, user, group) do |service|
         expect(service).to receive(:execute_system_hooks)
@@ -146,12 +152,12 @@ describe Projects::TransferService do
 
   context 'namespace which contains orphan repository with same projects path name' do
     let(:repository_storage) { 'default' }
-    let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
+    let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
 
     before do
       group.add_owner(user)
 
-      unless gitlab_shell.add_repository(repository_storage, "#{group.full_path}/#{project.path}")
+      unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}")
         raise 'failed to add repository'
       end
 
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index bfb86284d868ffe62ae1897f07377531f0ff104b..1b6caeab15d010a160bf53a540269686dc504a81 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -21,74 +21,72 @@ describe Projects::UpdatePagesService do
   end
 
   context 'legacy artifacts' do
-    %w(tar.gz zip).each do |format|
-      let(:extension) { format }
+    let(:extension) { 'zip' }
 
-      context "for valid #{format}" do
+    before do
+      build.update_attributes(legacy_artifacts_file: file)
+      build.update_attributes(legacy_artifacts_metadata: metadata)
+    end
+
+    describe 'pages artifacts' do
+      context 'with expiry date' do
         before do
-          build.update_attributes(legacy_artifacts_file: file)
-          build.update_attributes(legacy_artifacts_metadata: metadata)
+          build.artifacts_expire_in = "2 days"
+          build.save!
         end
 
-        describe 'pages artifacts' do
-          context 'with expiry date' do
-            before do
-              build.artifacts_expire_in = "2 days"
-            end
-
-            it "doesn't delete artifacts" do
-              expect(execute).to eq(:success)
-
-              expect(build.reload.artifacts?).to eq(true)
-            end
-          end
-
-          context 'without expiry date' do
-            it "does delete artifacts" do
-              expect(execute).to eq(:success)
+        it "doesn't delete artifacts" do
+          expect(execute).to eq(:success)
 
-              expect(build.reload.artifacts?).to eq(false)
-            end
-          end
+          expect(build.reload.artifacts?).to eq(true)
         end
+      end
 
-        it 'succeeds' do
-          expect(project.pages_deployed?).to be_falsey
+      context 'without expiry date' do
+        it "does delete artifacts" do
           expect(execute).to eq(:success)
-          expect(project.pages_deployed?).to be_truthy
 
-          # Check that all expected files are extracted
-          %w[index.html zero .hidden/file].each do |filename|
-            expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy
-          end
+          expect(build.reload.artifacts?).to eq(false)
         end
+      end
+    end
 
-        it 'limits pages size' do
-          stub_application_setting(max_pages_size: 1)
-          expect(execute).not_to eq(:success)
-        end
+    it 'succeeds' do
+      expect(project.pages_deployed?).to be_falsey
+      expect(execute).to eq(:success)
+      expect(project.pages_deployed?).to be_truthy
 
-        it 'removes pages after destroy' do
-          expect(PagesWorker).to receive(:perform_in)
-          expect(project.pages_deployed?).to be_falsey
-          expect(execute).to eq(:success)
-          expect(project.pages_deployed?).to be_truthy
-          project.destroy
-          expect(project.pages_deployed?).to be_falsey
-        end
+      # Check that all expected files are extracted
+      %w[index.html zero .hidden/file].each do |filename|
+        expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy
+      end
+    end
 
-        it 'fails if sha on branch is not latest' do
-          build.update_attributes(ref: 'feature')
+    it 'limits pages size' do
+      stub_application_setting(max_pages_size: 1)
+      expect(execute).not_to eq(:success)
+    end
 
-          expect(execute).not_to eq(:success)
-        end
+    it 'removes pages after destroy' do
+      expect(PagesWorker).to receive(:perform_in)
+      expect(project.pages_deployed?).to be_falsey
+      expect(execute).to eq(:success)
+      expect(project.pages_deployed?).to be_truthy
+      project.destroy
+      expect(project.pages_deployed?).to be_falsey
+    end
 
-        it 'fails for empty file fails' do
-          build.update_attributes(legacy_artifacts_file: empty_file)
+    it 'fails if sha on branch is not latest' do
+      build.update_attributes(ref: 'feature')
 
-          expect(execute).not_to eq(:success)
-        end
-      end
+      expect(execute).not_to eq(:success)
+    end
+
+    it 'fails for empty file fails' do
+      build.update_attributes(legacy_artifacts_file: empty_file)
+
+      expect { execute }
+        .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
     end
   end
 
@@ -105,6 +103,7 @@ describe Projects::UpdatePagesService do
         context 'with expiry date' do
           before do
             build.artifacts_expire_in = "2 days"
+            build.save!
           end
 
           it "doesn't delete artifacts" do
@@ -157,7 +156,54 @@ describe Projects::UpdatePagesService do
       it 'fails for empty file fails' do
         build.job_artifacts_archive.update_attributes(file: empty_file)
 
-        expect(execute).not_to eq(:success)
+        expect { execute }
+          .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
+      end
+
+      context 'when timeout happens by DNS error' do
+        before do
+          allow_any_instance_of(described_class)
+            .to receive(:extract_zip_archive!).and_raise(SocketError)
+        end
+
+        it 'raises an error' do
+          expect { execute }.to raise_error(SocketError)
+
+          build.reload
+          expect(deploy_status).to be_failed
+          expect(build.artifacts?).to be_truthy
+        end
+      end
+
+      context 'when failed to extract zip artifacts' do
+        before do
+          allow_any_instance_of(described_class)
+            .to receive(:extract_zip_archive!)
+            .and_raise(Projects::UpdatePagesService::FailedToExtractError)
+        end
+
+        it 'raises an error' do
+          expect { execute }
+            .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
+
+          build.reload
+          expect(deploy_status).to be_failed
+          expect(build.artifacts?).to be_truthy
+        end
+      end
+
+      context 'when missing artifacts metadata' do
+        before do
+          allow(build).to receive(:artifacts_metadata?).and_return(false)
+        end
+
+        it 'does not raise an error and remove artifacts as failed job' do
+          execute
+
+          build.reload
+          expect(deploy_status).to be_failed
+          expect(build.artifacts?).to be_falsey
+        end
       end
     end
   end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index ad5a289290cbc06f3b26d0a2b9746431d8191827..f48d466d26339dc97daf5d7ec142c98b522ec5b7 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -132,6 +132,15 @@ describe Projects::UpdateService do
         expect(result).to eq({ status: :success })
         expect(project.wiki_repository_exists?).to be false
       end
+
+      it 'handles empty project feature attributes' do
+        project.project_feature.update(wiki_access_level: ProjectFeature::DISABLED)
+
+        result = update_project(project, user, { name: 'test1' })
+
+        expect(result).to eq({ status: :success })
+        expect(project.wiki_repository_exists?).to be false
+      end
     end
 
     context 'when enabling a wiki' do
@@ -181,13 +190,13 @@ describe Projects::UpdateService do
 
     context 'when renaming a project' do
       let(:repository_storage) { 'default' }
-      let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
+      let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
 
       context 'with legacy storage' do
         let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) }
 
         before do
-          gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
+          gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
         end
 
         after do
@@ -232,6 +241,27 @@ describe Projects::UpdateService do
         })
       end
     end
+
+    context 'when updating #pages_https_only', :https_pages_enabled do
+      subject(:call_service) do
+        update_project(project, admin, pages_https_only: false)
+      end
+
+      it 'updates the attribute' do
+        expect { call_service }
+          .to change { project.pages_https_only? }
+          .to(false)
+      end
+
+      it 'calls Projects::UpdatePagesConfigurationService' do
+        expect(Projects::UpdatePagesConfigurationService)
+          .to receive(:new)
+          .with(project)
+          .and_call_original
+
+        call_service
+      end
+    end
   end
 
   describe '#run_auto_devops_pipeline?' do
diff --git a/spec/services/prometheus/adapter_service_spec.rb b/spec/services/prometheus/adapter_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..335fc5844aa4e6fed70c3284466b7865f1b703fe
--- /dev/null
+++ b/spec/services/prometheus/adapter_service_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Prometheus::AdapterService do
+  let(:project) { create(:project) }
+  subject { described_class.new(project) }
+
+  describe '#prometheus_adapter' do
+    let(:cluster) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [project]) }
+
+    context 'prometheus service can execute queries' do
+      let(:prometheus_service) { double(:prometheus_service, can_query?: true) }
+
+      before do
+        allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service
+      end
+
+      it 'return prometheus service as prometheus adapter' do
+        expect(subject.prometheus_adapter).to eq(prometheus_service)
+      end
+    end
+
+    context "prometheus service can't execute queries" do
+      let(:prometheus_service) { double(:prometheus_service, can_query?: false) }
+
+      context 'with cluster with prometheus installed' do
+        let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
+
+        it 'returns application handling all environments' do
+          expect(subject.prometheus_adapter).to eq(prometheus)
+        end
+      end
+
+      context 'with cluster without prometheus installed' do
+        it 'returns nil' do
+          expect(subject.prometheus_adapter).to be_nil
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 53b3e5e365dd5b456499e1df59619288663e31b1..786493c35779d9c296042e1ecf39c6bfb22c2559 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -35,5 +35,18 @@ describe ProtectedBranches::CreateService do
         expect { service.execute }.to raise_error(Gitlab::Access::AccessDeniedError)
       end
     end
+
+    context 'when a policy restricts rule creation' do
+      before do
+        policy = instance_double(ProtectedBranchPolicy, can?: false)
+        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+      end
+
+      it "prevents creation of the protected branch rule" do
+        expect do
+          service.execute
+        end.to raise_error(Gitlab::Access::AccessDeniedError)
+      end
+    end
   end
 end
diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4a391b6c25cc1a066338acc5d17515a3ef8efc86
--- /dev/null
+++ b/spec/services/protected_branches/destroy_service_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe ProtectedBranches::DestroyService do
+  let(:protected_branch) { create(:protected_branch) }
+  let(:project) { protected_branch.project }
+  let(:user) { project.owner }
+
+  describe '#execute' do
+    subject(:service) { described_class.new(project, user) }
+
+    it 'destroys a protected branch' do
+      service.execute(protected_branch)
+
+      expect(protected_branch).to be_destroyed
+    end
+
+    context 'when a policy restricts rule deletion' do
+      before do
+        policy = instance_double(ProtectedBranchPolicy, can?: false)
+        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+      end
+
+      it "prevents deletion of the protected branch rule" do
+        expect do
+          service.execute(protected_branch)
+        end.to raise_error(Gitlab::Access::AccessDeniedError)
+      end
+    end
+  end
+end
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 9fa5983db6677519f7038b53277b9cacc9c3b6b7..3f6f8e0956547fe9ef325a25abcf92bc6d0897e3 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -22,5 +22,16 @@ describe ProtectedBranches::UpdateService do
         expect { service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
       end
     end
+
+    context 'when a policy restricts rule creation' do
+      before do
+        policy = instance_double(ProtectedBranchPolicy, can?: false)
+        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
+      end
+
+      it "prevents creation of the protected branch rule" do
+        expect { service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
+      end
+    end
   end
 end
diff --git a/spec/services/protected_tags/destroy_service_spec.rb b/spec/services/protected_tags/destroy_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e12f53a2221af1eadc7ccec8ceb4379f9c2e2886
--- /dev/null
+++ b/spec/services/protected_tags/destroy_service_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe ProtectedTags::DestroyService do
+  let(:protected_tag) { create(:protected_tag) }
+  let(:project) { protected_tag.project }
+  let(:user) { project.owner }
+
+  describe '#execute' do
+    subject(:service) { described_class.new(project, user) }
+
+    it 'destroy a protected tag' do
+      service.execute(protected_tag)
+
+      expect(protected_tag).to be_destroyed
+    end
+  end
+end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index c40cd5b7548cd4feb986dbf44f5f5c6fa951fc17..51396d34f8f9ef9cb9a0b31b9e6d585ee74d3fed 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -30,6 +30,7 @@ describe SystemHooksService do
         :old_path_with_namespace
       )
     end
+
     it do
       project.old_path_with_namespace = 'transfered_from_path'
       expect(event_data(project, :transfer)).to include(
@@ -45,18 +46,21 @@ describe SystemHooksService do
         :owner_name, :owner_email
       )
     end
+
     it do
       expect(event_data(group, :destroy)).to include(
         :event_name, :name, :created_at, :updated_at, :path, :group_id,
         :owner_name, :owner_email
       )
     end
+
     it do
       expect(event_data(group_member, :create)).to include(
         :event_name, :created_at, :updated_at, :group_name, :group_path,
         :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
       )
     end
+
     it do
       expect(event_data(group_member, :destroy)).to include(
         :event_name, :created_at, :updated_at, :group_name, :group_path,
@@ -70,6 +74,14 @@ describe SystemHooksService do
       expect(data[:project_visibility]).to eq('private')
     end
 
+    it 'handles nil datetime columns' do
+      user.update_attributes(created_at: nil, updated_at: nil)
+      data = event_data(user, :destroy)
+
+      expect(data[:created_at]).to be(nil)
+      expect(data[:updated_at]).to be(nil)
+    end
+
     context 'group_rename' do
       it 'contains old and new path' do
         allow(group).to receive(:path_was).and_return('old-path')
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 5b5edc1aa0d269d4252b2db9e00bb62e9074c2d5..893804f1470429bbf96243a06fe8a0743ec150bd 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -743,7 +743,7 @@ describe SystemNoteService do
           expect(cross_reference(type)).to eq("Events for #{type.pluralize.humanize.downcase} are disabled.")
         end
 
-        it "blocks cross reference when #{type.underscore}_events is true" do
+        it "creates cross reference when #{type.underscore}_events is true" do
           jira_tracker.update("#{type}_events" => true)
 
           expect(cross_reference(type)).to eq(success_message)
@@ -789,7 +789,7 @@ describe SystemNoteService do
               object: {
                 url: project_commit_url(project, commit),
                 title: "GitLab: Mentioned on commit - #{commit.title}",
-                icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+                icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
                 status: { resolved: false }
               }
             )
@@ -815,7 +815,7 @@ describe SystemNoteService do
               object: {
                 url: project_issue_url(project, issue),
                 title: "GitLab: Mentioned on issue - #{issue.title}",
-                icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+                icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
                 status: { resolved: false }
               }
             )
@@ -841,7 +841,7 @@ describe SystemNoteService do
               object: {
                 url: project_snippet_url(project, snippet),
                 title: "GitLab: Mentioned on snippet - #{snippet.title}",
-                icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+                icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
                 status: { resolved: false }
               }
             )
@@ -909,7 +909,13 @@ describe SystemNoteService do
       it 'sets the note text' do
         noteable.update_attribute(:time_estimate, 277200)
 
-        expect(subject.note).to eq "changed time estimate to 1w 4d 5h"
+        expect(subject.note).to eq "changed time estimate to 1w 4d 5h,"
+      end
+
+      it 'appends a comma to separate the note from the update_at time' do
+        noteable.update_attribute(:time_estimate, 277200)
+
+        expect(subject.note).to end_with(',')
       end
     end
 
diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb
index 576db1dde2d1059b6792561cb40d896e12b3da78..d974cc0226f044a4502b9fb02eba1895e6c332d4 100644
--- a/spec/services/verify_pages_domain_service_spec.rb
+++ b/spec/services/verify_pages_domain_service_spec.rb
@@ -93,6 +93,25 @@ describe VerifyPagesDomainService do
           expect(domain).not_to be_enabled
         end
       end
+
+      context 'invalid domain' do
+        let(:domain) { build(:pages_domain, :expired, :with_missing_chain) }
+
+        before do
+          domain.save(validate: false)
+        end
+
+        it 'can be disabled' do
+          error_status[:message] += '. It is now disabled.'
+
+          stub_resolver
+
+          expect(service.execute).to eq(error_status)
+
+          expect(domain).not_to be_verified
+          expect(domain).not_to be_enabled
+        end
+      end
     end
 
     context 'timeout behaviour' do
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 21910e69d2eb2ddcef6db32edc162cfb013c70f3..2ef2e61babcdf2751e773b6df15b5e04a2f35e30 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -14,6 +14,20 @@ describe WebHookService do
   end
   let(:service_instance) { described_class.new(project_hook, data, :push_hooks) }
 
+  describe '#initialize' do
+    it 'allow_local_requests is true if hook is a SystemHook' do
+      instance = described_class.new(build(:system_hook), data, :system_hook)
+      expect(instance.request_options[:allow_local_requests]).to be_truthy
+    end
+
+    it 'allow_local_requests is false if hook is not a SystemHook' do
+      %i(project_hook service_hook web_hook_log).each do |hook|
+        instance = described_class.new(build(hook), data, hook)
+        expect(instance.request_options[:allow_local_requests]).to be_falsey
+      end
+    end
+  end
+
   describe '#execute' do
     before do
       project.hooks << [project_hook]
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9f6f0204a16f49b63b6378c50fdf03816fc4697e..83664bae046b4b6de1332a6a653567108fbf8a94 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -66,6 +66,7 @@ RSpec.configure do |config|
   config.include MigrationsHelpers, :migration
   config.include StubFeatureFlags
   config.include StubENV
+  config.include ExpectOffense
 
   config.infer_spec_type_from_file_location!
 
@@ -97,6 +98,10 @@ RSpec.configure do |config|
     TestEnv.init
   end
 
+  config.after(:all) do
+    TestEnv.clean_test_path
+  end
+
   config.before(:example) do
     # Skip pre-receive hook check so we can use the web editor and merge.
     allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
@@ -104,7 +109,8 @@ RSpec.configure do |config|
     allow_any_instance_of(Gitlab::Git::GitlabProjects).to receive(:fork_repository).and_wrap_original do |m, *args|
       m.call(*args)
 
-      shard_path, repository_relative_path = args
+      shard_name, repository_relative_path = args
+      shard_path = Gitlab.config.repositories.storages.fetch(shard_name).legacy_disk_path
       # We can't leave the hooks in place after a fork, as those would fail in tests
       # The "internal" API is not available
       FileUtils.rm_rf(File.join(shard_path, repository_relative_path, 'hooks'))
@@ -197,6 +203,22 @@ RSpec.configure do |config|
       Ability.allowed?(*args)
     end
   end
+
+  config.before(:each, :http_pages_enabled) do |_|
+    allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80'])
+  end
+
+  config.before(:each, :https_pages_enabled) do |_|
+    allow(Gitlab.config.pages).to receive(:external_https).and_return(['1.1.1.1:443'])
+  end
+
+  config.before(:each, :http_pages_disabled) do |_|
+    allow(Gitlab.config.pages).to receive(:external_http).and_return(false)
+  end
+
+  config.before(:each, :https_pages_disabled) do |_|
+    allow(Gitlab.config.pages).to receive(:external_https).and_return(false)
+  end
 end
 
 # add simpler way to match asset paths containing digest strings
diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb
index 38d11992dc285c657d547dbbb841f8d18be5948d..3f4a4243cb6e9e96de20f2f2f0501626ee6c8128 100644
--- a/spec/support/bare_repo_operations.rb
+++ b/spec/support/bare_repo_operations.rb
@@ -1,19 +1,23 @@
 require 'zlib'
 
 class BareRepoOperations
-  # The ID of empty tree.
-  # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
-  EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
-
   include Gitlab::Popen
 
   def initialize(path_to_repo)
     @path_to_repo = path_to_repo
   end
 
+  def commit_tree(tree_id, msg, parent: Gitlab::Git::EMPTY_TREE_ID)
+    commit_tree_args = ['commit-tree', tree_id, '-m', msg]
+    commit_tree_args += ['-p', parent] unless parent == Gitlab::Git::EMPTY_TREE_ID
+    commit_id = execute(commit_tree_args)
+
+    commit_id[0]
+  end
+
   # Based on https://stackoverflow.com/a/25556917/1856239
   def commit_file(file, dst_path, branch = 'master')
-    head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID
+    head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || Gitlab::Git::EMPTY_TREE_ID
 
     execute(['read-tree', '--empty'])
     execute(['read-tree', head_id])
@@ -26,11 +30,9 @@ class BareRepoOperations
 
     tree_id = execute(['write-tree'])
 
-    commit_tree_args = ['commit-tree', tree_id[0], '-m', "Add #{dst_path}"]
-    commit_tree_args += ['-p', head_id] unless head_id == EMPTY_TREE_ID
-    commit_id = execute(commit_tree_args)
+    commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id)
 
-    execute(['update-ref', "refs/heads/#{branch}", commit_id[0]])
+    execute(['update-ref', "refs/heads/#{branch}", commit_id])
   end
 
   private
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 8603b7f3e2c1a6f016c96885d5ac6fb86a7dbd9d..9ddcc5f2fbf90354da4a2cebd59946073d256577 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -7,6 +7,16 @@ require 'selenium-webdriver'
 # Give CI some extra time
 timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
 
+# Define an error class for JS console messages
+JSConsoleError = Class.new(StandardError)
+
+# Filter out innocuous JS console messages
+JS_CONSOLE_FILTER = Regexp.union([
+  '"[HMR] Waiting for update signal from WDS..."',
+  '"[WDS] Hot Module Replacement enabled."',
+  "Download the Vue Devtools extension"
+])
+
 Capybara.register_driver :chrome do |app|
   capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
     # This enables access to logs with `page.driver.manage.get_log(:browser)`
@@ -25,13 +35,7 @@ Capybara.register_driver :chrome do |app|
   options.add_argument("no-sandbox")
 
   # Run headless by default unless CHROME_HEADLESS specified
-  unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
-    options.add_argument("headless")
-
-    # Chrome documentation says this flag is needed for now
-    # https://developers.google.com/web/updates/2017/04/headless-chrome#cli
-    options.add_argument("disable-gpu")
-  end
+  options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
 
   # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
   options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
@@ -78,6 +82,15 @@ RSpec.configure do |config|
   end
 
   config.after(:example, :js) do |example|
+    # when a test fails, display any messages in the browser's console
+    if example.exception
+      console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
+      if console.present?
+        message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
+        raise JSConsoleError, message
+      end
+    end
+
     # prevent localStorage from introducing side effects based on test order
     unless ['', 'about:blank', 'data:,'].include? Capybara.current_session.driver.browser.current_url
       execute_script("localStorage.clear();")
diff --git a/spec/support/commit_trailers_spec_helper.rb b/spec/support/commit_trailers_spec_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..add359946db432a93538e043a9daed058fd32156
--- /dev/null
+++ b/spec/support/commit_trailers_spec_helper.rb
@@ -0,0 +1,41 @@
+module CommitTrailersSpecHelper
+  extend ActiveSupport::Concern
+
+  def expect_to_have_user_link_with_avatar(doc, user:, trailer:, email: nil)
+    wrapper = find_user_wrapper(doc, trailer)
+
+    expect_to_have_links_with_url_and_avatar(wrapper, urls.user_url(user), email || user.email)
+    expect(wrapper.attribute('data-user').value).to eq user.id.to_s
+  end
+
+  def expect_to_have_mailto_link(doc, email:, trailer:)
+    wrapper = find_user_wrapper(doc, trailer)
+
+    expect_to_have_links_with_url_and_avatar(wrapper, "mailto:#{CGI.escape_html(email)}", email)
+  end
+
+  def expect_to_have_links_with_url_and_avatar(doc, url, email)
+    expect(doc).not_to be_nil
+    expect(doc.xpath("a[position()<3 and @href='#{url}']").size).to eq 2
+    expect(doc.xpath("a[position()=3 and @href='mailto:#{CGI.escape_html(email)}']").size).to eq 1
+    expect(doc.css('img').size).to eq 1
+  end
+
+  def find_user_wrapper(doc, trailer)
+    doc.xpath("descendant-or-self::node()[@data-trailer='#{trailer}']").first
+  end
+
+  def build_commit_message(trailer:, name:, email:)
+    message = trailer_line(trailer, name, email)
+
+    [message, commit_html(message)]
+  end
+
+  def trailer_line(trailer, name, email)
+    "#{trailer} #{name} <#{email}>"
+  end
+
+  def commit_html(message)
+    "<pre>#{CGI.escape_html(message)}</pre>"
+  end
+end
diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb
index d72925e1838a690c959f90dded1bba878068e64b..5ff7b0b68c9268bf0868c22a5b9c5bfe9f30ccfa 100644
--- a/spec/support/cookie_helper.rb
+++ b/spec/support/cookie_helper.rb
@@ -2,12 +2,25 @@
 #
 module CookieHelper
   def set_cookie(name, value, options = {})
+    case page.driver
+    when Capybara::RackTest::Driver
+      rack_set_cookie(name, value)
+    else
+      selenium_set_cookie(name, value, options)
+    end
+  end
+
+  def selenium_set_cookie(name, value, options = {})
     # Selenium driver will not set cookies for a given domain when the browser is at `about:blank`.
     # It also doesn't appear to allow overriding the cookie path. loading `/` is the most inclusive.
     visit options.fetch(:path, '/') unless on_a_page?
     page.driver.browser.manage.add_cookie(name: name, value: value, **options)
   end
 
+  def rack_set_cookie(name, value)
+    page.driver.browser.set_cookie("#{name}=#{value}")
+  end
+
   def get_cookie(name)
     page.driver.browser.manage.cookie_named(name)
   end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index c8662d417696c2ffefcb88ce8a6616441436e791..80604395adfea7b9285ff5cbb90aa1eb12322664 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -81,7 +81,10 @@ shared_examples 'discussion comments' do |resource_name|
 
       # on issues page, the menu closes when clicking anywhere, on other pages it will
       # remain open if clicking divider or menu padding, but should not change button action
-      if resource_name == 'issue'
+      #
+      # if dropdown menu is not toggled (and also not present),
+      # it's "issue-type" dropdown
+      if first(menu_selector).nil?
         expect(find(dropdown_selector)).to have_content 'Comment'
 
         find(toggle_selector).click
@@ -107,8 +110,10 @@ shared_examples 'discussion comments' do |resource_name|
       end
 
       it 'updates the submit button text and closes the dropdown' do
+        button = find(submit_selector)
+
         # on issues page, the submit input is a <button>, on other pages it is <input>
-        if resource_name == 'issue'
+        if button.tag_name == 'button'
           expect(find(submit_selector)).to have_content 'Start discussion'
         else
           expect(find(submit_selector).value).to eq 'Start discussion'
@@ -132,6 +137,8 @@ shared_examples 'discussion comments' do |resource_name|
       describe 'creating a discussion' do
         before do
           find(submit_selector).click
+          wait_for_requests
+
           find(comments_selector, match: :first)
         end
 
@@ -197,11 +204,13 @@ shared_examples 'discussion comments' do |resource_name|
           end
 
           it 'updates the submit button text and closes the dropdown' do
+            button = find(submit_selector)
+
             # on issues page, the submit input is a <button>, on other pages it is <input>
-            if resource_name == 'issue'
-              expect(find(submit_selector)).to have_content 'Comment'
+            if button.tag_name == 'button'
+              expect(button).to have_content 'Comment'
             else
-              expect(find(submit_selector).value).to eq 'Comment'
+              expect(button.value).to eq 'Comment'
             end
 
             expect(page).not_to have_selector menu_selector
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 2c20821ac3f6c0b517b5e396989c8fde2c79a565..1bd6c25100e111b8c0c6f75053cf9e93962100f3 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -2,7 +2,7 @@
 # It takes a `issuable_type`, and expect an `issuable`.
 
 shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type|
-  include QuickActionsHelpers
+  include Spec::Support::Helpers::Features::NotesHelpers
 
   let(:master) { create(:user) }
   let(:project) do
@@ -61,7 +61,7 @@ shared_examples 'issuable record that supports quick actions in its description
     context 'with a note containing commands' do
       it 'creates a note without the commands and interpret the commands accordingly' do
         assignee = create(:user, username: 'bob')
-        write_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
+        add_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
 
         expect(page).to have_content 'Awesome!'
         expect(page).not_to have_content '/assign @bob'
@@ -82,7 +82,7 @@ shared_examples 'issuable record that supports quick actions in its description
     context 'with a note containing only commands' do
       it 'does not create a note but interpret the commands accordingly' do
         assignee = create(:user, username: 'bob')
-        write_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
+        add_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
 
         expect(page).not_to have_content '/assign @bob'
         expect(page).not_to have_content '/label ~bug'
@@ -105,7 +105,7 @@ shared_examples 'issuable record that supports quick actions in its description
 
       context "when current user can close #{issuable_type}" do
         it "closes the #{issuable_type}" do
-          write_note("/close")
+          add_note("/close")
 
           expect(page).not_to have_content '/close'
           expect(page).to have_content 'Commands applied'
@@ -125,9 +125,8 @@ shared_examples 'issuable record that supports quick actions in its description
         end
 
         it "does not close the #{issuable_type}" do
-          write_note("/close")
+          add_note("/close")
 
-          expect(page).to have_content '/close'
           expect(page).not_to have_content 'Commands applied'
 
           expect(issuable).to be_open
@@ -143,7 +142,7 @@ shared_examples 'issuable record that supports quick actions in its description
 
       context "when current user can reopen #{issuable_type}" do
         it "reopens the #{issuable_type}" do
-          write_note("/reopen")
+          add_note("/reopen")
 
           expect(page).not_to have_content '/reopen'
           expect(page).to have_content 'Commands applied'
@@ -163,9 +162,8 @@ shared_examples 'issuable record that supports quick actions in its description
         end
 
         it "does not reopen the #{issuable_type}" do
-          write_note("/reopen")
+          add_note("/reopen")
 
-          expect(page).to have_content '/reopen'
           expect(page).not_to have_content 'Commands applied'
 
           expect(issuable).to be_closed
@@ -176,7 +174,7 @@ shared_examples 'issuable record that supports quick actions in its description
     context "with a note changing the #{issuable_type}'s title" do
       context "when current user can change title of #{issuable_type}" do
         it "reopens the #{issuable_type}" do
-          write_note("/title Awesome new title")
+          add_note("/title Awesome new title")
 
           expect(page).not_to have_content '/title'
           expect(page).to have_content 'Commands applied'
@@ -195,10 +193,9 @@ shared_examples 'issuable record that supports quick actions in its description
           visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
         end
 
-        it "does not reopen the #{issuable_type}" do
-          write_note("/title Awesome new title")
+        it "does not change the #{issuable_type} title" do
+          add_note("/title Awesome new title")
 
-          expect(page).to have_content '/title'
           expect(page).not_to have_content 'Commands applied'
 
           expect(issuable.reload.title).not_to eq 'Awesome new title'
@@ -208,7 +205,7 @@ shared_examples 'issuable record that supports quick actions in its description
 
     context "with a note marking the #{issuable_type} as todo" do
       it "creates a new todo for the #{issuable_type}" do
-        write_note("/todo")
+        add_note("/todo")
 
         expect(page).not_to have_content '/todo'
         expect(page).to have_content 'Commands applied'
@@ -239,7 +236,7 @@ shared_examples 'issuable record that supports quick actions in its description
         expect(todo.author).to eq master
         expect(todo.user).to eq master
 
-        write_note("/done")
+        add_note("/done")
 
         expect(page).not_to have_content '/done'
         expect(page).to have_content 'Commands applied'
@@ -252,7 +249,7 @@ shared_examples 'issuable record that supports quick actions in its description
       it "creates a new todo for the #{issuable_type}" do
         expect(issuable.subscribed?(master, project)).to be_falsy
 
-        write_note("/subscribe")
+        add_note("/subscribe")
 
         expect(page).not_to have_content '/subscribe'
         expect(page).to have_content 'Commands applied'
@@ -269,7 +266,7 @@ shared_examples 'issuable record that supports quick actions in its description
       it "creates a new todo for the #{issuable_type}" do
         expect(issuable.subscribed?(master, project)).to be_truthy
 
-        write_note("/unsubscribe")
+        add_note("/unsubscribe")
 
         expect(page).not_to have_content '/unsubscribe'
         expect(page).to have_content 'Commands applied'
@@ -280,7 +277,7 @@ shared_examples 'issuable record that supports quick actions in its description
 
     context "with a note assigning the #{issuable_type} to the current user" do
       it "assigns the #{issuable_type} to the current user" do
-        write_note("/assign me")
+        add_note("/assign me")
 
         expect(page).not_to have_content '/assign me'
         expect(page).to have_content 'Commands applied'
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index b871b7ffc906a0e0f3df307df7a5ed481380caf9..721d359c2eeeda2a444b0dd81a3cc1c7cc8bdd6a 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -18,6 +18,11 @@ module FilterSpecHelper
       context.reverse_merge!(project: project)
     end
 
+    render_context = Banzai::RenderContext
+      .new(context[:project], context[:current_user])
+
+    context = context.merge(render_context: render_context)
+
     described_class.call(html, context)
   end
 
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
index f3f96bd1f0a01eb97869f2eab22a3e0807cd89dc..5f42ff77fb23a7f1fc84e5ad271187435be2df59 100644
--- a/spec/support/filtered_search_helpers.rb
+++ b/spec/support/filtered_search_helpers.rb
@@ -21,6 +21,29 @@ module FilteredSearchHelpers
     end
   end
 
+  # Select a label clicking in the search dropdown instead
+  # of entering label names on the input.
+  def select_label_on_dropdown(label_title)
+    input_filtered_search("label:", submit: false)
+
+    within('#js-dropdown-label') do
+      wait_for_requests
+
+      find('li', text: label_title).click
+    end
+
+    filtered_search.send_keys(:enter)
+  end
+
+  def expect_issues_list_count(open_count, closed_count = 0)
+    all_count = open_count + closed_count
+
+    expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
+    page.within '.issues-list' do
+      expect(page).to have_selector('.issue', count: open_count)
+    end
+  end
+
   # Enables input to be added character by character
   def input_filtered_search_keys(search_term)
     # Add an extra space to engage visual tokens
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
index c7e8a39a6170dbc24c0984ec1d0f5a11e81c7858..9cf541372b5118f7d4c91fe29331d2f30682c27b 100644
--- a/spec/support/gitaly.rb
+++ b/spec/support/gitaly.rb
@@ -1,11 +1,13 @@
 RSpec.configure do |config|
   config.before(:each) do |example|
     if example.metadata[:disable_gitaly]
-      allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
+      # Use 'and_wrap_original' to make sure the arguments are valid
+      allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original { |m, *args| m.call(*args) && false }
     else
       next if example.metadata[:skip_gitaly_mock]
 
-      allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
+      # Use 'and_wrap_original' to make sure the arguments are valid
+      allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original { |m, *args| m.call(*args) || true }
     end
   end
 end
diff --git a/spec/support/gitlab_verify.rb b/spec/support/gitlab_verify.rb
new file mode 100644
index 0000000000000000000000000000000000000000..13e2e37624da7eca2b281b02ae956ba10578393b
--- /dev/null
+++ b/spec/support/gitlab_verify.rb
@@ -0,0 +1,45 @@
+RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do
+  describe 'batching' do
+    let(:first_batch) { objects[0].id..objects[0].id }
+    let(:second_batch) { objects[1].id..objects[1].id }
+    let(:third_batch) { objects[2].id..objects[2].id }
+
+    it 'iterates through objects in batches' do
+      expect(collect_ranges).to eq([first_batch, second_batch, third_batch])
+    end
+
+    it 'allows the starting ID to be specified' do
+      expect(collect_ranges(start: second_batch.first)).to eq([second_batch, third_batch])
+    end
+
+    it 'allows the finishing ID to be specified' do
+      expect(collect_ranges(finish: second_batch.last)).to eq([first_batch, second_batch])
+    end
+  end
+end
+
+module GitlabVerifyHelpers
+  def collect_ranges(args = {})
+    verifier = described_class.new(args.merge(batch_size: 1))
+
+    collect_results(verifier).map { |range, _| range }
+  end
+
+  def collect_failures
+    verifier = described_class.new(batch_size: 1)
+
+    out = {}
+
+    collect_results(verifier).map { |_, failures| out.merge!(failures) }
+
+    out
+  end
+
+  def collect_results(verifier)
+    out = []
+
+    verifier.run_batches { |*args| out << args }
+
+    out
+  end
+end
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
new file mode 100644
index 0000000000000000000000000000000000000000..35718ba90c5edd12744ec55b718d51d074f2854e
--- /dev/null
+++ b/spec/support/helpers/expect_offense.rb
@@ -0,0 +1,20 @@
+require 'rubocop/rspec/support'
+
+# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
+# rubocop-rspec gem extension of RuboCop's ExpectOffense module.
+#
+# This mixin is the same as rubocop's ExpectOffense except the default
+# filename ends with `_spec.rb`
+module ExpectOffense
+  include RuboCop::RSpec::ExpectOffense
+
+  DEFAULT_FILENAME = 'example_spec.rb'.freeze
+
+  def expect_offense(source, filename = DEFAULT_FILENAME)
+    super
+  end
+
+  def expect_no_offenses(source, filename = DEFAULT_FILENAME)
+    super
+  end
+end
diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3525d9a70a710609b05a1ef0c3f333e57f985c4a
--- /dev/null
+++ b/spec/support/helpers/features/branches_helpers.rb
@@ -0,0 +1,33 @@
+# These helpers allow you to manipulate with sorting features.
+#
+# Usage:
+#   describe "..." do
+#     include Spec::Support::Helpers::Features::BranchesHelpers
+#     ...
+#
+#     create_branch("feature")
+#     select_branch("master")
+#
+module Spec
+  module Support
+    module Helpers
+      module Features
+        module BranchesHelpers
+          def create_branch(branch_name, source_branch_name = "master")
+            fill_in("branch_name", with: branch_name)
+            select_branch(source_branch_name)
+            click_button("Create branch")
+          end
+
+          def select_branch(branch_name)
+            find(".git-revision-dropdown-toggle").click
+
+            page.within("#new-branch-form .dropdown-menu") do
+              click_link(branch_name)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1a1d5853a7a7929a6d91bf174b7e0a29fd49a5b1
--- /dev/null
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -0,0 +1,27 @@
+# These helpers allow you to manipulate with notes.
+#
+# Usage:
+#   describe "..." do
+#     include Spec::Support::Helpers::Features::NotesHelpers
+#     ...
+#
+#     add_note("Hello world!")
+#
+module Spec
+  module Support
+    module Helpers
+      module Features
+        module NotesHelpers
+          def add_note(text)
+            Sidekiq::Testing.fake! do
+              page.within(".js-main-target-form") do
+                fill_in("note[note]", with: text)
+                find(".js-comment-submit-button").click
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..50457b647455b5d14fcc2c5d9bc0d1cfc12c2e92
--- /dev/null
+++ b/spec/support/helpers/features/sorting_helpers.rb
@@ -0,0 +1,26 @@
+# These helpers allow you to manipulate with sorting features.
+#
+# Usage:
+#   describe "..." do
+#     include Spec::Support::Helpers::Features::SortingHelpers
+#     ...
+#
+#     sort_by("Last updated")
+#
+module Spec
+  module Support
+    module Helpers
+      module Features
+        module SortingHelpers
+          def sort_by(value)
+            find('button.dropdown-toggle').click
+
+            page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
+              click_link(value)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/http_io/http_io_helpers.rb b/spec/support/http_io/http_io_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2c68c2cd9a6f959006ea7ba4c83401637008dff4
--- /dev/null
+++ b/spec/support/http_io/http_io_helpers.rb
@@ -0,0 +1,65 @@
+module HttpIOHelpers
+  def stub_remote_trace_206
+    WebMock.stub_request(:get, remote_trace_url)
+      .to_return { |request| remote_trace_response(request, 206) }
+  end
+
+  def stub_remote_trace_200
+    WebMock.stub_request(:get, remote_trace_url)
+      .to_return { |request| remote_trace_response(request, 200) }
+  end
+
+  def stub_remote_trace_500
+    WebMock.stub_request(:get, remote_trace_url)
+      .to_return(status: [500, "Internal Server Error"])
+  end
+
+  def remote_trace_url
+    "http://trace.com/trace"
+  end
+
+  def remote_trace_response(request, responce_status)
+    range = request.headers['Range'].match(/bytes=(\d+)-(\d+)/)
+
+    {
+      status: responce_status,
+      headers: remote_trace_response_headers(responce_status, range[1].to_i, range[2].to_i),
+      body: range_trace_body(range[1].to_i, range[2].to_i)
+    }
+  end
+
+  def remote_trace_response_headers(responce_status, from, to)
+    headers = { 'Content-Type' => 'text/plain' }
+
+    if responce_status == 206
+      headers.merge('Content-Range' => "bytes #{from}-#{to}/#{remote_trace_size}")
+    end
+
+    headers
+  end
+
+  def range_trace_body(from, to)
+    remote_trace_body[from..to]
+  end
+
+  def remote_trace_body
+    @remote_trace_body ||= File.read(expand_fixture_path('trace/sample_trace'))
+      .force_encoding(Encoding::BINARY)
+  end
+
+  def remote_trace_size
+    remote_trace_body.bytesize
+  end
+
+  def set_smaller_buffer_size_than(file_size)
+    blocks = (file_size / 128)
+    new_size = (blocks / 2) * 128
+    stub_const("Gitlab::Ci::Trace::HttpIO::BUFFER_SIZE", new_size)
+  end
+
+  def set_larger_buffer_size_than(file_size)
+    blocks = (file_size / 128)
+    new_size = (blocks * 2) * 128
+    stub_const("Gitlab::Ci::Trace::HttpIO::BUFFER_SIZE", new_size)
+  end
+end
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
index 75982432ab42e9a2628c175652e7534b3bb752c8..e61983c60b41e9e07af982a5af6c28234da5bf55 100644
--- a/spec/support/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -5,9 +5,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
     %w[fix improve/awesome].each do |source_branch|
       issuable =
         if issuable_type == :issue
-          create(issuable_type, project: project)
+          create(issuable_type, project: project, author: project.creator)
         else
-          create(issuable_type, source_project: project, source_branch: source_branch)
+          create(issuable_type, source_project: project, source_branch: source_branch, author: project.creator)
         end
 
       @issuable_ids << issuable.id
@@ -16,7 +16,7 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
 
   it "creates indexed meta-data object for issuable notes and votes count" do
     if action
-      get action
+      get action, author_id: project.creator.id
     else
       get :index, namespace_id: project.namespace, project_id: project
     end
@@ -35,7 +35,7 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
     it "doesn't execute any queries with false conditions" do
       get_action =
         if action
-          proc { get action }
+          proc { get action, author_id: project.creator.id }
         else
           proc { get :index, namespace_id: project2.namespace, project_id: project2 }
         end
diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..439ef5ed92e331581173728de3a300385b96b74b
--- /dev/null
+++ b/spec/support/issuables_requiring_filter_shared_examples.rb
@@ -0,0 +1,15 @@
+shared_examples 'issuables requiring filter' do |action|
+  it "doesn't load any issuables if no filter is set" do
+    expect_any_instance_of(described_class).not_to receive(:issuables_collection)
+
+    get action
+
+    expect(response).to render_template(action)
+  end
+
+  it "loads issuables if at least one filter is set" do
+    expect_any_instance_of(described_class).to receive(:issuables_collection).and_call_original
+
+    get action, author_id: user.id
+  end
+end
diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb
index 081ce0ad7b76cb11bd0f08eca59e435e713acc8b..0e87b3d359dd3efab03431b9fce399ad88400754 100644
--- a/spec/support/ldap_helpers.rb
+++ b/spec/support/ldap_helpers.rb
@@ -41,4 +41,9 @@ module LdapHelpers
 
     entry
   end
+
+  def raise_ldap_connection_error
+    allow_any_instance_of(Gitlab::Auth::LDAP::Adapter)
+      .to receive(:ldap_search).and_raise(Gitlab::Auth::LDAP::LDAPConnectionError)
+  end
 end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index d08183846a02c1b02e9a584f5ae28ce8284e094b..db34090e971b54eb19e95acb3ef93419a0a6339e 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -140,6 +140,10 @@ module LoginHelpers
     end
     allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
     stub_omniauth_setting(messages)
+    stub_saml_authorize_path_helpers
+  end
+
+  def stub_saml_authorize_path_helpers
     allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml')
     allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
   end
diff --git a/spec/support/matchers/have_emoji.rb b/spec/support/matchers/have_emoji.rb
new file mode 100644
index 0000000000000000000000000000000000000000..23fb8e9c1c44e3db97d2ba110cb9ca7f2c334d0c
--- /dev/null
+++ b/spec/support/matchers/have_emoji.rb
@@ -0,0 +1,5 @@
+RSpec::Matchers.define :have_emoji do |emoji_name|
+  match do |actual|
+    expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']")
+  end
+end
diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f5d9a97051ae0c9a2bbd8a42ca1ae88bc62fffd0
--- /dev/null
+++ b/spec/support/matchers/issuable_matchers.rb
@@ -0,0 +1,11 @@
+RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".wiki"|
+  match do |actual|
+    node = find("#{parent} h#{level} a#user-content-#{id}")
+
+    expect(node[:href]).to end_with("##{id}")
+
+    # Work around a weird Capybara behavior where calling `parent` on a node
+    # returns the whole document, not the node's actual parent element
+    expect(find(:xpath, "#{node.path}/..").text).to eq(text)
+  end
+end
diff --git a/spec/support/matchers/match_ids.rb b/spec/support/matchers/match_ids.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d8424405b9611c428457683652393e93f1d68384
--- /dev/null
+++ b/spec/support/matchers/match_ids.rb
@@ -0,0 +1,24 @@
+RSpec::Matchers.define :match_ids do |*expected|
+  match do |actual|
+    actual_ids = map_ids(actual)
+    expected_ids = map_ids(expected)
+
+    expect(actual_ids).to match_array(expected_ids)
+  end
+
+  description do
+    'matches elements by ids'
+  end
+
+  def map_ids(elements)
+    elements = elements.flatten if elements.respond_to?(:flatten)
+
+    if elements.respond_to?(:map)
+      elements.map(&:id)
+    elsif elements.respond_to?(:id)
+      [elements.id]
+    else
+      raise ArgumentError, "could not map elements to ids: #{elements}"
+    end
+  end
+end
diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb
index 6bf976a2cf9a857d0c7f623bb90b02593428ca87..5d6f662e8fe93e164063abbf4d3674cb9c2f6dcb 100644
--- a/spec/support/migrations_helpers.rb
+++ b/spec/support/migrations_helpers.rb
@@ -1,6 +1,9 @@
 module MigrationsHelpers
   def table(name)
-    Class.new(ActiveRecord::Base) { self.table_name = name }
+    Class.new(ActiveRecord::Base) do
+      self.table_name = name
+      self.inheritance_column = :_type_disabled
+    end
   end
 
   def migrations_paths
diff --git a/spec/support/quick_actions_helpers.rb b/spec/support/quick_actions_helpers.rb
deleted file mode 100644
index 361190aa3521ed0ef2999b96b4c8d3facfbcc251..0000000000000000000000000000000000000000
--- a/spec/support/quick_actions_helpers.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module QuickActionsHelpers
-  def write_note(text)
-    Sidekiq::Testing.fake! do
-      page.within('.js-main-target-form') do
-        fill_in 'note[note]', with: text
-        find('.js-comment-submit-button').click
-      end
-    end
-  end
-end
diff --git a/spec/support/reference_parser_helpers.rb b/spec/support/reference_parser_helpers.rb
index 01689194eac2b7b5d2f2caf5530d06edda2a2047..c01897ed1a1e3ac6141e806f1da1072d9561ecd8 100644
--- a/spec/support/reference_parser_helpers.rb
+++ b/spec/support/reference_parser_helpers.rb
@@ -2,4 +2,38 @@ module ReferenceParserHelpers
   def empty_html_link
     Nokogiri::HTML.fragment('<a></a>').children[0]
   end
+
+  shared_examples 'no N+1 queries' do
+    it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
+      context = Banzai::RenderContext.new(project, user)
+
+      record_queries = lambda do |links|
+        ActiveRecord::QueryRecorder.new do
+          described_class.new(context).nodes_visible_to_user(user, links)
+        end
+      end
+
+      control = record_queries.call(control_links)
+      actual = record_queries.call(actual_links)
+
+      expect(actual.count).to be <= control.count
+      expect(actual.cached_count).to be <= control.cached_count
+    end
+
+    it 'avoids N+1 queries in #records_for_nodes', :request_store do
+      context = Banzai::RenderContext.new(project, user)
+
+      record_queries = lambda do |links|
+        ActiveRecord::QueryRecorder.new do
+          described_class.new(context).records_for_nodes(links)
+        end
+      end
+
+      control = record_queries.call(control_links)
+      actual = record_queries.call(actual_links)
+
+      expect(actual.count).to be <= control.count
+      expect(actual.cached_count).to be <= control.cached_count
+    end
+  end
 end
diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb
index d7acf8c0032ab7333d5e2b88849eb9696e08ecc6..b615a8f54cfc9e96027b9069629d9e886f4199a7 100644
--- a/spec/support/shared_examples/controllers/variables_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb
@@ -16,19 +16,19 @@ shared_examples 'PATCH #update updates variables' do
   let(:variable_attributes) do
     { id: variable.id,
       key: variable.key,
-      value: variable.value,
+      secret_value: variable.value,
       protected: variable.protected?.to_s }
   end
   let(:new_variable_attributes) do
     { key: 'new_key',
-      value: 'dummy_value',
+      secret_value: 'dummy_value',
       protected: 'false' }
   end
 
   context 'with invalid new variable parameters' do
     let(:variables_attributes) do
       [
-        variable_attributes.merge(value: 'other_value'),
+        variable_attributes.merge(secret_value: 'other_value'),
         new_variable_attributes.merge(key: '...?')
       ]
     end
@@ -52,7 +52,7 @@ shared_examples 'PATCH #update updates variables' do
     let(:variables_attributes) do
       [
         new_variable_attributes,
-        new_variable_attributes.merge(value: 'other_value')
+        new_variable_attributes.merge(secret_value: 'other_value')
       ]
     end
 
@@ -74,7 +74,7 @@ shared_examples 'PATCH #update updates variables' do
   context 'with valid new variable parameters' do
     let(:variables_attributes) do
       [
-        variable_attributes.merge(value: 'other_value'),
+        variable_attributes.merge(secret_value: 'other_value'),
         new_variable_attributes
       ]
     end
diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..144af4fc475625fde1ad389ad7f51687ee65c7c8
--- /dev/null
+++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+shared_examples_for 'AtomicInternalId' do
+  describe '.has_internal_id' do
+    describe 'Module inclusion' do
+      subject { described_class }
+
+      it { is_expected.to include_module(AtomicInternalId) }
+    end
+
+    describe 'Validation' do
+      subject { instance }
+
+      before do
+        allow(InternalId).to receive(:generate_next).and_return(nil)
+      end
+
+      it { is_expected.to validate_presence_of(internal_id_attribute) }
+      it { is_expected.to validate_numericality_of(internal_id_attribute) }
+    end
+
+    describe 'internal id generation' do
+      subject { instance.save! }
+
+      it 'calls InternalId.generate_next and sets internal id attribute' do
+        iid = rand(1..1000)
+
+        expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid)
+        subject
+        expect(instance.public_send(internal_id_attribute)).to eq(iid)
+      end
+
+      it 'does not overwrite an existing internal id' do
+        instance.public_send("#{internal_id_attribute}=", 4711)
+
+        expect { subject }.not_to change { instance.public_send(internal_id_attribute) }
+      end
+    end
+  end
+end
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b6aeb30d69c1aa8db5bedd8d11f3b80e1f8be850
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -0,0 +1,169 @@
+shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
+  describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
+    it "returns an array of discussions" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user)
+
+      expect(response).to have_gitlab_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
+      expect(json_response.first['id']).to eq(note.discussion_id)
+    end
+
+    it "returns a 404 error when noteable id not found" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/12345/discussions", user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it "returns 404 when not authorized" do
+      parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+  end
+
+  describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do
+    it "returns a discussion by id" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{note.discussion_id}", user)
+
+      expect(response).to have_gitlab_http_status(200)
+      expect(json_response['id']).to eq(note.discussion_id)
+      expect(json_response['notes'].first['body']).to eq(note.note)
+    end
+
+    it "returns a 404 error if discussion not found" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/12345", user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+  end
+
+  describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
+    it "creates a new note" do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!'
+
+      expect(response).to have_gitlab_http_status(201)
+      expect(json_response['notes'].first['body']).to eq('hi!')
+      expect(json_response['notes'].first['author']['username']).to eq(user.username)
+    end
+
+    it "returns a 400 bad request error if body not given" do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user)
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+
+    it "returns a 401 unauthorized error if user not authenticated" do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), body: 'hi!'
+
+      expect(response).to have_gitlab_http_status(401)
+    end
+
+    context 'when an admin or owner makes the request' do
+      it 'accepts the creation date to be set' do
+        creation_time = 2.weeks.ago
+        post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user),
+          body: 'hi!', created_at: creation_time
+
+        expect(response).to have_gitlab_http_status(201)
+        expect(json_response['notes'].first['body']).to eq('hi!')
+        expect(json_response['notes'].first['author']['username']).to eq(user.username)
+        expect(Time.parse(json_response['notes'].first['created_at'])).to be_like_time(creation_time)
+      end
+    end
+
+    context 'when user does not have access to read the discussion' do
+      before do
+        parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+      end
+
+      it 'responds with 404' do
+        post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user),
+          body: 'Foo'
+
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+  end
+
+  describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do
+    it 'adds a new note to the discussion' do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+               "discussions/#{note.discussion_id}/notes", user), body: 'Hello!'
+
+      expect(response).to have_gitlab_http_status(201)
+      expect(json_response['body']).to eq('Hello!')
+      expect(json_response['type']).to eq('DiscussionNote')
+    end
+
+    it 'returns a 400 bad request error if body not given' do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+               "discussions/#{note.discussion_id}/notes", user)
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+
+    it "returns a 400 bad request error if discussion is individual note" do
+      note.update_attribute(:type, nil)
+
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+               "discussions/#{note.discussion_id}/notes", user), body: 'hi!'
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+  end
+
+  describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+    it 'returns modified note' do
+      put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+              "discussions/#{note.discussion_id}/notes/#{note.id}", user), body: 'Hello!'
+
+      expect(response).to have_gitlab_http_status(200)
+      expect(json_response['body']).to eq('Hello!')
+    end
+
+    it 'returns a 404 error when note id not found' do
+      put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+              "discussions/#{note.discussion_id}/notes/12345", user),
+              body: 'Hello!'
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it 'returns a 400 bad request error if body not given' do
+      put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+              "discussions/#{note.discussion_id}/notes/#{note.id}", user)
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+  end
+
+  describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+    it 'deletes a note' do
+      delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+                 "discussions/#{note.discussion_id}/notes/#{note.id}", user)
+
+      expect(response).to have_gitlab_http_status(204)
+      # Check if note is really deleted
+      delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+                 "discussions/#{note.discussion_id}/notes/#{note.id}", user)
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it 'returns a 404 error when note id not found' do
+      delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+                 "discussions/#{note.discussion_id}/notes/12345", user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it_behaves_like '412 response' do
+      let(:request) do
+        api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+            "discussions/#{note.discussion_id}/notes/#{note.id}", user)
+      end
+    end
+  end
+end
diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..79b2196660cc71d16733539411bf673949c8f8e9
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/notes.rb
@@ -0,0 +1,206 @@
+shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
+  describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
+    context 'sorting' do
+      before do
+        params = { noteable: noteable, author: user }
+        params[:project] = parent if parent.is_a?(Project)
+
+        create_list(:note, 3, params)
+      end
+
+      it 'sorts by created_at in descending order by default' do
+        get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
+
+        response_dates = json_response.map { |note| note['created_at'] }
+
+        expect(json_response.length).to eq(4)
+        expect(response_dates).to eq(response_dates.sort.reverse)
+      end
+
+      it 'sorts by ascending order when requested' do
+        get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?sort=asc", user)
+
+        response_dates = json_response.map { |note| note['created_at'] }
+
+        expect(json_response.length).to eq(4)
+        expect(response_dates).to eq(response_dates.sort)
+      end
+
+      it 'sorts by updated_at in descending order when requested' do
+        get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at", user)
+
+        response_dates = json_response.map { |note| note['updated_at'] }
+
+        expect(json_response.length).to eq(4)
+        expect(response_dates).to eq(response_dates.sort.reverse)
+      end
+
+      it 'sorts by updated_at in ascending order when requested' do
+        get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at&sort=asc", user)
+
+        response_dates = json_response.map { |note| note['updated_at'] }
+
+        expect(json_response.length).to eq(4)
+        expect(response_dates).to eq(response_dates.sort)
+      end
+    end
+
+    it "returns an array of notes" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
+
+      expect(response).to have_gitlab_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
+      expect(json_response.first['body']).to eq(note.note)
+    end
+
+    it "returns a 404 error when noteable id not found" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/12345/notes", user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it "returns 404 when not authorized" do
+      parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+  end
+
+  describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do
+    it "returns a note by id" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user)
+
+      expect(response).to have_gitlab_http_status(200)
+      expect(json_response['body']).to eq(note.note)
+    end
+
+    it "returns a 404 error if note not found" do
+      get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+  end
+
+  describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
+    it "creates a new note" do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!'
+
+      expect(response).to have_gitlab_http_status(201)
+      expect(json_response['body']).to eq('hi!')
+      expect(json_response['author']['username']).to eq(user.username)
+    end
+
+    it "returns a 400 bad request error if body not given" do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+
+    it "returns a 401 unauthorized error if user not authenticated" do
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"), body: 'hi!'
+
+      expect(response).to have_gitlab_http_status(401)
+    end
+
+    it "creates an activity event when a note is created" do
+      expect(Event).to receive(:create!)
+
+      post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!'
+    end
+
+    context 'when an admin or owner makes the request' do
+      it 'accepts the creation date to be set' do
+        creation_time = 2.weeks.ago
+        post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user),
+          body: 'hi!', created_at: creation_time
+
+        expect(response).to have_gitlab_http_status(201)
+        expect(json_response['body']).to eq('hi!')
+        expect(json_response['author']['username']).to eq(user.username)
+        expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
+      end
+    end
+
+    context 'when the user is posting an award emoji on a noteable created by someone else' do
+      it 'creates a new note' do
+        parent.add_developer(private_user)
+        post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), body: ':+1:'
+
+        expect(response).to have_gitlab_http_status(201)
+        expect(json_response['body']).to eq(':+1:')
+      end
+    end
+
+    context 'when the user is posting an award emoji on his/her own noteable' do
+      it 'creates a new note' do
+        post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: ':+1:'
+
+        expect(response).to have_gitlab_http_status(201)
+        expect(json_response['body']).to eq(':+1:')
+      end
+    end
+
+    context 'when user does not have access to read the noteable' do
+      before do
+        parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+      end
+
+      it 'responds with 404' do
+        post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user),
+          body: 'Foo'
+
+        expect(response).to have_gitlab_http_status(404)
+      end
+    end
+  end
+
+  describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do
+    it 'returns modified note' do
+      put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+                "notes/#{note.id}", user), body: 'Hello!'
+
+      expect(response).to have_gitlab_http_status(200)
+      expect(json_response['body']).to eq('Hello!')
+    end
+
+    it 'returns a 404 error when note id not found' do
+      put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user),
+              body: 'Hello!'
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it 'returns a 400 bad request error if body not given' do
+      put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+                "notes/#{note.id}", user)
+
+      expect(response).to have_gitlab_http_status(400)
+    end
+  end
+
+  describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do
+    it 'deletes a note' do
+      delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+                 "notes/#{note.id}", user)
+
+      expect(response).to have_gitlab_http_status(204)
+      # Check if note is really deleted
+      delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+                 "notes/#{note.id}", user)
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it 'returns a 404 error when note id not found' do
+      delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user)
+
+      expect(response).to have_gitlab_http_status(404)
+    end
+
+    it_behaves_like '412 response' do
+      let(:request) { api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user) }
+    end
+  end
+end
diff --git a/spec/support/shared_examples/serializers/note_entity_examples.rb b/spec/support/shared_examples/serializers/note_entity_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9097c8e5513aca4deb9c86f209589e0e4bb303c8
--- /dev/null
+++ b/spec/support/shared_examples/serializers/note_entity_examples.rb
@@ -0,0 +1,42 @@
+shared_examples 'note entity' do
+  subject { entity.as_json }
+
+  context 'basic note' do
+    it 'exposes correct elements' do
+      expect(subject).to include(:type, :author, :note, :note_html, :current_user,
+        :discussion_id, :emoji_awardable, :award_emoji, :report_abuse_path, :attachment)
+    end
+
+    it 'does not expose elements for specific notes cases' do
+      expect(subject).not_to include(:last_edited_by, :last_edited_at, :system_note_icon_name)
+    end
+
+    it 'exposes author correctly' do
+      expect(subject[:author]).to include(:id, :name, :username, :state, :avatar_url, :path)
+    end
+
+    it 'does not expose web_url for author' do
+      expect(subject[:author]).not_to include(:web_url)
+    end
+  end
+
+  context 'when note was edited' do
+    before do
+      note.update(updated_at: 1.minute.from_now, updated_by: user)
+    end
+
+    it 'exposes last_edited_at and last_edited_by elements' do
+      expect(subject).to include(:last_edited_at, :last_edited_by)
+    end
+  end
+
+  context 'when note is a system note' do
+    before do
+      note.update(system: true)
+    end
+
+    it 'exposes system_note_icon_name element' do
+      expect(subject).to include(:system_note_icon_name)
+    end
+  end
+end
diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5bdc04f660fc4db7f25c29b7934a74d8ce328ad6
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/boards_create_service.rb
@@ -0,0 +1,27 @@
+shared_examples 'boards create service' do
+  context 'when parent does not have a board' do
+    it 'creates a new board' do
+      expect { service.execute }.to change(Board, :count).by(1)
+    end
+
+    it 'creates the default lists' do
+      board = service.execute
+
+      expect(board.lists.size).to eq 2
+      expect(board.lists.first).to be_backlog
+      expect(board.lists.last).to be_closed
+    end
+  end
+
+  context 'when parent has a board' do
+    before do
+      create(:board, parent: parent)
+    end
+
+    it 'does not create a new board' do
+      expect(service).to receive(:can_create_board?) { false }
+
+      expect { service.execute }.not_to change(parent.boards, :count)
+    end
+  end
+end
diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e0d5a7c61f224be203c1a5562730444a47d0eae9
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/boards_list_service.rb
@@ -0,0 +1,29 @@
+shared_examples 'boards list service' do
+  context 'when parent does not have a board' do
+    it 'creates a new parent board' do
+      expect { service.execute }.to change(parent.boards, :count).by(1)
+    end
+
+    it 'delegates the parent board creation to Boards::CreateService' do
+      expect_any_instance_of(Boards::CreateService).to receive(:execute).once
+
+      service.execute
+    end
+  end
+
+  context 'when parent has a board' do
+    before do
+      create(:board, parent: parent)
+    end
+
+    it 'does not create a new board' do
+      expect { service.execute }.not_to change(parent.boards, :count)
+    end
+  end
+
+  it 'returns parent boards' do
+    board = create(:board, parent: parent)
+
+    expect(service.execute).to eq [board]
+  end
+end
diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e744323ceaf2f419ea683dfca0a41ae3f66a963
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/issues_list_service.rb
@@ -0,0 +1,60 @@
+shared_examples 'issues list service' do
+  it 'delegates search to IssuesFinder' do
+    params = { board_id: board.id, id: list1.id }
+
+    expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original
+
+    described_class.new(parent, user, params).execute
+  end
+
+  context 'issues are ordered by priority' do
+    it 'returns opened issues when list_id is missing' do
+      params = { board_id: board.id }
+
+      issues = described_class.new(parent, user, params).execute
+
+      expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
+    end
+
+    it 'returns opened issues when listing issues from Backlog' do
+      params = { board_id: board.id, id: backlog.id }
+
+      issues = described_class.new(parent, user, params).execute
+
+      expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
+    end
+
+    it 'returns closed issues when listing issues from Closed' do
+      params = { board_id: board.id, id: closed.id }
+
+      issues = described_class.new(parent, user, params).execute
+
+      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
+      params = { board_id: board.id, id: list1.id }
+
+      issues = described_class.new(parent, user, params).execute
+
+      expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2]
+    end
+  end
+
+  context 'with list that does not belong to the board' do
+    it 'raises an error' do
+      list = create(:list)
+      service = described_class.new(parent, user, board_id: board.id, id: list.id)
+
+      expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
+    end
+  end
+
+  context 'with invalid list id' do
+    it 'raises an error' do
+      service = described_class.new(parent, user, board_id: board.id, id: nil)
+
+      expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
+    end
+  end
+end
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..737863ea41126cddd6881b0475449ae492896dbf
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -0,0 +1,100 @@
+shared_examples 'issues move service' do |group|
+  context 'when moving an issue between lists' do
+    let(:issue)  { create(:labeled_issue, project: project, labels: [bug, development]) }
+    let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
+
+    it 'delegates the label changes to Issues::UpdateService' do
+      expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once
+
+      described_class.new(parent, user, params).execute(issue)
+    end
+
+    it 'removes the label from the list it came from and adds the label of the list it goes to' do
+      described_class.new(parent, user, params).execute(issue)
+
+      expect(issue.reload.labels).to contain_exactly(bug, testing)
+    end
+  end
+
+  context 'when moving to closed' do
+    let!(:list3) { create(:list, board: board2, label: regression, position: 1) }
+
+    let(:issue)  { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
+    let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
+
+    it 'delegates the close proceedings to Issues::CloseService' do
+      expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
+
+      described_class.new(parent, user, params).execute(issue)
+    end
+
+    it 'removes all list-labels from boards and close the issue' do
+      described_class.new(parent, user, params).execute(issue)
+      issue.reload
+
+      expect(issue.labels).to contain_exactly(bug)
+      expect(issue).to be_closed
+    end
+  end
+
+  context 'when moving from closed' do
+    let(:issue)  { create(:labeled_issue, :closed, project: project, labels: [bug]) }
+    let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
+
+    it 'delegates the re-open proceedings to Issues::ReopenService' do
+      expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
+
+      described_class.new(parent, user, params).execute(issue)
+    end
+
+    it 'adds the label of the list it goes to and reopen the issue' do
+      described_class.new(parent, user, params).execute(issue)
+      issue.reload
+
+      expect(issue.labels).to contain_exactly(bug, testing)
+      expect(issue).to be_opened
+    end
+  end
+
+  context 'when moving to same list' do
+    let(:issue)   { create(:labeled_issue, project: project, labels: [bug, development]) }
+    let(:issue1)  { create(:labeled_issue, project: project, labels: [bug, development]) }
+    let(:issue2)  { create(:labeled_issue, project: project, labels: [bug, development]) }
+    let(:params)  { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
+
+    it 'returns false' do
+      expect(described_class.new(parent, user, params).execute(issue)).to eq false
+    end
+
+    it 'keeps issues labels' do
+      described_class.new(parent, user, params).execute(issue)
+
+      expect(issue.reload.labels).to contain_exactly(bug, development)
+    end
+
+    it 'sorts issues' do
+      [issue, issue1, issue2].each do |issue|
+        issue.move_to_end && issue.save!
+      end
+
+      params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
+
+      described_class.new(parent, user, params).execute(issue)
+
+      expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
+    end
+
+    if group
+      context 'when on a group board' do
+        it 'sends the board_group_id parameter' do
+          params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
+
+          match_params = { move_between_ids: [issue1.id, issue2.id], board_group_id: parent.id }
+          expect(Issues::UpdateService).to receive(:new).with(issue.project, user, match_params).and_return(double(execute: build(:issue)))
+
+          described_class.new(parent, user, params).execute(issue)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62b6ffe18365bd32d7a31b6273d569f6fccb657e
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
@@ -0,0 +1,30 @@
+shared_examples 'lists destroy service' do
+  context 'when list type is label' do
+    it 'removes list from board' do
+      list = create(:list, board: board)
+      service = described_class.new(parent, user)
+
+      expect { service.execute(list) }.to change(board.lists, :count).by(-1)
+    end
+
+    it 'decrements position of higher lists' do
+      development = create(:list, board: board, position: 0)
+      review      = create(:list, board: board, position: 1)
+      staging     = create(:list, board: board, position: 2)
+      closed      = board.closed_list
+
+      described_class.new(parent, user).execute(development)
+
+      expect(review.reload.position).to eq 0
+      expect(staging.reload.position).to eq 1
+      expect(closed.reload.position).to be_nil
+    end
+  end
+
+  it 'does not remove list from board when list type is closed' do
+    list = board.closed_list
+    service = described_class.new(parent, user)
+
+    expect { service.execute(list) }.not_to change(board.lists, :count)
+  end
+end
diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0a8220111ab5e4d95afd32aeb13beb079a71bb6f
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/lists_list_service.rb
@@ -0,0 +1,23 @@
+shared_examples 'lists list service' do
+  context 'when the board has a backlog list' do
+    let!(:backlog_list) { create(:backlog_list, board: board) }
+
+    it 'does not create a backlog list' do
+      expect { service.execute(board) }.not_to change(board.lists, :count)
+    end
+
+    it "returns board's lists" do
+      expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
+    end
+  end
+
+  context 'when the board does not have a backlog list' do
+    it 'creates a backlog list' do
+      expect { service.execute(board) }.to change(board.lists, :count).by(1)
+    end
+
+    it "returns board's lists" do
+      expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
+    end
+  end
+end
diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..07c98cb29b79203c34083ef5a48f55ee4ed4ad64
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/lists_move_service.rb
@@ -0,0 +1,93 @@
+shared_examples 'lists move service' do
+  let!(:planning)    { create(:list, board: board, position: 0) }
+  let!(:development) { create(:list, board: board, position: 1) }
+  let!(:review)      { create(:list, board: board, position: 2) }
+  let!(:staging)     { create(:list, board: board, position: 3) }
+  let!(:closed)      { create(:closed_list, board: board) }
+
+  context 'when list type is set to label' do
+    it 'keeps position of lists when new position is nil' do
+      service = described_class.new(parent, user, position: nil)
+
+      service.execute(planning)
+
+      expect(current_list_positions).to eq [0, 1, 2, 3]
+    end
+
+    it 'keeps position of lists when new positon is equal to old position' do
+      service = described_class.new(parent, user, position: planning.position)
+
+      service.execute(planning)
+
+      expect(current_list_positions).to eq [0, 1, 2, 3]
+    end
+
+    it 'keeps position of lists when new positon is negative' do
+      service = described_class.new(parent, user, position: -1)
+
+      service.execute(planning)
+
+      expect(current_list_positions).to eq [0, 1, 2, 3]
+    end
+
+    it 'keeps position of lists when new positon is equal to number of labels lists' do
+      service = described_class.new(parent, user, position: board.lists.label.size)
+
+      service.execute(planning)
+
+      expect(current_list_positions).to eq [0, 1, 2, 3]
+    end
+
+    it 'keeps position of lists when new positon is greater than number of labels lists' do
+      service = described_class.new(parent, user, position: board.lists.label.size + 1)
+
+      service.execute(planning)
+
+      expect(current_list_positions).to eq [0, 1, 2, 3]
+    end
+
+    it 'increments position of intermediate lists when new positon is equal to first position' do
+      service = described_class.new(parent, user, position: 0)
+
+      service.execute(staging)
+
+      expect(current_list_positions).to eq [1, 2, 3, 0]
+    end
+
+    it 'decrements position of intermediate lists when new positon is equal to last position' do
+      service = described_class.new(parent, user, position: board.lists.label.last.position)
+
+      service.execute(planning)
+
+      expect(current_list_positions).to eq [3, 0, 1, 2]
+    end
+
+    it 'decrements position of intermediate lists when new position is greater than old position' do
+      service = described_class.new(parent, user, position: 2)
+
+      service.execute(planning)
+
+      expect(current_list_positions).to eq [2, 0, 1, 3]
+    end
+
+    it 'increments position of intermediate lists when new position is lower than old position' do
+      service = described_class.new(parent, user, position: 1)
+
+      service.execute(staging)
+
+      expect(current_list_positions).to eq [0, 2, 3, 1]
+    end
+  end
+
+  it 'keeps position of lists when list type is closed' do
+    service = described_class.new(parent, user, position: 2)
+
+    service.execute(closed)
+
+    expect(current_list_positions).to eq [0, 1, 2, 3]
+  end
+
+  def current_list_positions
+    [planning, development, review, staging].map { |list| list.reload.position }
+  end
+end
diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
index 934d53e7bbac4e098889272e208e8fc809693f19..93c21a99e598dc1fe6736b59369bcbfc9da12b4d 100644
--- a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
@@ -4,7 +4,7 @@ shared_examples "matches the method pattern" do |method|
   let(:pattern) { patterns[method] }
 
   it do
-    return skip "No pattern provided, skipping." unless pattern
+    skip "No pattern provided, skipping." unless pattern
 
     expect(target.method(method).call(*args)).to match(pattern)
   end
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6352f1527cdc9fb2283fc1e87c677fe68efede92
--- /dev/null
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -0,0 +1,138 @@
+shared_context 'with storage' do |store, **stub_params|
+  before do
+    subject.object_store = store
+  end
+end
+
+shared_examples "migrates" do |to_store:, from_store: nil|
+  let(:to) { to_store }
+  let(:from) { from_store || subject.object_store }
+
+  def migrate(to)
+    subject.migrate!(to)
+  end
+
+  def checksum
+    Digest::SHA256.hexdigest(subject.read)
+  end
+
+  before do
+    migrate(from)
+  end
+
+  it 'returns corresponding file type' do
+    expect(subject).to be_an(CarrierWave::Uploader::Base)
+    expect(subject).to be_a(ObjectStorage::Concern)
+
+    if from == described_class::Store::REMOTE
+      expect(subject.file).to be_a(CarrierWave::Storage::Fog::File)
+    elsif from == described_class::Store::LOCAL
+      expect(subject.file).to be_a(CarrierWave::SanitizedFile)
+    else
+      raise 'Unexpected file type'
+    end
+  end
+
+  it 'does nothing when migrating to the current store' do
+    expect { migrate(from) }.not_to change { subject.object_store }.from(from)
+  end
+
+  it 'migrate to the specified store' do
+    from_checksum = checksum
+
+    expect { migrate(to) }.to change { subject.object_store }.from(from).to(to)
+    expect(checksum).to eq(from_checksum)
+  end
+
+  it 'removes the original file after the migration' do
+    original_file = subject.file.path
+    migrate(to)
+
+    expect(File.exist?(original_file)).to be_falsey
+  end
+
+  it 'can access to the original file during migration' do
+    file = subject.file
+
+    allow(subject).to receive(:delete_migrated_file) { } # Remove as a callback of :migrate
+    allow(subject).to receive(:record_upload) { } # Remove as a callback of :store (:record_upload)
+
+    expect(file.exists?).to be_truthy
+    expect { migrate(to) }.not_to change { file.exists? }
+  end
+
+  context 'when migrate! is not occupied by another process' do
+    it 'executes migrate!' do
+      expect(subject).to receive(:object_store=).at_least(1)
+
+      migrate(to)
+    end
+
+    it 'executes use_file' do
+      expect(subject).to receive(:unsafe_use_file).once
+
+      subject.use_file
+    end
+  end
+
+  context 'when migrate! is occupied by another process' do
+    let(:exclusive_lease_key) { "object_storage_migrate:#{subject.model.class}:#{subject.model.id}" }
+
+    before do
+      @uuid = Gitlab::ExclusiveLease.new(exclusive_lease_key, timeout: 1.hour.to_i).try_obtain
+    end
+
+    it 'does not execute migrate!' do
+      expect(subject).not_to receive(:unsafe_migrate!)
+
+      expect { migrate(to) }.to raise_error('exclusive lease already taken')
+    end
+
+    it 'does not execute use_file' do
+      expect(subject).not_to receive(:unsafe_use_file)
+
+      expect { subject.use_file }.to raise_error('exclusive lease already taken')
+    end
+
+    after do
+      Gitlab::ExclusiveLease.cancel(exclusive_lease_key, @uuid)
+    end
+  end
+
+  context 'migration is unsuccessful' do
+    shared_examples "handles gracefully" do |error:|
+      it 'does not update the object_store' do
+        expect { migrate(to) }.to raise_error(error)
+        expect(subject.object_store).to eq(from)
+      end
+
+      it 'does not delete the original file' do
+        expect { migrate(to) }.to raise_error(error)
+        expect(subject.exists?).to be_truthy
+      end
+    end
+
+    context 'when the store is not supported' do
+      let(:to) { -1 } # not a valid store
+
+      include_examples "handles gracefully", error: ObjectStorage::UnknownStoreError
+    end
+
+    context 'upon a fog failure' do
+      before do
+        storage_class = subject.send(:storage_for, to).class
+        expect_any_instance_of(storage_class).to receive(:store!).and_raise("Store failure.")
+      end
+
+      include_examples "handles gracefully", error: "Store failure."
+    end
+
+    context 'upon a database failure' do
+      before do
+        expect(uploader).to receive(:persist_object_store!).and_raise("ActiveRecord failure.")
+      end
+
+      include_examples "handles gracefully", error: "ActiveRecord failure."
+    end
+  end
+end
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 5e1ce19eafb291798c711cc340fe754d71b061c9..07bc3a51fd8ef17fb8e7c72c6293efc1c848b14e 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -4,6 +4,11 @@ RSpec.shared_examples 'slack or mattermost notifications' do
   let(:chat_service) { described_class.new }
   let(:webhook_url) { 'https://example.gitlab.com/' }
 
+  def execute_with_options(options)
+    receive(:new).with(webhook_url, options)
+     .and_return(double(:slack_service).as_null_object)
+  end
+
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
@@ -33,6 +38,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
     let(:project) { create(:project, :repository) }
     let(:username) { 'slack_username' }
     let(:channel)  { 'slack_channel' }
+    let(:issue_service_options) { { title: 'Awesome issue', description: 'please fix' } }
 
     let(:push_sample_data) do
       Gitlab::DataBuilder::Push.build_sample(project, user)
@@ -48,12 +54,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
 
       WebMock.stub_request(:post, webhook_url)
 
-      opts = {
-        title: 'Awesome issue',
-        description: 'please fix'
-      }
-
-      issue_service = Issues::CreateService.new(project, user, opts)
+      issue_service = Issues::CreateService.new(project, user, issue_service_options)
       @issue = issue_service.execute
       @issues_sample_data = issue_service.hook_data(@issue, 'open')
 
@@ -164,6 +165,26 @@ RSpec.shared_examples 'slack or mattermost notifications' do
         chat_service.execute(@issues_sample_data)
       end
 
+      context 'for confidential issues' do
+        let(:issue_service_options) { { title: 'Secret', confidential: true } }
+
+        it "uses confidential issue channel" do
+          chat_service.update_attributes(confidential_issue_channel: 'confidential')
+
+          expect(Slack::Notifier).to execute_with_options(channel: 'confidential')
+
+          chat_service.execute(@issues_sample_data)
+        end
+
+        it 'falls back to issue channel' do
+          chat_service.update_attributes(issue_channel: 'fallback_channel')
+
+          expect(Slack::Notifier).to execute_with_options(channel: 'fallback_channel')
+
+          chat_service.execute(@issues_sample_data)
+        end
+      end
+
       it "uses the right channel for wiki event" do
         chat_service.update_attributes(wiki_page_channel: "random")
 
@@ -194,6 +215,32 @@ RSpec.shared_examples 'slack or mattermost notifications' do
 
           chat_service.execute(note_data)
         end
+
+        context 'for confidential notes' do
+          before do
+            issue_note.noteable.update!(confidential: true)
+          end
+
+          it "uses confidential channel" do
+            chat_service.update_attributes(confidential_note_channel: "confidential")
+
+            note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
+
+            expect(Slack::Notifier).to execute_with_options(channel: 'confidential')
+
+            chat_service.execute(note_data)
+          end
+
+          it 'falls back to note channel' do
+            chat_service.update_attributes(note_channel: "fallback_channel")
+
+            note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
+
+            expect(Slack::Notifier).to execute_with_options(channel: 'fallback_channel')
+
+            chat_service.execute(note_data)
+          end
+        end
       end
     end
   end
@@ -248,8 +295,9 @@ RSpec.shared_examples 'slack or mattermost notifications' do
         create(:note_on_issue, project: project, note: "issue note")
       end
 
+      let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) }
+
       it "calls Slack API for issue comment events" do
-        data = Gitlab::DataBuilder::Note.build(issue_note, user)
         chat_service.execute(data)
 
         expect(WebMock).to have_requested(:post, webhook_url).once
diff --git a/spec/support/sorting_helper.rb b/spec/support/sorting_helper.rb
deleted file mode 100644
index 577518d726c70dda1bad1101c69677f734694286..0000000000000000000000000000000000000000
--- a/spec/support/sorting_helper.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# Helper allows you to sort items
-#
-# Params
-#   value - value for sorting
-#
-# Usage:
-#   include SortingHelper
-#
-#   sorting_by('Oldest updated')
-#
-module SortingHelper
-  def sorting_by(value)
-    find('button.dropdown-toggle').click
-    page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
-      click_link value
-    end
-  end
-end
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index 52e47ae2d3485c6f6492ae197dfa8e55c8472c7d..21995c89a6e3c91b838aa1b5e241251923993c50 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -4,7 +4,7 @@ RSpec.configure do |config|
   end
 
   config.before(:all, :broken_storage) do
-    FileUtils.rm_rf Gitlab.config.repositories.storages.broken['path']
+    FileUtils.rm_rf Gitlab.config.repositories.storages.broken.legacy_disk_path
   end
 
   config.before(:each, :broken_storage) do
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 9f08c139322369a5e68a6b3d8d0ec1e15e4e5375..a75a3eaefcb0f119b987597a15ac1a3a34b42f99 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -45,13 +45,21 @@ module StubConfiguration
     allow(Gitlab.config.lfs).to receive_messages(to_settings(messages))
   end
 
+  def stub_artifacts_setting(messages)
+    allow(Gitlab.config.artifacts).to receive_messages(to_settings(messages))
+  end
+
   def stub_storage_settings(messages)
     messages.deep_stringify_keys!
 
     # Default storage is always required
     messages['default'] ||= Gitlab.config.repositories.storages.default
-    messages.each do |storage_name, storage_settings|
-      storage_settings['path'] = TestEnv.repos_path unless storage_settings.key?('path')
+    messages.each do |storage_name, storage_hash|
+      if !storage_hash.key?('path') || storage_hash['path'] == Gitlab::GitalyClient::StorageSettings::Deprecated
+        storage_hash['path'] = TestEnv.repos_path
+      end
+
+      messages[storage_name] = Gitlab::GitalyClient::StorageSettings.new(storage_hash.to_h)
     end
 
     allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages))
diff --git a/spec/support/stub_object_storage.rb b/spec/support/stub_object_storage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e88641da42bdfc3f36f25b7aab3898fe7999c38
--- /dev/null
+++ b/spec/support/stub_object_storage.rb
@@ -0,0 +1,48 @@
+module StubConfiguration
+  def stub_object_storage_uploader(
+        config:,
+        uploader:,
+        remote_directory:,
+        enabled: true,
+        proxy_download: false,
+        background_upload: false,
+        direct_upload: false
+  )
+    allow(config).to receive(:enabled) { enabled }
+    allow(config).to receive(:proxy_download) { proxy_download }
+    allow(config).to receive(:background_upload) { background_upload }
+    allow(config).to receive(:direct_upload) { direct_upload }
+
+    return unless enabled
+
+    Fog.mock!
+
+    ::Fog::Storage.new(uploader.object_store_credentials).tap do |connection|
+      begin
+        connection.directories.create(key: remote_directory)
+      rescue Excon::Error::Conflict
+      end
+    end
+  end
+
+  def stub_artifacts_object_storage(**params)
+    stub_object_storage_uploader(config: Gitlab.config.artifacts.object_store,
+                                 uploader: JobArtifactUploader,
+                                 remote_directory: 'artifacts',
+                                 **params)
+  end
+
+  def stub_lfs_object_storage(**params)
+    stub_object_storage_uploader(config: Gitlab.config.lfs.object_store,
+                                 uploader: LfsObjectUploader,
+                                 remote_directory: 'lfs-objects',
+                                 **params)
+  end
+
+  def stub_uploads_object_storage(uploader = described_class, **params)
+    stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
+                                 uploader: uploader,
+                                 remote_directory: 'uploads',
+                                 **params)
+  end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 01321989f015f636d1d2f89b5abb30b61a055a89..d87f265cdf0d1aebadbb1a4ecf69d0644064abc7 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -62,6 +62,7 @@ module TestEnv
   }.freeze
 
   TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**')
+  REPOS_STORAGE = 'default'.freeze
 
   # Test environment
   #
@@ -225,7 +226,7 @@ module TestEnv
   end
 
   def repos_path
-    Gitlab.config.repositories.storages.default['path']
+    Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path
   end
 
   def backup_path
diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache/clear/redis_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cca2b864e9befd02f959d2f6894df35ba1f7ffb8
--- /dev/null
+++ b/spec/tasks/cache/clear/redis_spec.rb
@@ -0,0 +1,19 @@
+require 'rake_helper'
+
+describe 'clearing redis cache' do
+  before do
+    Rake.application.rake_require 'tasks/cache'
+  end
+
+  describe 'clearing pipeline status cache' do
+    let(:pipeline_status) { create(:ci_pipeline).project.pipeline_status }
+
+    before do
+      allow(pipeline_status).to receive(:loaded).and_return(nil)
+    end
+
+    it 'clears pipeline status cache' do
+      expect { run_rake_task('cache:clear:redis') }.to change { pipeline_status.has_cache? }
+    end
+  end
+end
diff --git a/spec/tasks/gitlab/artifacts/check_rake_spec.rb b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d495b08aca076ef8b084899ced06bd0171cb14d2
--- /dev/null
+++ b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
@@ -0,0 +1,34 @@
+require 'rake_helper'
+
+describe 'gitlab:artifacts rake tasks' do
+  describe 'check' do
+    let!(:artifact) { create(:ci_job_artifact, :archive, :correct_checksum) }
+
+    before do
+      Rake.application.rake_require('tasks/gitlab/artifacts/check')
+      stub_env('VERBOSE' => 'true')
+    end
+
+    it 'outputs the integrity check for each batch' do
+      expect { run_rake_task('gitlab:artifacts:check') }.to output(/Failures: 0/).to_stdout
+    end
+
+    it 'errors out about missing files on the file system' do
+      FileUtils.rm_f(artifact.file.path)
+
+      expect { run_rake_task('gitlab:artifacts:check') }.to output(/No such file.*#{Regexp.quote(artifact.file.path)}/).to_stdout
+    end
+
+    it 'errors out about invalid checksum' do
+      artifact.update_column(:file_sha256, 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
+
+      expect { run_rake_task('gitlab:artifacts:check') }.to output(/Checksum mismatch/).to_stdout
+    end
+
+    it 'errors out about missing checksum' do
+      artifact.update_column(:file_sha256, nil)
+
+      expect { run_rake_task('gitlab:artifacts:check') }.to output(/Checksum missing/).to_stdout
+    end
+  end
+end
diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8544fb62b5a37124bf68309f0e3bd450d6b18485
--- /dev/null
+++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
@@ -0,0 +1,118 @@
+require 'rake_helper'
+
+describe 'gitlab:artifacts namespace rake task' do
+  before(:context) do
+    Rake.application.rake_require 'tasks/gitlab/artifacts/migrate'
+  end
+
+  let(:object_storage_enabled) { false }
+
+  before do
+    stub_artifacts_object_storage(enabled: object_storage_enabled)
+  end
+
+  subject { run_rake_task('gitlab:artifacts:migrate') }
+
+  context 'legacy artifacts' do
+    describe 'migrate' do
+      let!(:build) { create(:ci_build, :legacy_artifacts, artifacts_file_store: store, artifacts_metadata_store: store) }
+
+      context 'when local storage is used' do
+        let(:store) { ObjectStorage::Store::LOCAL }
+
+        context 'and job does not have file store defined' do
+          let(:object_storage_enabled) { true }
+          let(:store) { nil }
+
+          it "migrates file to remote storage" do
+            subject
+
+            expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
+            expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
+          end
+        end
+
+        context 'and remote storage is defined' do
+          let(:object_storage_enabled) { true }
+
+          it "migrates file to remote storage" do
+            subject
+
+            expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
+            expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
+          end
+        end
+
+        context 'and remote storage is not defined' do
+          it "fails to migrate to remote storage" do
+            subject
+
+            expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::LOCAL)
+            expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::LOCAL)
+          end
+        end
+      end
+
+      context 'when remote storage is used' do
+        let(:object_storage_enabled) { true }
+
+        let(:store) { ObjectStorage::Store::REMOTE }
+
+        it "file stays on remote storage" do
+          subject
+
+          expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
+          expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
+        end
+      end
+    end
+  end
+
+  context 'job artifacts' do
+    let!(:artifact) { create(:ci_job_artifact, :archive, file_store: store) }
+
+    context 'when local storage is used' do
+      let(:store) { ObjectStorage::Store::LOCAL }
+
+      context 'and job does not have file store defined' do
+        let(:object_storage_enabled) { true }
+        let(:store) { nil }
+
+        it "migrates file to remote storage" do
+          subject
+
+          expect(artifact.reload.file_store).to eq(ObjectStorage::Store::REMOTE)
+        end
+      end
+
+      context 'and remote storage is defined' do
+        let(:object_storage_enabled) { true }
+
+        it "migrates file to remote storage" do
+          subject
+
+          expect(artifact.reload.file_store).to eq(ObjectStorage::Store::REMOTE)
+        end
+      end
+
+      context 'and remote storage is not defined' do
+        it "fails to migrate to remote storage" do
+          subject
+
+          expect(artifact.reload.file_store).to eq(ObjectStorage::Store::LOCAL)
+        end
+      end
+    end
+
+    context 'when remote storage is used' do
+      let(:object_storage_enabled) { true }
+      let(:store) { ObjectStorage::Store::REMOTE }
+
+      it "file stays on remote storage" do
+        subject
+
+        expect(artifact.reload.file_store).to eq(ObjectStorage::Store::REMOTE)
+      end
+    end
+  end
+end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 168facd51a6ae9b05f87265adaad8b5b554ec7ff..0d24782f3170ce15a67665f91caf2e01079b0115 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -195,14 +195,23 @@ describe 'gitlab:app namespace rake task' do
     end
 
     context 'multiple repository storages' do
-      let(:gitaly_address) { Gitlab.config.repositories.storages.default.gitaly_address }
+      let(:storage_default) do
+        Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage'))
+      end
+      let(:test_second_storage) do
+        Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/custom_storage'))
+      end
       let(:storages) do
         {
-          'default' => { 'path' => Settings.absolute('tmp/tests/default_storage'), 'gitaly_address' => gitaly_address  },
-          'test_second_storage' => { 'path' => Settings.absolute('tmp/tests/custom_storage'), 'gitaly_address' => gitaly_address }
+          'default' => storage_default,
+          'test_second_storage' => test_second_storage
         }
       end
 
+      before(:all) do
+        @default_storage_hash = Gitlab.config.repositories.storages.default.to_h
+      end
+
       before do
         # We only need a backup of the repositories for this test
         stub_env('SKIP', 'db,uploads,builds,artifacts,lfs,registry')
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index 9e746ceddd6c4ef15bb560641b56667c1b081aac..2bf873c923f449b23f5c627c0066d72f21d9626d 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -6,13 +6,16 @@ describe 'gitlab:cleanup rake tasks' do
   end
 
   describe 'cleanup' do
-    let(:gitaly_address) { Gitlab.config.repositories.storages.default.gitaly_address }
     let(:storages) do
       {
-        'default' => { 'path' => Settings.absolute('tmp/tests/default_storage'), 'gitaly_address' => gitaly_address  }
+        'default' => Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage'))
       }
     end
 
+    before(:all) do
+      @default_storage_hash = Gitlab.config.repositories.storages.default.to_h
+    end
+
     before do
       FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage'))
       allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb
index 9aebf7b0b4a4c9b06a5333e045895b304f78b971..1efaecc63a5dc74d4554912ebbafd1b2ab77c68a 100644
--- a/spec/tasks/gitlab/git_rake_spec.rb
+++ b/spec/tasks/gitlab/git_rake_spec.rb
@@ -1,10 +1,13 @@
 require 'rake_helper'
 
 describe 'gitlab:git rake tasks' do
+  before(:all) do
+    @default_storage_hash = Gitlab.config.repositories.storages.default.to_h
+  end
+
   before do
     Rake.application.rake_require 'tasks/gitlab/git'
-
-    storages = { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } }
+    storages = { 'default' => Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage')) }
 
     FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git'))
     allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 1f4053ff9ad9b8bb38fb90643033d1742e7423d2..1e507c0236e53d12f5a8b1348ae8fe90bb113b33 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -99,14 +99,14 @@ describe 'gitlab:gitaly namespace rake task' do
   describe 'storage_config' do
     it 'prints storage configuration in a TOML format' do
       config = {
-        'default' => {
+        'default' => Gitlab::GitalyClient::StorageSettings.new(
           'path' => '/path/to/default',
           'gitaly_address' => 'unix:/path/to/my.socket'
-        },
-        'nfs_01' => {
+        ),
+        'nfs_01' => Gitlab::GitalyClient::StorageSettings.new(
           'path' => '/path/to/nfs_01',
           'gitaly_address' => 'unix:/path/to/my.socket'
-        }
+        )
       }
       allow(Gitlab.config.repositories).to receive(:storages).and_return(config)
       allow(Rails.env).to receive(:test?).and_return(false)
@@ -134,7 +134,7 @@ describe 'gitlab:gitaly namespace rake task' do
 
       parsed_output = TomlRB.parse(expected_output)
       config.each do |name, params|
-        expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params['path'] })
+        expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params.legacy_disk_path })
       end
     end
   end
diff --git a/spec/tasks/gitlab/lfs/check_rake_spec.rb b/spec/tasks/gitlab/lfs/check_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2610edf8bac9a94609747bd5a9875c13c8a3accc
--- /dev/null
+++ b/spec/tasks/gitlab/lfs/check_rake_spec.rb
@@ -0,0 +1,28 @@
+require 'rake_helper'
+
+describe 'gitlab:lfs rake tasks' do
+  describe 'check' do
+    let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) }
+
+    before do
+      Rake.application.rake_require('tasks/gitlab/lfs/check')
+      stub_env('VERBOSE' => 'true')
+    end
+
+    it 'outputs the integrity check for each batch' do
+      expect { run_rake_task('gitlab:lfs:check') }.to output(/Failures: 0/).to_stdout
+    end
+
+    it 'errors out about missing files on the file system' do
+      FileUtils.rm_f(lfs_object.file.path)
+
+      expect { run_rake_task('gitlab:lfs:check') }.to output(/No such file.*#{Regexp.quote(lfs_object.file.path)}/).to_stdout
+    end
+
+    it 'errors out about invalid checksum' do
+      File.truncate(lfs_object.file.path, 0)
+
+      expect { run_rake_task('gitlab:lfs:check') }.to output(/Checksum mismatch/).to_stdout
+    end
+  end
+end
diff --git a/spec/tasks/gitlab/lfs/migrate_rake_spec.rb b/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..66d1a192a96086775b17cc47495a3f1baf424126
--- /dev/null
+++ b/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
@@ -0,0 +1,37 @@
+require 'rake_helper'
+
+describe 'gitlab:lfs namespace rake task' do
+  before :all do
+    Rake.application.rake_require 'tasks/gitlab/lfs/migrate'
+  end
+
+  describe 'migrate' do
+    let(:local) { ObjectStorage::Store::LOCAL }
+    let(:remote) { ObjectStorage::Store::REMOTE }
+    let!(:lfs_object) { create(:lfs_object, :with_file, file_store: local) }
+
+    def lfs_migrate
+      run_rake_task('gitlab:lfs:migrate')
+    end
+
+    context 'object storage disabled' do
+      before do
+        stub_lfs_object_storage(enabled: false)
+      end
+
+      it "doesn't migrate files" do
+        expect { lfs_migrate }.not_to change { lfs_object.reload.file_store }
+      end
+    end
+
+    context 'object storage enabled' do
+      before do
+        stub_lfs_object_storage
+      end
+
+      it 'migrates local file to object storage' do
+        expect { lfs_migrate }.to change { lfs_object.reload.file_store }.from(local).to(remote)
+      end
+    end
+  end
+end
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index 65155cb044debfbe6b3777720ed071e1a3694722..4a756c5742d55bb20f8e100b856b2e4f86033bc6 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -11,7 +11,7 @@ describe 'gitlab:shell rake tasks' do
     it 'invokes create_hooks task' do
       expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
 
-      storages = Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
+      storages = Gitlab.config.repositories.storages.values.map(&:legacy_disk_path)
       expect(Kernel).to receive(:system).with('bin/install', *storages).and_call_original
       expect(Kernel).to receive(:system).with('bin/compile').and_call_original
 
diff --git a/spec/tasks/gitlab/traces_rake_spec.rb b/spec/tasks/gitlab/traces_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bd18e8ffc1e1b1b017867ed51a7e9e34e3b6eadf
--- /dev/null
+++ b/spec/tasks/gitlab/traces_rake_spec.rb
@@ -0,0 +1,55 @@
+require 'rake_helper'
+
+describe 'gitlab:traces rake tasks' do
+  before do
+    Rake.application.rake_require 'tasks/gitlab/traces'
+  end
+
+  shared_examples 'passes the job id to worker' do
+    it do
+      expect(ArchiveTraceWorker).to receive(:bulk_perform_async).with([[job.id]])
+
+      run_rake_task('gitlab:traces:archive')
+    end
+  end
+
+  shared_examples 'does not pass the job id to worker' do
+    it do
+      expect(ArchiveTraceWorker).not_to receive(:bulk_perform_async)
+
+      run_rake_task('gitlab:traces:archive')
+    end
+  end
+
+  context 'when trace file stored in default path' do
+    let!(:job) { create(:ci_build, :success, :trace_live) }
+
+    it_behaves_like 'passes the job id to worker'
+  end
+
+  context 'when trace is stored in database' do
+    let!(:job) { create(:ci_build, :success) }
+
+    before do
+      job.update_column(:trace, 'trace in db')
+    end
+
+    it_behaves_like 'passes the job id to worker'
+  end
+
+  context 'when job has trace artifact' do
+    let!(:job) { create(:ci_build, :success) }
+
+    before do
+      create(:ci_job_artifact, :trace, job: job)
+    end
+
+    it_behaves_like 'does not pass the job id to worker'
+  end
+
+  context 'when job is not finished yet' do
+    let!(:build) { create(:ci_build, :running, :trace_live) }
+
+    it_behaves_like 'does not pass the job id to worker'
+  end
+end
diff --git a/spec/tasks/gitlab/uploads/check_rake_spec.rb b/spec/tasks/gitlab/uploads/check_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5d597c66133008a0bf13bd50b178fd0a8dbeb953
--- /dev/null
+++ b/spec/tasks/gitlab/uploads/check_rake_spec.rb
@@ -0,0 +1,28 @@
+require 'rake_helper'
+
+describe 'gitlab:uploads rake tasks' do
+  describe 'check' do
+    let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
+
+    before do
+      Rake.application.rake_require('tasks/gitlab/uploads/check')
+      stub_env('VERBOSE' => 'true')
+    end
+
+    it 'outputs the integrity check for each batch' do
+      expect { run_rake_task('gitlab:uploads:check') }.to output(/Failures: 0/).to_stdout
+    end
+
+    it 'errors out about missing files on the file system' do
+      missing_upload = create(:upload)
+
+      expect { run_rake_task('gitlab:uploads:check') }.to output(/No such file.*#{Regexp.quote(missing_upload.absolute_path)}/).to_stdout
+    end
+
+    it 'errors out about invalid checksum' do
+      upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e')
+
+      expect { run_rake_task('gitlab:uploads:check') }.to output(/Checksum mismatch/).to_stdout
+    end
+  end
+end
diff --git a/spec/tasks/gitlab/uploads/migrate_rake_spec.rb b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6fcfae358ec018ec6118755cf324b4caf338c2dc
--- /dev/null
+++ b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
@@ -0,0 +1,143 @@
+require 'rake_helper'
+
+describe 'gitlab:uploads:migrate rake tasks' do
+  let(:model_class) { nil }
+  let(:uploader_class) { nil }
+  let(:mounted_as) { nil }
+  let(:batch_size) { 3 }
+
+  before do
+    stub_env('BATCH', batch_size.to_s)
+    stub_uploads_object_storage(uploader_class)
+    Rake.application.rake_require 'tasks/gitlab/uploads/migrate'
+
+    allow(ObjectStorage::MigrateUploadsWorker).to receive(:perform_async)
+  end
+
+  def run
+    args = [uploader_class.to_s, model_class.to_s, mounted_as].compact
+    run_rake_task("gitlab:uploads:migrate", *args)
+  end
+
+  shared_examples 'enqueue jobs in batch' do |batch:|
+    it do
+      expect(ObjectStorage::MigrateUploadsWorker)
+        .to receive(:perform_async).exactly(batch).times
+              .and_return("A fake job.")
+
+      run
+    end
+  end
+
+  context "for AvatarUploader" do
+    let(:uploader_class) { AvatarUploader }
+    let(:mounted_as) { :avatar }
+
+    context "for Project" do
+      let(:model_class) { Project }
+      let!(:projects) { create_list(:project, 10, :with_avatar) }
+
+      it_behaves_like 'enqueue jobs in batch', batch: 4
+
+      context 'Upload has store = nil' do
+        before do
+          Upload.where(model: projects).update_all(store: nil)
+        end
+
+        it_behaves_like 'enqueue jobs in batch', batch: 4
+      end
+    end
+
+    context "for Group" do
+      let(:model_class) { Group }
+
+      before do
+        create_list(:group, 10, :with_avatar)
+      end
+
+      it_behaves_like 'enqueue jobs in batch', batch: 4
+    end
+
+    context "for User" do
+      let(:model_class) { User }
+
+      before do
+        create_list(:user, 10, :with_avatar)
+      end
+
+      it_behaves_like 'enqueue jobs in batch', batch: 4
+    end
+  end
+
+  context "for AttachmentUploader" do
+    let(:uploader_class) { AttachmentUploader }
+
+    context "for Note" do
+      let(:model_class) { Note }
+      let(:mounted_as) { :attachment }
+
+      before do
+        create_list(:note, 10, :with_attachment)
+      end
+
+      it_behaves_like 'enqueue jobs in batch', batch: 4
+    end
+
+    context "for Appearance" do
+      let(:model_class) { Appearance }
+      let(:mounted_as) { :logo }
+
+      before do
+        create(:appearance, :with_logos)
+      end
+
+      %i(logo header_logo).each do |mount|
+        it_behaves_like 'enqueue jobs in batch', batch: 1 do
+          let(:mounted_as) { mount }
+        end
+      end
+    end
+  end
+
+  context "for FileUploader" do
+    let(:uploader_class) { FileUploader }
+    let(:model_class) { Project }
+
+    before do
+      create_list(:project, 10) do |model|
+        uploader_class.new(model)
+          .store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+      end
+    end
+
+    it_behaves_like 'enqueue jobs in batch', batch: 4
+  end
+
+  context "for PersonalFileUploader" do
+    let(:uploader_class) { PersonalFileUploader }
+    let(:model_class) { PersonalSnippet }
+
+    before do
+      create_list(:personal_snippet, 10) do |model|
+        uploader_class.new(model)
+          .store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+      end
+    end
+
+    it_behaves_like 'enqueue jobs in batch', batch: 4
+  end
+
+  context "for NamespaceFileUploader" do
+    let(:uploader_class) { NamespaceFileUploader }
+    let(:model_class) { Snippet }
+
+    before do
+      create_list(:snippet, 10) do |model|
+        uploader_class.new(model)
+          .store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+      end
+    end
+
+    it_behaves_like 'enqueue jobs in batch', batch: 4
+  end
+end
diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb
deleted file mode 100644
index ac0005e51e0bfc8c05b987553effe211d94faa86..0000000000000000000000000000000000000000
--- a/spec/tasks/gitlab/uploads_rake_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'rake_helper'
-
-describe 'gitlab:uploads rake tasks' do
-  describe 'check' do
-    let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
-
-    before do
-      Rake.application.rake_require 'tasks/gitlab/uploads'
-    end
-
-    it 'outputs the integrity check for each uploaded file' do
-      expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{upload.id}\): #{Regexp.quote(upload.absolute_path)}/).to_stdout
-    end
-
-    it 'errors out about missing files on the file system' do
-      create(:upload)
-
-      expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout
-    end
-
-    it 'errors out about invalid checksum' do
-      upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e')
-
-      expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{upload.checksum}\)/).to_stdout
-    end
-  end
-end
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index 091ba824fc6b573289921e6939ce6460604aa26a..d302c14efb987fa51b2958092b2ca23193e7eb99 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -11,4 +11,26 @@ describe AttachmentUploader do
                   store_dir: %r[uploads/-/system/note/attachment/],
                   upload_path: %r[uploads/-/system/note/attachment/],
                   absolute_path: %r[#{CarrierWave.root}/uploads/-/system/note/attachment/]
+
+  context "object_store is REMOTE" do
+    before do
+      stub_uploads_object_storage
+    end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    it_behaves_like 'builds correct paths',
+                    store_dir: %r[note/attachment/],
+                    upload_path: %r[note/attachment/]
+  end
+
+  describe "#migrate!" do
+    before do
+      uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/doc_sample.txt')))
+      stub_uploads_object_storage
+    end
+
+    it_behaves_like "migrates", to_store: described_class::Store::REMOTE
+    it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL
+  end
 end
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index bf9028c92606f5fda9a34b8153545ee97482e6f0..b0468bc35ffa374c64b35b407ad46146301bba4b 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe AvatarUploader do
-  let(:model) { create(:user, :with_avatar) }
+  let(:model) { build_stubbed(:user) }
   let(:uploader) { described_class.new(model, :avatar) }
   let(:upload) { create(:upload, model: model) }
 
@@ -12,15 +12,28 @@ describe AvatarUploader do
                   upload_path: %r[uploads/-/system/user/avatar/],
                   absolute_path: %r[#{CarrierWave.root}/uploads/-/system/user/avatar/]
 
-  describe '#move_to_cache' do
-    it 'is false' do
-      expect(uploader.move_to_cache).to eq(false)
+  context "object_store is REMOTE" do
+    before do
+      stub_uploads_object_storage
     end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    it_behaves_like 'builds correct paths',
+                    store_dir: %r[user/avatar/],
+                    upload_path: %r[user/avatar/]
   end
 
-  describe '#move_to_store' do
-    it 'is false' do
-      expect(uploader.move_to_store).to eq(false)
+  context "with a file" do
+    let(:project) { create(:project, :with_avatar) }
+    let(:uploader) { project.avatar }
+    let(:upload) { uploader.upload }
+
+    before do
+      stub_uploads_object_storage
     end
+
+    it_behaves_like "migrates", to_store: described_class::Store::REMOTE
+    it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL
   end
 end
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index bc024cd307c7edd31fc3e9d3ffd033a98edb7880..68b7e24776d93c95771c2ddc6b3feef017a94eae 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -36,6 +36,12 @@ describe FileMover do
       it 'creates a new update record' do
         expect { subject }.to change { Upload.count }.by(1)
       end
+
+      it 'schedules a background migration' do
+        expect_any_instance_of(PersonalFileUploader).to receive(:schedule_background_upload).once
+
+        subject
+      end
     end
 
     context 'when update_markdown fails' do
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index b42ce982b27c2b1e13c9f4b42954b086b7707e05..db2810bbe1dc26b21d5b4f4feb46c58c1c841471 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -11,32 +11,41 @@ describe FileUploader do
   shared_examples 'builds correct legacy storage paths' do
     include_examples 'builds correct paths',
                      store_dir: %r{awesome/project/\h+},
+                     upload_path: %r{\h+/<filename>},
                      absolute_path: %r{#{described_class.root}/awesome/project/secret/foo.jpg}
   end
 
-  shared_examples 'uses hashed storage' do
-    context 'when rolled out attachments' do
-      let(:project) { build_stubbed(:project, namespace: group, name: 'project') }
+  context 'legacy storage' do
+    it_behaves_like 'builds correct legacy storage paths'
 
-      before do
-        allow(project).to receive(:disk_path).and_return('ca/fe/fe/ed')
-      end
+    context 'uses hashed storage' do
+      context 'when rolled out attachments' do
+        let(:project) { build_stubbed(:project, namespace: group, name: 'project') }
 
-      it_behaves_like 'builds correct paths',
-                      store_dir: %r{ca/fe/fe/ed/\h+},
-                      absolute_path: %r{#{described_class.root}/ca/fe/fe/ed/secret/foo.jpg}
-    end
+        include_examples 'builds correct paths',
+                         store_dir: %r{@hashed/\h{2}/\h{2}/\h+},
+                         upload_path: %r{\h+/<filename>}
+      end
 
-    context 'when only repositories are rolled out' do
-      let(:project) { build_stubbed(:project, namespace: group, name: 'project', storage_version: Project::HASHED_STORAGE_FEATURES[:repository]) }
+      context 'when only repositories are rolled out' do
+        let(:project) { build_stubbed(:project, namespace: group, name: 'project', storage_version: Project::HASHED_STORAGE_FEATURES[:repository]) }
 
-      it_behaves_like 'builds correct legacy storage paths'
+        it_behaves_like 'builds correct legacy storage paths'
+      end
     end
   end
 
-  context 'legacy storage' do
-    it_behaves_like 'builds correct legacy storage paths'
-    include_examples 'uses hashed storage'
+  context 'object store is remote' do
+    before do
+      stub_uploads_object_storage
+    end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    # always use hashed storage path for remote uploads
+    it_behaves_like 'builds correct paths',
+                     store_dir: %r{@hashed/\h{2}/\h{2}/\h+},
+                     upload_path: %r{@hashed/\h{2}/\h{2}/\h+/\h+/<filename>}
   end
 
   describe 'initialize' do
@@ -78,6 +87,16 @@ describe FileUploader do
     end
   end
 
+  describe "#migrate!" do
+    before do
+      uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')))
+      stub_uploads_object_storage
+    end
+
+    it_behaves_like "migrates", to_store: described_class::Store::REMOTE
+    it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL
+  end
+
   describe '#upload=' do
     let(:secret) { SecureRandom.hex }
     let(:upload) { create(:upload, :issuable_upload, secret: secret, filename: 'file.txt') }
@@ -93,15 +112,5 @@ describe FileUploader do
 
       uploader.upload = upload
     end
-
-    context 'uploader_context is empty' do
-      it 'fallbacks to regex based extraction' do
-        expect(upload).to receive(:uploader_context).and_return({})
-
-        uploader.upload = upload
-        expect(uploader.secret).to eq(secret)
-        expect(uploader.instance_variable_get(:@identifier)).to eq('file.txt')
-      end
-    end
   end
 end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index 60e35dcf2356fac9ac02c5a342274c0e8b349e32..4fba122cce1d0be95793c11461bd63f7549b0f53 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -27,7 +27,7 @@ describe GitlabUploader do
   describe '#file_cache_storage?' do
     context 'when file storage is used' do
       before do
-        uploader_class.cache_storage(:file)
+        expect(uploader_class).to receive(:cache_storage) { CarrierWave::Storage::File }
       end
 
       it { is_expected.to be_file_cache_storage }
@@ -35,7 +35,7 @@ describe GitlabUploader do
 
     context 'when is remote storage' do
       before do
-        uploader_class.cache_storage(:fog)
+        expect(uploader_class).to receive(:cache_storage) { CarrierWave::Storage::Fog }
       end
 
       it { is_expected.not_to be_file_cache_storage }
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index 5612ec7e661f12bfb60d25f4403485671342aec4..42036d67f3de7eccf152fca753a2c45fbb800aab 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -1,7 +1,8 @@
 require 'spec_helper'
 
 describe JobArtifactUploader do
-  let(:job_artifact) { create(:ci_job_artifact) }
+  let(:store) { described_class::Store::LOCAL }
+  let(:job_artifact) { create(:ci_job_artifact, file_store: store) }
   let(:uploader) { described_class.new(job_artifact, :file) }
 
   subject { uploader }
@@ -11,6 +12,17 @@ describe JobArtifactUploader do
                   cache_dir: %r[artifacts/tmp/cache],
                   work_dir: %r[artifacts/tmp/work]
 
+  context "object store is REMOTE" do
+    before do
+      stub_artifacts_object_storage
+    end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    it_behaves_like "builds correct paths",
+                    store_dir: %r[\h{2}/\h{2}/\h{64}/\d{4}_\d{1,2}_\d{1,2}/\d+/\d+\z]
+  end
+
   describe '#open' do
     subject { uploader.open }
 
@@ -36,6 +48,17 @@ describe JobArtifactUploader do
         end
       end
     end
+
+    context 'when trace is stored in Object storage' do
+      before do
+        allow(uploader).to receive(:file_storage?) { false }
+        allow(uploader).to receive(:url) { 'http://object_storage.com/trace' }
+      end
+
+      it 'returns http io stream' do
+        is_expected.to be_a(Gitlab::Ci::Trace::HttpIO)
+      end
+    end
   end
 
   context 'file is stored in valid local_path' do
@@ -55,4 +78,14 @@ describe JobArtifactUploader do
     it { is_expected.to include("/#{job_artifact.job_id}/#{job_artifact.id}/") }
     it { is_expected.to end_with("ci_build_artifacts.zip") }
   end
+
+  describe "#migrate!" do
+    before do
+      uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/trace/sample_trace')))
+      stub_artifacts_object_storage
+    end
+
+    it_behaves_like "migrates", to_store: described_class::Store::REMOTE
+    it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL
+  end
 end
diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb
index 54c6a8b869b3a5a51dceedf86198b9d0b95f3ba9..eeb6fd90c9d20b538c0e7dfc9c35c9f617e7c8ec 100644
--- a/spec/uploaders/legacy_artifact_uploader_spec.rb
+++ b/spec/uploaders/legacy_artifact_uploader_spec.rb
@@ -1,7 +1,8 @@
 require 'rails_helper'
 
 describe LegacyArtifactUploader do
-  let(:job) { create(:ci_build) }
+  let(:store) { described_class::Store::LOCAL }
+  let(:job) { create(:ci_build, artifacts_file_store: store) }
   let(:uploader) { described_class.new(job, :legacy_artifacts_file) }
   let(:local_path) { described_class.root }
 
@@ -20,6 +21,17 @@ describe LegacyArtifactUploader do
                   cache_dir: %r[artifacts/tmp/cache],
                   work_dir: %r[artifacts/tmp/work]
 
+  context 'object store is remote' do
+    before do
+      stub_artifacts_object_storage
+    end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    it_behaves_like "builds correct paths",
+                    store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z]
+  end
+
   describe '#filename' do
     # we need to use uploader, as this makes to use mounter
     # which initialises uploader.file object
diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb
index 6ebc885daa8fb6034d974cfe5427e858a8e7a0f6..a2fb388661012495984ffbea7393f7f3aeaa20d6 100644
--- a/spec/uploaders/lfs_object_uploader_spec.rb
+++ b/spec/uploaders/lfs_object_uploader_spec.rb
@@ -11,4 +11,62 @@ describe LfsObjectUploader do
                   store_dir: %r[\h{2}/\h{2}],
                   cache_dir: %r[/lfs-objects/tmp/cache],
                   work_dir: %r[/lfs-objects/tmp/work]
+
+  context "object store is REMOTE" do
+    before do
+      stub_lfs_object_storage
+    end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    it_behaves_like "builds correct paths",
+                    store_dir: %r[\h{2}/\h{2}]
+  end
+
+  describe 'migration to object storage' do
+    context 'with object storage disabled' do
+      it "is skipped" do
+        expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+        lfs_object
+      end
+    end
+
+    context 'with object storage enabled' do
+      before do
+        stub_lfs_object_storage(background_upload: true)
+      end
+
+      it 'is scheduled to run after creation' do
+        expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with(described_class.name, 'LfsObject', :file, kind_of(Numeric))
+
+        lfs_object
+      end
+    end
+  end
+
+  describe 'remote file' do
+    let(:remote) { described_class::Store::REMOTE }
+    let(:lfs_object) { create(:lfs_object, file_store: remote) }
+
+    context 'with object storage enabled' do
+      before do
+        stub_lfs_object_storage
+      end
+
+      it 'can store file remotely' do
+        allow(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async)
+
+        store_file(lfs_object)
+
+        expect(lfs_object.file_store).to eq remote
+        expect(lfs_object.file.path).not_to be_blank
+      end
+    end
+  end
+
+  def store_file(lfs_object)
+    lfs_object.file = fixture_file_upload(Rails.root.join("spec/fixtures/dk.png"), "`/png")
+    lfs_object.save!
+  end
 end
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
index 24a2fc0f72e5f06b4dd9a0948fae79d633329d97..a8ba01d70b89c4bad311e6338c0ae847d2c068d9 100644
--- a/spec/uploaders/namespace_file_uploader_spec.rb
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -13,4 +13,26 @@ describe NamespaceFileUploader do
                   store_dir: %r[uploads/-/system/namespace/\d+],
                   upload_path: IDENTIFIER,
                   absolute_path: %r[#{CarrierWave.root}/uploads/-/system/namespace/\d+/#{IDENTIFIER}]
+
+  context "object_store is REMOTE" do
+    before do
+      stub_uploads_object_storage
+    end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    it_behaves_like 'builds correct paths',
+                    store_dir: %r[namespace/\d+/\h+],
+                    upload_path: IDENTIFIER
+  end
+
+  describe "#migrate!" do
+    before do
+      uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/doc_sample.txt')))
+      stub_uploads_object_storage
+    end
+
+    it_behaves_like "migrates", to_store: described_class::Store::REMOTE
+    it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL
+  end
 end
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e7277b337f6579a7bb085b7ccaa23facdec15691
--- /dev/null
+++ b/spec/uploaders/object_storage_spec.rb
@@ -0,0 +1,660 @@
+require 'rails_helper'
+require 'carrierwave/storage/fog'
+
+class Implementation < GitlabUploader
+  include ObjectStorage::Concern
+  include ::RecordsUploads::Concern
+  prepend ::ObjectStorage::Extension::RecordsUploads
+
+  storage_options Gitlab.config.uploads
+
+  private
+
+  # user/:id
+  def dynamic_segment
+    File.join(model.class.to_s.underscore, model.id.to_s)
+  end
+end
+
+describe ObjectStorage do
+  let(:uploader_class) { Implementation }
+  let(:object) { build_stubbed(:user) }
+  let(:uploader) { uploader_class.new(object, :file) }
+
+  describe '#object_store=' do
+    before do
+      allow(uploader_class).to receive(:object_store_enabled?).and_return(true)
+    end
+
+    it "reload the local storage" do
+      uploader.object_store = described_class::Store::LOCAL
+      expect(uploader.file_storage?).to be_truthy
+    end
+
+    it "reload the REMOTE storage" do
+      uploader.object_store = described_class::Store::REMOTE
+      expect(uploader.file_storage?).to be_falsey
+    end
+
+    context 'object_store is Store::LOCAL' do
+      before do
+        uploader.object_store = described_class::Store::LOCAL
+      end
+
+      describe '#store_dir' do
+        it 'is the composition of (base_dir, dynamic_segment)' do
+          expect(uploader.store_dir).to start_with("uploads/-/system/user/")
+        end
+      end
+    end
+
+    context 'object_store is Store::REMOTE' do
+      before do
+        uploader.object_store = described_class::Store::REMOTE
+      end
+
+      describe '#store_dir' do
+        it 'is the composition of (dynamic_segment)' do
+          expect(uploader.store_dir).to start_with("user/")
+        end
+      end
+    end
+  end
+
+  describe '#object_store' do
+    subject { uploader.object_store }
+
+    it "delegates to <mount>_store on model" do
+      expect(object).to receive(:file_store)
+
+      subject
+    end
+
+    context 'when store is null' do
+      before do
+        expect(object).to receive(:file_store).and_return(nil)
+      end
+
+      it "uses Store::LOCAL" do
+        is_expected.to eq(described_class::Store::LOCAL)
+      end
+    end
+
+    context 'when value is set' do
+      before do
+        expect(object).to receive(:file_store).and_return(described_class::Store::REMOTE)
+      end
+
+      it "returns the given value" do
+        is_expected.to eq(described_class::Store::REMOTE)
+      end
+    end
+  end
+
+  describe '#file_cache_storage?' do
+    context 'when file storage is used' do
+      before do
+        expect(uploader_class).to receive(:cache_storage) { CarrierWave::Storage::File }
+      end
+
+      it { expect(uploader).to be_file_cache_storage }
+    end
+
+    context 'when is remote storage' do
+      before do
+        expect(uploader_class).to receive(:cache_storage) { CarrierWave::Storage::Fog }
+      end
+
+      it { expect(uploader).not_to be_file_cache_storage }
+    end
+  end
+
+  # this means the model shall include
+  #   include RecordsUpload::Concern
+  #   prepend ObjectStorage::Extension::RecordsUploads
+  # the object_store persistence is delegated to the `Upload` model.
+  #
+  context 'when persist_object_store? is false' do
+    let(:object) { create(:project, :with_avatar) }
+    let(:uploader) { object.avatar }
+
+    it { expect(object).to be_a(Avatarable) }
+    it { expect(uploader.persist_object_store?).to be_falsey }
+
+    describe 'delegates the object_store logic to the `Upload` model' do
+      it 'sets @upload to the found `upload`' do
+        expect(uploader.upload).to eq(uploader.upload)
+      end
+
+      it 'sets @object_store to the `Upload` value' do
+        expect(uploader.object_store).to eq(uploader.upload.store)
+      end
+    end
+
+    describe '#migrate!' do
+      let(:new_store) { ObjectStorage::Store::REMOTE }
+
+      before do
+        stub_uploads_object_storage(uploader: AvatarUploader)
+      end
+
+      subject { uploader.migrate!(new_store) }
+
+      it 'persist @object_store to the recorded upload' do
+        subject
+
+        expect(uploader.upload.store).to eq(new_store)
+      end
+
+      describe 'fails' do
+        it 'is handled gracefully' do
+          store = uploader.object_store
+          expect_any_instance_of(Upload).to receive(:save!).and_raise("An error")
+
+          expect { subject }.to raise_error("An error")
+          expect(uploader.exists?).to be_truthy
+          expect(uploader.upload.store).to eq(store)
+        end
+      end
+    end
+  end
+
+  # this means the model holds an <mounted_as>_store attribute directly
+  # and do not delegate the object_store persistence to the `Upload` model.
+  #
+  context 'persist_object_store? is true' do
+    context 'when using JobArtifactsUploader' do
+      let(:store) { described_class::Store::LOCAL }
+      let(:object) { create(:ci_job_artifact, :archive, file_store: store) }
+      let(:uploader) { object.file }
+
+      context 'checking described_class' do
+        it "uploader include described_class::Concern" do
+          expect(uploader).to be_a(described_class::Concern)
+        end
+      end
+
+      describe '#use_file' do
+        context 'when file is stored locally' do
+          it "calls a regular path" do
+            expect { |b| uploader.use_file(&b) }.not_to yield_with_args(%r[tmp/cache])
+          end
+        end
+
+        context 'when file is stored remotely' do
+          let(:store) { described_class::Store::REMOTE }
+
+          before do
+            stub_artifacts_object_storage
+          end
+
+          it "calls a cache path" do
+            expect { |b| uploader.use_file(&b) }.to yield_with_args(%r[tmp/cache])
+          end
+        end
+      end
+
+      describe '#migrate!' do
+        subject { uploader.migrate!(new_store) }
+
+        shared_examples "updates the underlying <mounted>_store" do
+          it do
+            subject
+
+            expect(object.file_store).to eq(new_store)
+          end
+        end
+
+        context 'when using the same storage' do
+          let(:new_store) { store }
+
+          it "to not migrate the storage" do
+            subject
+
+            expect(uploader).not_to receive(:store!)
+            expect(uploader.object_store).to eq(store)
+          end
+        end
+
+        context 'when migrating to local storage' do
+          let(:store) { described_class::Store::REMOTE }
+          let(:new_store) { described_class::Store::LOCAL }
+
+          before do
+            stub_artifacts_object_storage
+          end
+
+          include_examples "updates the underlying <mounted>_store"
+
+          it "local file does not exist" do
+            expect(File.exist?(uploader.path)).to eq(false)
+          end
+
+          it "remote file exist" do
+            expect(uploader.file.exists?).to be_truthy
+          end
+
+          it "does migrate the file" do
+            subject
+
+            expect(uploader.object_store).to eq(new_store)
+            expect(File.exist?(uploader.path)).to eq(true)
+          end
+        end
+
+        context 'when migrating to remote storage' do
+          let(:new_store) { described_class::Store::REMOTE }
+          let!(:current_path) { uploader.path }
+
+          it "file does exist" do
+            expect(File.exist?(current_path)).to eq(true)
+          end
+
+          context 'when storage is disabled' do
+            before do
+              stub_artifacts_object_storage(enabled: false)
+            end
+
+            it "to raise an error" do
+              expect { subject }.to raise_error(/Object Storage is not enabled/)
+            end
+          end
+
+          context 'when credentials are set' do
+            before do
+              stub_artifacts_object_storage
+            end
+
+            include_examples "updates the underlying <mounted>_store"
+
+            it "does migrate the file" do
+              subject
+
+              expect(uploader.object_store).to eq(new_store)
+            end
+
+            it "does delete original file" do
+              subject
+
+              expect(File.exist?(current_path)).to eq(false)
+            end
+
+            context 'when subject save fails' do
+              before do
+                expect(uploader).to receive(:persist_object_store!).and_raise(RuntimeError, "exception")
+              end
+
+              it "original file is not removed" do
+                expect { subject }.to raise_error(/exception/)
+
+                expect(File.exist?(current_path)).to eq(true)
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+
+  describe '#fog_directory' do
+    let(:remote_directory) { 'directory' }
+
+    before do
+      allow(uploader_class).to receive(:options) do
+        double(object_store: double(remote_directory: remote_directory))
+      end
+    end
+
+    subject { uploader.fog_directory }
+
+    it { is_expected.to eq(remote_directory) }
+  end
+
+  context 'when file is in use' do
+    def when_file_is_in_use
+      uploader.use_file do
+        yield
+      end
+    end
+
+    it 'cannot migrate' do
+      when_file_is_in_use do
+        expect(uploader).not_to receive(:unsafe_migrate!)
+
+        expect { uploader.migrate!(described_class::Store::REMOTE) }.to raise_error('exclusive lease already taken')
+      end
+    end
+
+    it 'cannot use_file' do
+      when_file_is_in_use do
+        expect(uploader).not_to receive(:unsafe_use_file)
+
+        expect { uploader.use_file }.to raise_error('exclusive lease already taken')
+      end
+    end
+  end
+
+  describe '#fog_credentials' do
+    let(:connection) { Settingslogic.new("provider" => "AWS") }
+
+    before do
+      allow(uploader_class).to receive(:options) do
+        double(object_store: double(connection: connection))
+      end
+    end
+
+    subject { uploader.fog_credentials }
+
+    it { is_expected.to eq(provider: 'AWS') }
+  end
+
+  describe '#fog_public' do
+    subject { uploader.fog_public }
+
+    it { is_expected.to eq(false) }
+  end
+
+  describe '.workhorse_authorize' do
+    subject { uploader_class.workhorse_authorize }
+
+    before do
+      # ensure that we use regular Fog libraries
+      # other tests might call `Fog.mock!` and
+      # it will make tests to fail
+      Fog.unmock!
+    end
+
+    shared_examples 'uses local storage' do
+      it "returns temporary path" do
+        is_expected.to have_key(:TempPath)
+
+        expect(subject[:TempPath]).to start_with(uploader_class.root)
+        expect(subject[:TempPath]).to include(described_class::TMP_UPLOAD_PATH)
+      end
+
+      it "does not return remote store" do
+        is_expected.not_to have_key('RemoteObject')
+      end
+    end
+
+    shared_examples 'uses remote storage' do
+      it "returns remote store" do
+        is_expected.to have_key(:RemoteObject)
+
+        expect(subject[:RemoteObject]).to have_key(:ID)
+        expect(subject[:RemoteObject]).to have_key(:GetURL)
+        expect(subject[:RemoteObject]).to have_key(:DeleteURL)
+        expect(subject[:RemoteObject]).to have_key(:StoreURL)
+        expect(subject[:RemoteObject][:GetURL]).to include(described_class::TMP_UPLOAD_PATH)
+        expect(subject[:RemoteObject][:DeleteURL]).to include(described_class::TMP_UPLOAD_PATH)
+        expect(subject[:RemoteObject][:StoreURL]).to include(described_class::TMP_UPLOAD_PATH)
+      end
+
+      it "does not return local store" do
+        is_expected.not_to have_key('TempPath')
+      end
+    end
+
+    context 'when object storage is disabled' do
+      before do
+        allow(Gitlab.config.uploads.object_store).to receive(:enabled) { false }
+      end
+
+      it_behaves_like 'uses local storage'
+    end
+
+    context 'when object storage is enabled' do
+      before do
+        allow(Gitlab.config.uploads.object_store).to receive(:enabled) { true }
+      end
+
+      context 'when direct upload is enabled' do
+        before do
+          allow(Gitlab.config.uploads.object_store).to receive(:direct_upload) { true }
+        end
+
+        context 'uses AWS' do
+          before do
+            expect(uploader_class).to receive(:object_store_credentials) do
+              { provider: "AWS",
+                aws_access_key_id: "AWS_ACCESS_KEY_ID",
+                aws_secret_access_key: "AWS_SECRET_ACCESS_KEY",
+                region: "eu-central-1" }
+            end
+          end
+
+          it_behaves_like 'uses remote storage' do
+            let(:storage_url) { "https://uploads.s3-eu-central-1.amazonaws.com/" }
+
+            it 'returns links for S3' do
+              expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+              expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+              expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+            end
+          end
+        end
+
+        context 'uses Google' do
+          before do
+            expect(uploader_class).to receive(:object_store_credentials) do
+              { provider: "Google",
+                google_storage_access_key_id: 'ACCESS_KEY_ID',
+                google_storage_secret_access_key: 'SECRET_ACCESS_KEY' }
+            end
+          end
+
+          it_behaves_like 'uses remote storage' do
+            let(:storage_url) { "https://storage.googleapis.com/uploads/" }
+
+            it 'returns links for Google Cloud' do
+              expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+              expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+              expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+            end
+          end
+        end
+
+        context 'uses GDK/minio' do
+          before do
+            expect(uploader_class).to receive(:object_store_credentials) do
+              { provider: "AWS",
+                aws_access_key_id: "AWS_ACCESS_KEY_ID",
+                aws_secret_access_key: "AWS_SECRET_ACCESS_KEY",
+                endpoint: 'http://127.0.0.1:9000',
+                path_style: true,
+                region: "gdk" }
+            end
+          end
+
+          it_behaves_like 'uses remote storage' do
+            let(:storage_url) { "http://127.0.0.1:9000/uploads/" }
+
+            it 'returns links for S3' do
+              expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+              expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+              expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+            end
+          end
+        end
+      end
+
+      context 'when direct upload is disabled' do
+        before do
+          allow(Gitlab.config.uploads.object_store).to receive(:direct_upload) { false }
+        end
+
+        it_behaves_like 'uses local storage'
+      end
+    end
+  end
+
+  describe '#cache!' do
+    subject do
+      uploader.cache!(uploaded_file)
+    end
+
+    context 'when local file is used' do
+      context 'when valid file is used' do
+        let(:uploaded_file) do
+          fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg')
+        end
+
+        it "properly caches the file" do
+          subject
+
+          expect(uploader).to be_exists
+          expect(uploader.path).to start_with(uploader_class.root)
+          expect(uploader.filename).to eq('rails_sample.jpg')
+        end
+      end
+    end
+
+    context 'when local file is used' do
+      let(:temp_file) { Tempfile.new("test") }
+
+      before do
+        FileUtils.touch(temp_file)
+      end
+
+      after do
+        FileUtils.rm_f(temp_file)
+      end
+
+      context 'when valid file is used' do
+        context 'when valid file is specified' do
+          let(:uploaded_file) { temp_file }
+
+          context 'when object storage and direct upload is specified' do
+            before do
+              stub_uploads_object_storage(uploader_class, enabled: true, direct_upload: true)
+            end
+
+            context 'when file is stored' do
+              subject do
+                uploader.store!(uploaded_file)
+              end
+
+              it 'file to be remotely stored in permament location' do
+                subject
+
+                expect(uploader).to be_exists
+                expect(uploader).not_to be_cached
+                expect(uploader).not_to be_file_storage
+                expect(uploader.path).not_to be_nil
+                expect(uploader.path).not_to include('tmp/upload')
+                expect(uploader.path).not_to include('tmp/cache')
+                expect(uploader.object_store).to eq(described_class::Store::REMOTE)
+              end
+            end
+          end
+
+          context 'when object storage and direct upload is not used' do
+            before do
+              stub_uploads_object_storage(uploader_class, enabled: true, direct_upload: false)
+            end
+
+            context 'when file is stored' do
+              subject do
+                uploader.store!(uploaded_file)
+              end
+
+              it 'file to be remotely stored in permament location' do
+                subject
+
+                expect(uploader).to be_exists
+                expect(uploader).not_to be_cached
+                expect(uploader).to be_file_storage
+                expect(uploader.path).not_to be_nil
+                expect(uploader.path).not_to include('tmp/upload')
+                expect(uploader.path).not_to include('tmp/cache')
+                expect(uploader.object_store).to eq(described_class::Store::LOCAL)
+              end
+            end
+          end
+        end
+      end
+    end
+
+    context 'when remote file is used' do
+      let(:temp_file) { Tempfile.new("test") }
+
+      let!(:fog_connection) do
+        stub_uploads_object_storage(uploader_class)
+      end
+
+      before do
+        FileUtils.touch(temp_file)
+      end
+
+      after do
+        FileUtils.rm_f(temp_file)
+      end
+
+      context 'when valid file is used' do
+        context 'when invalid file is specified' do
+          let(:uploaded_file) do
+            UploadedFile.new(temp_file.path, remote_id: "../test/123123")
+          end
+
+          it 'raises an error' do
+            expect { subject }.to raise_error(uploader_class::RemoteStoreError, /Bad file path/)
+          end
+        end
+
+        context 'when non existing file is specified' do
+          let(:uploaded_file) do
+            UploadedFile.new(temp_file.path, remote_id: "test/123123")
+          end
+
+          it 'raises an error' do
+            expect { subject }.to raise_error(uploader_class::RemoteStoreError, /Missing file/)
+          end
+        end
+
+        context 'when valid file is specified' do
+          let(:uploaded_file) do
+            UploadedFile.new(temp_file.path, filename: "my_file.txt", remote_id: "test/123123")
+          end
+
+          let!(:fog_file) do
+            fog_connection.directories.get('uploads').files.create(
+              key: 'tmp/upload/test/123123',
+              body: 'content'
+            )
+          end
+
+          it 'file to be cached and remote stored' do
+            expect { subject }.not_to raise_error
+
+            expect(uploader).to be_exists
+            expect(uploader).to be_cached
+            expect(uploader).not_to be_file_storage
+            expect(uploader.path).not_to be_nil
+            expect(uploader.path).not_to include('tmp/cache')
+            expect(uploader.path).not_to include('tmp/cache')
+            expect(uploader.object_store).to eq(described_class::Store::REMOTE)
+          end
+
+          context 'when file is stored' do
+            subject do
+              uploader.store!(uploaded_file)
+            end
+
+            it 'file to be remotely stored in permament location' do
+              subject
+
+              expect(uploader).to be_exists
+              expect(uploader).not_to be_cached
+              expect(uploader).not_to be_file_storage
+              expect(uploader.path).not_to be_nil
+              expect(uploader.path).not_to include('tmp/upload')
+              expect(uploader.path).not_to include('tmp/cache')
+              expect(uploader.url).to include('/my_file.txt')
+              expect(uploader.object_store).to eq(described_class::Store::REMOTE)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index ed1fba6eddab52acdd6bb0b5e2cbd3cf47049148..c70521d90dc91e570b0fe2419556fc82df7b61cb 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -14,6 +14,18 @@ describe PersonalFileUploader do
                   upload_path: IDENTIFIER,
                   absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet/\d+/#{IDENTIFIER}]
 
+  context "object_store is REMOTE" do
+    before do
+      stub_uploads_object_storage
+    end
+
+    include_context 'with storage', described_class::Store::REMOTE
+
+    it_behaves_like 'builds correct paths',
+                    store_dir: %r[\d+/\h+],
+                    upload_path: IDENTIFIER
+  end
+
   describe '#to_h' do
     before do
       subject.instance_variable_set(:@secret, 'secret')
@@ -30,4 +42,14 @@ describe PersonalFileUploader do
       )
     end
   end
+
+  describe "#migrate!" do
+    before do
+      uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/doc_sample.txt')))
+      stub_uploads_object_storage
+    end
+
+    it_behaves_like "migrates", to_store: described_class::Store::REMOTE
+    it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL
+  end
 end
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b34f427fd8a3fb793949d96ba96eb0f79bac782d
--- /dev/null
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+
+describe ObjectStorage::BackgroundMoveWorker do
+  let(:local) { ObjectStorage::Store::LOCAL }
+  let(:remote) { ObjectStorage::Store::REMOTE }
+
+  def perform
+    described_class.perform_async(uploader_class.name, subject_class, file_field, subject_id)
+  end
+
+  context 'for LFS' do
+    let!(:lfs_object) { create(:lfs_object, :with_file, file_store: local) }
+    let(:uploader_class) { LfsObjectUploader }
+    let(:subject_class) { LfsObject }
+    let(:file_field) { :file }
+    let(:subject_id) { lfs_object.id }
+
+    context 'when object storage is enabled' do
+      before do
+        stub_lfs_object_storage(background_upload: true)
+      end
+
+      it 'uploads object to storage' do
+        expect { perform }.to change { lfs_object.reload.file_store }.from(local).to(remote)
+      end
+
+      context 'when background upload is disabled' do
+        before do
+          allow(Gitlab.config.lfs.object_store).to receive(:background_upload) { false }
+        end
+
+        it 'is skipped' do
+          expect { perform }.not_to change { lfs_object.reload.file_store }
+        end
+      end
+    end
+
+    context 'when object storage is disabled' do
+      before do
+        stub_lfs_object_storage(enabled: false)
+      end
+
+      it "doesn't migrate files" do
+        perform
+
+        expect(lfs_object.reload.file_store).to eq(local)
+      end
+    end
+  end
+
+  context 'for legacy artifacts' do
+    let(:build) { create(:ci_build, :legacy_artifacts) }
+    let(:uploader_class) { LegacyArtifactUploader }
+    let(:subject_class) { Ci::Build }
+    let(:file_field) { :artifacts_file }
+    let(:subject_id) { build.id }
+
+    context 'when local storage is used' do
+      let(:store) { local }
+
+      context 'and remote storage is defined' do
+        before do
+          stub_artifacts_object_storage(background_upload: true)
+        end
+
+        it "migrates file to remote storage" do
+          perform
+
+          expect(build.reload.artifacts_file_store).to eq(remote)
+        end
+
+        context 'for artifacts_metadata' do
+          let(:file_field) { :artifacts_metadata }
+
+          it 'migrates metadata to remote storage' do
+            perform
+
+            expect(build.reload.artifacts_metadata_store).to eq(remote)
+          end
+        end
+      end
+    end
+  end
+
+  context 'for job artifacts' do
+    let(:artifact) { create(:ci_job_artifact, :archive) }
+    let(:uploader_class) { JobArtifactUploader }
+    let(:subject_class) { Ci::JobArtifact }
+    let(:file_field) { :file }
+    let(:subject_id) { artifact.id }
+
+    context 'when local storage is used' do
+      let(:store) { local }
+
+      context 'and remote storage is defined' do
+        before do
+          stub_artifacts_object_storage(background_upload: true)
+        end
+
+        it "migrates file to remote storage" do
+          perform
+
+          expect(artifact.reload.file_store).to eq(remote)
+        end
+      end
+    end
+  end
+
+  context 'for uploads' do
+    let!(:project) { create(:project, :with_avatar) }
+    let(:uploader_class) { AvatarUploader }
+    let(:file_field) { :avatar }
+
+    context 'when local storage is used' do
+      let(:store) { local }
+
+      context 'and remote storage is defined' do
+        before do
+          stub_uploads_object_storage(uploader_class, background_upload: true)
+        end
+
+        describe 'supports using the model' do
+          let(:subject_class) { project.class }
+          let(:subject_id) { project.id }
+
+          it "migrates file to remote storage" do
+            perform
+
+            expect(project.reload.avatar.file_storage?).to be_falsey
+          end
+        end
+
+        describe 'supports using the Upload' do
+          let(:subject_class) { Upload }
+          let(:subject_id) { project.avatar.upload.id }
+
+          it "migrates file to remote storage" do
+            perform
+
+            expect(project.reload.avatar.file_storage?).to be_falsey
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7a7dcb716804b1304456e2ea25905fce171e73c7
--- /dev/null
+++ b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
@@ -0,0 +1,119 @@
+require 'spec_helper'
+
+describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
+  shared_context 'sanity_check! fails' do
+    before do
+      expect(described_class).to receive(:sanity_check!).and_raise(described_class::SanityCheckError)
+    end
+  end
+
+  let!(:projects) { create_list(:project, 10, :with_avatar) }
+  let(:uploads) { Upload.all }
+  let(:model_class) { Project }
+  let(:mounted_as) { :avatar }
+  let(:to_store) { ObjectStorage::Store::REMOTE }
+
+  before do
+    stub_uploads_object_storage(AvatarUploader)
+  end
+
+  describe '.enqueue!' do
+    def enqueue!
+      described_class.enqueue!(uploads, Project, mounted_as, to_store)
+    end
+
+    it 'is guarded by .sanity_check!' do
+      expect(described_class).to receive(:perform_async)
+      expect(described_class).to receive(:sanity_check!)
+
+      enqueue!
+    end
+
+    context 'sanity_check! fails' do
+      include_context 'sanity_check! fails'
+
+      it 'does not enqueue a job' do
+        expect(described_class).not_to receive(:perform_async)
+
+        expect { enqueue! }.to raise_error(described_class::SanityCheckError)
+      end
+    end
+  end
+
+  describe '.sanity_check!' do
+    shared_examples 'raises a SanityCheckError' do
+      let(:mount_point) { nil }
+
+      it do
+        expect { described_class.sanity_check!(uploads, model_class, mount_point) }
+          .to raise_error(described_class::SanityCheckError)
+      end
+    end
+
+    context 'uploader types mismatch' do
+      let!(:outlier) { create(:upload, uploader: 'FileUploader') }
+
+      include_examples 'raises a SanityCheckError'
+    end
+
+    context 'model types mismatch' do
+      let!(:outlier) { create(:upload, model_type: 'Potato') }
+
+      include_examples 'raises a SanityCheckError'
+    end
+
+    context 'mount point not found' do
+      include_examples 'raises a SanityCheckError' do
+        let(:mount_point) { :potato }
+      end
+    end
+  end
+
+  describe '#perform' do
+    def perform
+      described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, to_store)
+    rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
+      # swallow
+    end
+
+    shared_examples 'outputs correctly' do |success: 0, failures: 0|
+      total = success + failures
+
+      if success > 0
+        it 'outputs the reports' do
+          expect(Rails.logger).to receive(:info).with(%r{Migrated #{success}/#{total} files})
+
+          perform
+        end
+      end
+
+      if failures > 0
+        it 'outputs upload failures' do
+          expect(Rails.logger).to receive(:warn).with(/Error .* I am a teapot/)
+
+          perform
+        end
+      end
+    end
+
+    it_behaves_like 'outputs correctly', success: 10
+
+    it 'migrates files' do
+      perform
+
+      aggregate_failures do
+        projects.each do |project|
+          expect(project.reload.avatar.upload.local?).to be_falsey
+        end
+      end
+    end
+
+    context 'migration is unsuccessful' do
+      before do
+        allow_any_instance_of(ObjectStorage::Concern).to receive(:migrate!).and_raise(CarrierWave::UploadError, "I am a teapot.")
+      end
+
+      it_behaves_like 'outputs correctly', failures: 10
+    end
+  end
+end
diff --git a/spec/validators/url_placeholder_validator_spec.rb b/spec/validators/url_placeholder_validator_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b76d8acdf88c521f6c2d3ef3f472e84bbedc1320
--- /dev/null
+++ b/spec/validators/url_placeholder_validator_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe UrlPlaceholderValidator do
+  let(:validator) { described_class.new(attributes: [:link_url],  **options) }
+  let!(:badge) { build(:badge) }
+  let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
+
+  subject { validator.validate_each(badge, :link_url, badge.link_url) }
+
+  describe '#validates_each' do
+    context 'with no options' do
+      let(:options) { {} }
+
+      it 'allows http and https protocols by default' do
+        expect(validator.send(:default_options)[:protocols]).to eq %w(http https)
+      end
+
+      it 'checks that the url structure is valid' do
+        badge.link_url = placeholder_url
+
+        subject
+
+        expect(badge.errors.empty?).to be false
+      end
+    end
+
+    context 'with placeholder regex' do
+      let(:options) { { placeholder_regex: /(project_path|project_id|commit_sha|default_branch)/ } }
+
+      it 'checks that the url is valid and obviate placeholders that match regex' do
+        badge.link_url = placeholder_url
+
+        subject
+
+        expect(badge.errors.empty?).to be true
+      end
+    end
+  end
+end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..763dff181d2fd107e1685f9391d50e818022fbe9
--- /dev/null
+++ b/spec/validators/url_validator_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe UrlValidator do
+  let(:validator) { described_class.new(attributes: [:link_url],  **options) }
+  let!(:badge) { build(:badge) }
+
+  subject { validator.validate_each(badge, :link_url, badge.link_url) }
+
+  describe '#validates_each' do
+    context 'with no options' do
+      let(:options) { {} }
+
+      it 'allows http and https protocols by default' do
+        expect(validator.send(:default_options)[:protocols]).to eq %w(http https)
+      end
+
+      it 'checks that the url structure is valid' do
+        badge.link_url = 'http://www.google.es/%{whatever}'
+
+        subject
+
+        expect(badge.errors.empty?).to be false
+      end
+    end
+
+    context 'with protocols' do
+      let(:options) { { protocols: %w(http) } }
+
+      it 'allows urls with the defined protocols' do
+        badge.link_url = 'http://www.example.com'
+
+        subject
+
+        expect(badge.errors.empty?).to be true
+      end
+
+      it 'add error if the url protocol does not match the selected ones' do
+        badge.link_url = 'https://www.example.com'
+
+        subject
+
+        expect(badge.errors.empty?).to be false
+      end
+    end
+  end
+end
diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb
index b4359d819a076cda67bc252a91f2a948628e4921..099baacf0197e8e9509ca9e7dcf405e03c7eea58 100644
--- a/spec/views/admin/dashboard/index.html.haml_spec.rb
+++ b/spec/views/admin/dashboard/index.html.haml_spec.rb
@@ -18,4 +18,10 @@ describe 'admin/dashboard/index.html.haml' do
     expect(rendered).to have_content 'GitLab Workhorse'
     expect(rendered).to have_content Gitlab::Workhorse.version
   end
+
+  it "includes revision of GitLab" do
+    render
+
+    expect(rendered).to have_content "#{Gitlab::VERSION} (#{Gitlab::REVISION})"
+  end
 end
diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb
deleted file mode 100644
index 7724d54c569f0da9be2c840136f59077fa48d0ec..0000000000000000000000000000000000000000
--- a/spec/views/ci/lints/show.html.haml_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'spec_helper'
-
-describe 'ci/lints/show' do
-  include Devise::Test::ControllerHelpers
-
-  describe 'XSS protection' do
-    let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(content)) }
-    before do
-      assign(:status, true)
-      assign(:builds, config_processor.builds)
-      assign(:stages, config_processor.stages)
-      assign(:jobs, config_processor.jobs)
-    end
-
-    context 'when builds attrbiutes contain HTML nodes' do
-      let(:content) do
-        {
-          rspec: {
-            script: '<h1>rspec</h1>',
-            stage: 'test'
-          }
-        }
-      end
-
-      it 'does not render HTML elements' do
-        render
-
-        expect(rendered).not_to have_css('h1', text: 'rspec')
-      end
-    end
-
-    context 'when builds attributes do not contain HTML nodes' do
-      let(:content) do
-        {
-          rspec: {
-            script: 'rspec',
-            stage: 'test'
-          }
-        }
-      end
-
-      it 'shows configuration in the table' do
-        render
-
-        expect(rendered).to have_css('td pre', text: 'rspec')
-      end
-    end
-  end
-
-  let(:content) do
-    {
-      build_template: {
-        script: './build.sh',
-        tags: ['dotnet'],
-        only: ['test@dude/repo'],
-        except: ['deploy'],
-        environment: 'testing'
-      }
-    }
-  end
-
-  let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(content)) }
-
-  context 'when the content is valid' do
-    before do
-      assign(:status, true)
-      assign(:builds, config_processor.builds)
-      assign(:stages, config_processor.stages)
-      assign(:jobs, config_processor.jobs)
-    end
-
-    it 'shows the correct values' do
-      render
-
-      expect(rendered).to have_content('Tag list: dotnet')
-      expect(rendered).to have_content('Only policy: refs, test@dude/repo')
-      expect(rendered).to have_content('Except policy: refs, deploy')
-      expect(rendered).to have_content('Environment: testing')
-      expect(rendered).to have_content('When: on_success')
-    end
-  end
-
-  context 'when the content is invalid' do
-    before do
-      assign(:status, false)
-      assign(:error, 'Undefined error')
-    end
-
-    it 'shows error message' do
-      render
-
-      expect(rendered).to have_content('Status: syntax is incorrect')
-      expect(rendered).to have_content('Error: Undefined error')
-      expect(rendered).not_to have_content('Tag list:')
-    end
-  end
-end
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index c5f455b8948dfc2056b91d4798a80e61c249b7c3..f28bf430f02fa352d4f79c6ea7a03b5bbbe09285 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -12,7 +12,7 @@ describe 'layouts/nav/sidebar/_project' do
   end
 
   describe 'issue boards' do
-    it 'has boards tab when multiple issue boards available' do
+    it 'has board tab' do
       render
 
       expect(rendered).to have_css('a[title="Board"]')
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index 62af946dcab78b628edd8b16102f2880d00876c1..15fce65979b6fa9c581f65e17664b03cf1411e6b 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -1,7 +1,8 @@
 require 'spec_helper'
 
 describe 'projects/_home_panel' do
-  let(:project) { create(:project, :public) }
+  let(:group) { create(:group) }
+  let(:project) { create(:project, :public, namespace: group) }
 
   let(:notification_settings) do
     user&.notification_settings_for(project)
@@ -35,4 +36,55 @@ describe 'projects/_home_panel' do
       expect(rendered).not_to have_selector('.notification_dropdown')
     end
   end
+
+  context 'when project' do
+    let!(:user) { create(:user) }
+    let(:badges) { project.badges }
+
+    context 'has no badges' do
+      it 'should not render any badge' do
+        render
+
+        expect(rendered).to have_selector('.project-badges')
+        expect(rendered).not_to have_selector('.project-badges > a')
+      end
+    end
+
+    shared_examples 'show badges' do
+      it 'should render the all badges' do
+        render
+
+        expect(rendered).to have_selector('.project-badges a')
+
+        badges.each do |badge|
+          expect(rendered).to have_link(href: badge.rendered_link_url)
+        end
+      end
+    end
+
+    context 'only has group badges' do
+      before do
+        create(:group_badge, group: project.group)
+      end
+
+      it_behaves_like 'show badges'
+    end
+
+    context 'only has project badges' do
+      before do
+        create(:project_badge, project: project)
+      end
+
+      it_behaves_like 'show badges'
+    end
+
+    context 'has both group and project badges' do
+      before do
+        create(:project_badge, project: project)
+        create(:group_badge, group: project.group)
+      end
+
+      it_behaves_like 'show badges'
+    end
+  end
 end
diff --git a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
index d0e692635b9ff30320b3d3d3b09e90c0ffbcea87..8b9aab30286b5a8115958aa039c8f2f4a193d2ce 100644
--- a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
@@ -8,7 +8,8 @@ describe 'projects/buttons/_dropdown' do
       assign(:project, project)
 
       allow(view).to receive(:current_user).and_return(user)
-      allow(view).to receive(:can?).and_return(true)
+      allow(view).to receive(:can?).with(user, :push_code, project).and_return(true)
+      allow(view).to receive(:can_collaborate_with_project?).and_return(true)
     end
 
     context 'empty repository' do
diff --git a/spec/views/projects/ci/lints/show.html.haml_spec.rb b/spec/views/projects/ci/lints/show.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2f0cd38c14a55086fa5ee918724d0029ae53a894
--- /dev/null
+++ b/spec/views/projects/ci/lints/show.html.haml_spec.rb
@@ -0,0 +1,99 @@
+require 'spec_helper'
+
+describe 'projects/ci/lints/show' do
+  include Devise::Test::ControllerHelpers
+  let(:project) { create(:project, :repository) }
+  let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(content)) }
+
+  describe 'XSS protection' do
+    before do
+      assign(:project, project)
+      assign(:status, true)
+      assign(:builds, config_processor.builds)
+      assign(:stages, config_processor.stages)
+      assign(:jobs, config_processor.jobs)
+    end
+
+    context 'when builds attrbiutes contain HTML nodes' do
+      let(:content) do
+        {
+          rspec: {
+            script: '<h1>rspec</h1>',
+            stage: 'test'
+          }
+        }
+      end
+
+      it 'does not render HTML elements' do
+        render
+
+        expect(rendered).not_to have_css('h1', text: 'rspec')
+      end
+    end
+
+    context 'when builds attributes do not contain HTML nodes' do
+      let(:content) do
+        {
+          rspec: {
+            script: 'rspec',
+            stage: 'test'
+          }
+        }
+      end
+
+      it 'shows configuration in the table' do
+        render
+
+        expect(rendered).to have_css('td pre', text: 'rspec')
+      end
+    end
+  end
+
+  context 'when the content is valid' do
+    let(:content) do
+      {
+        build_template: {
+          script: './build.sh',
+          tags: ['dotnet'],
+          only: ['test@dude/repo'],
+          except: ['deploy'],
+          environment: 'testing'
+        }
+      }
+    end
+
+    before do
+      assign(:project, project)
+      assign(:status, true)
+      assign(:builds, config_processor.builds)
+      assign(:stages, config_processor.stages)
+      assign(:jobs, config_processor.jobs)
+    end
+
+    it 'shows the correct values' do
+      render
+
+      expect(rendered).to have_content('Tag list: dotnet')
+      expect(rendered).to have_content('Only policy: refs, test@dude/repo')
+      expect(rendered).to have_content('Except policy: refs, deploy')
+      expect(rendered).to have_content('Environment: testing')
+      expect(rendered).to have_content('When: on_success')
+    end
+  end
+
+  context 'when the content is invalid' do
+    before do
+      assign(:project, project)
+      assign(:status, false)
+      assign(:error, 'Undefined error')
+    end
+
+    it 'shows error message' do
+      render
+
+      expect(rendered).to have_content('Status: syntax is incorrect')
+      expect(rendered).to have_content('Error: Undefined error')
+      expect(rendered).not_to have_content('Tag list:')
+    end
+  end
+end
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index 448b925cf343ae5cb4c5a2570f4e472315008b38..2fdd28a3be41c976ef7592e5349ae6e3a7643746 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -7,6 +7,7 @@ describe 'projects/commit/_commit_box.html.haml' do
   before do
     assign(:project, project)
     assign(:commit, project.commit)
+    allow(view).to receive(:current_user).and_return(user)
     allow(view).to receive(:can_collaborate_with_project?).and_return(false)
   end
 
@@ -47,7 +48,8 @@ describe 'projects/commit/_commit_box.html.haml' do
   context 'viewing a commit' do
     context 'as a developer' do
       before do
-        expect(view).to receive(:can_collaborate_with_project?).and_return(true)
+        project.add_developer(user)
+        allow(view).to receive(:can_collaborate_with_project?).and_return(true)
       end
 
       it 'has a link to create a new tag' do
@@ -58,10 +60,6 @@ describe 'projects/commit/_commit_box.html.haml' do
     end
 
     context 'as a non-developer' do
-      before do
-        expect(view).to receive(:can_collaborate_with_project?).and_return(false)
-      end
-
       it 'does not have a link to create a new tag' do
         render
 
diff --git a/spec/views/projects/diffs/_stats.html.haml_spec.rb b/spec/views/projects/diffs/_stats.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c7d2f85747c4a1788734aaffc7ad30c097296035
--- /dev/null
+++ b/spec/views/projects/diffs/_stats.html.haml_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe 'projects/diffs/_stats.html.haml' do
+  let(:project) { create(:project, :repository) }
+  let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
+
+  def render_view
+    render partial: "projects/diffs/stats", locals: { diff_files: commit.diffs.diff_files }
+  end
+
+  context 'when the commit contains several changes' do
+    it 'uses plural for additions' do
+      render_view
+
+      expect(rendered).to have_text('additions')
+    end
+
+    it 'uses plural for deletions' do
+      render_view
+    end
+  end
+
+  context 'when the commit contains no addition and no deletions' do
+    let(:commit) { project.commit('4cd80ccab63c82b4bad16faa5193fbd2aa06df40') }
+
+    it 'uses plural for additions' do
+      render_view
+
+      expect(rendered).to have_text('additions')
+    end
+
+    it 'uses plural for deletions' do
+      render_view
+
+      expect(rendered).to have_text('deletions')
+    end
+  end
+
+  context 'when the commit contains exactly one addition and one deletion' do
+    let(:commit) { project.commit('08f22f255f082689c0d7d39d19205085311542bc') }
+
+    it 'uses singular for additions' do
+      render_view
+
+      expect(rendered).to have_text('addition')
+      expect(rendered).not_to have_text('additions')
+    end
+
+    it 'uses singular for deletions' do
+      render_view
+
+      expect(rendered).to have_text('deletion')
+      expect(rendered).not_to have_text('deletions')
+    end
+  end
+end
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 6a67da79ec585fa13375531a37d6fbd82596a94e..c93152b88e30abd99833813617928a01aacdaa5a 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -1,8 +1,10 @@
 require 'spec_helper'
 
 describe 'projects/jobs/show' do
+  let(:user) { create(:user) }
   let(:project) { create(:project, :repository) }
   let(:build) { create(:ci_build, pipeline: pipeline) }
+  let(:builds) { project.builds.present(current_user: user) }
 
   let(:pipeline) do
     create(:ci_pipeline, project: project, sha: project.commit.id)
@@ -11,6 +13,7 @@ describe 'projects/jobs/show' do
   before do
     assign(:build, build.present)
     assign(:project, project)
+    assign(:builds, builds)
 
     allow(view).to receive(:can?).and_return(true)
   end
@@ -18,7 +21,7 @@ describe 'projects/jobs/show' do
   describe 'environment info in job view' do
     context 'job with latest deployment' do
       let(:build) do
-        create(:ci_build, :success, environment: 'staging')
+        create(:ci_build, :success, :trace_artifact, environment: 'staging')
       end
 
       before do
@@ -37,11 +40,11 @@ describe 'projects/jobs/show' do
 
     context 'job with outdated deployment' do
       let(:build) do
-        create(:ci_build, :success, environment: 'staging', pipeline: pipeline)
+        create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
       end
 
       let(:second_build) do
-        create(:ci_build, :success, environment: 'staging', pipeline: pipeline)
+        create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
       end
 
       let(:environment) do
@@ -67,7 +70,7 @@ describe 'projects/jobs/show' do
 
     context 'job failed to deploy' do
       let(:build) do
-        create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
+        create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
       end
 
       let!(:environment) do
@@ -85,7 +88,7 @@ describe 'projects/jobs/show' do
 
     context 'job will deploy' do
       let(:build) do
-        create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
+        create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
       end
 
       context 'when environment exists' do
@@ -133,7 +136,7 @@ describe 'projects/jobs/show' do
 
     context 'job that failed to deploy and environment has not been created' do
       let(:build) do
-        create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
+        create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
       end
 
       let!(:environment) do
@@ -151,7 +154,7 @@ describe 'projects/jobs/show' do
 
     context 'job that will deploy and environment has not been created' do
       let(:build) do
-        create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
+        create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
       end
 
       let!(:environment) do
@@ -171,8 +174,9 @@ describe 'projects/jobs/show' do
   end
 
   context 'when job is running' do
+    let(:build) { create(:ci_build, :trace_live, :running, pipeline: pipeline) }
+
     before do
-      build.run!
       render
     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 3ca6711455880879cb0b9873b2a2eda6368eb22a..b1c6565c08a856fdfe00f69abe78753c072472eb 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -28,6 +28,6 @@ describe 'projects/merge_requests/_commits.html.haml' do
     commit = merge_request.commits.first # HEAD
     href = diffs_project_merge_request_path(target_project, merge_request, commit_id: commit)
 
-    expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
+    expect(rendered).to have_link(href: href)
   end
 end
diff --git a/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb b/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
deleted file mode 100644
index 7b3001508748b4edf3e52aabae3f1fd360ead940..0000000000000000000000000000000000000000
--- a/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'spec_helper'
-
-describe 'projects/pipelines_settings/_show' do
-  let(:project) { create(:project, :repository) }
-
-  before do
-    assign :project, project
-  end
-
-  context 'when kubernetes is not active' do
-    context 'when auto devops domain is not defined' do
-      it 'shows warning message' do
-        render
-
-        expect(rendered).to have_css('.settings-message')
-        expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and a')
-        expect(rendered).to have_link('Kubernetes cluster')
-      end
-    end
-
-    context 'when auto devops domain is defined' do
-      before do
-        project.build_auto_devops(domain: 'example.com')
-      end
-
-      it 'shows warning message' do
-        render
-
-        expect(rendered).to have_css('.settings-message')
-        expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a')
-        expect(rendered).to have_link('Kubernetes cluster')
-      end
-    end
-  end
-
-  context 'when kubernetes is active' do
-    before do
-      create(:kubernetes_service, project: project)
-    end
-
-    context 'when auto devops domain is not defined' do
-      it 'shows warning message' do
-        render
-
-        expect(rendered).to have_css('.settings-message')
-        expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
-      end
-    end
-
-    context 'when auto devops domain is defined' do
-      before do
-        project.build_auto_devops(domain: 'example.com')
-      end
-
-      it 'does not show warning message' do
-        render
-
-        expect(rendered).not_to have_css('.settings-message')
-      end
-    end
-  end
-end
diff --git a/spec/views/projects/services/_form.haml_spec.rb b/spec/views/projects/services/_form.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85167bca115131866ac7b12995a622576a18940e
--- /dev/null
+++ b/spec/views/projects/services/_form.haml_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe 'projects/services/_form' do
+  let(:project) { create(:redmine_project) }
+  let(:user) { create(:admin) }
+
+  before do
+    assign(:project, project)
+
+    allow(controller).to receive(:current_user).and_return(user)
+
+    allow(view).to receive_messages(current_user: user,
+                                    can?: true,
+                                    current_application_settings: Gitlab::CurrentSettings.current_application_settings)
+  end
+
+  context 'commit_events and merge_request_events' do
+    before do
+      assign(:service, project.redmine_service)
+    end
+
+    it 'display merge_request_events and commit_events descriptions' do
+      allow(RedmineService).to receive(:supported_events).and_return(%w(commit merge_request))
+
+      render
+
+      expect(rendered).to have_content('Event will be triggered when a commit is created/updated')
+      expect(rendered).to have_content('Event will be triggered when a merge request is created/updated/merged')
+    end
+
+    context 'when service is JIRA' do
+      let(:project) { create(:jira_project) }
+
+      before do
+        assign(:service, project.jira_service)
+      end
+
+      it 'display merge_request_events and commit_events descriptions' do
+        render
+
+        expect(rendered).to have_content('JIRA comments will be created when an issue gets referenced in a commit.')
+        expect(rendered).to have_content('JIRA comments will be created when an issue gets referenced in a merge request.')
+      end
+    end
+  end
+end
diff --git a/spec/views/projects/settings/ci_cd/_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_form.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..be9a4d9c57c45a8c81d0a2680590a791557ff71a
--- /dev/null
+++ b/spec/views/projects/settings/ci_cd/_form.html.haml_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe 'projects/settings/ci_cd/_form' do
+  let(:project) { create(:project, :repository) }
+
+  before do
+    assign :project, project
+  end
+
+  context 'when kubernetes is not active' do
+    context 'when auto devops domain is not defined' do
+      it 'shows warning message' do
+        render
+
+        expect(rendered).to have_css('.settings-message')
+        expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and a')
+        expect(rendered).to have_link('Kubernetes cluster')
+      end
+    end
+
+    context 'when auto devops domain is defined' do
+      before do
+        project.build_auto_devops(domain: 'example.com')
+      end
+
+      it 'shows warning message' do
+        render
+
+        expect(rendered).to have_css('.settings-message')
+        expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a')
+        expect(rendered).to have_link('Kubernetes cluster')
+      end
+    end
+  end
+
+  context 'when kubernetes is active' do
+    before do
+      create(:kubernetes_service, project: project)
+    end
+
+    context 'when auto devops domain is not defined' do
+      it 'shows warning message' do
+        render
+
+        expect(rendered).to have_css('.settings-message')
+        expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
+      end
+    end
+
+    context 'when auto devops domain is defined' do
+      before do
+        project.build_auto_devops(domain: 'example.com')
+      end
+
+      it 'does not show warning message' do
+        render
+
+        expect(rendered).not_to have_css('.settings-message')
+      end
+    end
+  end
+end
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 44b32df0395a2dde4707c5a446818e67beed4f09..3b098320ad73c464274b4eabf1502e7b20bfdd4d 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -13,6 +13,7 @@ describe 'projects/tree/show' do
 
     allow(view).to receive(:can?).and_return(true)
     allow(view).to receive(:can_collaborate_with_project?).and_return(true)
+    allow(view).to receive_message_chain('user_access.can_push_to_branch?').and_return(true)
     allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
   end
 
diff --git a/spec/views/shared/milestones/_top.html.haml.rb b/spec/views/shared/milestones/_top.html.haml.rb
new file mode 100644
index 0000000000000000000000000000000000000000..516d81c87acfd462cb71799c5f3e005b35d8d5c2
--- /dev/null
+++ b/spec/views/shared/milestones/_top.html.haml.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'shared/milestones/_top.html.haml' do
+  set(:group) { create(:group) }
+  let(:project) { create(:project, group: group) }
+  let(:milestone) { create(:milestone, project: project) }
+
+  before do
+    allow(milestone).to receive(:milestones) { [] }
+  end
+
+  it 'renders a deprecation message for a legacy milestone' do
+    allow(milestone).to receive(:legacy_group_milestone?) { true }
+
+    render 'shared/milestones/top', milestone: milestone
+
+    expect(rendered).to have_css('.milestone-deprecation-message')
+  end
+
+  it 'renders a deprecation message for a dashboard milestone' do
+    allow(milestone).to receive(:dashboard_milestone?) { true }
+
+    render 'shared/milestones/top', milestone: milestone
+
+    expect(rendered).to have_css('.milestone-deprecation-message')
+  end
+
+  it 'does not render a deprecation message for a non-legacy and non-dashboard milestone' do
+    assign :group, group
+
+    render 'shared/milestones/top', milestone: milestone
+
+    expect(rendered).not_to have_css('.milestone-deprecation-message')
+  end
+end
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b768588c6e13f31d6784d3268105f324bcfd8067
--- /dev/null
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe ArchiveTraceWorker do
+  describe '#perform' do
+    subject { described_class.new.perform(job&.id) }
+
+    context 'when job is found' do
+      let(:job) { create(:ci_build) }
+
+      it 'executes service' do
+        expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!)
+
+        subject
+      end
+    end
+
+    context 'when job is not found' do
+      let(:job) { nil }
+
+      it 'does not execute service' do
+        expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!)
+
+        subject
+      end
+    end
+  end
+end
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index c7ff8cf3b92c390675e3d7223495ee9e3376b7f0..acd8da11d8d45d25c7cddc32383c1a49eeb63788 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -14,7 +14,7 @@ describe BuildFinishedWorker do
         expect_any_instance_of(BuildTraceSectionsWorker).to receive(:perform)
         expect_any_instance_of(BuildCoverageWorker).to receive(:perform)
         expect(BuildHooksWorker).to receive(:perform_async)
-        expect(CreateTraceArtifactWorker).to receive(:perform_async)
+        expect(ArchiveTraceWorker).to receive(:perform_async)
 
         described_class.new.perform(build.id)
       end
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index 68cfe9d5545eed4540e72b90c6f57630fba2149a..615462380e05590a990b7b320bd545e1f5e9d607 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::GithubImport::ObjectImporter do
       importer_class = double(:importer_class)
       importer_instance = double(:importer_instance)
       representation = double(:representation)
-      project = double(:project, path_with_namespace: 'foo/bar')
+      project = double(:project, full_path: 'foo/bar')
       client = double(:client)
 
       expect(worker)
diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..24c0a3c6a20a8bb07efa0f0ae3cf4c9e43efed51
--- /dev/null
+++ b/spec/workers/concerns/pipeline_background_queue_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe PipelineBackgroundQueue do
+  let(:worker) do
+    Class.new do
+      def self.name
+        'DummyWorker'
+      end
+
+      include ApplicationWorker
+      include PipelineBackgroundQueue
+    end
+  end
+
+  it 'sets a default object storage queue automatically' do
+    expect(worker.sidekiq_options['queue'])
+      .to eq 'pipeline_background:dummy'
+  end
+end
diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb
index 4af0de86ac9a53179a03f372a6adf7c607283808..54ab07981a4100d827467604a6ef1dcc215fd074 100644
--- a/spec/workers/concerns/waitable_worker_spec.rb
+++ b/spec/workers/concerns/waitable_worker_spec.rb
@@ -14,6 +14,12 @@ describe WaitableWorker do
       include ApplicationWorker
       prepend WaitableWorker
 
+      # This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore
+      # the visibility of prepended modules. See
+      # https://github.com/rspec/rspec-mocks/issues/1231 for more details.
+      def self.bulk_perform_inline(args_list)
+      end
+
       def perform(i = 0)
         self.class.counter += i
       end
diff --git a/spec/workers/create_trace_artifact_worker_spec.rb b/spec/workers/create_trace_artifact_worker_spec.rb
deleted file mode 100644
index 854abd9cca77eb0aa35fc983d27c125b69cb856f..0000000000000000000000000000000000000000
--- a/spec/workers/create_trace_artifact_worker_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-
-describe CreateTraceArtifactWorker do
-  describe '#perform' do
-    subject { described_class.new.perform(job&.id) }
-
-    context 'when job is found' do
-      let(:job) { create(:ci_build) }
-
-      it 'executes service' do
-        expect_any_instance_of(Ci::CreateTraceArtifactService)
-          .to receive(:execute).with(job)
-
-        subject
-      end
-    end
-
-    context 'when job is not found' do
-      let(:job) { nil }
-
-      it 'does not execute service' do
-        expect_any_instance_of(Ci::CreateTraceArtifactService)
-          .not_to receive(:execute)
-
-        subject
-      end
-    end
-  end
-end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 47297de738b0492d513960ed3a76676324bd4a16..74539a7e493763816124703b8a7ecbf2d58c7e96 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -195,6 +195,12 @@ describe GitGarbageCollectWorker do
 
         expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
       end
+
+      it 'cleans up repository after finishing' do
+        expect_any_instance_of(Project).to receive(:cleanup).and_call_original
+
+        subject.perform(project.id, 'gc', lease_key, lease_uuid)
+      end
     end
 
     context 'with bitmaps enabled' do
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 7c8c665a9b30fe13b565da86532f3d68ab6baf3b..48e7eaf32fca4c5ba07be1e8490708b90b662369 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportDiffNoteWorker do
 
   describe '#import' do
     it 'imports a diff note' do
-      project = double(:project, path_with_namespace: 'foo/bar')
+      project = double(:project, full_path: 'foo/bar')
       client = double(:client)
       importer = double(:importer)
       hash = {
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index 4116380ff4ddb1a7dddab724f8559fb5d1227ab2..8cf6ac15919fcfb6ccbddfa007871e51f8b52ca2 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportIssueWorker do
 
   describe '#import' do
     it 'imports an issue' do
-      project = double(:project, path_with_namespace: 'foo/bar')
+      project = double(:project, full_path: 'foo/bar')
       client = double(:client)
       importer = double(:importer)
       hash = {
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index 0ca825a722bace21c998587a22c712d7f489dee2..677697c02dfdbadf2f31efa4ab9ad06399dacb44 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportNoteWorker do
 
   describe '#import' do
     it 'imports a note' do
-      project = double(:project, path_with_namespace: 'foo/bar')
+      project = double(:project, full_path: 'foo/bar')
       client = double(:client)
       importer = double(:importer)
       hash = {
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index d49f560af42681569f48deffaa37550b16e647f8..e287ddbe0d736d8f02606299669b52f3c7962825 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportPullRequestWorker do
 
   describe '#import' do
     it 'imports a pull request' do
-      project = double(:project, path_with_namespace: 'foo/bar')
+      project = double(:project, full_path: 'foo/bar')
       client = double(:client)
       importer = double(:importer)
       hash = {
diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7b60835fd2692aeb22a33582f673ad0ab63cf55b
--- /dev/null
+++ b/spec/workers/issue_due_scheduler_worker_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe IssueDueSchedulerWorker do
+  describe '#perform' do
+    it 'schedules one MailScheduler::IssueDueWorker per project with open issues due tomorrow' do
+      project1 = create(:project)
+      project2 = create(:project)
+      project_closed_issue = create(:project)
+      project_issue_due_another_day = create(:project)
+
+      create(:issue, :opened, project: project1, due_date: Date.tomorrow)
+      create(:issue, :opened, project: project1, due_date: Date.tomorrow)
+      create(:issue, :opened, project: project2, due_date: Date.tomorrow)
+      create(:issue, :closed, project: project_closed_issue, due_date: Date.tomorrow)
+      create(:issue, :opened, project: project_issue_due_another_day, due_date: Date.today)
+
+      expect(MailScheduler::IssueDueWorker).to receive(:bulk_perform_async).with([[project1.id], [project2.id]])
+
+      described_class.new.perform
+    end
+  end
+end
diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..48ac1b8a1a4adf290c241d08946d78526a5b0b5e
--- /dev/null
+++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe MailScheduler::IssueDueWorker do
+  describe '#perform' do
+    let(:worker) { described_class.new }
+    let(:project) { create(:project) }
+
+    it 'sends emails for open issues due tomorrow in the project specified' do
+      issue1 = create(:issue, :opened, project: project, due_date: Date.tomorrow)
+      issue2 = create(:issue, :opened, project: project, due_date: Date.tomorrow)
+      create(:issue, :closed, project: project, due_date: Date.tomorrow) # closed
+      create(:issue, :opened, project: project, due_date: 2.days.from_now) # due on another day
+      create(:issue, :opened, due_date: Date.tomorrow) # different project
+
+      expect_any_instance_of(NotificationService).to receive(:issue_due).with(issue1)
+      expect_any_instance_of(NotificationService).to receive(:issue_due).with(issue2)
+
+      worker.perform(project.id)
+    end
+  end
+end
diff --git a/spec/workers/object_storage_upload_worker_spec.rb b/spec/workers/object_storage_upload_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32ddcbe97577f8919ec14a0192e9445ba0b9261a
--- /dev/null
+++ b/spec/workers/object_storage_upload_worker_spec.rb
@@ -0,0 +1,108 @@
+require 'spec_helper'
+
+describe ObjectStorageUploadWorker do
+  let(:local) { ObjectStorage::Store::LOCAL }
+  let(:remote) { ObjectStorage::Store::REMOTE }
+
+  def perform
+    described_class.perform_async(uploader_class.name, subject_class, file_field, subject_id)
+  end
+
+  context 'for LFS' do
+    let!(:lfs_object) { create(:lfs_object, :with_file, file_store: local) }
+    let(:uploader_class) { LfsObjectUploader }
+    let(:subject_class) { LfsObject }
+    let(:file_field) { :file }
+    let(:subject_id) { lfs_object.id }
+
+    context 'when object storage is enabled' do
+      before do
+        stub_lfs_object_storage(background_upload: true)
+      end
+
+      it 'uploads object to storage' do
+        expect { perform }.to change { lfs_object.reload.file_store }.from(local).to(remote)
+      end
+
+      context 'when background upload is disabled' do
+        before do
+          allow(Gitlab.config.lfs.object_store).to receive(:background_upload) { false }
+        end
+
+        it 'is skipped' do
+          expect { perform }.not_to change { lfs_object.reload.file_store }
+        end
+      end
+    end
+
+    context 'when object storage is disabled' do
+      before do
+        stub_lfs_object_storage(enabled: false)
+      end
+
+      it "doesn't migrate files" do
+        perform
+
+        expect(lfs_object.reload.file_store).to eq(local)
+      end
+    end
+  end
+
+  context 'for legacy artifacts' do
+    let(:build) { create(:ci_build, :legacy_artifacts) }
+    let(:uploader_class) { LegacyArtifactUploader }
+    let(:subject_class) { Ci::Build }
+    let(:file_field) { :artifacts_file }
+    let(:subject_id) { build.id }
+
+    context 'when local storage is used' do
+      let(:store) { local }
+
+      context 'and remote storage is defined' do
+        before do
+          stub_artifacts_object_storage(background_upload: true)
+        end
+
+        it "migrates file to remote storage" do
+          perform
+
+          expect(build.reload.artifacts_file_store).to eq(remote)
+        end
+
+        context 'for artifacts_metadata' do
+          let(:file_field) { :artifacts_metadata }
+
+          it 'migrates metadata to remote storage' do
+            perform
+
+            expect(build.reload.artifacts_metadata_store).to eq(remote)
+          end
+        end
+      end
+    end
+  end
+
+  context 'for job artifacts' do
+    let(:artifact) { create(:ci_job_artifact, :archive) }
+    let(:uploader_class) { JobArtifactUploader }
+    let(:subject_class) { Ci::JobArtifact }
+    let(:file_field) { :file }
+    let(:subject_id) { artifact.id }
+
+    context 'when local storage is used' do
+      let(:store) { local }
+
+      context 'and remote storage is defined' do
+        before do
+          stub_artifacts_object_storage(background_upload: true)
+        end
+
+        it "migrates file to remote storage" do
+          perform
+
+          expect(artifact.reload.file_store).to eq(remote)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 5d9b067979623719072e13c0deb894fc366a29e1..cd6661f09a17573fa83c28ad4e94a0c9e73d8545 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -114,6 +114,18 @@ describe PostReceive do
     end
   end
 
+  describe '#process_wiki_changes' do
+    let(:gl_repository) { "wiki-#{project.id}" }
+
+    it 'updates project activity' do
+      described_class.new.perform(gl_repository, key_id, base64_changes)
+
+      expect { project.reload }
+        .to change(project, :last_activity_at)
+        .and change(project, :last_repository_updated_at)
+    end
+  end
+
   context "webhook" do
     it "fetches the correct project" do
       expect(Project).to receive(:find_by).with(id: project.id.to_s)
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 76ef57b6b1e5bfcbc600e2bf8b567880c1b3fa6c..ac79d9c0ac12c78c9e73ced2fd058ac5f80c8529 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -20,32 +20,6 @@ describe ProcessCommitWorker do
       worker.perform(project.id, -1, commit.to_hash)
     end
 
-    context 'when commit is a merge request merge commit' do
-      let(:merge_request) do
-        create(:merge_request,
-               description: "Closes #{issue.to_reference}",
-               source_branch: 'feature-merged',
-               target_branch: 'master',
-               source_project: project)
-      end
-
-      let(:commit) do
-        project.repository.create_branch('feature-merged', 'feature')
-
-        sha = project.repository.merge(user,
-                                       merge_request.diff_head_sha,
-                                       merge_request,
-                                       "Closes #{issue.to_reference}")
-        project.repository.commit(sha)
-      end
-
-      it 'it does not close any issues from the commit message' do
-        expect(worker).not_to receive(:close_issues)
-
-        worker.perform(project.id, user.id, commit.to_hash)
-      end
-    end
-
     it 'processes the commit message' do
       expect(worker).to receive(:process_commit_message).and_call_original
 
@@ -73,13 +47,21 @@ 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
+      before do
         allow(commit).to receive(:safe_message).and_return("Closes #{issue.to_reference}")
+      end
 
+      it 'closes issues that should be closed per the commit message' do
         expect(worker).to receive(:close_issues).with(project, user, user, commit, [issue])
 
         worker.process_commit_message(project, commit, user, user, true)
       end
+
+      it 'creates cross references' do
+        expect(commit).to receive(:create_cross_references!).with(user, [issue])
+
+        worker.process_commit_message(project, commit, user, user, true)
+      end
     end
 
     context 'when pushing to a non-default branch' do
@@ -90,12 +72,44 @@ describe ProcessCommitWorker do
 
         worker.process_commit_message(project, commit, user, user, false)
       end
+
+      it 'does not create cross references' do
+        expect(commit).to receive(:create_cross_references!).with(user, [])
+
+        worker.process_commit_message(project, commit, user, user, false)
+      end
     end
 
-    it 'creates cross references' do
-      expect(commit).to receive(:create_cross_references!)
+    context 'when commit is a merge request merge commit to the default branch' do
+      let(:merge_request) do
+        create(:merge_request,
+               description: "Closes #{issue.to_reference}",
+               source_branch: 'feature-merged',
+               target_branch: 'master',
+               source_project: project)
+      end
 
-      worker.process_commit_message(project, commit, user, user)
+      let(:commit) do
+        project.repository.create_branch('feature-merged', 'feature')
+
+        MergeRequests::MergeService
+          .new(project, merge_request.author)
+          .execute(merge_request)
+
+        merge_request.reload.merge_commit
+      end
+
+      it 'does not close any issues from the commit message' do
+        expect(worker).not_to receive(:close_issues)
+
+        worker.process_commit_message(project, commit, user, user, true)
+      end
+
+      it 'still creates cross references' do
+        expect(commit).to receive(:create_cross_references!).with(user, [])
+
+        worker.process_commit_message(project, commit, user, user, true)
+      end
     end
   end
 
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8899969c178b2d610ad57ff7b9c53f972e650945
--- /dev/null
+++ b/spec/workers/project_export_worker_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe ProjectExportWorker do
+  let!(:user) { create(:user) }
+  let!(:project) { create(:project) }
+
+  subject { described_class.new }
+
+  describe '#perform' do
+    context 'when it succeeds' do
+      it 'calls the ExportService' do
+        expect_any_instance_of(::Projects::ImportExport::ExportService).to receive(:execute)
+
+        subject.perform(user.id, project.id, { 'klass' => 'Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy' })
+      end
+    end
+
+    context 'when it fails' do
+      it 'raises an exception when params are invalid' do
+        expect_any_instance_of(::Projects::ImportExport::ExportService).not_to receive(:execute)
+
+        expect { subject.perform(1234, project.id, {}) }.to raise_exception(ActiveRecord::RecordNotFound)
+        expect { subject.perform(user.id, 1234, {}) }.to raise_exception(ActiveRecord::RecordNotFound)
+        expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.to raise_exception(Gitlab::ImportExport::AfterExportStrategyBuilder::StrategyNotFoundError)
+      end
+    end
+  end
+end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 6c66658d8c323b73396bdfea9301b7554be0992f..4b3c1736ea05fe7277140b46d6da8cc9db713d3b 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -9,70 +9,91 @@ describe RepositoryForkWorker do
 
   describe "#perform" do
     let(:project) { create(:project, :repository) }
-    let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) }
     let(:shell) { Gitlab::Shell.new }
+    let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) }
 
-    before do
-      allow(subject).to receive(:gitlab_shell).and_return(shell)
-    end
+    shared_examples 'RepositoryForkWorker performing' do
+      before do
+        allow(subject).to receive(:gitlab_shell).and_return(shell)
+      end
 
-    def perform!
-      subject.perform(fork_project.id, '/test/path', project.disk_path)
-    end
+      def expect_fork_repository
+        expect(shell).to receive(:fork_repository).with(
+          'default',
+          project.disk_path,
+          fork_project.repository_storage,
+          fork_project.disk_path
+        )
+      end
 
-    def expect_fork_repository
-      expect(shell).to receive(:fork_repository).with(
-        '/test/path',
-        project.disk_path,
-        fork_project.repository_storage_path,
-        fork_project.disk_path
-      )
-    end
+      describe 'when a worker was reset without cleanup' do
+        let(:jid) { '12345678' }
 
-    describe 'when a worker was reset without cleanup' do
-      let(:jid) { '12345678' }
+        it 'creates a new repository from a fork' do
+          allow(subject).to receive(:jid).and_return(jid)
 
-      it 'creates a new repository from a fork' do
-        allow(subject).to receive(:jid).and_return(jid)
+          expect_fork_repository.and_return(true)
 
+          perform!
+        end
+      end
+
+      it "creates a new repository from a fork" do
         expect_fork_repository.and_return(true)
 
         perform!
       end
-    end
 
-    it "creates a new repository from a fork" do
-      expect_fork_repository.and_return(true)
+      it 'protects the default branch' do
+        expect_fork_repository.and_return(true)
 
-      perform!
-    end
+        perform!
+
+        expect(fork_project.protected_branches.first.name).to eq(fork_project.default_branch)
+      end
+
+      it 'flushes various caches' do
+        expect_fork_repository.and_return(true)
 
-    it 'protects the default branch' do
-      expect_fork_repository.and_return(true)
+        expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
+          .and_call_original
 
-      perform!
+        expect_any_instance_of(Repository).to receive(:expire_exists_cache)
+          .and_call_original
 
-      expect(fork_project.protected_branches.first.name).to eq(fork_project.default_branch)
-    end
+        perform!
+      end
+
+      it "handles bad fork" do
+        error_message = "Unable to fork project #{fork_project.id} for repository #{project.disk_path} -> #{fork_project.disk_path}"
 
-    it 'flushes various caches' do
-      expect_fork_repository.and_return(true)
+        expect_fork_repository.and_return(false)
 
-      expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
-        .and_call_original
+        expect { perform! }.to raise_error(StandardError, error_message)
+      end
+    end
 
-      expect_any_instance_of(Repository).to receive(:expire_exists_cache)
-        .and_call_original
+    context 'only project ID passed' do
+      def perform!
+        subject.perform(fork_project.id)
+      end
 
-      perform!
+      it_behaves_like 'RepositoryForkWorker performing'
     end
 
-    it "handles bad fork" do
-      error_message = "Unable to fork project #{fork_project.id} for repository #{project.disk_path} -> #{fork_project.disk_path}"
+    context 'project ID, storage and repo paths passed' do
+      def perform!
+        subject.perform(fork_project.id, TestEnv.repos_path, project.disk_path)
+      end
 
-      expect_fork_repository.and_return(false)
+      it_behaves_like 'RepositoryForkWorker performing'
 
-      expect { perform! }.to raise_error(StandardError, error_message)
+      it 'logs a message about forking with old-style arguments' do
+        allow(Rails.logger).to receive(:info).with(anything) # To compensate for other logs
+        expect(Rails.logger).to receive(:info).with("Project #{fork_project.id} is being forked using old-style arguments.")
+
+        perform!
+      end
     end
   end
 end
diff --git a/vendor/assets/javascripts/peek.js b/vendor/assets/javascripts/peek.js
deleted file mode 100644
index 695eeb27c17d41ff6ba1654b301552ce7e71e620..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/peek.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * This is a modified version of https://github.com/peek/peek/blob/master/app/assets/javascripts/peek.js
- *
- * - Removed the dependency on jquery.tipsy
- * - Removed the initializeTipsy and toggleBar functions
- * - Customized updatePerformanceBar to handle SQL queries report specificities
- * - Changed /peek/results to /-/peek/results
- * - Removed the keypress, pjax:end, page:change, and turbolinks:load handlers
- */
-(function($) {
-  var fetchRequestResults, getRequestId, peekEnabled, updatePerformanceBar;
-  getRequestId = function() {
-    return $('#peek').data('requestId');
-  };
-  peekEnabled = function() {
-    return $('#peek').length;
-  };
-  updatePerformanceBar = function(results) {
-    var key, label, data, table, html, tr, duration_td, sql_td, strong;
-
-    Object.keys(results.data).forEach(function(key) {
-      Object.keys(results.data[key]).forEach(function(label) {
-        data = results.data[key][label];
-
-        if (label == 'queries') {
-          table = document.createElement('table');
-
-          for (var i = 0; i < data.length; i += 1) {
-            tr = document.createElement('tr');
-            duration_td = document.createElement('td');
-            sql_td = document.createElement('td');
-            strong = document.createElement('strong');
-
-            strong.append(data[i]['duration'] + 'ms');
-            duration_td.appendChild(strong);
-            tr.appendChild(duration_td);
-
-            sql_td.appendChild(document.createTextNode(data[i]['sql']));
-            tr.appendChild(sql_td);
-
-            table.appendChild(tr);
-          }
-
-          table.className = 'table';
-          $("[data-defer-to=" + key + "-" + label + "]").html(table);
-        } else {
-          $("[data-defer-to=" + key + "-" + label + "]").text(results.data[key][label]);
-        }
-      });
-    });
-    return $(document).trigger('peek:render', [getRequestId(), results]);
-  };
-  fetchRequestResults = function() {
-    return $.ajax('/-/peek/results', {
-      data: {
-        request_id: getRequestId()
-      },
-      success: function(data, textStatus, xhr) {
-        return updatePerformanceBar(data);
-      },
-      error: function(xhr, textStatus, error) {}
-    });
-  };
-  $(document).on('peek:update', fetchRequestResults);
-  return $(function() {
-    if (peekEnabled()) {
-      return $(this).trigger('peek:update');
-    }
-  });
-})(jQuery);
diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore
index d57137223ed15a5f394a98a2477fb1207be37f41..39b6783cef8485a95c9e484a2855367a72405855 100644
--- a/vendor/gitignore/Android.gitignore
+++ b/vendor/gitignore/Android.gitignore
@@ -37,8 +37,10 @@ captures/
 .idea/workspace.xml
 .idea/tasks.xml
 .idea/gradle.xml
+.idea/assetWizardSettings.xml
 .idea/dictionaries
 .idea/libraries
+.idea/caches
 
 # Keystore files
 # Uncomment the following line if you do not want to check your keystore files in.
diff --git a/vendor/gitignore/Dart.gitignore b/vendor/gitignore/Dart.gitignore
index 58950beb4fa77637f8d70b795eb49aab79d7d802..7bf00e82cc9e5575bd97873ef8524eab911be3f2 100644
--- a/vendor/gitignore/Dart.gitignore
+++ b/vendor/gitignore/Dart.gitignore
@@ -1,4 +1,4 @@
-# See https://www.dartlang.org/tools/private-files.html
+# See https://www.dartlang.org/guides/libraries/private-files
 
 # Files and directories created by pub
 .dart_tool/
diff --git a/vendor/gitignore/Elixir.gitignore b/vendor/gitignore/Elixir.gitignore
index b6d65867dac44cc51fa8d268e39d4cb6ef91c09d..86e4c3f3905928ce3a48896f04dddf47f25ef2fb 100644
--- a/vendor/gitignore/Elixir.gitignore
+++ b/vendor/gitignore/Elixir.gitignore
@@ -6,3 +6,4 @@
 erl_crash.dump
 *.ez
 *.beam
+/config/*.secret.exs
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
index 9c01e12b05028cf455ab7d0abf26d7803f994542..a83a428c844f1588918c7ca8bba888b123130bde 100644
--- a/vendor/gitignore/Global/JetBrains.gitignore
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -1,12 +1,12 @@
 # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
 
-# User-specific stuff:
+# User-specific stuff
 .idea/**/workspace.xml
 .idea/**/tasks.xml
 .idea/dictionaries
 
-# Sensitive or high-churn files:
+# Sensitive or high-churn files
 .idea/**/dataSources/
 .idea/**/dataSources.ids
 .idea/**/dataSources.local.xml
@@ -14,7 +14,7 @@
 .idea/**/dynamic.xml
 .idea/**/uiDesigner.xml
 
-# Gradle:
+# Gradle
 .idea/**/gradle.xml
 .idea/**/libraries
 
@@ -22,14 +22,12 @@
 cmake-build-debug/
 cmake-build-release/
 
-# Mongo Explorer plugin:
+# Mongo Explorer plugin
 .idea/**/mongoSettings.xml
 
-## File-based project format:
+# File-based project format
 *.iws
 
-## Plugin-specific files:
-
 # IntelliJ
 out/
 
@@ -47,3 +45,6 @@ com_crashlytics_export_strings.xml
 crashlytics.properties
 crashlytics-build.properties
 fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
diff --git a/vendor/gitignore/Global/Windows.gitignore b/vendor/gitignore/Global/Windows.gitignore
index 846a1db836c01f32480bde22abe3c81b285bfc4b..0251dd21ad8764665868b03cd4a6b842fb0eedb1 100644
--- a/vendor/gitignore/Global/Windows.gitignore
+++ b/vendor/gitignore/Global/Windows.gitignore
@@ -15,6 +15,7 @@ $RECYCLE.BIN/
 # Windows Installer files
 *.cab
 *.msi
+*.msix
 *.msm
 *.msp
 
diff --git a/vendor/gitignore/Godot.gitignore b/vendor/gitignore/Godot.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ba45ca4582e5ef56f8fb3da5000e3f3ecd4f0c3f
--- /dev/null
+++ b/vendor/gitignore/Godot.gitignore
@@ -0,0 +1,8 @@
+
+# Godot-specific ignores
+.import/
+export.cfg
+export_presets.cfg
+
+# Mono-specific ignores
+.mono/
diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore
index b6bf3a9c96adc6b1ebe748b3b0343bc333e16110..378c158bddf86b6e6cf22460e370eabf9596dfee 100644
--- a/vendor/gitignore/Joomla.gitignore
+++ b/vendor/gitignore/Joomla.gitignore
@@ -1,4 +1,3 @@
-/.gitignore
 /.htaccess
 /administrator/cache/*
 /administrator/components/com_admin/*
diff --git a/vendor/gitignore/KiCad.gitignore b/vendor/gitignore/KiCad.gitignore
index 208bc4fc591b2af2a6dd12e6b88445d283e76eb9..198392e551e4b3fb6aa42bca181840a3480b83b5 100644
--- a/vendor/gitignore/KiCad.gitignore
+++ b/vendor/gitignore/KiCad.gitignore
@@ -1,4 +1,5 @@
 # For PCBs designed using KiCad: http://www.kicad-pcb.org/
+# Format documentation: http://kicad-pcb.org/help/file-formats/
 
 # Temporary files
 *.000
@@ -8,6 +9,10 @@
 *~
 _autosave-*
 *.tmp
+*-cache.lib
+*-rescue.lib
+*-save.pro
+*-save.kicad_pcb
 
 # Netlist files (exported from Eeschema)
 *.net
diff --git a/vendor/gitignore/Leiningen.gitignore b/vendor/gitignore/Leiningen.gitignore
index a9fe6fba80d90b63aee464ecfd89ea59f697c6e1..a4cb69a32cccf287d2e818c80ba5be860a0767f4 100644
--- a/vendor/gitignore/Leiningen.gitignore
+++ b/vendor/gitignore/Leiningen.gitignore
@@ -11,3 +11,4 @@ pom.xml.asc
 .lein-plugins/
 .lein-failures
 .nrepl-port
+.cpcache/
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
index d1bed128fa83b3ed094d8387f4b8f19c019e2ae8..ad46b30886fa350c1f59761b100e5e4b01f9a7ec 100644
--- a/vendor/gitignore/Node.gitignore
+++ b/vendor/gitignore/Node.gitignore
@@ -36,7 +36,7 @@ build/Release
 node_modules/
 jspm_packages/
 
-# Typescript v1 declaration files
+# TypeScript v1 declaration files
 typings/
 
 # Optional npm cache directory
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index b989be6ca157679d1b287b3ca700518423219392..894a44cc066a027465cd26d634948d56d13af9af 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -53,9 +53,8 @@ coverage.xml
 
 # Django stuff:
 *.log
-.static_storage/
-.media/
 local_settings.py
+db.sqlite3
 
 # Flask stuff:
 instance/
diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore
index 037a1e7579003025c2de7b23ab48e01e1e2e0234..5291a385b2598093cb2ca3d11e7f49c7f386d1ca 100644
--- a/vendor/gitignore/Qt.gitignore
+++ b/vendor/gitignore/Qt.gitignore
@@ -1,5 +1,4 @@
 # C++ objects and libs
-
 *.slo
 *.lo
 *.o
@@ -11,7 +10,6 @@
 *.dylib
 
 # Qt-es
-
 object_script.*.Release
 object_script.*.Debug
 *_plugin_import.cpp
@@ -35,13 +33,11 @@ Makefile*
 target_wrapper.*
 
 # QtCreator
-
 *.autosave
 
-# QtCtreator Qml
+# QtCreator Qml
 *.qmlproject.user
 *.qmlproject.user.*
 
-# QtCtreator CMake
+# QtCreator CMake
 CMakeLists.txt.user*
-
diff --git a/vendor/gitignore/R.gitignore b/vendor/gitignore/R.gitignore
index fcff087aebb621ca6fd0351733d304943d91a0d3..26fad6fadff13ef5969e6ffff9163fec5f032338 100644
--- a/vendor/gitignore/R.gitignore
+++ b/vendor/gitignore/R.gitignore
@@ -31,3 +31,6 @@ vignettes/*.pdf
 # Temporary files created by R markdown
 *.utf8.md
 *.knit.md
+
+# Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html
+rsconnect/
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index 828ab1d556ad0f0dde9ad559f70ce3a5095e7737..e62f78e17bcb78d57f4b10850480c98326dc7963 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -14,6 +14,7 @@ pickle-email-*.html
 
 # TODO Comment out this rule if you are OK with secrets being uploaded to the repo
 config/initializers/secret_token.rb
+config/master.key
 
 # Only include if you have production secrets in this file, which is no longer a Rails default
 # config/secrets.yml
diff --git a/vendor/gitignore/Rust.gitignore b/vendor/gitignore/Rust.gitignore
index 50281a44270e0fe457f02c7c9edebe234eaa708d..088ba6ba7d345b76aa2b8dc021dd25e1323189b3 100644
--- a/vendor/gitignore/Rust.gitignore
+++ b/vendor/gitignore/Rust.gitignore
@@ -3,7 +3,7 @@
 /target/
 
 # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
 Cargo.lock
 
 # These are backup files generated by rustfmt
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 5359e544bcfde2cda1a78bda188c99f1aa0fa902..c560658e45ce9e5d925965931b5b58fb1ffe6bec 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -110,6 +110,14 @@ acs-*.bib
 *.gaux
 *.gtex
 
+# htlatex
+*.4ct
+*.4tc
+*.idv
+*.lg
+*.trc
+*.xref
+
 # hyperref
 *.brf
 
@@ -145,7 +153,9 @@ _minted*
 *.mw
 
 # nomencl
+*.nlg
 *.nlo
+*.nls
 
 # pax
 *.pax
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index 75e5b1405daa14c8f1a55e695131b27c73ad59b7..a7c0c70a0b4d9660b2b750b4b780d5919684fea7 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -5,7 +5,7 @@
 [Bb]uilds/
 Assets/AssetStoreTools*
 
-# Visual Studio 2015 cache directory
+# Visual Studio cache directory
 /.vs/
 
 # Autogenerated VS/MD/Consulo solution and project files
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index c49041ff7d2c716be6e5a3960f208b1e0d9b3797..29063cf60728f478152b03ee5880d82be155d1ed 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -64,8 +64,10 @@ StyleCopReport.xml
 *.ilk
 *.meta
 *.obj
+*.iobj
 *.pch
 *.pdb
+*.ipdb
 *.pgc
 *.pgd
 *.rsp
@@ -248,6 +250,7 @@ ServiceFabricBackup/
 *.rdl.data
 *.bim.layout
 *.bim_*.settings
+*.rptproj.rsuser
 
 # Microsoft Fakes
 FakesAssemblies/
@@ -259,9 +262,6 @@ FakesAssemblies/
 .ntvs_analysis.dat
 node_modules/
 
-# TypeScript v1 declaration files
-typings/
-
 # Visual Studio 6 build log
 *.plg
 
@@ -322,3 +322,8 @@ ASALocalRun/
 # MSBuild Binary and Structured Log
 *.binlog
 
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder 
+.mfractor/
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 0ac8885405e932db9fd9e33d4bc462f0a3f82c81..3b77055b644f863d06144aefc79b2a48576bcd67 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -36,7 +36,6 @@ variables:
 
   KUBERNETES_VERSION: 1.8.6
   HELM_VERSION: 2.6.1
-  CODECLIMATE_VERSION: 0.69.0
 
 stages:
   - build
@@ -51,9 +50,9 @@ stages:
 
 build:
   stage: build
-  image: docker:git
+  image: docker:stable-git
   services:
-  - docker:dind
+  - docker:stable-dind
   variables:
     DOCKER_DRIVER: overlay2
   script:
@@ -77,12 +76,12 @@ test:
     - branches
 
 codequality:
-  image: docker:latest
+  image: docker:stable
   variables:
     DOCKER_DRIVER: overlay2
   allow_failure: true
   services:
-    - docker:dind
+    - docker:stable-dind
   script:
     - setup_docker
     - codeclimate
@@ -91,12 +90,12 @@ codequality:
 
 performance:
   stage: performance
-  image: docker:latest
+  image: docker:stable
   variables:
     DOCKER_DRIVER: overlay2
   allow_failure: true
   services:
-    - docker:dind
+    - docker:stable-dind
   script:
     - setup_docker
     - performance
@@ -110,25 +109,37 @@ performance:
     kubernetes: active
 
 sast:
-  image: docker:latest
+  image: docker:stable
   variables:
     DOCKER_DRIVER: overlay2
   allow_failure: true
   services:
-    - docker:dind
+    - docker:stable-dind
   script:
     - setup_docker
     - sast
   artifacts:
     paths: [gl-sast-report.json]
 
+dependency_scanning:
+  image: docker:stable
+  variables:
+    DOCKER_DRIVER: overlay2
+  allow_failure: true
+  services:
+    - docker:stable-dind
+  script:
+    - setup_docker
+    - dependency_scanning
+  artifacts:
+    paths: [gl-dependency-scanning-report.json]
 sast:container:
-  image: docker:latest
+  image: docker:stable
   variables:
     DOCKER_DRIVER: overlay2
   allow_failure: true
   services:
-    - docker:dind
+    - docker:stable-dind
   script:
     - setup_docker
     - sast_container
@@ -138,7 +149,7 @@ sast:container:
 dast:
   stage: dast
   allow_failure: true
-  image: owasp/zap2docker-stable
+  image: registry.gitlab.com/gitlab-org/security-products/zaproxy
   variables:
     POSTGRES_DB: "false"
   script:
@@ -286,6 +297,8 @@ production:
   export CI_APPLICATION_TAG=$CI_COMMIT_SHA
   export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
   export TILLER_NAMESPACE=$KUBE_NAMESPACE
+  # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
+  export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
 
   function sast_container() {
     if [[ -n "$CI_REGISTRY_USER" ]]; then
@@ -302,24 +315,22 @@ production:
     mv clair-scanner_linux_amd64 clair-scanner
     chmod +x clair-scanner
     touch clair-whitelist.yml
+    retries=0
+    echo "Waiting for clair daemon to start"
+    while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
     ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
   }
 
   function codeclimate() {
-    cc_opts="--env CODECLIMATE_CODE="$PWD" \
-             --volume "$PWD":/code \
-             --volume /var/run/docker.sock:/var/run/docker.sock \
-             --volume /tmp/cc:/tmp/cc"
-
-    docker run ${cc_opts} "codeclimate/codeclimate:${CODECLIMATE_VERSION}" init
-    docker run ${cc_opts} "codeclimate/codeclimate:${CODECLIMATE_VERSION}" analyze -f json > codeclimate.json
+    docker run --env SOURCE_CODE="$PWD" \
+               --volume "$PWD":/code \
+               --volume /var/run/docker.sock:/var/run/docker.sock \
+               "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
   }
 
   function sast() {
     case "$CI_SERVER_VERSION" in
       *-ee)
-        # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable"
-        SAST_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
 
         # Deprecation notice for CONFIDENCE_LEVEL variable
         if [ -z "$SAST_CONFIDENCE_LEVEL" -a "$CONFIDENCE_LEVEL" ]; then
@@ -328,10 +339,23 @@ production:
         fi
 
         docker run --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}" \
-                   --env SAST_DISABLE_REMOTE_CHECKS="${SAST_DISABLE_REMOTE_CHECKS:-false}" \
                    --volume "$PWD:/code" \
                    --volume /var/run/docker.sock:/var/run/docker.sock \
-                   "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
+                   "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
+        ;;
+      *)
+        echo "GitLab EE is required"
+        ;;
+    esac
+  }
+
+  function dependency_scanning() {
+    case "$CI_SERVER_VERSION" in
+      *-ee)
+        docker run --env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}" \
+                   --volume "$PWD:/code" \
+                   --volume /var/run/docker.sock:/var/run/docker.sock \
+                   "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
         ;;
       *)
         echo "GitLab EE is required"
@@ -359,10 +383,16 @@ production:
     if [[ "$track" == "stable" ]]; then
       # for stable track get number of replicas from `PRODUCTION_REPLICAS`
       eval new_replicas=\$${env_slug}_REPLICAS
+      if [[ -z "$new_replicas" ]]; then
+        new_replicas=$REPLICAS
+      fi
       service_enabled="true"
     else
       # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
       eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
+      if [[ -z "$new_replicas" ]]; then
+        eval new_replicas=\${env_track}_REPLICAS
+      fi
     fi
     if [[ -n "$new_replicas" ]]; then
       replicas="$new_replicas"
diff --git a/vendor/gitlab-ci-yml/Chef.gitlab-ci.yml b/vendor/gitlab-ci-yml/Chef.gitlab-ci.yml
index 4d5b6484d6efbee431e2ff13ad45dd837503b365..ff7c87c29f03a12625ddce1478b15e00e6f7f0c1 100644
--- a/vendor/gitlab-ci-yml/Chef.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Chef.gitlab-ci.yml
@@ -7,7 +7,7 @@
 
 image: "chef/chefdk"
 services:
-  - docker:dind
+  - docker:stable-dind
 
 variables:
   DOCKER_HOST: "tcp://docker:2375"
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
index eeefadaa019b69025fe22aea60e23f1e82e86d04..58d48d1284b03c647d006890935aa603e42009f2 100644
--- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -2,7 +2,7 @@
 image: docker:latest
 
 services:
-  - docker:dind
+  - docker:stable-dind
 
 before_script:
   - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
diff --git a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
index 0ad662cf704cc31454116914365e5798f2839343..0688f77a1d21ec4cf8cc58c63accd041b01bd80d 100644
--- a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
@@ -32,7 +32,7 @@ before_script:
   - apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq
 
   # Install php extensions
-  - docker-php-ext-install mbstring mcrypt pdo_mysql curl json intl gd xml zip bz2 opcache
+  - docker-php-ext-install mbstring pdo_mysql curl json intl gd xml zip bz2 opcache
 
   # Install & enable Xdebug for code coverage reports
   - pecl install xdebug
diff --git a/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9df2a4797b25afb84a079730ae3531f43d16649a
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml
@@ -0,0 +1,17 @@
+image: node:latest
+
+# This folder is cached between builds
+# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
+cache:
+  paths:
+  - node_modules/
+
+pages:
+  script:
+  - yarn install
+  - ./node_modules/.bin/gatsby build --prefix-paths
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
index a72b82814016c6fbc7116646f1b4c267cdb90fbd..b8cfb0f56f6cddf7cbbd76a89c3abee5f5d68bc5 100644
--- a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
@@ -1,5 +1,5 @@
 # Full project: https://gitlab.com/pages/hugo
-image: publysher/hugo
+image: dettmering/hugo-build
 
 pages:
   script:
diff --git a/vendor/gitlab-ci-yml/Python.gitlab-ci.yml b/vendor/gitlab-ci-yml/Python.gitlab-ci.yml
index a2882a5407d36cea550dcb8b017d290bf4787f86..2e0589de6524b3eb26443408532b4fdc40a60409 100644
--- a/vendor/gitlab-ci-yml/Python.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Python.gitlab-ci.yml
@@ -1,8 +1,27 @@
-# This file is a template, and might need editing before it works on your project.
+# Official language image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/python/tags/
 image: python:latest
 
+# Change pip's cache directory to be inside the project directory since we can
+# only cache local items.
+variables:
+  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache"
+
+# Pip's cache doesn't store the python packages
+# https://pip.pypa.io/en/stable/reference/pip_install/#caching
+#
+# If you want to also cache the installed packages, you have to install
+# them in a virtualenv and cache it as well.
+cache:
+  paths:
+    - .cache/pip
+    - venv/
+
 before_script:
-  - python -V                                   # Print out python version for debugging
+  - python -V               # Print out python version for debugging
+  - pip install virtualenv
+  - virtualenv venv
+  - source venv/bin/activate
 
 test:
   script:
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 8f127468e254c4be43c91e7b06231447e6448676..ca88f867fe541513d14d0cf066740b1320530494 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -4,19 +4,22 @@
 @babel/template,7.0.0-beta.32,MIT
 @babel/traverse,7.0.0-beta.32,MIT
 @babel/types,7.0.0-beta.32,MIT
-@gitlab-org/gitlab-svgs,1.8.0,SEE LICENSE IN LICENSE
+@gitlab-org/gitlab-svgs,1.17.0,SEE LICENSE IN LICENSE
 @types/jquery,2.0.48,MIT
 JSONStream,1.3.2,MIT
 RedCloth,4.3.2,MIT
 abbrev,1.0.9,ISC
+abbrev,1.1.1,ISC
 accepts,1.3.3,MIT
+accepts,1.3.4,MIT
 ace-rails-ap,4.1.2,MIT
 acorn,3.3.0,MIT
 acorn,4.0.13,MIT
 acorn,5.1.1,MIT
-acorn,5.3.0,MIT
+acorn,5.4.1,MIT
 acorn-dynamic-import,2.0.2,MIT
 acorn-jsx,3.0.1,MIT
+acorn-node,1.3.0,Apache 2.0
 actionmailer,4.2.10,MIT
 actionpack,4.2.10,MIT
 actionview,4.2.10,MIT
@@ -24,67 +27,80 @@ activejob,4.2.10,MIT
 activemodel,4.2.10,MIT
 activerecord,4.2.10,MIT
 activesupport,4.2.10,MIT
-acts-as-taggable-on,4.0.0,MIT
+acts-as-taggable-on,5.0.0,MIT
+address,1.0.3,MIT
 addressable,2.5.2,Apache 2.0
 addressparser,1.0.1,MIT
+aes_key_wrap,1.0.1,MIT
 after,0.8.2,MIT
 agent-base,2.1.1,MIT
 ajv,4.11.8,MIT
 ajv,5.2.2,MIT
 ajv,5.5.2,MIT
+ajv,6.1.1,MIT
 ajv-keywords,1.5.1,MIT
-ajv-keywords,2.1.0,MIT
+ajv-keywords,3.1.0,MIT
 akismet,2.0.0,MIT
 align-text,0.1.4,MIT
 allocations,1.0.5,MIT
 alphanum-sort,1.0.2,MIT
 amdefine,1.0.1,BSD-3-Clause OR MIT
+amqplib,0.5.2,MIT
+ansi-align,2.0.0,ISC
 ansi-escapes,1.4.0,MIT
-ansi-html,0.0.5,"Apache, Version 2.0"
+ansi-escapes,3.0.0,MIT
 ansi-html,0.0.7,Apache 2.0
 ansi-regex,2.1.1,MIT
 ansi-regex,3.0.0,MIT
 ansi-styles,2.2.1,MIT
 ansi-styles,3.2.0,MIT
 anymatch,1.3.2,ISC
+anymatch,2.0.0,ISC
 append-transform,0.4.0,MIT
-aproba,1.1.2,ISC
+aproba,1.2.0,ISC
 are-we-there-yet,1.1.4,ISC
 arel,6.0.4,MIT
 argparse,1.0.9,MIT
 arr-diff,2.0.0,MIT
-arr-flatten,1.0.1,MIT
+arr-diff,4.0.0,MIT
+arr-flatten,1.1.0,MIT
+arr-union,3.1.0,MIT
 array-filter,0.0.1,MIT
 array-find,1.0.0,MIT
 array-find-index,1.0.2,MIT
 array-flatten,1.1.1,MIT
 array-flatten,2.1.1,MIT
+array-includes,3.0.3,MIT
 array-map,0.0.0,MIT
 array-reduce,0.0.0,MIT
 array-slice,0.2.3,MIT
 array-union,1.0.2,MIT
 array-uniq,1.0.3,MIT
 array-unique,0.2.1,MIT
+array-unique,0.3.2,MIT
 arraybuffer.slice,0.0.7,MIT
 arrify,1.0.1,MIT
 asana,0.6.0,MIT
-asciidoctor,1.5.3,MIT
-asciidoctor-plantuml,0.0.7,MIT
+asciidoctor,1.5.6.2,MIT
+asciidoctor-plantuml,0.0.8,MIT
 asn1,0.2.3,MIT
-asn1.js,4.9.1,MIT
+asn1.js,4.10.1,MIT
 assert,1.4.1,MIT
 assert-plus,0.2.0,MIT
 assert-plus,1.0.0,MIT
 asset_sync,2.2.0,MIT
-ast-types,0.10.1,MIT
+assign-symbols,1.0.0,MIT
+ast-types,0.11.1,MIT
 astw,2.2.0,MIT
 async,0.9.2,MIT
 async,1.5.2,MIT
 async,2.1.5,MIT
 async,2.4.1,MIT
+async,2.6.0,MIT
 async-each,1.0.1,MIT
 async-limiter,1.0.0,MIT
 asynckit,0.4.0,MIT
+atob,2.0.3,(MIT OR Apache-2.0)
 atomic,1.1.99,Apache 2.0
 attr_encrypted,3.0.3,MIT
 attr_required,1.0.0,MIT
@@ -176,9 +192,10 @@ babylon,7.0.0-beta.32,MIT
 backo2,1.0.2,MIT
 balanced-match,0.4.2,MIT
 balanced-match,1.0.0,MIT
+base,0.11.2,MIT
 base32,0.3.2,MIT
 base64-arraybuffer,0.1.5,MIT
-base64-js,1.2.0,MIT
+base64-js,1.2.3,MIT
 base64id,1.0.0,MIT
 batch,0.6.1,MIT
 batch-loader,1.2.1,MIT
@@ -186,50 +203,57 @@ bcrypt,3.1.11,MIT
 bcrypt-pbkdf,1.0.1,New BSD
 bcrypt_pbkdf,1.0.0,MIT
 better-assert,1.0.2,MIT
+bfj-node4,5.2.1,MIT
 big.js,3.1.3,MIT
-binary-extensions,1.10.0,MIT
-bindata,2.4.1,ruby
+binary-extensions,1.11.0,MIT
+bindata,2.4.3,ruby
+bitsyntax,0.0.4,UNKNOWN
 bl,1.1.2,MIT
 blackst0ne-mermaid,7.1.0-fixed,MIT
 blob,0.0.4,MIT*
 block-stream,0.0.9,ISC
-bluebird,2.11.0,MIT
 bluebird,3.5.0,MIT
-bn.js,4.11.6,MIT
-body-parser,1.17.2,MIT
+bluebird,3.5.1,MIT
+bn.js,4.11.8,MIT
+body-parser,1.18.2,MIT
 bonjour,3.5.0,MIT
 boom,2.10.1,New BSD
 boom,4.3.1,New BSD
 boom,5.2.0,New BSD
 bootstrap-sass,3.3.6,MIT
 bootstrap_form,2.7.0,MIT
+boxen,1.3.0,MIT
+brace-expansion,1.1.11,MIT
 brace-expansion,1.1.8,MIT
 braces,0.1.5,MIT
 braces,1.8.5,MIT
-brorand,1.0.7,MIT
+braces,2.3.1,MIT
+brorand,1.1.0,MIT
 browser,2.2.0,MIT
-browser-pack,6.0.2,MIT
+browser-pack,6.0.4,MIT
 browser-resolve,1.11.2,MIT
 browserify,14.5.0,MIT
-browserify-aes,1.0.6,MIT
+browserify-aes,1.1.1,MIT
 browserify-cipher,1.0.0,MIT
 browserify-des,1.0.0,MIT
 browserify-rsa,4.0.1,MIT
-browserify-sign,4.0.0,ISC
+browserify-sign,4.0.4,ISC
 browserify-zlib,0.1.4,MIT
 browserify-zlib,0.2.0,MIT
 browserslist,1.7.7,MIT
 buffer,4.9.1,MIT
-buffer,5.0.8,MIT
+buffer,5.1.0,MIT
 buffer-indexof,1.1.0,MIT
+buffer-more-ints,0.0.2,MIT
 buffer-xor,1.0.3,MIT
 builder,3.2.3,MIT
 buildmail,4.0.1,MIT
 builtin-modules,1.1.1,MIT
 builtin-status-codes,3.0.0,MIT
-bytes,2.4.0,MIT
 bytes,2.5.0,MIT
 bytes,3.0.0,MIT
+cacache,10.0.4,ISC
+cache-base,1.0.1,MIT
 cached-path-relative,1.0.1,MIT
 caller-path,0.1.0,MIT
 callsite,1.0.0,MIT*
@@ -241,6 +265,7 @@ camelcase,4.1.0,MIT
 camelcase-keys,2.1.0,MIT
 caniuse-api,1.6.1,MIT
 caniuse-db,1.0.30000649,CC-BY-4.0
+capture-stack-trace,1.0.0,MIT
 carrierwave,1.2.1,MIT
 caseless,0.11.0,Apache 2.0
 caseless,0.12.0,Apache 2.0
@@ -248,18 +273,27 @@ cause,0.1,MIT
 center-align,0.1.3,MIT
 chalk,1.1.3,MIT
 chalk,2.3.0,MIT
-charlock_holmes,0.7.5,MIT
+chalk,2.3.1,MIT
+chardet,0.4.2,MIT
+charlock_holmes,0.7.6,MIT
+chart.js,1.0.2,MIT
+check-types,7.3.0,MIT
 chokidar,1.7.0,MIT
+chokidar,2.0.2,MIT
+chownr,1.0.1,ISC
 chronic,0.10.2,MIT
 chronic_duration,0.10.6,MIT
 chunky_png,1.3.5,MIT
-cipher-base,1.0.3,MIT
+cipher-base,1.0.4,MIT
 circular-json,0.3.3,MIT
-circular-json,0.4.0,MIT
+circular-json,0.5.1,MIT
 citrus,3.0.2,MIT
 clap,1.1.3,MIT
+class-utils,0.3.6,MIT
 classlist-polyfill,1.2.0,Unlicense
+cli-boxes,1.0.0,MIT
 cli-cursor,1.0.2,MIT
+cli-cursor,2.1.0,MIT
 cli-width,2.1.0,ISC
 clipboard,1.7.1,MIT
 cliui,2.1.0,ISC
@@ -270,6 +304,7 @@ co,4.6.0,MIT
 coa,1.0.1,MIT
 code-point-at,1.1.0,MIT
 coercible,1.0.0,MIT
+collection-visit,1.0.0,MIT
 color,0.11.4,MIT
 color-convert,1.9.1,MIT
 color-name,1.1.2,MIT
@@ -278,21 +313,23 @@ colormin,1.1.2,MIT
 colors,1.1.2,MIT
 combine-lists,1.0.1,MIT
 combine-source-map,0.7.2,MIT
-combined-stream,1.0.5,MIT
-commander,2.9.0,MIT
+combine-source-map,0.8.0,MIT
+combined-stream,1.0.6,MIT
+commander,2.15.1,MIT
 commondir,1.0.1,MIT
+commonmarker,0.17.8,MIT
 component-bind,1.0.0,MIT*
 component-emitter,1.2.1,MIT
 component-inherit,0.0.3,MIT*
 compressible,2.0.11,MIT
 compression,1.7.0,MIT
-compression-webpack-plugin,1.0.0,MIT
+compression-webpack-plugin,1.1.7,MIT
 concat-map,0.0.1,MIT
 concat-stream,1.5.2,MIT
 concat-stream,1.6.0,MIT
 concurrent-ruby-ext,1.0.5,MIT
-configstore,1.4.0,Simplified BSD
-connect,3.6.3,MIT
+configstore,3.1.1,Simplified BSD
+connect,3.6.6,MIT
 connect-history-api-fallback,1.3.0,MIT
 connection_pool,2.2.1,MIT
 console-browserify,1.1.0,MIT
@@ -301,21 +338,26 @@ consolidate,0.14.5,MIT
 constants-browserify,1.0.0,MIT
 contains-path,0.1.0,MIT
 content-disposition,0.5.2,MIT
-content-type,1.0.2,MIT
+content-type,1.0.4,MIT
 convert-source-map,1.1.3,MIT
 convert-source-map,1.5.0,MIT
 cookie,0.3.1,MIT
 cookie-signature,1.0.6,MIT
-copy-webpack-plugin,4.0.1,MIT
+copy-concurrently,1.0.5,ISC
+copy-descriptor,0.1.1,MIT
+copy-webpack-plugin,4.4.1,MIT
 core-js,2.3.0,MIT
 core-js,2.4.1,MIT
 core-js,2.5.1,MIT
+core-js,2.5.3,MIT
 core-util-is,1.0.2,MIT
 cosmiconfig,2.1.1,MIT
 crack,0.4.3,MIT
+crass,1.0.3,MIT
 create-ecdh,4.0.0,MIT
-create-hash,1.1.2,MIT
-create-hmac,1.1.4,MIT
+create-error-class,3.0.2,MIT
+create-hash,1.1.3,MIT
+create-hmac,1.1.6,MIT
 creole,0.5.0,ruby
 cropper,2.3.0,MIT
 cross-spawn,5.1.0,MIT
@@ -323,9 +365,9 @@ cryptiles,2.0.5,New BSD
 cryptiles,3.1.2,New BSD
 crypto-browserify,3.11.0,MIT
 crypto-browserify,3.12.0,MIT
+crypto-random-string,1.0.0,MIT
 css-color-names,0.0.4,MIT
-css-loader,0.28.0,MIT
-css-selector-tokenizer,0.6.0,MIT
+css-loader,0.28.9,MIT
 css-selector-tokenizer,0.7.0,MIT
 css_parser,1.5.0,MIT
 cssesc,0.1.0,MIT
@@ -333,6 +375,7 @@ cssnano,3.10.0,MIT
 csso,2.3.2,MIT
 currently-unhandled,0.4.1,MIT
 custom-event,1.0.1,MIT
+cyclist,0.2.2,MIT*
 d,0.1.1,MIT
 d,1.0.0,MIT
 d3,3.5.17,New BSD
@@ -363,7 +406,6 @@ date-format,1.2.0,MIT
 date-now,0.1.4,MIT
 de-indent,1.0.2,MIT
 debug,2.2.0,MIT
-debug,2.6.7,MIT
 debug,2.6.8,MIT
 debug,2.6.9,MIT
 debug,3.1.0,MIT
@@ -372,6 +414,7 @@ decamelize,1.2.0,MIT
 deckar01-task_list,2.0.0,MIT
 declarative,0.0.10,MIT
 declarative-option,0.1.0,MIT
+decode-uri-component,0.2.0,MIT
 decompress-response,3.3.0,MIT
 deep-equal,1.0.1,MIT
 deep-extend,0.4.2,MIT
@@ -379,6 +422,9 @@ deep-is,0.1.3,MIT
 default-require-extensions,1.0.0,MIT
 default_value_for,3.0.2,MIT
 define-properties,1.1.2,MIT
+define-property,0.2.5,MIT
+define-property,1.0.0,MIT
+define-property,2.0.2,MIT
 defined,1.0.0,MIT
 degenerator,1.0.4,MIT
 del,2.2.2,MIT
@@ -386,14 +432,15 @@ del,3.0.0,MIT
 delayed-stream,1.0.0,MIT
 delegate,3.1.2,MIT
 delegates,1.0.0,MIT
-depd,1.1.0,MIT
 depd,1.1.1,MIT
 deps-sort,2.0.0,MIT
 des.js,1.0.0,MIT
 descendants_tracker,0.0.4,MIT
 destroy,1.0.4,MIT
 detect-indent,4.0.0,MIT
+detect-libc,1.0.3,Apache 2.0
 detect-node,2.0.3,ISC
+detect-port-alt,1.1.5,MIT
 detective,4.7.1,MIT
 devise,4.2.0,MIT
 devise-two-factor,3.0.0,MIT
@@ -402,6 +449,7 @@ diff,3.4.0,New BSD
 diff-lcs,1.3,"MIT,Artistic-2.0,GPL-2.0+"
 diffie-hellman,5.0.2,MIT
 diffy,3.1.0,MIT
+dir-glob,2.0.0,MIT
 dns-equal,1.0.0,MIT
 dns-packet,1.2.2,MIT
 dns-txt,2.0.2,MIT
@@ -411,33 +459,35 @@ document-register-element,1.3.0,MIT
 dom-serialize,2.2.1,MIT
 dom-serializer,0.1.0,MIT
 domain-browser,1.1.7,MIT
-domain_name,0.5.20161021,"Simplified BSD,New BSD,Mozilla Public License 2.0"
+domain_name,0.5.20170404,"Simplified BSD,New BSD,Mozilla Public License 2.0"
 domelementtype,1.1.3,Simplified BSD
 domelementtype,1.3.0,Simplified BSD
 domhandler,2.4.1,Simplified BSD
 domutils,1.6.2,Simplified BSD
-doorkeeper,4.2.6,MIT
-doorkeeper-openid_connect,1.2.0,MIT
+doorkeeper,4.3.1,MIT
+doorkeeper-openid_connect,1.3.0,MIT
+dot-prop,4.2.0,MIT
 double-ended-queue,2.1.0-0,MIT
 dropzone,4.2.0,MIT
 dropzonejs-rails,0.7.2,MIT
 duplexer,0.1.1,MIT
 duplexer2,0.1.4,New BSD
 duplexer3,0.1.4,New BSD
-duplexify,3.5.1,MIT
+duplexify,3.5.3,MIT
 ecc-jsbn,0.1.1,MIT
 ee-first,1.1.1,MIT
-ejs,2.5.6,Apache 2.0
+ejs,2.5.7,Apache 2.0
 electron-to-chromium,1.3.3,ISC
-elliptic,6.3.3,MIT
+elliptic,6.4.0,MIT
 email_reply_trimmer,0.1.6,MIT
 emoji-unicode-version,0.2.1,MIT
 emojis-list,2.1.0,MIT
-encodeurl,1.0.1,MIT
+encodeurl,1.0.2,MIT
 encryptor,3.0.0,MIT
 end-of-stream,1.4.0,MIT
-engine.io,3.1.4,MIT
-engine.io-client,3.1.4,MIT
+end-of-stream,1.4.1,MIT
+engine.io,3.1.5,MIT
+engine.io-client,3.1.5,MIT
 engine.io-parser,2.1.2,MIT
 enhanced-resolve,0.9.1,MIT
 enhanced-resolve,3.4.1,MIT
@@ -447,7 +497,7 @@ equalizer,0.0.11,MIT
 errno,0.1.4,MIT
 error-ex,1.3.0,MIT
 erubis,2.7.0,MIT
-es-abstract,1.8.2,MIT
+es-abstract,1.10.0,MIT
 es-to-primitive,1.1.1,MIT
 es5-ext,0.10.24,MIT
 es6-iterator,2.0.1,MIT
@@ -487,28 +537,35 @@ estraverse,4.1.1,Simplified BSD
 estraverse,4.2.0,Simplified BSD
 esutils,2.0.2,Simplified BSD
 et-orbi,1.0.3,MIT
-etag,1.8.0,MIT
+etag,1.8.1,MIT
 eve-raphael,0.5.0,Apache 2.0
 event-emitter,0.3.5,MIT
 event-stream,3.3.4,MIT
 eventemitter3,1.2.0,MIT
 events,1.1.1,MIT
 eventsource,0.1.6,MIT
-evp_bytestokey,1.0.0,MIT
-excon,0.57.1,MIT
+evp_bytestokey,1.0.3,MIT
+excon,0.60.0,MIT
 execa,0.7.0,MIT
 execjs,2.6.0,MIT
 exit-hook,1.1.1,MIT
 expand-braces,0.1.2,MIT
 expand-brackets,0.1.5,MIT
+expand-brackets,2.1.4,MIT
 expand-range,0.1.1,MIT
 expand-range,1.8.2,MIT
-exports-loader,0.6.4,MIT
-express,4.15.4,MIT
+expand-tilde,2.0.2,MIT
+exports-loader,0.7.0,MIT
+express,4.16.2,MIT
 expression_parser,0.9.0,MIT
 extend,3.0.1,MIT
+extend-shallow,2.0.1,MIT
+extend-shallow,3.0.2,MIT
+external-editor,2.1.0,MIT
 extglob,0.3.2,MIT
+extglob,2.0.4,MIT
 extsprintf,1.3.0,MIT
+extsprintf,1.4.0,MIT
 faraday,0.12.2,MIT
 faraday_middleware,0.11.0.1,MIT
 faraday_middleware-multi_json,0.0.6,MIT
@@ -516,36 +573,38 @@ fast-deep-equal,1.0.0,MIT
 fast-json-stable-stringify,2.0.0,MIT
 fast-levenshtein,2.0.6,MIT
 fast_blank,1.0.0,MIT
-fast_gettext,1.4.0,"MIT,ruby"
+fast_gettext,1.6.0,"MIT,ruby"
 fastparse,1.1.1,MIT
 faye-websocket,0.10.0,MIT
 faye-websocket,0.11.1,MIT
-faye-websocket,0.7.3,MIT
 ffi,1.9.18,New BSD
 figures,1.7.0,MIT
+figures,2.0.0,MIT
 file-entry-cache,2.0.0,MIT
-file-loader,0.11.1,MIT
+file-loader,1.1.8,MIT
 file-uri-to-path,1.0.0,MIT
-filename-regex,2.0.0,MIT
+filename-regex,2.0.1,MIT
 fileset,2.0.3,MIT
-filesize,3.3.0,New BSD
-filesize,3.5.10,New BSD
+filesize,3.5.11,New BSD
+filesize,3.6.0,New BSD
 fill-range,2.2.3,MIT
-finalhandler,1.0.4,MIT
+fill-range,4.0.0,MIT
+finalhandler,1.1.0,MIT
 find-cache-dir,1.0.0,MIT
 find-root,0.1.2,MIT
 find-up,1.1.2,MIT
 find-up,2.1.0,MIT
 flat-cache,1.2.2,MIT
 flatten,1.0.2,MIT
-flipper,0.11.0,MIT
-flipper-active_record,0.11.0,MIT
-flipper-active_support_cache_store,0.11.0,MIT
+flipper,0.13.0,MIT
+flipper-active_record,0.13.0,MIT
+flipper-active_support_cache_store,0.13.0,MIT
 flowdock,0.7.1,MIT
+flush-write-stream,1.0.2,MIT
 fog-aliyun,0.2.0,MIT
-fog-aws,1.4.0,MIT
-fog-core,1.44.3,MIT
-fog-google,0.5.3,MIT
+fog-aws,2.0.1,MIT
+fog-core,1.45.0,MIT
+fog-google,1.3.3,MIT
 fog-json,1.0.2,MIT
 fog-local,0.3.1,MIT
 fog-openstack,0.1.21,MIT
@@ -554,26 +613,26 @@ fog-xml,0.1.3,MIT
 follow-redirects,1.0.0,MIT
 follow-redirects,1.2.6,MIT
 font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
-for-each,0.3.2,MIT
-for-in,0.1.6,MIT
-for-own,0.1.4,MIT
+for-in,1.0.2,MIT
+for-own,0.1.5,MIT
 foreach,2.0.5,MIT
 forever-agent,0.6.1,Apache 2.0
 form-data,2.0.0,MIT
 form-data,2.1.4,MIT
-form-data,2.3.1,MIT
+form-data,2.3.2,MIT
 formatador,0.2.5,MIT
-forwarded,0.1.0,MIT
-fresh,0.5.0,MIT
+forwarded,0.1.2,MIT
+fragment-cache,0.2.1,MIT
+fresh,0.5.2,MIT
 from,0.1.7,MIT
+from2,2.3.0,MIT
 fs-access,1.0.1,MIT
-fs-extra,0.26.7,MIT
+fs-write-stream-atomic,1.0.10,ISC
 fs.realpath,1.0.0,ISC
-fsevents,1.1.2,MIT
+fsevents,1.1.3,MIT
 fstream,1.0.11,ISC
 fstream-ignore,1.0.5,ISC
 ftp,0.3.10,MIT
-function-bind,1.1.0,MIT
 function-bind,1.1.1,MIT
 fuzzaldrin-plus,0.5.0,MIT
 gauge,2.7.4,ISC
@@ -585,49 +644,55 @@ get-caller-file,1.0.2,ISC
 get-stdin,4.0.1,MIT
 get-stream,3.0.0,MIT
 get-uri,2.0.1,MIT
+get-value,2.0.6,MIT
 get_process_mem,0.2.0,MIT
 getpass,0.1.7,MIT
 gettext_i18n_rails,1.8.0,MIT
-gettext_i18n_rails_js,1.2.0,MIT
-gitaly-proto,0.84.0,MIT
-github-linguist,4.7.6,MIT
+gettext_i18n_rails_js,1.3.0,MIT
+gitaly-proto,0.94.0,MIT
+github-linguist,5.3.3,MIT
 github-markup,1.6.1,MIT
 gitlab-flowdock-git-hook,1.0.1,MIT
 gitlab-grit,2.8.2,MIT
 gitlab-markup,1.6.3,MIT
 gitlab_omniauth-ldap,2.0.4,MIT
 glob,5.0.15,ISC
-glob,6.0.4,ISC
 glob,7.1.1,ISC
 glob,7.1.2,ISC
 glob-base,0.3.0,MIT
 glob-parent,2.0.0,ISC
+glob-parent,3.1.0,ISC
+global-dirs,0.1.1,MIT
+global-modules,1.0.0,MIT
+global-prefix,1.0.2,MIT
 globalid,0.4.1,MIT
 globals,10.4.0,MIT
 globals,9.18.0,MIT
 globby,5.0.0,MIT
 globby,6.1.0,MIT
+globby,7.1.1,MIT
+goldiloader,2.0.1,MIT
 gollum-grit_adapter,1.0.1,MIT
 gollum-lib,4.2.7,MIT
 gollum-rugged_adapter,0.4.4,MIT
 gon,6.1.0,MIT
 good-listener,1.2.2,MIT
-google-api-client,0.13.6,Apache 2.0
+google-api-client,0.19.8,Apache 2.0
 google-protobuf,3.5.1,New BSD
 googleapis-common-protos-types,1.0.1,Apache 2.0
-googleauth,0.5.3,Apache 2.0
-got,3.3.1,MIT
+googleauth,0.6.2,Apache 2.0
+got,6.7.1,MIT
 got,7.1.0,MIT
 gpgme,2.0.13,LGPL-2.1+
 graceful-fs,4.1.11,ISC
-graceful-readlink,1.0.1,MIT
-grape,1.0.0,MIT
+grape,1.0.2,MIT
 grape-entity,0.6.0,MIT
 grape-route-helpers,2.1.0,MIT
 grape_logging,1.7.0,MIT
 graphlib,2.1.1,MIT
-grpc,1.8.3,Apache 2.0
+grpc,1.10.0,Apache 2.0
 gzip-size,3.0.0,MIT
+gzip-size,4.1.0,MIT
 hamlit,2.6.1,MIT
 handle-thing,1.2.5,MIT
 handlebars,4.0.6,MIT
@@ -642,12 +707,19 @@ has-binary2,1.0.2,MIT
 has-cors,1.1.0,MIT
 has-flag,1.0.0,MIT
 has-flag,2.0.0,MIT
+has-flag,3.0.0,MIT
 has-symbol-support-x,1.3.0,MIT
 has-to-string-tag-x,1.3.0,MIT
 has-unicode,2.0.1,ISC
+has-value,0.3.1,MIT
+has-value,1.0.0,MIT
+has-values,0.1.4,MIT
+has-values,1.0.0,MIT
+hash-base,2.0.2,MIT
+hash-base,3.0.4,MIT
 hash-sum,1.0.2,MIT
-hash.js,1.0.3,MIT
-hashie,3.5.6,MIT
+hash.js,1.1.3,MIT
+hashie,3.5.7,MIT
 hashie-forbidden_attributes,0.1.1,MIT
 hawk,3.1.3,New BSD
 hawk,6.0.2,New BSD
@@ -655,24 +727,25 @@ he,1.1.1,MIT
 health_check,2.6.0,MIT
 hipchat,1.5.2,MIT
 hipchat-notifier,1.1.0,MIT
+hmac-drbg,1.0.1,MIT
 hoek,2.16.3,New BSD
-hoek,4.2.0,New BSD
+hoek,4.2.1,New BSD
 home-or-tmp,2.0.0,MIT
+homedir-polyfill,1.0.1,MIT
 hosted-git-info,2.2.0,ISC
 hpack.js,2.1.6,MIT
 html-comment-regex,1.1.1,MIT
 html-entities,1.2.0,MIT
-html-pipeline,1.11.0,MIT
+html-pipeline,2.7.1,MIT
 html2text,0.2.0,MIT
 htmlentities,4.3.4,MIT
 htmlescape,1.1.1,MIT
 htmlparser2,3.9.2,MIT
-http,0.9.8,MIT
+http,2.2.2,MIT
 http-cookie,1.0.3,MIT
 http-deceiver,1.2.7,MIT
-http-errors,1.6.1,MIT
 http-errors,1.6.2,MIT
-http-form_data,1.0.1,MIT
+http-form_data,1.0.3,MIT
 http-proxy,1.16.2,MIT
 http-proxy-agent,1.0.0,MIT
 http-proxy-middleware,0.17.4,MIT
@@ -680,36 +753,41 @@ http-signature,1.1.1,MIT
 http-signature,1.2.0,MIT
 http_parser.rb,0.6.0,MIT
 httparty,0.13.7,MIT
-httpclient,2.8.2,ruby
+httpclient,2.8.3,ruby
 httpntlm,1.6.1,MIT
 httpreq,0.4.24,MIT
 https-browserify,0.0.1,MIT
 https-browserify,1.0.0,MIT
 https-proxy-agent,1.0.0,MIT
-i18n,0.9.1,MIT
+i18n,0.9.5,MIT
 ice_nine,0.11.2,MIT
 iconv-lite,0.4.15,MIT
 iconv-lite,0.4.19,MIT
-icss-replace-symbols,1.0.2,ISC
+icss-replace-symbols,1.1.0,ISC
+icss-utils,2.1.0,ISC
 ieee754,1.1.8,New BSD
+iferr,0.1.5,MIT
 ignore,3.3.3,MIT
+ignore,3.3.7,MIT
 ignore-by-default,1.0.1,ISC
 immediate,3.0.6,MIT
-imports-loader,0.7.1,MIT
+import-lazy,2.1.0,MIT
+import-local,1.0.0,MIT
+imports-loader,0.8.0,MIT
 imurmurhash,0.1.4,MIT
 indent-string,2.1.0,MIT
 indexes-of,1.0.1,MIT
 indexof,0.0.1,MIT*
-infinity-agent,2.0.3,MIT
 inflection,1.10.0,MIT
 inflection,1.3.8,MIT
 inflight,1.0.6,ISC
 influxdb,0.2.3,MIT
 inherits,2.0.1,ISC
 inherits,2.0.3,ISC
-ini,1.3.4,ISC
+ini,1.3.5,ISC
 inline-source-map,0.6.2,MIT
 inquirer,0.12.0,MIT
+inquirer,3.3.0,MIT
 insert-module-globals,7.0.1,MIT
 internal-ip,1.2.0,MIT
 interpret,1.0.1,MIT
@@ -717,46 +795,62 @@ invariant,2.2.2,New BSD
 invert-kv,1.0.0,MIT
 ip,1.0.1,MIT
 ip,1.1.5,MIT
-ipaddr.js,1.4.0,MIT
+ipaddr.js,1.6.0,MIT
 ipaddress,0.8.3,MIT
 is-absolute,0.2.6,MIT
 is-absolute-url,2.1.0,MIT
+is-accessor-descriptor,0.1.6,MIT
+is-accessor-descriptor,1.0.0,MIT
 is-arrayish,0.2.1,MIT
 is-binary-path,1.0.1,MIT
 is-buffer,1.1.5,MIT
 is-buffer,1.1.6,MIT
 is-builtin-module,1.0.0,MIT
 is-callable,1.1.3,MIT
+is-data-descriptor,0.1.4,MIT
+is-data-descriptor,1.0.0,MIT
 is-date-object,1.0.1,MIT
-is-dotfile,1.0.2,MIT
+is-descriptor,0.1.6,MIT
+is-descriptor,1.0.2,MIT
+is-dotfile,1.0.3,MIT
 is-equal-shallow,0.1.3,MIT
 is-extendable,0.1.1,MIT
+is-extendable,1.0.1,MIT
 is-extglob,1.0.0,MIT
 is-extglob,2.1.1,MIT
 is-finite,1.0.2,MIT
 is-fullwidth-code-point,1.0.0,MIT
 is-fullwidth-code-point,2.0.0,MIT
-is-function,1.0.1,MIT
 is-glob,2.0.1,MIT
 is-glob,3.1.0,MIT
+is-glob,4.0.0,MIT
+is-installed-globally,0.1.0,MIT
+is-my-ip-valid,1.0.0,MIT
 is-my-json-valid,2.16.0,MIT
-is-my-json-valid,2.17.1,MIT
+is-my-json-valid,2.17.2,MIT
 is-npm,1.0.0,MIT
 is-number,0.1.1,MIT
 is-number,2.1.0,MIT
+is-number,3.0.0,MIT
+is-number,4.0.0,MIT
+is-obj,1.0.1,MIT
 is-object,1.0.1,MIT
+is-odd,2.0.0,MIT
 is-path-cwd,1.0.0,MIT
 is-path-in-cwd,1.0.0,MIT
 is-path-inside,1.0.0,MIT
 is-plain-obj,1.1.0,MIT
+is-plain-object,2.0.4,MIT
 is-posix-bracket,0.1.1,MIT
 is-primitive,2.0.0,MIT
+is-promise,2.1.0,MIT
 is-property,1.0.2,MIT
 is-redirect,1.0.0,MIT
 is-regex,1.0.4,MIT
 is-relative,0.2.1,MIT
 is-resolvable,1.0.0,MIT
 is-retry-allowed,1.1.0,MIT
+is-root,1.0.0,MIT
 is-stream,1.1.0,MIT
 is-svg,2.1.0,MIT
 is-symbol,1.0.1,MIT
@@ -764,12 +858,16 @@ is-typedarray,1.0.0,MIT
 is-unc-path,0.1.2,MIT
 is-utf8,0.2.1,MIT
 is-windows,0.2.0,MIT
+is-windows,1.0.2,MIT
+is-wsl,1.1.0,MIT
 isarray,0.0.1,MIT
 isarray,1.0.0,MIT
 isarray,2.0.1,MIT
 isbinaryfile,3.0.2,MIT
 isexe,1.1.2,ISC
+isexe,2.0.0,ISC
 isobject,2.1.0,MIT
+isobject,3.0.1,MIT
 isstream,0.1.2,MIT
 istanbul,0.4.5,New BSD
 istanbul-api,1.2.1,New BSD
@@ -784,10 +882,10 @@ jasmine-core,2.9.0,MIT
 jasmine-jquery,2.1.1,MIT
 jed,1.1.1,MIT
 jira-ruby,1.4.1,MIT
-jquery,2.2.4,MIT
+jquery,3.3.1,MIT
 jquery-atwho-rails,1.3.2,MIT
-jquery-rails,4.3.1,MIT
 jquery-ujs,1.2.2,MIT
+jquery.waitforimages,2.2.0,MIT
 js-base64,2.1.9,New BSD
 js-cookie,2.1.3,MIT
 js-tokens,3.0.2,MIT
@@ -797,7 +895,7 @@ jsbn,0.1.1,MIT
 jsesc,0.5.0,MIT
 jsesc,1.3.0,MIT
 json,1.8.6,ruby
-json-jwt,1.7.2,MIT
+json-jwt,1.9.2,MIT
 json-loader,0.5.7,MIT
 json-schema,0.2.3,BSD
 json-schema-traverse,0.3.1,MIT
@@ -806,7 +904,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
 jsonparse,1.3.1,MIT
 jsonpointer,4.0.1,MIT
@@ -820,25 +917,30 @@ kaminari-activerecord,1.0.1,MIT
 kaminari-core,1.0.1,MIT
 karma,2.0.0,MIT
 karma-chrome-launcher,2.2.0,MIT
-karma-coverage-istanbul-reporter,1.3.3,MIT
+karma-coverage-istanbul-reporter,1.4.1,MIT
 karma-jasmine,1.1.1,MIT
 karma-mocha-reporter,2.2.5,MIT
 karma-sourcemap-loader,0.3.7,MIT
 karma-webpack,2.0.7,MIT
+katex,0.8.3,MIT
 kgio,2.10.0,LGPL-2.1+
-kind-of,3.1.0,MIT
-klaw,1.3.1,MIT
-kubeclient,2.2.0,MIT
+killable,1.0.0,ISC
+kind-of,3.2.2,MIT
+kind-of,4.0.0,MIT
+kind-of,5.1.0,MIT
+kind-of,6.0.2,MIT
+kubeclient,3.0.0,MIT
 labeled-stream-splicer,2.0.0,MIT
-latest-version,1.0.1,MIT
+latest-version,3.1.0,MIT
 lazy-cache,1.0.4,MIT
+lazy-cache,2.0.2,MIT
 lcid,1.0.0,MIT
 levn,0.3.0,MIT
 lexical-scope,1.2.0,MIT
 libbase64,0.1.0,MIT
 libmime,3.0.0,MIT
 libqp,1.1.0,MIT
-licensee,8.7.0,MIT
+licensee,8.9.2,MIT
 lie,3.1.1,MIT
 little-plugger,1.1.4,MIT
 load-json-file,1.1.0,MIT
@@ -850,47 +952,40 @@ locale,2.1.2,"ruby,LGPLv3+"
 locate-path,2.0.0,MIT
 lodash,3.10.1,MIT
 lodash,4.17.4,MIT
-lodash._baseassign,3.2.0,MIT
-lodash._basecopy,3.0.1,MIT
+lodash,4.17.5,MIT
 lodash._baseget,3.7.2,MIT
-lodash._bindcallback,3.0.1,MIT
-lodash._createassigner,3.1.1,MIT
-lodash._getnative,3.9.1,MIT
-lodash._isiterateecall,3.0.9,MIT
 lodash._topath,3.8.1,MIT
-lodash.assign,3.2.0,MIT
 lodash.camelcase,4.1.1,MIT
 lodash.camelcase,4.3.0,MIT
 lodash.capitalize,4.2.1,MIT
 lodash.clonedeep,4.5.0,MIT
 lodash.cond,4.5.2,MIT
 lodash.deburr,4.1.0,MIT
-lodash.defaults,3.1.2,MIT
+lodash.endswith,4.2.1,MIT
 lodash.escaperegexp,4.1.2,MIT
 lodash.get,3.7.0,MIT
-lodash.isarguments,3.1.0,MIT
 lodash.isarray,3.0.4,MIT
+lodash.isfunction,3.0.9,MIT
+lodash.isstring,4.0.1,MIT
 lodash.kebabcase,4.0.1,MIT
-lodash.keys,3.1.2,MIT
 lodash.memoize,3.0.4,MIT
 lodash.memoize,4.1.2,MIT
 lodash.mergewith,4.6.0,MIT
-lodash.restparam,3.6.1,MIT
 lodash.snakecase,4.0.1,MIT
+lodash.startswith,4.2.1,MIT
 lodash.uniq,4.5.0,MIT
 lodash.words,4.2.0,MIT
 log-symbols,2.1.0,MIT
-log4js,2.4.1,Apache 2.0
+log4js,2.5.3,Apache 2.0
 logging,2.2.2,MIT
 loggly,1.1.1,MIT
 loglevel,1.4.1,MIT
 lograge,0.5.1,MIT
 longest,1.0.1,MIT
-loofah,2.0.3,MIT
+loofah,2.2.2,MIT
 loose-envify,1.3.1,MIT
 loud-rejection,1.6.0,MIT
 lowercase-keys,1.0.0,MIT
-lru-cache,2.2.4,MIT
 lru-cache,2.6.5,ISC
 lru-cache,4.1.1,ISC
 macaddress,0.2.8,MIT
@@ -899,10 +994,14 @@ mail_room,0.9.1,MIT
 mailcomposer,4.0.1,MIT
 mailgun-js,0.7.15,MIT
 make-dir,1.0.0,MIT
+map-cache,0.2.2,MIT
 map-obj,1.0.1,MIT
-map-stream,0.1.0,Unknown
+map-stream,0.1.0,UNKNOWN
+map-visit,1.0.0,MIT
 marked,0.3.12,MIT
+match-at,0.1.1,MIT
 math-expression-evaluator,1.2.16,MIT
+md5.js,1.3.4,MIT
 media-typer,0.3.0,MIT
 mem,1.1.0,MIT
 memoist,0.16.0,MIT
@@ -913,57 +1012,61 @@ merge-descriptors,1.0.1,MIT
 method_source,0.8.2,MIT
 methods,1.1.2,MIT
 micromatch,2.3.11,MIT
-miller-rabin,4.0.0,MIT
-mime,1.3.4,MIT
+micromatch,3.1.6,MIT
+miller-rabin,4.0.1,MIT
+mime,1.4.1,MIT
 mime,1.6.0,MIT
-mime-db,1.27.0,MIT
 mime-db,1.29.0,MIT
-mime-db,1.30.0,MIT
-mime-types,2.1.15,MIT
-mime-types,2.1.17,MIT
+mime-db,1.33.0,MIT
+mime-types,2.1.18,MIT
 mime-types,3.1,MIT
 mime-types-data,3.2016.0521,MIT
 mimemagic,0.3.0,MIT
 mimic-fn,1.1.0,MIT
 mimic-response,1.0.0,MIT
-mini_mime,0.1.4,MIT
+mini_mime,1.0.0,MIT
 mini_portile2,2.3.0,MIT
 minimalistic-assert,1.0.0,ISC
+minimalistic-crypto-utils,1.0.1,MIT
 minimatch,3.0.3,ISC
 minimatch,3.0.4,ISC
+minimist,0.0.10,MIT
 minimist,0.0.8,MIT
 minimist,1.2.0,MIT
+mississippi,2.0.0,Simplified BSD
+mixin-deep,1.3.1,MIT
 mkdirp,0.5.1,MIT
 module-deps,4.1.1,MIT
 moment,2.19.2,MIT
 monaco-editor,0.10.0,MIT
 mousetrap,1.4.6,Apache 2.0
 mousetrap-rails,1.4.6,"MIT,Apache"
+move-concurrently,1.0.1,ISC
 ms,0.7.1,MIT
 ms,2.0.0,MIT
-multi_json,1.12.2,MIT
+multi_json,1.13.1,MIT
 multi_xml,0.6.0,MIT
 multicast-dns,6.1.1,MIT
 multicast-dns-service-types,1.1.0,MIT
 multipart-post,2.0.0,MIT
-mustermann,1.0.0,MIT
+mustermann,1.0.2,MIT
 mustermann-grape,1.0.0,MIT
 mute-stream,0.0.5,ISC
+mute-stream,0.0.7,ISC
 mysql2,0.4.10,MIT
 name-all-modules-plugin,1.0.1,MIT
-nan,2.6.2,MIT
+nan,2.8.0,MIT
+nanomatch,1.2.9,MIT
 natural-compare,1.4.0,MIT
 negotiator,0.6.1,MIT
-nested-error-stacks,1.0.2,MIT
 net-ldap,0.16.0,MIT
-net-ssh,4.1.0,MIT
+net-ssh,4.2.0,MIT
 netmask,1.0.6,MIT
 netrc,0.11.0,MIT
-node-dir,0.1.17,MIT
 node-forge,0.6.33,New BSD
 node-libs-browser,1.1.1,MIT
 node-libs-browser,2.0.0,MIT
-node-pre-gyp,0.6.37,New BSD
+node-pre-gyp,0.6.39,New BSD
 node-uuid,1.4.8,MIT
 nodemailer,2.7.2,MIT
 nodemailer-direct-transport,3.3.2,MIT
@@ -972,7 +1075,7 @@ nodemailer-shared,1.1.0,MIT
 nodemailer-smtp-pool,2.8.2,MIT
 nodemailer-smtp-transport,2.7.2,MIT
 nodemailer-wellknown,0.1.10,MIT
-nodemon,1.11.0,MIT
+nodemon,1.15.1,MIT
 nokogiri,1.8.2,MIT
 nopt,1.0.10,MIT
 nopt,3.0.6,ISC
@@ -987,42 +1090,44 @@ null-check,1.0.0,MIT
 num2fraction,1.2.2,MIT
 number-is-nan,1.0.1,MIT
 numerizer,0.1.1,MIT
-oauth,0.5.1,MIT
+oauth,0.5.4,MIT
 oauth-sign,0.8.2,Apache 2.0
 oauth2,1.4.0,MIT
-object-assign,3.0.0,MIT
 object-assign,4.1.1,MIT
 object-component,0.0.3,MIT*
-object-inspect,1.3.0,MIT
+object-copy,0.1.0,MIT
 object-keys,1.0.11,MIT
+object-visit,1.0.1,MIT
 object.omit,2.0.1,MIT
+object.pick,1.3.0,MIT
 obuf,1.1.1,MIT
-octokit,4.6.2,MIT
-oj,2.17.5,MIT
-omniauth,1.4.2,MIT
-omniauth-auth0,1.4.1,MIT
+octokit,4.8.0,MIT
+omniauth,1.8.1,MIT
+omniauth-auth0,2.0.0,MIT
 omniauth-authentiq,0.3.1,MIT
 omniauth-azure-oauth2,0.0.9,MIT
 omniauth-cas3,1.1.4,MIT
 omniauth-facebook,4.0.0,MIT
 omniauth-github,1.1.2,MIT
 omniauth-gitlab,1.0.2,MIT
-omniauth-google-oauth2,0.5.2,MIT
+omniauth-google-oauth2,0.5.3,MIT
+omniauth-jwt,0.0.2,MIT
 omniauth-kerberos,0.3.0,MIT
 omniauth-multipassword,0.4.2,MIT
 omniauth-oauth,1.1.0,MIT
-omniauth-oauth2,1.4.0,MIT
+omniauth-oauth2,1.5.0,MIT
 omniauth-oauth2-generic,0.2.2,MIT
-omniauth-saml,1.7.0,MIT
+omniauth-saml,1.10.0,MIT
 omniauth-shibboleth,1.2.1,MIT
-omniauth-twitter,1.2.1,MIT
+omniauth-twitter,1.4.0,MIT
 omniauth_crowd,2.2.3,MIT
 on-finished,2.3.0,MIT
 on-headers,1.0.1,MIT
 once,1.4.0,ISC
 onetime,1.1.0,MIT
+onetime,2.0.1,MIT
 opener,1.4.3,(WTFPL OR MIT)
-opn,4.0.2,MIT
+opn,5.2.0,MIT
 optimist,0.6.1,MIT
 optionator,0.8.2,MIT
 org-ruby,0.9.12,MIT
@@ -1035,27 +1140,33 @@ os-homedir,1.0.2,MIT
 os-locale,1.4.0,MIT
 os-locale,2.1.0,MIT
 os-tmpdir,1.0.2,MIT
-osenv,0.1.4,ISC
+osenv,0.1.5,ISC
 p-cancelable,0.3.0,MIT
 p-finally,1.0.0,MIT
 p-limit,1.1.0,MIT
+p-limit,1.2.0,MIT
 p-locate,2.0.0,MIT
 p-map,1.1.1,MIT
 p-timeout,1.2.0,MIT
+p-try,1.0.0,MIT
 pac-proxy-agent,1.1.0,MIT
 pac-resolver,2.0.0,MIT
-package-json,1.2.0,MIT
+package-json,4.0.1,MIT
 pako,0.2.9,MIT
 pako,1.0.5,(MIT AND Zlib)
 pako,1.0.6,(MIT AND Zlib)
+parallel-transform,1.1.0,MIT
 parents,1.0.1,MIT
-parse-asn1,5.0.0,ISC
+parse-asn1,5.1.0,ISC
 parse-glob,3.0.4,MIT
 parse-json,2.2.0,MIT
+parse-passwd,1.0.0,MIT
 parseqs,0.0.5,MIT
 parseuri,0.0.5,MIT
-parseurl,1.3.1,MIT
+parseurl,1.3.2,MIT
+pascalcase,0.1.1,MIT
 path-browserify,0.0.0,MIT
+path-dirname,1.0.2,MIT
 path-exists,2.1.0,MIT
 path-exists,3.0.0,MIT
 path-is-absolute,1.0.1,MIT
@@ -1067,13 +1178,13 @@ path-proxy,1.0.0,MIT
 path-to-regexp,0.1.7,MIT
 path-type,1.1.0,MIT
 path-type,2.0.0,MIT
+path-type,3.0.0,MIT
 pause-stream,0.0.11,Apache 2.0
-pbkdf2,3.0.9,MIT
+pbkdf2,3.0.14,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.3.0,MIT
+peek-performance_bar,1.3.1,MIT
 peek-pg,1.3.0,MIT
 peek-rblineprof,0.2.0,MIT
 peek-redis,1.2.0,MIT
@@ -1092,10 +1203,12 @@ pkg-up,1.0.0,MIT
 pluralize,1.2.1,MIT
 po_to_json,1.0.1,MIT
 portfinder,1.0.13,MIT
+posix-character-classes,0.1.1,MIT
 posix-spawn,0.3.13,MIT
 postcss,5.2.16,MIT
 postcss,6.0.14,MIT
 postcss,6.0.15,MIT
+postcss,6.0.19,MIT
 postcss-calc,5.3.1,MIT
 postcss-colormin,2.2.2,MIT
 postcss-convert-values,2.6.1,MIT
@@ -1116,10 +1229,10 @@ postcss-minify-font-values,1.0.5,MIT
 postcss-minify-gradients,1.0.5,MIT
 postcss-minify-params,1.2.2,MIT
 postcss-minify-selectors,2.1.1,MIT
-postcss-modules-extract-imports,1.0.1,ISC
-postcss-modules-local-by-default,1.1.1,MIT
-postcss-modules-scope,1.0.2,ISC
-postcss-modules-values,1.2.2,ISC
+postcss-modules-extract-imports,1.2.0,ISC
+postcss-modules-local-by-default,1.2.0,MIT
+postcss-modules-scope,1.1.0,ISC
+postcss-modules-values,1.3.0,ISC
 postcss-normalize-charset,1.1.1,MIT
 postcss-normalize-url,3.0.8,MIT
 postcss-ordered-values,2.2.3,MIT
@@ -1136,61 +1249,64 @@ premailer,1.10.4,New BSD
 premailer-rails,1.9.7,MIT
 prepend-http,1.0.4,MIT
 preserve,0.2.0,MIT
+prettier,1.11.1,MIT
 prettier,1.8.2,MIT
-prettier,1.9.2,MIT
 prismjs,1.6.0,MIT
 private,0.1.8,MIT
+process,0.11.10,MIT
 process,0.11.9,MIT
 process-nextick-args,1.0.7,MIT
+process-nextick-args,2.0.0,MIT
 progress,1.1.8,MIT
 prometheus-client-mmap,0.9.1,Apache 2.0
-proxy-addr,1.1.5,MIT
+promise-inflight,1.0.1,ISC
+proxy-addr,2.0.3,MIT
 proxy-agent,2.0.0,MIT
 prr,0.0.0,MIT
 ps-tree,1.1.0,MIT
 pseudomap,1.0.2,ISC
+pstree.remy,1.1.0,MIT
 public-encrypt,4.0.0,MIT
-public_suffix,3.0.0,MIT
+public_suffix,3.0.2,MIT
+pump,2.0.1,MIT
+pumpify,1.4.0,MIT
 punycode,1.3.2,MIT
 punycode,1.4.1,MIT
 pyu-ruby-sasl,0.0.3.3,MIT
 q,1.4.1,MIT
 q,1.5.0,MIT
-qjobs,1.1.5,MIT
+qjobs,1.2.0,MIT
 qs,6.2.3,New BSD
 qs,6.4.0,New BSD
-qs,6.5.0,New BSD
 qs,6.5.1,New BSD
 query-string,4.3.2,MIT
 querystring,0.2.0,MIT
 querystring-es3,0.2.1,MIT
 querystringify,0.0.4,MIT
 querystringify,1.0.0,MIT
-rack,1.6.8,MIT
+rack,1.6.9,MIT
 rack-accept,0.4.5,MIT
 rack-attack,4.4.1,MIT
 rack-cors,1.0.2,MIT
 rack-oauth2,1.2.3,MIT
-rack-protection,1.5.3,MIT
+rack-protection,2.0.1,MIT
 rack-proxy,0.6.0,MIT
 rack-test,0.6.3,MIT
 rails,4.2.10,MIT
 rails-deprecated_sanitizer,1.0.3,MIT
-rails-dom-testing,1.0.8,MIT
-rails-html-sanitizer,1.0.3,MIT
+rails-dom-testing,1.0.9,MIT
+rails-html-sanitizer,1.0.4,MIT
 rails-i18n,4.0.9,MIT
 railties,4.2.10,MIT
 rainbow,2.2.2,MIT
 raindrops,0.18.0,LGPL-2.1+
 rake,12.3.0,MIT
-randomatic,1.1.6,MIT
-randombytes,2.0.3,MIT
+randomatic,1.1.7,MIT
 randombytes,2.0.6,MIT
-randomfill,1.0.3,MIT
+randomfill,1.0.4,MIT
 range-parser,1.2.0,MIT
 raphael,2.2.7,MIT
 raven-js,3.22.1,Simplified BSD
-raw-body,2.2.0,MIT
 raw-body,2.3.2,MIT
 raw-loader,0.5.1,MIT
 rb-fsevent,0.10.2,MIT
@@ -1198,10 +1314,11 @@ rb-inotify,0.9.10,MIT
 rbnacl,4.0.2,MIT
 rbnacl-libsodium,1.0.11,MIT
 rc,1.2.1,(BSD-2-Clause OR MIT OR Apache-2.0)
+rc,1.2.5,(BSD-2-Clause OR MIT OR Apache-2.0)
 rdoc,4.2.2,ruby
 re2,1.1.1,New BSD
-react-dev-utils,0.5.2,New BSD
-read-all-stream,3.1.0,MIT
+react-dev-utils,5.0.0,MIT
+react-error-overlay,4.0.0,MIT
 read-only-stream,2.0.0,MIT
 read-pkg,1.1.0,MIT
 read-pkg,2.0.0,MIT
@@ -1210,12 +1327,13 @@ read-pkg-up,2.0.0,MIT
 readable-stream,1.1.14,MIT
 readable-stream,2.0.6,MIT
 readable-stream,2.3.3,MIT
+readable-stream,2.3.4,MIT
 readdirp,2.1.0,MIT
 readline2,1.0.1,MIT
 recaptcha,3.0.0,MIT
 rechoir,0.6.2,MIT
-recursive-open-struct,1.0.0,MIT
-recursive-readdir,2.1.1,MIT
+recursive-open-struct,1.0.5,MIT
+recursive-readdir,2.2.1,MIT
 redcarpet,3.4.0,MIT
 redent,1.0.0,MIT
 redis,2.8.0,MIT
@@ -1233,9 +1351,11 @@ reduce-function-call,1.0.2,MIT
 regenerate,1.3.2,MIT
 regenerator-runtime,0.11.0,MIT
 regenerator-transform,0.10.1,BSD
-regex-cache,0.4.3,MIT
+regex-cache,0.4.4,MIT
+regex-not,1.0.2,MIT
 regexpu-core,1.0.0,MIT
 regexpu-core,2.0.0,MIT
+registry-auth-token,3.3.2,MIT
 registry-url,3.1.0,MIT
 regjsgen,0.2.0,MIT
 regjsparser,0.1.5,Simplified BSD
@@ -1243,14 +1363,13 @@ remove-trailing-separator,1.1.0,ISC
 repeat-element,1.1.2,MIT
 repeat-string,0.2.2,MIT
 repeat-string,1.6.1,MIT
-repeating,1.1.3,MIT
 repeating,2.0.1,MIT
 representable,3.0.4,MIT
 request,2.75.0,Apache 2.0
 request,2.81.0,Apache 2.0
 request,2.83.0,Apache 2.0
 request_store,1.3.1,MIT
-requestretry,1.12.2,MIT
+requestretry,1.13.0,MIT
 require-all,2.2.0,MIT
 require-directory,2.1.1,MIT
 require-from-string,1.2.1,MIT
@@ -1258,34 +1377,44 @@ require-main-filename,1.0.1,ISC
 require-uncached,1.0.3,MIT
 requires-port,1.0.0,MIT
 resolve,1.1.7,MIT
-resolve,1.4.0,MIT
 resolve,1.5.0,MIT
+resolve-cwd,2.0.0,MIT
+resolve-dir,1.0.1,MIT
 resolve-from,1.0.1,MIT
+resolve-from,3.0.0,MIT
+resolve-url,0.2.1,MIT
 responders,2.3.0,MIT
-rest-client,2.0.0,MIT
+rest-client,2.0.2,MIT
 restore-cursor,1.0.1,MIT
-resumer,0.0.0,MIT
+restore-cursor,2.0.0,MIT
+ret,0.1.15,MIT
 retriable,3.1.1,MIT
 right-align,0.1.3,MIT
 rimraf,2.6.1,ISC
+rimraf,2.6.2,ISC
 rinku,2.0.0,ISC
-ripemd160,1.0.1,New BSD
+ripemd160,2.0.1,MIT
 rotp,2.1.2,MIT
 rouge,2.2.1,MIT
 rqrcode,0.7.0,MIT
 rqrcode-rails3,0.1.7,MIT
+ruby-enum,0.7.2,MIT
 ruby-fogbugz,0.2.1,MIT
-ruby-prof,0.16.2,Simplified BSD
-ruby-saml,1.4.1,MIT
+ruby-prof,0.17.0,Simplified BSD
+ruby-saml,1.7.2,MIT
 ruby_parser,3.9.0,MIT
 rubyntlm,0.6.2,MIT
 rubypants,0.2.0,BSD
 rufus-scheduler,3.4.0,MIT
-rugged,0.26.0,MIT
+rugged,0.27.0,MIT
 run-async,0.1.0,MIT
+run-async,2.3.0,MIT
+run-queue,1.0.3,ISC
 rx-lite,3.1.2,Apache 2.0
-safe-buffer,5.0.1,MIT
+rx-lite,4.0.8,Apache 2.0
+rx-lite-aggregates,4.0.8,Apache 2.0
 safe-buffer,5.1.1,MIT
+safe-regex,1.1.0,MIT
 safe_yaml,1.0.4,MIT
 sanitize,2.1.0,MIT
 sanitize-html,1.16.3,MIT
@@ -1295,6 +1424,7 @@ sass-rails,5.0.6,MIT
 sawyer,0.8.1,MIT
 sax,1.2.2,ISC
 schema-utils,0.3.0,MIT
+schema-utils,0.4.5,MIT
 securecompare,1.0.0,MIT
 seed-fu,2.3.7,MIT
 select,1.1.2,MIT
@@ -1304,19 +1434,24 @@ select2-rails,3.5.9.3,MIT
 selfsigned,1.10.1,MIT
 semver,5.0.3,ISC
 semver,5.3.0,ISC
+semver,5.5.0,ISC
 semver-diff,2.1.0,MIT
-send,0.15.4,MIT
-sentry-raven,2.5.3,Apache 2.0
+send,0.16.1,MIT
+sentry-raven,2.7.2,Apache 2.0
+serialize-javascript,1.4.0,New BSD
 serve-index,1.9.0,MIT
-serve-static,1.12.4,MIT
+serve-static,1.13.1,MIT
 set-blocking,2.0.0,ISC
+set-getter,0.1.0,MIT
 set-immediate-shim,1.0.1,MIT
+set-value,0.4.3,MIT
+set-value,2.0.0,MIT
 setimmediate,1.0.5,MIT
 setprototypeof,1.0.3,ISC
+setprototypeof,1.1.0,ISC
 settingslogic,2.0.9,MIT
 sexp_processor,4.9.0,MIT
-sha.js,2.4.8,MIT
-sha.js,2.4.9,MIT
+sha.js,2.4.10,MIT
 shasum,1.0.2,MIT
 shebang-command,1.2.0,MIT
 shebang-regex,1.0.0,MIT
@@ -1326,64 +1461,72 @@ sidekiq,5.0.5,LGPL
 sidekiq-cron,0.6.0,MIT
 sidekiq-limit_fetch,3.4.0,MIT
 signal-exit,3.0.2,ISC
-signet,0.7.3,Apache 2.0
+signet,0.8.1,Apache 2.0
 slack-node,0.2.0,MIT
 slack-notifier,1.5.1,MIT
 slash,1.0.0,MIT
 slice-ansi,0.0.4,MIT
-slide,1.1.6,ISC
 smart-buffer,1.1.15,MIT
 smtp-connection,2.12.0,MIT
+snapdragon,0.8.1,MIT
+snapdragon-node,2.1.1,MIT
+snapdragon-util,3.0.1,MIT
 sntp,1.0.9,BSD
 sntp,2.1.0,BSD
 socket.io,2.0.4,MIT
 socket.io-adapter,1.1.1,MIT
 socket.io-client,2.0.4,MIT
 socket.io-parser,3.1.2,MIT
-sockjs,0.3.18,MIT
-sockjs-client,1.0.1,MIT
+sockjs,0.3.19,MIT
 sockjs-client,1.1.4,MIT
 socks,1.1.10,MIT
 socks,1.1.9,MIT
 socks-proxy-agent,2.1.1,MIT
 sort-keys,1.1.2,MIT
-source-list-map,0.1.8,MIT
 source-list-map,2.0.0,MIT
 source-map,0.2.0,New BSD
 source-map,0.4.4,New BSD
+source-map,0.5.0,New BSD
 source-map,0.5.6,New BSD
 source-map,0.5.7,New BSD
 source-map,0.6.1,New BSD
+source-map-resolve,0.5.1,MIT
 source-map-support,0.4.18,MIT
+source-map-url,0.4.0,MIT
 spdx-correct,1.0.2,Apache 2.0
 spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0)
 spdx-license-ids,1.2.2,Unlicense
 spdy,3.4.7,MIT
 spdy-transport,2.0.20,MIT
 split,0.3.3,MIT
+split-string,3.1.0,MIT
 sprintf-js,1.0.3,New BSD
 sprockets,3.7.1,MIT
 sprockets-rails,3.2.1,MIT
 sql.js,0.4.0,MIT
 srcset,1.0.0,MIT
+sshkey,1.9.0,MIT
 sshpk,1.13.1,MIT
-state_machines,0.4.0,MIT
-state_machines-activemodel,0.4.0,MIT
-state_machines-activerecord,0.4.0,MIT
+ssri,5.2.4,ISC
+state_machines,0.5.0,MIT
+state_machines-activemodel,0.5.1,MIT
+state_machines-activerecord,0.5.1,MIT
+static-extend,0.1.2,MIT
 statuses,1.3.1,MIT
+statuses,1.4.0,MIT
 stream-browserify,2.0.1,MIT
 stream-combiner,0.0.4,MIT
 stream-combiner2,1.1.1,MIT
+stream-each,1.2.2,MIT
 stream-http,2.6.3,MIT
-stream-http,2.7.2,MIT
+stream-http,2.8.0,MIT
 stream-shift,1.0.0,MIT
 stream-splicer,2.0.0,MIT
 streamroller,0.7.0,MIT
 strict-uri-encode,1.1.0,MIT
-string-length,1.0.1,MIT
 string-width,1.0.2,MIT
 string-width,2.0.0,MIT
-string.prototype.trim,1.1.2,MIT
+string-width,2.1.1,MIT
 string_decoder,0.10.31,MIT
 string_decoder,1.0.3,MIT
 stringex,2.7.1,MIT
@@ -1395,23 +1538,25 @@ strip-bom,3.0.0,MIT
 strip-eof,1.0.0,MIT
 strip-indent,1.0.1,MIT
 strip-json-comments,2.0.1,MIT
+style-loader,0.20.2,MIT
 subarg,1.0.0,MIT
 supports-color,2.0.0,MIT
 supports-color,3.2.3,MIT
 supports-color,4.2.1,MIT
 supports-color,4.5.0,MIT
 supports-color,5.1.0,MIT
+supports-color,5.2.0,MIT
 svg4everybody,2.1.9,CC0-1.0
 svgo,0.7.2,MIT
-syntax-error,1.3.0,MIT
+syntax-error,1.4.0,MIT
 sys-filesystem,1.1.6,Artistic 2.0
 table,3.8.3,New BSD
 tapable,0.1.10,MIT
 tapable,0.2.8,MIT
-tape,4.8.0,MIT
 tar,2.2.1,ISC
-tar-pack,3.4.0,Simplified BSD
+tar-pack,3.4.1,Simplified BSD
 temple,0.7.7,MIT
+term-size,1.2.0,MIT
 test-exclude,4.1.1,ISC
 text,1.3.1,MIT
 text-table,0.2.0,MIT
@@ -1427,37 +1572,39 @@ thunky,0.1.0,MIT*
 tilt,2.0.6,MIT
 time-stamp,2.0.0,MIT
 timeago.js,3.0.2,MIT
-timed-out,2.0.0,MIT
 timed-out,4.0.1,MIT
 timers-browserify,1.4.2,MIT
 timers-browserify,2.0.4,MIT
 timespan,2.3.0,MIT
 timfel-krb5-auth,0.8.3,LGPL
 tiny-emitter,2.0.2,MIT
-tmp,0.0.31,MIT
 tmp,0.0.33,MIT
 to-array,0.1.4,MIT
 to-arraybuffer,1.0.1,MIT
 to-fast-properties,1.0.3,MIT
 to-fast-properties,2.0.0,MIT
-toml-rb,0.3.15,MIT
-touch,1.0.0,ISC
-tough-cookie,2.3.2,New BSD
+to-object-path,0.3.0,MIT
+to-regex,3.0.1,MIT
+to-regex-range,2.1.1,MIT
+toml-rb,1.0.0,MIT
+touch,3.1.0,ISC
 tough-cookie,2.3.3,New BSD
 traverse,0.6.6,MIT
 trim-newlines,1.0.0,MIT
 trim-right,1.0.1,MIT
 truncato,0.7.10,MIT
+tryer,1.0.0,MIT
 tryit,1.0.3,MIT
 tsscmp,1.0.5,MIT
 tty-browserify,0.0.0,MIT
+tty-browserify,0.0.1,MIT
 tunnel-agent,0.4.3,Apache 2.0
 tunnel-agent,0.6.0,Apache 2.0
 tweetnacl,0.14.5,Unlicense
 type-check,0.3.2,MIT
-type-is,1.6.15,MIT
+type-is,1.6.16,MIT
 typedarray,0.0.6,MIT
-tzinfo,1.2.4,MIT
+tzinfo,1.2.5,MIT
 u2f,0.2.1,MIT
 uber,0.1.0,MIT
 uglifier,2.7.2,MIT
@@ -1465,40 +1612,48 @@ uglify-js,2.8.29,Simplified BSD
 uglify-to-browserify,1.0.2,MIT
 uglifyjs-webpack-plugin,0.4.6,MIT
 uid-number,0.0.6,ISC
-ultron,1.1.0,MIT
+ultron,1.1.1,MIT
 umd,3.0.1,MIT
 unc-path-regex,0.1.2,MIT
-undefsafe,0.0.3,MIT / http://rem.mit-license.org
+undefsafe,2.0.2,MIT
 underscore,1.7.0,MIT
 underscore,1.8.3,MIT
 unf,0.1.4,BSD
-unf_ext,0.0.7.4,MIT
+unf_ext,0.0.7.5,MIT
 unicorn,5.1.0,ruby
 unicorn-worker-killer,0.4.4,ruby
+union-value,1.0.0,MIT
 uniq,1.0.1,MIT
 uniqid,4.1.1,MIT
 uniqs,2.0.0,MIT
+unique-filename,1.1.0,ISC
+unique-slug,2.0.0,ISC
+unique-string,1.0.0,MIT
 unpipe,1.0.0,MIT
-update-notifier,0.5.0,Simplified BSD
+unset-value,1.0.0,MIT
+unzip-response,2.0.1,MIT
+upath,1.0.2,MIT
+update-notifier,2.3.0,Simplified BSD
+urix,0.1.0,MIT
 url,0.11.0,MIT
-url-loader,0.5.8,MIT
+url-loader,0.6.2,MIT
 url-parse,1.0.5,MIT
-url-parse,1.1.7,MIT
 url-parse,1.1.9,MIT
 url-parse-lax,1.0.0,MIT
 url-to-options,1.0.1,MIT
 url_safe_base64,0.2.2,MIT
+use,2.0.2,MIT
 user-home,2.0.0,MIT
-useragent,2.2.1,MIT
+useragent,2.3.0,MIT
 util,0.10.3,MIT
 util-deprecate,1.0.2,MIT
-utils-merge,1.0.0,MIT
-uuid,2.0.3,MIT
-uuid,3.1.0,MIT
-uws,0.14.5,Zlib
+utils-merge,1.0.1,MIT
+uuid,3.2.1,MIT
+uws,9.14.0,Zlib
 validate-npm-package-license,3.0.1,Apache 2.0
 validates_hostname,1.0.6,MIT
 vary,1.1.1,MIT
+vary,1.1.2,MIT
 vendors,1.0.1,MIT
 verror,1.10.0,MIT
 version_sorter,2.1.0,MIT
@@ -1510,21 +1665,20 @@ void-elements,2.0.1,MIT
 vue,2.5.13,MIT
 vue-eslint-parser,2.0.1,MIT
 vue-hot-reload-api,2.2.4,MIT
-vue-loader,13.7.0,MIT
+vue-loader,14.1.1,MIT
 vue-resource,1.3.5,MIT
 vue-router,3.0.1,MIT
-vue-style-loader,3.0.3,MIT
+vue-style-loader,4.0.2,MIT
 vue-template-compiler,2.5.13,MIT
 vue-template-es2015-compiler,1.6.0,MIT
 vuex,3.0.1,MIT
 warden,1.2.6,MIT
 watchpack,1.4.0,MIT
 wbuf,1.7.2,MIT
-webpack,3.5.5,MIT
-webpack-bundle-analyzer,2.8.2,MIT
-webpack-dev-middleware,1.11.0,MIT
+webpack,3.11.0,MIT
+webpack-bundle-analyzer,2.10.0,MIT
 webpack-dev-middleware,1.12.2,MIT
-webpack-dev-server,2.7.1,MIT
+webpack-dev-server,2.11.2,MIT
 webpack-rails,0.9.10,MIT
 webpack-sources,1.0.1,MIT
 webpack-stats-plugin,0.1.5,MIT
@@ -1533,9 +1687,11 @@ websocket-extensions,0.1.1,MIT
 when,3.7.8,MIT
 whet.extend,0.9.9,MIT
 which,1.2.12,ISC
+which,1.3.0,ISC
 which-module,1.0.0,ISC
 which-module,2.0.0,ISC
 wide-align,1.1.2,ISC
+widest-line,2.0.0,MIT
 wikicloth,0.8.1,MIT
 window-size,0.1.0,MIT
 wordwrap,0.0.2,MIT
@@ -1545,15 +1701,16 @@ worker-loader,1.1.0,MIT
 wrap-ansi,2.1.0,MIT
 wrappy,1.0.2,ISC
 write,0.2.1,MIT
-write-file-atomic,1.3.4,ISC
-ws,2.3.1,MIT
+write-file-atomic,2.3.0,ISC
 ws,3.3.3,MIT
-xdg-basedir,2.0.0,MIT
+ws,4.0.0,MIT
+xdg-basedir,3.0.0,MIT
 xml-simple,1.1.5,ruby
 xmlhttprequest-ssl,1.5.5,MIT
 xregexp,2.0.0,MIT
 xtend,4.0.1,MIT
 y18n,3.2.1,ISC
+y18n,4.0.0,ISC
 yallist,2.1.2,ISC
 yargs,3.10.0,MIT
 yargs,6.6.0,MIT
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
index 859f2ad82a468d5dfaef813f3b0466fb8ebed44e..c432be72163460cb87bd5937e3ab25413215f636 100644
--- a/vendor/prometheus/values.yaml
+++ b/vendor/prometheus/values.yaml
@@ -14,6 +14,7 @@ rbac:
   create: false
 
 server:
+  fullnameOverride: "prometheus-prometheus-server"
   image:
     tag: v2.1.0
 
diff --git a/vendor/runner/values.yaml b/vendor/runner/values.yaml
index b7e2e24acafe0270d5987b50f25a53dd0167022f..e5f95152ac732f486ee73fbaa0df4d0ef48657f6 100644
--- a/vendor/runner/values.yaml
+++ b/vendor/runner/values.yaml
@@ -15,10 +15,8 @@ rbac:
   clusterWideAccess: false
 
 ## Configuration for the Pods that that the runner launches for each new job
-##
 runners:
   image: ubuntu:16.04
-  privileged: false
   builds: {}
   services: {}
   helpers: {}
diff --git a/yarn.lock b/yarn.lock
index ab0ad265d81450cf8d5e34a76b37772d03a17798..55a86a9a57725df5c0b34706fe5e9dca16f74d98 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -54,9 +54,9 @@
     lodash "^4.2.0"
     to-fast-properties "^2.0.0"
 
-"@gitlab-org/gitlab-svgs@^1.8.0":
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.8.0.tgz#95d6afa94395860699ddad60a82bd1bbbc2ba89f"
+"@gitlab-org/gitlab-svgs@^1.18.0":
+  version "1.18.0"
+  resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.18.0.tgz#7829f0e6de0647dace54c1fcd597ee3424afb233"
 
 "@types/jquery@^2.0.40":
   version "2.0.48"
@@ -1859,9 +1859,9 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.13.0, commander@^2.9.0:
-  version "2.14.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
+commander@^2.13.0, commander@^2.15.1, commander@^2.9.0:
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
 
 commondir@^1.0.1:
   version "1.0.1"
@@ -4232,7 +4232,7 @@ ignore@^3.2.0:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
 
-ignore@^3.3.5:
+ignore@^3.3.5, ignore@^3.3.7:
   version "3.3.7"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
 
@@ -6685,9 +6685,9 @@ preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
 
-prettier@1.9.2:
-  version "1.9.2"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827"
+prettier@1.11.1:
+  version "1.11.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
 
 prettier@^1.7.0:
   version "1.8.2"