diff --git a/.gitlab/issue_templates/Acceptance_Testing.md b/.gitlab/issue_templates/Acceptance_Testing.md
new file mode 100644
index 0000000000000000000000000000000000000000..f1fbb96ce6115a9a6db5708081b97078f801f73d
--- /dev/null
+++ b/.gitlab/issue_templates/Acceptance_Testing.md
@@ -0,0 +1,100 @@
+## Details
+- **Feature Toggle Name**: `FEATURE_NAME`
+- **Required GitLab Version**: `vX.X`
+
+--------------------------------------------------------------------------------
+
+## 1. Preparation
+
+- [ ] **Controllers and workers**:
+  1. Please link to dashboards of the workers, and the controllers and actions that can be impacted
+  2. ...
+  3. ...
+
+## 2. Development Trial
+
+#### Check Dev Server Versions
+- [ ] GitLab: https://dev.gitlab.org/help
+
+#### Enable on `dev.gitlab.org`:
+- [ ] `/chatops feature set FEATURE_NAME true --dev` in [`#dev-gitlab`](https://gitlab.slack.com/messages/C6WQ87MU3)
+
+Then leave running while monitoring and performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
+
+## 2. Staging Trial
+
+#### Check Staging Server Versions
+- [ ] GitLab: https://staging.gitlab.com/help
+
+#### Enable on `staging.gitlab.com`
+- [ ] `/chatops run feature set FEATURE_NAME true --staging` in [`#development`](https://gitlab.slack.com/messages/C02PF508L/)
+
+Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 4. Production Server Version Check
+
+- [ ] GitLab: https://gitlab.com/help
+
+## 5. Initial Impact Check
+
+- [ ] Enable for a subset of users, when using percentage gates: 1%.
+
+Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 6. Low Impact Check
+
+- [ ] Enable for a bigger subset of users, when using percentage gates: 10%.
+
+Then leave running while monitoring for at least **30 minutes** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 7. Mid Impact Trial
+
+- [ ] Enable for a big subset of users, when using percentage gates: 50%.
+
+Then leave running while monitoring for at least **12 hours** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 8. Full Impact Trial
+
+- [ ] Enable for all users: `/chatops run feature set FEATURE_NAME true
+
+Then leave running while monitoring for at least **1 week**.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
+
+#### Success?
+
+- [ ] Remove the feature gate from the code, and close this issue with that MR.
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 831446cbd27a6de403344b21c9fa93a25357f43d..09b254e90c61ed28bb68a54752cf04f6a736a7d3 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-5.1.0
+6.0.0
diff --git a/Gemfile.lock b/Gemfile.lock
index 8bb287d266b7d6d9edfc83524da52a4a447d7eac..5a3ec46f8893aaa8bb2ce7c0c85aa974709f9861 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -772,7 +772,7 @@ GEM
     retriable (3.1.1)
     rinku (2.0.0)
     rotp (2.1.2)
-    rouge (3.2.0)
+    rouge (3.2.1)
     rqrcode (0.7.0)
       chunky_png
     rqrcode-rails3 (0.1.7)
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 3e610a4088ccfe4f49346802a062bd3f630c2994..bfc8d9b03ad01f9baa0d0af8badf70ee58541538 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -203,7 +203,7 @@ export default {
       this.showIssueForm = !this.showIssueForm;
     },
     onScroll() {
-      if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
+      if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
         this.loadNextPage();
       }
     },
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f9badb01535ca91c0f61ae353a0d5be0c9dab020..f55aa843444b7e41def9a0d89433016a52000890 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -133,7 +133,6 @@ export default {
         .then(() =>
           this.getRawFileData({
             path: this.file.path,
-            baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
           }),
         )
         .then(() => {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index c9795750d65f4b5926936dbd9d90760960b717f4..28b9d0df20142225f14834a40e3a00af9a2214d2 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -92,7 +92,7 @@ export const setFileMrChange = ({ commit }, { file, mrChange }) => {
   commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange });
 };
 
-export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => {
+export const getRawFileData = ({ state, commit, dispatch, getters }, { path }) => {
   const file = state.entries[path];
   return new Promise((resolve, reject) => {
     service
@@ -100,6 +100,9 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) =
       .then(raw => {
         if (!(file.tempFile && !file.prevPath)) commit(types.SET_FILE_RAW_DATA, { file, raw });
         if (file.mrChange && file.mrChange.new_file === false) {
+          const baseSha =
+            (getters.currentMergeRequest && getters.currentMergeRequest.baseCommitSha) || '';
+
           service
             .getBaseRawFileData(file, baseSha)
             .then(baseRaw => {
@@ -122,7 +125,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) =
           action: payload =>
             dispatch('getRawFileData', payload).then(() => dispatch('setErrorMessage', null)),
           actionText: __('Please try again'),
-          actionPayload: { path, baseSha },
+          actionPayload: { path },
         });
         reject();
       });
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 1eda576870991de83508847f42613a653d64da51..56a8d9430c701b07e65074d2f0746326b88c59af 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -200,6 +200,7 @@ export default {
   },
   [types.DELETE_ENTRY](state, path) {
     const entry = state.entries[path];
+    const { tempFile = false } = entry;
     const parent = entry.parentPath
       ? state.entries[entry.parentPath]
       : state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
@@ -209,7 +210,11 @@ export default {
     parent.tree = parent.tree.filter(f => f.path !== entry.path);
 
     if (entry.type === 'blob') {
-      state.changedFiles = state.changedFiles.concat(entry);
+      if (tempFile) {
+        state.changedFiles = state.changedFiles.filter(f => f.path !== path);
+      } else {
+        state.changedFiles = state.changedFiles.concat(entry);
+      }
     }
   },
   [types.RENAME_ENTRY](state, { path, name, entryPath = null }) {
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7f4852955139708bde7a26b83b6d5e7f1e5041ca
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -0,0 +1,64 @@
+<script>
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+export default {
+  components: {
+    ClipboardButton,
+  },
+  props: {
+    pipelineShortSha: {
+      type: String,
+      required: true,
+    },
+    pipelineShaPath: {
+      type: String,
+      required: true,
+    },
+    mergeRequestReference: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    mergeRequestPath: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    gitCommitTitlte: {
+      type: String,
+      required: true,
+    },
+  },
+};
+</script>
+<template>
+  <div class="block">
+    <p>
+      {{ __('Commit') }}
+
+      <a
+        :href="pipelineShaPath"
+        class="js-commit-sha commit-sha link-commit"
+      >
+        {{ pipelineShortSha }}
+      </a>
+
+      <clipboard-button
+        :text="pipelineShortSha"
+        :title="__('Copy commit SHA to clipboard')"
+      />
+
+      <a
+        v-if="mergeRequestPath && mergeRequestReference"
+        :href="mergeRequestPath"
+        class="js-link-commit link-commit"
+      >
+        {{ mergeRequestReference }}
+      </a>
+    </p>
+
+    <p class="build-light-text append-bottom-0">
+      {{ gitCommitTitlte }}
+    </p>
+  </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4faf08387fb4fa95c9fd0d67a035f9db917a2102
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -0,0 +1,76 @@
+<script>
+  export default {
+    props: {
+      illustrationPath: {
+        type: String,
+        required: true,
+      },
+      illustrationSizeClass: {
+        type: String,
+        required: true,
+      },
+      title: {
+        type: String,
+        required: true,
+      },
+      content: {
+        type: String,
+        required: false,
+        default: null,
+      },
+      action: {
+        type: Object,
+        required: false,
+        default: null,
+        validator(value) {
+          return (
+            value === null ||
+            (Object.prototype.hasOwnProperty.call(value, 'link') &&
+              Object.prototype.hasOwnProperty.call(value, 'method') &&
+              Object.prototype.hasOwnProperty.call(value, 'title'))
+          );
+        },
+      },
+    },
+  };
+</script>
+<template>
+  <div class="row empty-state">
+    <div class="col-12">
+      <div
+        :class="illustrationSizeClass"
+        class="svg-content"
+      >
+        <img :src="illustrationPath" />
+      </div>
+    </div>
+
+    <div class="col-12">
+      <div class="text-content">
+        <h4 class="js-job-empty-state-title text-center">
+          {{ title }}
+        </h4>
+
+        <p
+          v-if="content"
+          class="js-job-empty-state-content"
+        >
+          {{ content }}
+        </p>
+
+        <div
+          v-if="action"
+          class="text-center"
+        >
+          <a
+            :href="action.link"
+            :data-method="action.method"
+            class="js-job-empty-state-action btn btn-primary"
+          >
+            {{ action.title }}
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
new file mode 100644
index 0000000000000000000000000000000000000000..513851e376f999de7c7aa188f82abdf0b5329a39
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -0,0 +1,139 @@
+<script>
+  import Icon from '~/vue_shared/components/icon.vue';
+  import tooltip from '~/vue_shared/directives/tooltip';
+  import { numberToHumanSize } from '~/lib/utils/number_utils';
+  import { s__, sprintf } from '~/locale';
+
+  export default {
+    components: {
+      Icon,
+    },
+    directives: {
+      tooltip,
+    },
+    props: {
+      canEraseJob: {
+        type: Boolean,
+        required: true,
+      },
+      size: {
+        type: Number,
+        required: true,
+      },
+      rawTracePath: {
+        type: String,
+        required: false,
+        default: null,
+      },
+      canScrollToTop: {
+        type: Boolean,
+        required: true,
+      },
+      canScrollToBottom: {
+        type: Boolean,
+        required: true,
+      },
+    },
+    computed: {
+      jobLogSize() {
+        return sprintf('Showing last %{startSpanTag} %{size} %{endSpanTag} of log -', {
+          startSpanTag: '<span class="s-truncated-info-size truncated-info-size">',
+          endSpanTag: '</span>',
+          size: numberToHumanSize(this.size),
+        });
+      },
+    },
+    methods: {
+      handleEraseJobClick() {
+        // eslint-disable-next-line no-alert
+        if (window.confirm(s__('Job|Are you sure you want to erase this job?'))) {
+          this.$emit('eraseJob');
+        }
+      },
+      handleScrollToTop() {
+        this.$emit('scrollJobLogTop');
+      },
+      handleScrollToBottom() {
+        this.$emit('scrollJobLogBottom');
+      },
+    },
+  };
+</script>
+<template>
+  <div class="top-bar">
+    <!-- truncate information -->
+    <div class="js-truncated-info truncated-info d-none d-sm-block float-left">
+      <p v-html="jobLogSize"></p>
+
+      <a
+        v-if="rawTracePath"
+        :href="rawTracePath"
+        class="js-raw-link raw-link"
+      >
+        {{ s__("Job|Complete Raw") }}
+      </a>
+    </div>
+    <!-- eo truncate information -->
+
+    <div class="controllers float-right">
+      <!-- links -->
+      <a
+        v-tooltip
+        v-if="rawTracePath"
+        :title="s__('Job|Show complete raw')"
+        :href="rawTracePath"
+        class="js-raw-link-controller controllers-buttons"
+        data-container="body"
+      >
+        <icon name="doc-text" />
+      </a>
+
+      <button
+        v-tooltip
+        v-if="canEraseJob"
+        :title="s__('Job|Erase job log')"
+        type="button"
+        class="js-erase-link controllers-buttons"
+        data-container="body"
+        @click="handleEraseJobClick"
+      >
+        <icon name="remove" />
+      </button>
+      <!-- eo links -->
+
+      <!-- scroll buttons -->
+      <div
+        v-tooltip
+        :title="s__('Job|Scroll to top')"
+        class="controllers-buttons"
+        data-container="body"
+      >
+        <button
+          :disabled="!canScrollToTop"
+          type="button"
+          class="js-scroll-top btn-scroll btn-transparent btn-blank"
+          @click="handleScrollToTop"
+        >
+          <icon name="scroll_up"/>
+        </button>
+      </div>
+
+      <div
+        v-tooltip
+        :title="s__('Job|Scroll to bottom')"
+        class="controllers-buttons"
+        data-container="body"
+      >
+        <button
+          :disabled="!canScrollToBottom"
+          type="button"
+          class="js-scroll-bottom btn-scroll btn-transparent btn-blank"
+          @click="handleScrollToBottom"
+        >
+          <icon name="scroll_down"/>
+        </button>
+      </div>
+      <!-- eo scroll buttons -->
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8a88e5da6aa6a2c9c3396342235389863c0f6a48
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -0,0 +1,84 @@
+<script>
+  export default {
+    props: {
+      shortToken: {
+        type: String,
+        required: false,
+        default: null,
+      },
+
+      variables: {
+        type: Object,
+        required: false,
+        default: () => ({}),
+      },
+    },
+    data() {
+      return {
+        areVariablesVisible: false,
+      };
+    },
+    computed: {
+      hasVariables() {
+        return Object.keys(this.variables).length > 0;
+      },
+    },
+    methods: {
+      revealVariables() {
+        this.areVariablesVisible = true;
+      },
+    },
+  };
+</script>
+
+<template>
+  <div class="build-widget block">
+    <h4 class="title">
+      {{ __('Trigger') }}
+    </h4>
+
+    <p
+      v-if="shortToken"
+      class="js-short-token"
+    >
+      <span class="build-light-text">
+        {{ __('Token') }}
+      </span>
+      {{ shortToken }}
+    </p>
+
+    <p v-if="hasVariables">
+      <button
+        type="button"
+        class="btn btn-default group js-reveal-variables"
+        @click="revealVariables"
+      >
+        {{ __('Reveal Variables') }}
+      </button>
+
+    </p>
+
+    <dl
+      v-if="areVariablesVisible"
+      class="js-build-variables trigger-build-variables"
+    >
+      <template
+        v-for="(value, key) in variables"
+      >
+        <dt
+          :key="`${key}-variable`"
+          class="js-build-variable trigger-build-variable"
+        >
+          {{ key }}
+        </dt>
+
+        <dd
+          :key="`${key}-value`"
+          class="js-build-value trigger-build-value"
+        >
+          {{ value }}
+        </dd>
+      </template>
+    </dl>
+  </div>
+</template>
diff --git a/app/assets/javascripts/pages/admin/cohorts/index.js b/app/assets/javascripts/pages/instance_statistics/cohorts/index.js
similarity index 100%
rename from app/assets/javascripts/pages/admin/cohorts/index.js
rename to app/assets/javascripts/pages/instance_statistics/cohorts/index.js
diff --git a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js b/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js
similarity index 100%
rename from app/assets/javascripts/pages/admin/cohorts/usage_ping.js
rename to app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js
diff --git a/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js
similarity index 100%
rename from app/assets/javascripts/pages/admin/conversational_development_index/show/index.js
rename to app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index af91497d0eacf0d405fa0fe5fd45fbed7e7b7dea..eac1345742d3310ce4b4f11ee2d0125a3dc1cd0c 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1293,6 +1293,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
 
   &.build-page .top-bar {
     top: 0;
+    height: auto;
     font-size: 12px;
     border-top-right-radius: $border-radius-default;
   }
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 8d28daac75003a35fc95e85c4d2b3f2540181d42..2e1b21268876a6ff0191717238d72b189972be97 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -141,9 +141,6 @@ ul.notes {
     }
 
     .note-body {
-      overflow-x: auto;
-      overflow-y: hidden;
-
       .note-text {
         @include md-typography;
         // Reset ul style types since we're nested inside a ul already
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index fe5f68ba3d502dc97ca2f402bec786d352d90505..e032f568913f185047013e9e24ed2b46a837ed89 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class AbuseReportMailer < BaseMailer
   def notify(abuse_report_id)
     return unless deliverable?
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 654468bc7fe478d014dc9d63149a3b2dd026e895..5fd209c4761d2dd38506cc714d48628c2609365d 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class BaseMailer < ActionMailer::Base
   around_action :render_with_default_locale
 
diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb
index 962570a0efd1ec211d4f4c3348fbb6823570c0b1..7aa75ee30e625ed16ecd6cbeab9d3f84a664df3e 100644
--- a/app/mailers/devise_mailer.rb
+++ b/app/mailers/devise_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class DeviseMailer < Devise::Mailer
   default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
   default reply_to: Gitlab.config.gitlab.email_reply_to
@@ -9,8 +11,9 @@ class DeviseMailer < Devise::Mailer
   protected
 
   def subject_for(key)
-    subject = super
-    subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
-    subject
+    subject = [super]
+    subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+    subject.join(' | ')
   end
 end
diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 76db31a4c4546e1f75c8739afd49f91e4b2b92c0..45fc5a6c3837ce3e6dddf3b2354842744f9c2b6c 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class EmailRejectionMailer < BaseMailer
   def rejection(reason, original_raw, can_retry = false)
     @reason = reason
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 392cc0bee03dcf8fc69595977d55ef2ce83dabd0..c8b1ab5033a99df5f7dd8be52ddc35577b7a3e84 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module Issues
     def new_issue_email(recipient_id, issue_id, reason = nil)
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 75cf56a51f239d51f53523cc9609ca88257a24f7..91dfdf589824a71981f0f22e91ece7674375e44e 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module Members
     extend ActiveSupport::Concern
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index eaa0fbeffaa05ff359c7fa563497251a4447bf50..ced867f54979e6dc8784d0aa0aea9304bf8ad588 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module MergeRequests
     def new_merge_request_email(recipient_id, merge_request_id, reason = nil)
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 95a3bc8c56d08f603124948bc27601ac1af86069..a11b6b41e612fbdd694fc381819f3b599f2f3d2b 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module Notes
     prepend Emails::EE::Notes
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index 0027dfdc36b1d9bc4b440e15f13b19d0f22f47bc..ce449237ef642b97675b3c0add995fb369a78e26 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module PagesDomains
     def pages_domain_enabled_email(domain, recipient)
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index f9f45ab987b39c08429f59eed603ec752e6521d2..31e183640adc4022db38ae350fe341534d992fc8 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module Pipelines
     def pipeline_success_email(pipeline, recipients)
@@ -39,10 +41,10 @@ def add_pipeline_headers
     end
 
     def pipeline_subject(status)
-      commit = @pipeline.short_sha
-      commit << " in #{@merge_request.to_reference}" if @merge_request
+      commit = [@pipeline.short_sha]
+      commit << "in #{@merge_request.to_reference}" if @merge_request
 
-      subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit)
+      subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit.join(' '))
     end
   end
 end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 4f5edeb9bda172872def7fd8a6d7396ff77a5e62..40d7b9ccd7a83c01ae2733f1b4c5c72ca4182cfc 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module Profile
     def new_user_email(user_id, token = nil)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 1a3913bc29940174034f057df69bca2c157f7ed0..f079694ccc125f5197769689e5f4d73821336f22 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Emails
   module Projects
     prepend Emails::EE::Projects
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 33da2d97ef4ce9fbcf85ca6cf5b2f97ddec8ee73..859c6c41dd34f85861f3d78506098afcf4410589 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class Notify < BaseMailer
   prepend ::EE::Notify
 
@@ -94,12 +96,14 @@ def recipient(recipient_id)
   #   >> subject('Lorem ipsum', 'Dolor sit amet')
   #   => "Lorem ipsum | Dolor sit amet"
   def subject(*extra)
-    subject = ""
-    subject << "#{@project.name} | " if @project
-    subject << "#{@group.name} | " if @group
-    subject << extra.join(' | ') if extra.present?
-    subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
-    subject
+    subject = []
+
+    subject << @project.name if @project
+    subject << @group.name if @group
+    subject.concat(extra) if extra.present?
+    subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+    subject.join(' | ')
   end
 
   # Return a string suitable for inclusion in the 'Message-Id' mail header.
diff --git a/app/mailers/previews/devise_mailer_preview.rb b/app/mailers/previews/devise_mailer_preview.rb
index d6588efc4860ab2c9675da45f06b69eb29f08a68..3b9ef0d3ac04fb833af2f3aac5e79f7cdbcd04ad 100644
--- a/app/mailers/previews/devise_mailer_preview.rb
+++ b/app/mailers/previews/devise_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class DeviseMailerPreview < ActionMailer::Preview
   def confirmation_instructions_for_signup
     DeviseMailer.confirmation_instructions(unsaved_user, 'faketoken', {})
diff --git a/app/mailers/previews/email_rejection_mailer_preview.rb b/app/mailers/previews/email_rejection_mailer_preview.rb
index 639e8471232f0704e5ff6fff25ab2b9ec3b00a3d..402066151ef074fc793aa2fa3f9af8fd0eac2dd1 100644
--- a/app/mailers/previews/email_rejection_mailer_preview.rb
+++ b/app/mailers/previews/email_rejection_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class EmailRejectionMailerPreview < ActionMailer::Preview
   def rejection
     EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index c55e40142410340282d9f380fb54030e32ab5faa..a29bf72d6e38f0ce15a9c7624243d01750e18747 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class NotifyPreview < ActionMailer::Preview
   prepend EE::Preview::NotifyPreview
 
diff --git a/app/mailers/previews/repository_check_mailer_preview.rb b/app/mailers/previews/repository_check_mailer_preview.rb
index 19d4eab18054dc3cddf62a2ac1d5fde7abbdd9b1..834d7594719545504102e7bb4b88ddd3b59795e8 100644
--- a/app/mailers/previews/repository_check_mailer_preview.rb
+++ b/app/mailers/previews/repository_check_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class RepositoryCheckMailerPreview < ActionMailer::Preview
   def notify
     RepositoryCheckMailer.notify(3).message
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 22a9f5da646fe3ec64c9929b1bae41f52c6121a2..4bcf371cfc039404978efe836f9d14ff19e6b5fd 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class RepositoryCheckMailer < BaseMailer
   def notify(failed_count)
     @message =
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6f7390c398872e26c93377fa93659fadbea2205a..ee825e78776055d4a840ce65f2d944144e10a422 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -71,6 +71,10 @@ def persisted_environment
         '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
     end
 
+    scope :with_archived_trace, ->() do
+      where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
+    end
+
     scope :without_archived_trace, ->() do
       where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
     end
@@ -81,6 +85,7 @@ def persisted_environment
     end
 
     scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
+    scope :with_archived_trace_stored_locally, -> { with_archived_trace.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) }
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 7d34b52f2007a75a77202aa0d3cc7dc7c9718337..edfc1630ecc20765ad24ab78efad88f0082a6682 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -11,6 +11,7 @@ class Namespace < ActiveRecord::Base
   include Storage::LegacyNamespace
   include Gitlab::SQL::Pattern
   include IgnorableColumn
+  include FeatureGate
 
   ignore_column :deleted_at
 
@@ -125,7 +126,6 @@ def visibility_level_field
   def to_param
     full_path
   end
-  alias_method :flipper_id, :to_param
 
   def human_name
     owner_name
diff --git a/app/models/project.rb b/app/models/project.rb
index bfc0a50646c3dc3a0b634d710e7aa70faf5325de..f8c336ae835879a8fb98fed98927a69c4c91dab8 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -27,6 +27,7 @@ class Project < ActiveRecord::Base
   include FastDestroyAll::Helpers
   include WithUploads
   include BatchDestroyDependentAssociations
+  include FeatureGate
   extend Gitlab::Cache::RequestCache
 
   # EE specific modules
@@ -534,18 +535,19 @@ def lfs_enabled?
 
   def auto_devops_enabled?
     if auto_devops&.enabled.nil?
-      Gitlab::CurrentSettings.auto_devops_enabled?
+      has_auto_devops_implicitly_enabled?
     else
       auto_devops.enabled?
     end
   end
 
   def has_auto_devops_implicitly_enabled?
-    auto_devops&.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?
+    auto_devops&.enabled.nil? &&
+      (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
   end
 
   def has_auto_devops_implicitly_disabled?
-    auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled?
+    auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
   end
 
   def empty_repo?
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 155400d1a43d0047c9c90e6376ed11fc08c17d07..dc6736dd9cdc97fde32a8e5a667f8b394dabfa24 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -47,12 +47,8 @@ def create_gitlab_deploy_token
   end
 
   def needs_to_create_deploy_token?
-    auto_devops_enabled? &&
+    project.auto_devops_enabled? &&
       !project.public? &&
       !project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present?
   end
-
-  def auto_devops_enabled?
-    Gitlab::CurrentSettings.auto_devops_enabled? || enabled?
-  end
 end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 228c1abae76fccdb72fe62d06abfe13fe4b33f8e..a836d9c7e33ed4d773d9f2a3fa02a847a0335288 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -32,7 +32,7 @@ def execute
     def run_auto_devops_pipeline?
       return false if project.repository.gitlab_ci_yml || !project.auto_devops&.previous_changes&.include?('enabled')
 
-      project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?)
+      project.auto_devops_enabled?
     end
 
     private
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 4507218d023eee2942ea9bed58c7992986546dd0..274a1fcb5c3c2e59a0a95ef9a2074d8208a6c212 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -41,6 +41,8 @@
       .form-text.text-muted
         Set the default expiration time for each job's artifacts.
         0 for unlimited.
+        The default unit is in seconds, but you can define an alternative. For example:
+        <code>4 mins 2 sec</code>, <code>2h42min</code>.
         = 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/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 249ada761dc3b016dce525c128de86b3b1b89caf..3e6ec90d9d87adfd02914f32b5fcbd592c3e2f2c 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -1,15 +1,18 @@
 %fieldset
   %legend Access
   .form-group.row
-    = f.label :projects_limit, class: 'col-form-label col-sm-2'
+    .col-sm-2.text-right
+      = f.label :projects_limit, class: 'col-form-label'
     .col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
 
   .form-group.row
-    = f.label :can_create_group, class: 'col-form-label col-sm-2'
+    .col-sm-2.text-right
+      = f.label :can_create_group, class: 'col-form-label'
     .col-sm-10= f.check_box :can_create_group
 
   .form-group.row
-    = f.label :access_level, class: 'col-form-label col-sm-2'
+    .col-sm-2.text-right
+      = f.label :access_level, class: 'col-form-label'
     .col-sm-10
       - editing_current_user = (current_user == @user)
 
@@ -36,7 +39,8 @@
           You cannot remove your own admin rights.
 
   .form-group.row
-    = f.label :external, class: 'col-form-label col-sm-2'
+    .col-sm-2.text-right
+      = f.label :external, class: 'col-form-label'
     .col-sm-10
       = f.check_box :external do
         External
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 9222e414f8c6e034aed65c26bc2b5eca54c63c42..ca1d6c1863d6a7156ff430cb5aa0a2c21c9e3f70 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -5,17 +5,20 @@
     %fieldset
       %legend Account
       .form-group.row
-        = f.label :name, class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :name, class: 'col-form-label'
         .col-sm-10
           = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
           %span.help-inline * required
       .form-group.row
-        = f.label :username, class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :username, class: 'col-form-label'
         .col-sm-10
           = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
           %span.help-inline * required
       .form-group.row
-        = f.label :email, class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :email, class: 'col-form-label'
         .col-sm-10
           = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
           %span.help-inline * required
@@ -24,7 +27,8 @@
       %fieldset
         %legend Password
         .form-group.row
-          = f.label :password, class: 'col-form-label col-sm-2'
+          .col-sm-2.text-right
+            = f.label :password, class: 'col-form-label'
           .col-sm-10
             %strong
               Reset link will be generated and sent to the user.
@@ -34,10 +38,12 @@
       %fieldset
         %legend Password
         .form-group.row
-          = f.label :password, class: 'col-form-label col-sm-2'
+          .col-sm-2.text-right
+            = f.label :password, class: 'col-form-label'
           .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
         .form-group.row
-          = f.label :password_confirmation, class: 'col-form-label col-sm-2'
+          .col-sm-2.text-right
+            = f.label :password_confirmation, class: 'col-form-label'
           .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
 
     = render partial: 'access_levels', locals: { f: f }
@@ -53,21 +59,26 @@
     %fieldset
       %legend Profile
       .form-group.row
-        = f.label :avatar, class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :avatar, class: 'col-form-label'
         .col-sm-10
           = f.file_field :avatar
 
       .form-group.row
-        = f.label :skype, class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :skype, class: 'col-form-label'
         .col-sm-10= f.text_field :skype, class: 'form-control'
       .form-group.row
-        = f.label :linkedin, class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :linkedin, class: 'col-form-label'
         .col-sm-10= f.text_field :linkedin, class: 'form-control'
       .form-group.row
-        = f.label :twitter, class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :twitter, class: 'col-form-label'
         .col-sm-10= f.text_field :twitter, class: 'form-control'
       .form-group.row
-        = f.label :website_url, 'Website', class: 'col-form-label col-sm-2'
+        .col-sm-2.text-right
+          = f.label :website_url, 'Website', class: 'col-form-label'
         .col-sm-10= f.text_field :website_url, class: 'form-control'
 
     %fieldset
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 434aed2f603259e4e46de98cc5aec5b41b31b452..9134257b6317f2f500780131197694aa12ea5171 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -17,7 +17,7 @@
           %h5.prepend-top-0
             = _("Git strategy for pipelines")
           %p
-            = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code")
+            = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code").html_safe
             = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank'
           .form-check
             = f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' }
@@ -47,7 +47,7 @@
           = f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold'
           = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
           %p.form-text.text-muted
-            = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>")
+            = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>").html_safe
             = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
 
         %hr
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 8a5abb64515941869d9dab6156f35bed4fd7077e..df8a574245015312c25743485377c9dae7dda481 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -21,8 +21,7 @@
   %nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
     = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
     = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
-    - if Feature.enabled?(:repository_languages, @project.namespace.becomes(Namespace))
-      = repository_languages_bar(@project.repository_languages)
+    = repository_languages_bar(@project.repository_languages)
 
 %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
   - if @project.archived?
diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb
index cfbbbffc8a6ca3fe24d355f4fe6f52dc9aec190c..854b74b884a46616d2c3044efc8a6bfe0253f952 100644
--- a/app/workers/detect_repository_languages_worker.rb
+++ b/app/workers/detect_repository_languages_worker.rb
@@ -16,8 +16,6 @@ def perform(project_id, user_id)
     user = User.find_by(id: user_id)
     return unless project && user
 
-    return if Feature.disabled?(:repository_languages, project.namespace)
-
     try_obtain_lease do
       ::Projects::DetectRepositoryLanguagesService.new(project, user).execute
     end
diff --git a/changelogs/unreleased/41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml b/changelogs/unreleased/41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc0150c6586e5b25659d2d03c6854cdde3179c22
--- /dev/null
+++ b/changelogs/unreleased/41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: "Fix If-Check the result that a function was executed several times"
+merge_request: 20640
+author: Max Dicker
+type: fixed
diff --git a/changelogs/unreleased/49770-fixes-input-alignment-on-user-admin-form-with-errors.yml b/changelogs/unreleased/49770-fixes-input-alignment-on-user-admin-form-with-errors.yml
new file mode 100644
index 0000000000000000000000000000000000000000..00e1f6e638a3bc2c2d1bb5709490426ca2d93637
--- /dev/null
+++ b/changelogs/unreleased/49770-fixes-input-alignment-on-user-admin-form-with-errors.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes input alignment in user admin form with errors
+merge_request: 21108
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/50101-commit-block.yml b/changelogs/unreleased/50101-commit-block.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f6bad4c8154e3e13a322dcc3d99b63ace606f3ff
--- /dev/null
+++ b/changelogs/unreleased/50101-commit-block.yml
@@ -0,0 +1,5 @@
+---
+title: Creates vue component for commit block in job log page
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50101-empty-state-component.yml b/changelogs/unreleased/50101-empty-state-component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ee99b65d9642ec6e8e69c17d05031f345cdbef13
--- /dev/null
+++ b/changelogs/unreleased/50101-empty-state-component.yml
@@ -0,0 +1,5 @@
+---
+title: Creates empty state vue component for job view
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50101-trigger.yml b/changelogs/unreleased/50101-trigger.yml
new file mode 100644
index 0000000000000000000000000000000000000000..df4243afa636fb347108f3e9486975f7e32acfa8
--- /dev/null
+++ b/changelogs/unreleased/50101-trigger.yml
@@ -0,0 +1,5 @@
+---
+title: Creates Vue component for trigger variables block in job log page
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50101-truncated-job-information.yml b/changelogs/unreleased/50101-truncated-job-information.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b873b8b7bf694d4e0f498a6d44310aaa56034b37
--- /dev/null
+++ b/changelogs/unreleased/50101-truncated-job-information.yml
@@ -0,0 +1,5 @@
+---
+title: Creates vue component for job log top bar with controllers
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50312-instance-statistics-convdev-index-intro-banner-is-not-dismissable.yml b/changelogs/unreleased/50312-instance-statistics-convdev-index-intro-banner-is-not-dismissable.yml
new file mode 100644
index 0000000000000000000000000000000000000000..50a3b9c9affdc1abf4699a9a58d572b79271a061
--- /dev/null
+++ b/changelogs/unreleased/50312-instance-statistics-convdev-index-intro-banner-is-not-dismissable.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issue stopping Instance Statistics javascript to be executed
+merge_request: 21211
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml b/changelogs/unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d963dc5bac34c78be651d176910cee47531b3819
--- /dev/null
+++ b/changelogs/unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml
@@ -0,0 +1,5 @@
+---
+title: Add an example of the configuration of archive trace cron worker in gitlab.yml.example
+merge_request: 20583
+author:
+type: other
diff --git a/changelogs/unreleased/add-rake-command-to-migrate-locally-persisted-archived-traces.yml b/changelogs/unreleased/add-rake-command-to-migrate-locally-persisted-archived-traces.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b82344e3c9c7f990aa460f4332189b8a7476f6cb
--- /dev/null
+++ b/changelogs/unreleased/add-rake-command-to-migrate-locally-persisted-archived-traces.yml
@@ -0,0 +1,5 @@
+---
+title: Add rake command to migrate archived traces from local storage to object storage
+merge_request: 21193
+author:
+type: added
diff --git a/changelogs/unreleased/emoji-cutoff-1px.yml b/changelogs/unreleased/emoji-cutoff-1px.yml
new file mode 100644
index 0000000000000000000000000000000000000000..815d9c177e843c4ff035231696c7a009ff9bbfa4
--- /dev/null
+++ b/changelogs/unreleased/emoji-cutoff-1px.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 1px cutoff of emojis
+merge_request: 21180
+author: gfyoung
+type: fixed
diff --git a/changelogs/unreleased/frozen-string-enable-app-mailers.yml b/changelogs/unreleased/frozen-string-enable-app-mailers.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2cd247ca76cdd251384870a00e2d0e5225032d13
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-enable-app-mailers.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen in app/mailers/**/*.rb
+merge_request: 21147
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/ide-delete-new-files-state.yml b/changelogs/unreleased/ide-delete-new-files-state.yml
new file mode 100644
index 0000000000000000000000000000000000000000..500115d19d08d8b2b29c9afa2e30596d83fb7af4
--- /dev/null
+++ b/changelogs/unreleased/ide-delete-new-files-state.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed IDE deleting new files creating wrong state
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-job-top-bar-ui-polish.yml b/changelogs/unreleased/ide-job-top-bar-ui-polish.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d917c14e5f8b079aed4fdce73d9e3d4339289e81
--- /dev/null
+++ b/changelogs/unreleased/ide-job-top-bar-ui-polish.yml
@@ -0,0 +1,5 @@
+---
+title: Improved styling of top bar in IDE job trace pane
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml b/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bcf3d2c8e16bd6949f3080b9184ce850ec5dc023
--- /dev/null
+++ b/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml
@@ -0,0 +1,6 @@
+---
+title: Combines emoji award spec files into single user_interacts_with_awards_in_issue_spec.rb
+  file
+merge_request: 21126
+author: Nate Geslin
+type: other
diff --git a/changelogs/unreleased/rails5-verbose-query-logs.yml b/changelogs/unreleased/rails5-verbose-query-logs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7585e75d30b0f6a3982e995145c719ca9eaca646
--- /dev/null
+++ b/changelogs/unreleased/rails5-verbose-query-logs.yml
@@ -0,0 +1,5 @@
+---
+title: 'Rails5: Enable verbose query logs'
+merge_request: 21231
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/rouge_3-2-1.yml b/changelogs/unreleased/rouge_3-2-1.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b281a4f0e95bf8692753d64138707cbc2219052b
--- /dev/null
+++ b/changelogs/unreleased/rouge_3-2-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update to Rouge 3.2.1, which includes a critical fix to the Perl Lexer
+merge_request: 21263
+author:
+type: changed
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index cc0a07600bc5de3c4530a25135c6157b14df283a..e05954d9259590807b92eead1b7c30ca8f54b710 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -281,6 +281,9 @@ production: &base
     # once per hour you will have concurrent 'git fsck' jobs.
     repository_check_worker:
       cron: "20 * * * *"
+    # Archive live traces which have not been archived yet
+    ci_archive_traces_cron_worker:
+      cron: "17 * * * *"
     # Send admin emails once a week
     admin_email_worker:
       cron: "0 0 * * 0"
diff --git a/config/initializers/active_record_verbose_query_logs.rb b/config/initializers/active_record_verbose_query_logs.rb
index 44f86fec7e09389320ddb7eae04643a60ff51ac1..1c5fbc8e830240986e5121622144ffd1a2b3f438 100644
--- a/config/initializers/active_record_verbose_query_logs.rb
+++ b/config/initializers/active_record_verbose_query_logs.rb
@@ -47,7 +47,9 @@ def ignored_callstack(path)
       end
     end
 
-    unless Gitlab.rails5?
+    if Rails.version.start_with?("5.2")
+      raise "Remove this monkey patch: #{__FILE__}"
+    else
       prepend(VerboseQueryLogs) unless Rails.env.production?
     end
   end
diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md
index 24d1a3fd1518b926dbbd0a58288d6d2bb7adcfc0..6e2f67f61bc4c5f8cd26c06e5af335d85aa373cc 100644
--- a/doc/administration/job_traces.md
+++ b/doc/administration/job_traces.md
@@ -3,10 +3,6 @@
 Job traces are sent by GitLab Runner while it's processing a job. You can see
 traces in job pages, pipelines, email notifications, etc.
 
-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.
-
 ## Data flow
 
 In general, there are two states in job traces: "live trace" and "archived trace".
@@ -57,11 +53,55 @@ To change the location where the job logs will be stored, follow the steps below
 
 ## Uploading traces to object storage
 
-An archived trace is considered as a [job artifact](job_artifacts.md).
-Therefore, when you [set up an object storage](job_artifacts.md#object-storage-settings),
+Archived traces are considered as [job artifacts](job_artifacts.md).
+Therefore, when you [set up the object storage integration](job_artifacts.md#object-storage-settings),
 job traces are automatically migrated to it along with the other job artifacts.
 
-See [Data flow](#data-flow) to learn about the process.
+See "Phase 4: uploading" in [Data flow](#data-flow) to learn about the process.
+
+## How to archive legacy job trace files
+
+Legacy job traces, which were created before GitLab 10.5, were not archived regularly.
+It's the same state with the "2: overwriting" in the above [Data flow](#data-flow).
+To archive those legacy job traces, please follow the instruction below.
+
+1. Execute the following command
+
+      ```bash
+      gitlab-rake gitlab:traces:archive
+      ```
+
+      After you executed this task, GitLab instance queues up Sidekiq jobs (asynchronous processes)
+      for migrating job trace files from local storage to object storage. 
+      It could take time to complete the all migration jobs. You can check the progress by the following command
+
+      ```bash
+      sudo gitlab-rails console
+      ```
+
+      ```bash
+      [1] pry(main)> Sidekiq::Stats.new.queues['pipeline_background:archive_trace']
+      => 100
+      ```
+
+      If the count becomes zero, the archiving processes are done
+
+## How to migrate archived job traces to object storage
+
+If job traces have already been archived into local storage, and you want to migrate those traces to object storage, please follow the instruction below.
+
+1. Ensure [Object storage integration for Job Artifacts](job_artifacts.md#object-storage-settings) is enabled
+1. Execute the following command
+
+      ```bash
+      gitlab-rake gitlab:traces:migrate
+      ```
+
+## How to remove job traces
+
+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.
 
 ## New live trace architecture
 
diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md
index b0e01d74f7ea9afcda98d826da5b0f2e8d52a8c6..8ae80b2bc02fb4561da5ffaf65ca1963aaaf8a00 100644
--- a/doc/ci/docker/README.md
+++ b/doc/ci/docker/README.md
@@ -6,3 +6,4 @@ comments: false
 
 - [Using Docker Images](using_docker_images.md)
 - [Using Docker Build](using_docker_build.md)
+- [Using kaniko](using_kaniko.md)
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
new file mode 100644
index 0000000000000000000000000000000000000000..7d4f28e1f47ea5eef2af590f22f6d4c3b8aeb1d0
--- /dev/null
+++ b/doc/ci/docker/using_kaniko.md
@@ -0,0 +1,60 @@
+# Building images with kaniko and GitLab CI/CD
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45512) in GitLab 11.2.
+Requires GitLab Runner 11.2 and above.
+
+[kaniko](https://github.com/GoogleContainerTools/kaniko) is a tool to build
+container images from a Dockerfile, inside a container or Kubernetes cluster.
+
+kaniko solves two problems with using the
+[docker-in-docker build](using_docker_build.md#use-docker-in-docker-executor) method:
+
+1. Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
+   in order to function, which is a significant security concern.
+1. Docker-in-docker generally incurs a performance penalty and can be quite slow.
+
+## Requirements
+
+In order to utilize kaniko with GitLab, a [GitLab Runner](https://docs.gitlab.com/runner/)
+using either the [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html),
+[Docker](https://docs.gitlab.com/runner/executors/docker.html), or
+[Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html)
+executors is required.
+
+## Building a Docker image with kaniko
+
+When building an image with kaniko and GitLab CI/CD, you should be aware of a
+few important details:
+
+- The kaniko debug image is recommended (`gcr.io/kaniko-project/executor:debug`)
+  because it has a shell, and a shell is required for an image to be used with
+  GitLab CI/CD.
+- The entrypoint will need to be [overridden](using_docker_images.md#overriding-the-entrypoint-of-an-image),
+  otherwise the build script will not run.
+- A Docker `config.json` file needs to be created with the authentication
+  information for the desired container registry.
+
+---
+
+In the following example, kaniko is used to build a Docker image and then push
+it to [GitLab Container Registry](../../user/project/container_registry.md).
+The job will run only when a tag is pushed. A `config.json` file is created under
+`/root/.docker` with the needed GitLab Container Registry credentials taken from the
+[environment variables](../variables/README.md#predefined-variables-environment-variables)
+GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the
+root directory of the project, builds the Docker image and pushes it to the
+project's Container Registry while tagging it with the Git tag:
+
+```yaml
+build:
+  stage: build
+  image:
+    name: gcr.io/kaniko-project/executor:debug
+    entrypoint: [""]
+  script:
+    - mkdir -p /root/.docker
+    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json
+    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
+  only:
+    - tags
+```
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index b4854e5dd949a588dbfe67ec1e00a7d14dfe6a78..d9b2a4fc9d227f98d4110327bc1511805673fe9e 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1075,8 +1075,10 @@ keep artifacts forever.
 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 parsable values:
+The value of `expire_in` is an elapsed time in seconds, unless a unit is
+provided. Examples of parsable values:
 
+- '42'
 - '3 mins 4 sec'
 - '2 hrs 20 min'
 - '2h20min'
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 5d1f657015c9e971e3cf6e36ff08e2b33e078d03..09ea8c05be6708e26217bbb988c05ab99af12699 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -20,7 +20,40 @@ dynamic (querying the DB etc.).
 Once defined in `lib/feature.rb`, you will be able to activate a
 feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
 
+For GitLab.com, team members have access to feature flags through chatops. Only
+percentage gates are supported at this time. Setting a feature to be used 50% of
+the time, you should execute `/chatops run feature set my_feature_flag 50`.
+
 ## Feature flags for user applications
 
 GitLab does not yet support the use of feature flags in deployed user applications.
-You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779).
\ No newline at end of file
+You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779).
+
+## Developing with feature flags
+
+In general, it's better to have a group- or user-based gate, and you should prefer
+it over the use of percentage gates. This would make debugging easier, as you
+filter for example logs and errors based on actors too. Futhermore, this allows
+for enabling for the `gitlab-org` group first, while the rest of the users
+aren't impacted.
+
+```ruby
+# Good
+Feature.enabled?(:feature_flag, project)
+
+# Avoid, if possible
+Feature.enabled?(:feature_flag)
+```
+
+To use feature gates based on actors, the model needs to respond to
+`flipper_id`. For example, to enable for the Foo model:
+
+```ruby
+class Foo < ActiveRecord::Base
+  include FeatureGate
+end
+```
+
+Features that are developed and are intended to be merged behind a feature flag
+should not include a changelog entry. The entry should be added in the merge
+request removing the feature flags.
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 3467234a5fbee3602e03968ff4cf8d6700c9d338..7e52ada50730d8c980fafe97d9cdcf0b0a5d22cf 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -233,6 +233,11 @@ in **Admin Area > Settings > Continuous Integration and Deployment**. Doing that
 all the projects that haven't explicitly set an option will have Auto DevOps
 enabled by default.
 
+NOTE: **Note:**
+There is also a feature flag to enable Auto DevOps to a percentage of projects
+which can be enabled from the console with
+`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`.
+
 ### Deployment strategy
 
 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38542) in GitLab 11.0.
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 6fdcb244c9074ec3315e96b592778ca8518b403d..83db8ce585c365ea8a89d81a146d166ae957a9c2 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -21,8 +21,9 @@ that this setting is set for each job.
 The default expiration time of the [job artifacts][art-yml] can be set in
 the Admin area of your GitLab instance. The syntax of duration is described
 in [artifacts:expire_in][duration-syntax]. The default is `30 days`. Note that
-this setting is set for each job. Set it to 0 if you don't want default
-expiration.
+this setting is set for each job. Set it to `0` if you don't want default
+expiration. The default unit is in seconds.
+
 
 1. Go to **Admin area > Settings** (`/admin/application_settings`).
 
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index f84a90b2c0c13352d20599a11c48fad2ebc37ac7..7a20eaa4b396895af454ff6f63ba310851691494 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -235,7 +235,12 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji
 
 	Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup:
 
-	Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support.
+	Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support.
+
+	On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support.
+
+	Ubuntu 18.04 (like many modern Linux distros) has this font installed by default.
+
 
 Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
 
@@ -247,7 +252,13 @@ If you are new to this, don't be :fearful:. You can easily join the emoji :famil
 
 Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup:
 
-Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support.
+Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support.
+
+On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support.
+
+Ubuntu 18.04 (like many modern Linux distros) has this font installed by default.
+
+
 
 ### Special GitLab References
 
diff --git a/doc/user/project/import/manifest.md b/doc/user/project/import/manifest.md
index 296e30aa0c327e5c3f6b3e7dbe02594f80074e2e..24bf6541a9db126210f114baaccf24fc32cb38eb 100644
--- a/doc/user/project/import/manifest.md
+++ b/doc/user/project/import/manifest.md
@@ -1,5 +1,8 @@
 # Import multiple repositories by uploading a manifest file
 
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28811) in
+GitLab 11.2.
+
 GitLab allows you to import all the required Git repositories
 based on a manifest file like the one used by the
 [Android repository](https://android.googlesource.com/platform/manifest/+/2d6f081a3b05d8ef7a2b1b52b0d536b2b74feab4/default.xml).
diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md
index 6ab44420a10472c4305ff8f5cd1e4329fed65a64..47525617d9575861969994c69dc39ed08ec5a59a 100644
--- a/doc/user/project/integrations/hangouts_chat.md
+++ b/doc/user/project/integrations/hangouts_chat.md
@@ -1,5 +1,7 @@
 # Hangouts Chat service
 
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/43756) in GitLab 11.2.
+
 The Hangouts Chat service sends notifications from GitLab to the room for which the webhook was created.
 
 ## On Hangouts Chat
diff --git a/doc/user/project/repository/img/repository_languages.png b/doc/user/project/repository/img/repository_languages.png
new file mode 100644
index 0000000000000000000000000000000000000000..d9fb1278e06c3658e7c7c5b1b48d74c9e7f64556
Binary files /dev/null and b/doc/user/project/repository/img/repository_languages.png differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index f39ab5ad92dc5826d607b4c4fc42da54e3c72b1d..150cc4b4f69c5450c913d74a41828e2b0907631a 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -155,6 +155,16 @@ The repository graph displays visually the Git flow strategy used in that reposi
 
 Find it under your project's **Repository > Graph**.
 
+## Repository Languages
+
+For the default branch of each repository, GitLab will determine what programming languages
+were used and display this on the projects pages.
+
+![Repository Languages bar](img/repository_languages.png)
+
+Not all files are detected, among others; documentation, 
+vendored code, and most markup languages are excluded.
+
 ## Compare
 
 Select branches to compare and view the changes inline:
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index e9c901f8592dcaa00a0245a54d6e427f68c11585..9521a2d63a0ae784152fff3b7fbc7f3c17561817 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -543,14 +543,8 @@ def add_tag(tag_name, user:, target:, message: nil)
       end
 
       def update_branch(branch_name, user:, newrev:, oldrev:)
-        gitaly_migrate(:operation_user_update_branch) do |is_enabled|
-          if is_enabled
-            gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev)
-          else
-            Gitlab::GitalyClient::StorageSettings.allow_disk_access do
-              OperationService.new(user, self).update_branch(branch_name, newrev, oldrev)
-            end
-          end
+        wrapped_gitaly_errors do
+          gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev)
         end
       end
 
diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb
index 65eb5cc18cf310ac20d418e63ab7e0f43c0c2cd4..752a91fbb602809ea4e6948b7911ff5e0eb1e432 100644
--- a/lib/gitlab/git/repository_mirroring.rb
+++ b/lib/gitlab/git/repository_mirroring.rb
@@ -2,34 +2,7 @@ module Gitlab
   module Git
     module RepositoryMirroring
       def remote_branches(remote_name)
-        gitaly_migrate(:ref_find_all_remote_branches) do |is_enabled|
-          if is_enabled
-            gitaly_ref_client.remote_branches(remote_name)
-          else
-            Gitlab::GitalyClient::StorageSettings.allow_disk_access do
-              rugged_remote_branches(remote_name)
-            end
-          end
-        end
-      end
-
-      private
-
-      def rugged_remote_branches(remote_name)
-        branches = []
-
-        rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref|
-          name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '')
-
-          begin
-            target_commit = Gitlab::Git::Commit.find(self, ref.target.oid)
-            branches << Gitlab::Git::Branch.new(self, name, ref.target, target_commit)
-          rescue Rugged::ReferenceError
-            # Omit invalid branch
-          end
-        end
-
-        branches
+        gitaly_ref_client.remote_branches(remote_name)
       end
     end
   end
diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake
index ddcca69711f80714c0bae1a5b2acd50dc4283eae..5a232091a7edd8febf93d2a8f3b636562820a0f8 100644
--- a/lib/tasks/gitlab/traces.rake
+++ b/lib/tasks/gitlab/traces.rake
@@ -18,5 +18,22 @@ namespace :gitlab do
         logger.info("Scheduled #{job_ids.count} jobs. From #{job_ids.min} to #{job_ids.max}")
       end
     end
+
+    task migrate: :environment do
+      logger = Logger.new(STDOUT)
+      logger.info('Starting transfer of job traces')
+
+      Ci::Build.joins(:project)
+        .with_archived_trace_stored_locally
+        .find_each(batch_size: 10) do |build|
+        begin
+          build.job_artifacts_trace.file.migrate!(ObjectStorage::Store::REMOTE)
+
+          logger.info("Transferred job trace of #{build.id} 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/locale/gitlab.pot b/locale/gitlab.pot
index eecf3a9a2771fddd30f661dc0aaace22178aa212..81cdc999facea423e211b98529def8ad6806b61c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4073,12 +4073,21 @@ msgstr ""
 msgid "Jobs"
 msgstr ""
 
+msgid "Job|Are you sure you want to erase this job?"
+msgstr ""
+
 msgid "Job|Browse"
 msgstr ""
 
+msgid "Job|Complete Raw"
+msgstr ""
+
 msgid "Job|Download"
 msgstr ""
 
+msgid "Job|Erase job log"
+msgstr ""
+
 msgid "Job|Job artifacts"
 msgstr ""
 
@@ -4091,6 +4100,15 @@ msgstr ""
 msgid "Job|Keep"
 msgstr ""
 
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
 msgid "Job|The artifacts were removed"
 msgstr ""
 
@@ -6069,6 +6087,9 @@ msgstr ""
 msgid "Retry verification"
 msgstr ""
 
+msgid "Reveal Variables"
+msgstr ""
+
 msgid "Reveal value"
 msgid_plural "Reveal values"
 msgstr[0] ""
@@ -7464,6 +7485,9 @@ msgstr ""
 msgid "Trending"
 msgstr ""
 
+msgid "Trigger"
+msgstr ""
+
 msgid "Trigger pipelines for mirror updates"
 msgstr ""
 
diff --git a/spec/features/instance_statistics/cohorts_spec.rb b/spec/features/instance_statistics/cohorts_spec.rb
index 81fc5eff980c0323f6d4ef01b33fc0aad58eb79b..573f8600be18ffcf49f86d707534b7c3d60a5055 100644
--- a/spec/features/instance_statistics/cohorts_spec.rb
+++ b/spec/features/instance_statistics/cohorts_spec.rb
@@ -12,4 +12,12 @@
 
     expect(page).to have_content("#{Time.now.strftime('%b %Y')} 3 0")
   end
+
+  it 'shows usage data', :js do
+    visit instance_statistics_cohorts_path
+
+    wait_for_requests
+
+    expect(find('.js-syntax-highlight').text).not_to eq('')
+  end
 end
diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/conversational_development_index_spec.rb
index d441a7a5af9bc833dc94141d7ec3a4acb09a1a02..a6c16b6a2a3421d6403e35576ba0a07a7d7add10 100644
--- a/spec/features/instance_statistics/conversational_development_index_spec.rb
+++ b/spec/features/instance_statistics/conversational_development_index_spec.rb
@@ -5,6 +5,16 @@
     sign_in(create(:admin))
   end
 
+  it 'has dismissable intro callout', :js do
+    visit instance_statistics_conversational_development_index_index_path
+
+    expect(page).to have_content 'Introducing Your Conversational Development Index'
+
+    find('.js-close-callout').click
+
+    expect(page).not_to have_content 'Introducing Your Conversational Development Index'
+  end
+
   context 'when usage ping is disabled' do
     it 'shows empty state' do
       stub_application_setting(usage_ping_enabled: false)
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
deleted file mode 100644
index bf60b18873c81abc8cbc48ffd8d064d4d6bac470..0000000000000000000000000000000000000000
--- a/spec/features/issues/award_emoji_spec.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-require 'rails_helper'
-
-describe 'Awards Emoji' do
-  let!(:project)   { create(:project, :public) }
-  let!(:user)      { create(:user) }
-  let(:issue) do
-    create(:issue,
-           assignees: [user],
-           project: project)
-  end
-
-  context 'authorized user' do
-    before do
-      project.add_maintainer(user)
-      sign_in(user)
-    end
-
-    describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
-      before do
-        # The `heart_tip` emoji is not valid anymore so we need to skip validation
-        issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
-        visit project_issue_path(project, issue)
-        wait_for_requests
-      end
-
-      # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
-      it 'does not shows a 500 page', :js do
-        expect(page).to have_text(issue.title)
-      end
-    end
-
-    describe 'Click award emoji from issue#show' do
-      let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
-
-      before do
-        visit project_issue_path(project, issue)
-        wait_for_requests
-      end
-
-      it 'increments the thumbsdown emoji', :js do
-        find('[data-name="thumbsdown"]').click
-        wait_for_requests
-        expect(thumbsdown_emoji).to have_text("1")
-      end
-
-      context 'click the thumbsup emoji' do
-        it 'increments the thumbsup emoji', :js do
-          find('[data-name="thumbsup"]').click
-          wait_for_requests
-          expect(thumbsup_emoji).to have_text("1")
-        end
-
-        it 'decrements the thumbsdown emoji', :js do
-          expect(thumbsdown_emoji).to have_text("0")
-        end
-      end
-
-      context 'click the thumbsdown emoji' do
-        it 'increments the thumbsdown emoji', :js do
-          find('[data-name="thumbsdown"]').click
-          wait_for_requests
-          expect(thumbsdown_emoji).to have_text("1")
-        end
-
-        it 'decrements the thumbsup emoji', :js do
-          expect(thumbsup_emoji).to have_text("0")
-        end
-      end
-
-      it 'toggles the smiley emoji on a note', :js do
-        toggle_smiley_emoji(true)
-
-        within('.note-body') do
-          expect(find(emoji_counter)).to have_text("1")
-        end
-
-        toggle_smiley_emoji(false)
-
-        within('.note-body') do
-          expect(page).not_to have_selector(emoji_counter)
-        end
-      end
-
-      context 'execute /award quick action' do
-        it 'toggles the emoji award on noteable', :js do
-          execute_quick_action('/award :100:')
-
-          expect(find(noteable_award_counter)).to have_text("1")
-
-          execute_quick_action('/award :100:')
-
-          expect(page).not_to have_selector(noteable_award_counter)
-        end
-      end
-    end
-  end
-
-  context 'unauthorized user', :js do
-    before do
-      visit project_issue_path(project, issue)
-    end
-
-    it 'has disabled emoji button' do
-      expect(first('.award-control')[:class]).to have_text('disabled')
-    end
-  end
-
-  def execute_quick_action(cmd)
-    within('.js-main-target-form') do
-      fill_in 'note[note]', with: cmd
-      click_button 'Comment'
-    end
-
-    wait_for_requests
-  end
-
-  def thumbsup_emoji
-    page.all(emoji_counter).first
-  end
-
-  def thumbsdown_emoji
-    page.all(emoji_counter).last
-  end
-
-  def emoji_counter
-    'span.js-counter'
-  end
-
-  def noteable_award_counter
-    ".awards .active"
-  end
-
-  def toggle_smiley_emoji(status)
-    within('.note') do
-      find('.note-emoji-button').click
-    end
-
-    unless status
-      first('[data-name="smiley"]').click
-    else
-      find('[data-name="smiley"]').click
-    end
-
-    wait_for_requests
-  end
-end
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
deleted file mode 100644
index e53a4ce49c7c8587fb834d7981d6800a088fd272..0000000000000000000000000000000000000000
--- a/spec/features/issues/award_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'rails_helper'
-
-describe 'Issue awards', :js do
-  let(:user)      { create(:user) }
-  let(:project)   { create(:project, :public) }
-  let(:issue)     { create(:issue, project: project) }
-
-  describe 'logged in' do
-    before do
-      sign_in(user)
-      visit project_issue_path(project, issue)
-      wait_for_requests
-    end
-
-    it 'adds award to issue' do
-      first('.js-emoji-btn').click
-      expect(page).to have_selector('.js-emoji-btn.active')
-      expect(first('.js-emoji-btn')).to have_content '1'
-
-      visit project_issue_path(project, issue)
-      expect(first('.js-emoji-btn')).to have_content '1'
-    end
-
-    it 'removes award from issue' do
-      first('.js-emoji-btn').click
-      find('.js-emoji-btn.active').click
-      expect(first('.js-emoji-btn')).to have_content '0'
-
-      visit project_issue_path(project, issue)
-      expect(first('.js-emoji-btn')).to have_content '0'
-    end
-
-    it 'only has one menu on the page' do
-      first('.js-add-award').click
-      expect(page).to have_selector('.emoji-menu')
-
-      expect(page).to have_selector('.emoji-menu', count: 1)
-    end
-  end
-
-  describe 'logged out' do
-    before do
-      visit project_issue_path(project, issue)
-      wait_for_requests
-    end
-
-    it 'does not see award menu button' do
-      expect(page).not_to have_selector('.js-award-holder')
-    end
-  end
-end
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..afa425c2cec2ee76f85c736b4ecd036ffbab41ed
--- /dev/null
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -0,0 +1,347 @@
+require 'spec_helper'
+
+describe 'User interacts with awards' do
+  let(:user) { create(:user) }
+
+  describe 'User interacts with awards in an issue', :js do
+    let(:issue) { create(:issue, project: project)}
+    let(:project) { create(:project) }
+
+    before do
+      project.add_maintainer(user)
+      sign_in(user)
+
+      visit(project_issue_path(project, issue))
+    end
+
+    it 'toggles the thumbsup award emoji' do
+      page.within('.awards') do
+        thumbsup = page.first('.award-control')
+        thumbsup.click
+        thumbsup.hover
+
+        expect(page).to have_selector('.js-emoji-btn')
+        expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+        expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+
+        thumbsup = page.first('.award-control')
+        thumbsup.click
+        thumbsup.hover
+
+        expect(page).to have_selector('.award-control.js-emoji-btn')
+        expect(page.all('.award-control.js-emoji-btn').size).to eq(2)
+
+        page.all('.award-control.js-emoji-btn').each do |element|
+          expect(element['title']).to eq('')
+        end
+
+        expect(page.all('.award-control .js-counter')).to all(have_content('0'))
+
+        thumbsup = page.first('.award-control')
+        thumbsup.click
+        thumbsup.hover
+
+        expect(page).to have_selector('.js-emoji-btn')
+        expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+        expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+      end
+    end
+
+    it 'toggles a custom award emoji' do
+      page.within('.awards') do
+        page.find('.js-add-award').click
+      end
+
+      page.find('.emoji-menu.is-visible')
+
+      expect(page).to have_selector('.js-emoji-menu-search')
+      expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
+
+      page.within('.emoji-menu-content') do
+        emoji_button = page.first('.js-emoji-btn')
+        emoji_button.hover
+        emoji_button.click
+      end
+
+      page.within('.awards') do
+        expect(page).to have_selector('.js-emoji-btn')
+        expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+        expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+
+        expect do
+          page.find('.js-emoji-btn.active').click
+          wait_for_requests
+        end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2)
+      end
+    end
+
+    it 'shows the list of award emoji categories' do
+      page.within('.awards') do
+        page.find('.js-add-award').click
+      end
+
+      page.find('.emoji-menu.is-visible')
+
+      expect(page).to have_selector('.js-emoji-menu-search')
+      expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
+
+      fill_in('emoji-menu-search', with: 'hand')
+
+      page.within('.emoji-menu-content') do
+        expect(page).to have_selector('[data-name="raised_hand"]')
+      end
+    end
+
+    it 'adds an award emoji by a comment' do
+      page.within('.js-main-target-form') do
+        fill_in('note[note]', with: ':smile:')
+
+        click_button('Comment')
+      end
+
+      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 'User interacts with 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
+
+  describe 'User interacts with awards on an issue', :js do
+    let(:project)   { create(:project, :public) }
+    let(:issue)     { create(:issue, project: project) }
+
+    describe 'logged in' do
+      before do
+        sign_in(user)
+        visit project_issue_path(project, issue)
+        wait_for_requests
+      end
+
+      it 'adds award to issue' do
+        first('.js-emoji-btn').click
+
+        expect(page).to have_selector('.js-emoji-btn.active')
+        expect(first('.js-emoji-btn')).to have_content '1'
+
+        visit project_issue_path(project, issue)
+
+        expect(first('.js-emoji-btn')).to have_content '1'
+      end
+
+      it 'removes award from issue' do
+        first('.js-emoji-btn').click
+        find('.js-emoji-btn.active').click
+
+        expect(first('.js-emoji-btn')).to have_content '0'
+
+        visit project_issue_path(project, issue)
+
+        expect(first('.js-emoji-btn')).to have_content '0'
+      end
+
+      it 'only has one menu on the page' do
+        first('.js-add-award').click
+
+        expect(page).to have_selector('.emoji-menu', count: 1)
+      end
+    end
+
+    describe 'logged out' do
+      before do
+        visit project_issue_path(project, issue)
+        wait_for_requests
+      end
+
+      it 'does not see award menu button' do
+        expect(page).not_to have_selector('.js-award-holder')
+      end
+    end
+  end
+
+  describe 'Awards Emoji' do
+    let!(:project)   { create(:project, :public) }
+    let(:issue)      { create(:issue, assignees: [user], project: project) }
+
+    context 'authorized user' do
+      before do
+        project.add_maintainer(user)
+        sign_in(user)
+      end
+
+      describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
+        before do
+          # The `heart_tip` emoji is not valid anymore so we need to skip validation
+          issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
+          visit project_issue_path(project, issue)
+          wait_for_requests
+        end
+
+        # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
+        it 'does not shows a 500 page', :js do
+          expect(page).to have_text(issue.title)
+        end
+      end
+
+      describe 'Click award emoji from issue#show' do
+        let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
+
+        before do
+          visit project_issue_path(project, issue)
+          wait_for_requests
+        end
+
+        context 'click the thumbsdown emoji' do
+          it 'increments the thumbsdown emoji', :js do
+            find('[data-name="thumbsdown"]').click
+            wait_for_requests
+            expect(thumbsdown_emoji).to have_text("1")
+          end
+
+          it 'decrements the thumbsup emoji', :js do
+            expect(thumbsup_emoji).to have_text("0")
+          end
+        end
+
+        it 'toggles the smiley emoji on a note', :js do
+          toggle_smiley_emoji(true)
+
+          within('.note-body') do
+            expect(find(emoji_counter)).to have_text("1")
+          end
+
+          toggle_smiley_emoji(false)
+
+          within('.note-body') do
+            expect(page).not_to have_selector(emoji_counter)
+          end
+        end
+
+        context 'execute /award quick action' do
+          it 'toggles the emoji award on noteable', :js do
+            execute_quick_action('/award :100:')
+
+            expect(find(noteable_award_counter)).to have_text("1")
+
+            execute_quick_action('/award :100:')
+
+            expect(page).not_to have_selector(noteable_award_counter)
+          end
+        end
+      end
+    end
+
+    context 'unauthorized user', :js do
+      before do
+        visit project_issue_path(project, issue)
+      end
+
+      it 'has disabled emoji button' do
+        expect(first('.award-control')[:class]).to have_text('disabled')
+      end
+    end
+
+    def execute_quick_action(cmd)
+      within('.js-main-target-form') do
+        fill_in 'note[note]', with: cmd
+        click_button 'Comment'
+      end
+
+      wait_for_requests
+    end
+
+    def thumbsup_emoji
+      page.all(emoji_counter).first
+    end
+
+    def thumbsdown_emoji
+      page.all(emoji_counter).last
+    end
+
+    def emoji_counter
+      'span.js-counter'
+    end
+
+    def noteable_award_counter
+      ".awards .active"
+    end
+
+    def toggle_smiley_emoji(status)
+      within('.note') do
+        find('.note-emoji-button').click
+      end
+
+      if !status
+        first('[data-name="smiley"]').click
+      else
+        find('[data-name="smiley"]').click
+      end
+
+      wait_for_requests
+    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
deleted file mode 100644
index 4d860893abe0c6f8144f253414fda1f605fcbdf9..0000000000000000000000000000000000000000
--- a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
+++ /dev/null
@@ -1,172 +0,0 @@
-require 'spec_helper'
-
-describe 'User interacts with awards in an issue', :js do
-  let(:issue) { create(:issue, project: project)}
-  let(:project) { create(:project) }
-  let(:user) { create(:user) }
-
-  before do
-    project.add_maintainer(user)
-    sign_in(user)
-
-    visit(project_issue_path(project, issue))
-  end
-
-  it 'toggles the thumbsup award emoji' do
-    page.within('.awards') do
-      thumbsup = page.first('.award-control')
-      thumbsup.click
-      thumbsup.hover
-
-      expect(page).to have_selector('.js-emoji-btn')
-      expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
-      expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
-
-      thumbsup = page.first('.award-control')
-      thumbsup.click
-      thumbsup.hover
-
-      expect(page).to have_selector('.award-control.js-emoji-btn')
-      expect(page.all('.award-control.js-emoji-btn').size).to eq(2)
-
-      page.all('.award-control.js-emoji-btn').each do |element|
-        expect(element['title']).to eq('')
-      end
-
-      page.all('.award-control .js-counter').each do |element|
-        expect(element).to have_content('0')
-      end
-
-      thumbsup = page.first('.award-control')
-      thumbsup.click
-      thumbsup.hover
-
-      expect(page).to have_selector('.js-emoji-btn')
-      expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
-      expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
-    end
-  end
-
-  it 'toggles a custom award emoji' do
-    page.within('.awards') do
-      page.find('.js-add-award').click
-    end
-
-    page.find('.emoji-menu.is-visible')
-
-    expect(page).to have_selector('.js-emoji-menu-search')
-    expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
-
-    page.within('.emoji-menu-content') do
-      emoji_button = page.first('.js-emoji-btn')
-      emoji_button.hover
-      emoji_button.click
-    end
-
-    page.within('.awards') do
-      expect(page).to have_selector('.js-emoji-btn')
-      expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
-      expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
-
-      expect do
-        page.find('.js-emoji-btn.active').click
-        wait_for_requests
-      end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2)
-    end
-  end
-
-  it 'shows the list of award emoji categories' do
-    page.within('.awards') do
-      page.find('.js-add-award').click
-    end
-
-    page.find('.emoji-menu.is-visible')
-
-    expect(page).to have_selector('.js-emoji-menu-search')
-    expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
-
-    fill_in('emoji-menu-search', with: 'hand')
-
-    page.within('.emoji-menu-content') do
-      expect(page).to have_selector('[data-name="raised_hand"]')
-    end
-  end
-
-  it 'adds an award emoji by a comment' do
-    page.within('.js-main-target-form') do
-      fill_in('note[note]', with: ':smile:')
-
-      click_button('Comment')
-    end
-
-    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/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 13175a9a3f76ea472a89f8e82d2bc57716a1f7dd..3a79ef996f5f04d368dacdaac5bf6a27a5755d59 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -210,6 +210,15 @@ describe('Board list component', () => {
     });
   });
 
+  it('does not load issues if already loading', () => {
+    component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue(new Promise(() => {}));
+
+    component.onScroll();
+    component.onScroll();
+
+    expect(component.list.nextPage).toHaveBeenCalledTimes(1);
+  });
+
   it('shows loading more spinner', (done) => {
     component.showCount = true;
     component.list.loadingMore = true;
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index 72eb20bdc873887bde31a6d54ff8291cb9247f8f..bca2033ff97612271ef52ee31ab32b8d559487cc 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -352,10 +352,22 @@ describe('IDE store file actions', () => {
       it('calls also getBaseRawFileData service method', done => {
         spyOn(service, 'getBaseRawFileData').and.returnValue(Promise.resolve('baseraw'));
 
+        store.state.currentProjectId = 'gitlab-org/gitlab-ce';
+        store.state.currentMergeRequestId = '1';
+        store.state.projects = {
+          'gitlab-org/gitlab-ce': {
+            mergeRequests: {
+              1: {
+                baseCommitSha: 'SHA',
+              },
+            },
+          },
+        };
+
         tmpFile.mrChange = { new_file: false };
 
         store
-          .dispatch('getRawFileData', { path: tmpFile.path, baseSha: 'SHA' })
+          .dispatch('getRawFileData', { path: tmpFile.path })
           .then(() => {
             expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
             expect(tmpFile.baseRaw).toBe('baseraw');
@@ -392,10 +404,7 @@ describe('IDE store file actions', () => {
         const dispatch = jasmine.createSpy('dispatch');
 
         actions
-          .getRawFileData(
-            { state: store.state, commit() {}, dispatch },
-            { path: tmpFile.path, baseSha: tmpFile.baseSha },
-          )
+          .getRawFileData({ state: store.state, commit() {}, dispatch }, { path: tmpFile.path })
           .then(done.fail)
           .catch(() => {
             expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
@@ -404,7 +413,6 @@ describe('IDE store file actions', () => {
               actionText: 'Please try again',
               actionPayload: {
                 path: tmpFile.path,
-                baseSha: tmpFile.baseSha,
               },
             });
 
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 1e836dbc3f90d2b7422bc2924ddda05f81980f27..6ce76aaa03b425b059256f48686dec3248da67c9 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -213,6 +213,33 @@ describe('Multi-file store mutations', () => {
 
       expect(localState.changedFiles).toEqual([localState.entries.filePath]);
     });
+
+    it('does not add tempFile into changedFiles', () => {
+      localState.entries.filePath = {
+        deleted: false,
+        type: 'blob',
+        tempFile: true,
+      };
+
+      mutations.DELETE_ENTRY(localState, 'filePath');
+
+      expect(localState.changedFiles).toEqual([]);
+    });
+
+    it('removes tempFile from changedFiles when deleted', () => {
+      localState.entries.filePath = {
+        path: 'filePath',
+        deleted: false,
+        type: 'blob',
+        tempFile: true,
+      };
+
+      localState.changedFiles.push({ ...localState.entries.filePath });
+
+      mutations.DELETE_ENTRY(localState, 'filePath');
+
+      expect(localState.changedFiles).toEqual([]);
+    });
   });
 
   describe('UPDATE_FILE_AFTER_COMMIT', () => {
diff --git a/spec/javascripts/jobs/commit_block_spec.js b/spec/javascripts/jobs/commit_block_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f755a5042b58aae4aee13516c3b8f26a500730a3
--- /dev/null
+++ b/spec/javascripts/jobs/commit_block_spec.js
@@ -0,0 +1,73 @@
+import Vue from 'vue';
+import component from '~/jobs/components/commit_block.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Commit block', () => {
+  const Component = Vue.extend(component);
+  let vm;
+
+  const props = {
+    pipelineShortSha: '1f0fb84f',
+    pipelineShaPath: 'commit/1f0fb84fb6770d74d97eee58118fd3909cd4f48c',
+    mergeRequestReference: '!21244',
+    mergeRequestPath: 'merge_requests/21244',
+    gitCommitTitlte: 'Regenerate pot files',
+  };
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('pipeline short sha', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, {
+        ...props,
+      });
+    });
+
+    it('renders pipeline short sha link', () => {
+      expect(vm.$el.querySelector('.js-commit-sha').getAttribute('href')).toEqual(props.pipelineShaPath);
+      expect(vm.$el.querySelector('.js-commit-sha').textContent.trim()).toEqual(props.pipelineShortSha);
+    });
+
+    it('renders clipboard button', () => {
+      expect(vm.$el.querySelector('button').getAttribute('data-clipboard-text')).toEqual(props.pipelineShortSha);
+    });
+  });
+
+  describe('with merge request', () => {
+    it('renders merge request link and reference', () => {
+      vm = mountComponent(Component, {
+        ...props,
+      });
+
+      expect(vm.$el.querySelector('.js-link-commit').getAttribute('href')).toEqual(props.mergeRequestPath);
+      expect(vm.$el.querySelector('.js-link-commit').textContent.trim()).toEqual(props.mergeRequestReference);
+
+    });
+  });
+
+  describe('without merge request', () => {
+    it('does not render merge request', () => {
+      const copyProps = Object.assign({}, props);
+      delete copyProps.mergeRequestPath;
+      delete copyProps.mergeRequestReference;
+
+      vm = mountComponent(Component, {
+        ...copyProps,
+      });
+
+      expect(vm.$el.querySelector('.js-link-commit')).toBeNull();
+    });
+  });
+
+  describe('git commit title', () => {
+    it('renders git commit title', () => {
+      vm = mountComponent(Component, {
+        ...props,
+      });
+
+      expect(vm.$el.textContent).toContain(props.gitCommitTitlte);
+    });
+  });
+});
diff --git a/spec/javascripts/jobs/components/job_log_controllers_spec.js b/spec/javascripts/jobs/components/job_log_controllers_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..416dfab8a48521ef1d55228feec8ad320926c54f
--- /dev/null
+++ b/spec/javascripts/jobs/components/job_log_controllers_spec.js
@@ -0,0 +1,217 @@
+import Vue from 'vue';
+import component from '~/jobs/components/job_log_controllers.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Job log controllers', () => {
+  const Component = Vue.extend(component);
+  let vm;
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('Truncate information', () => {
+
+    beforeEach(() => {
+      vm = mountComponent(Component, {
+        rawTracePath: '/raw',
+        canEraseJob: true,
+        size: 511952,
+        canScrollToTop: true,
+        canScrollToBottom: true,
+      });
+    });
+
+    it('renders size information', () => {
+      expect(vm.$el.querySelector('.js-truncated-info').textContent).toContain('499.95 KiB');
+    });
+
+    it('renders link to raw trace', () => {
+      expect(vm.$el.querySelector('.js-raw-link').getAttribute('href')).toEqual('/raw');
+    });
+
+  });
+
+  describe('links section', () => {
+    describe('with raw trace path', () => {
+      it('renders raw trace link', () => {
+        vm = mountComponent(Component, {
+          rawTracePath: '/raw',
+          canEraseJob: true,
+          size: 511952,
+          canScrollToTop: true,
+          canScrollToBottom: true,
+        });
+
+        expect(vm.$el.querySelector('.js-raw-link-controller').getAttribute('href')).toEqual('/raw');
+      });
+    });
+
+    describe('without raw trace path', () => {
+      it('does not render raw trace link', () => {
+        vm = mountComponent(Component, {
+          canEraseJob: true,
+          size: 511952,
+          canScrollToTop: true,
+          canScrollToBottom: true,
+        });
+
+        expect(vm.$el.querySelector('.js-raw-link-controller')).toBeNull();
+      });
+    });
+
+    describe('when is erasable', () => {
+      beforeEach(() => {
+        vm = mountComponent(Component, {
+          rawTracePath: '/raw',
+          canEraseJob: true,
+          size: 511952,
+          canScrollToTop: true,
+          canScrollToBottom: true,
+        });
+      });
+
+      it('renders erase job button', () => {
+        expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
+      });
+
+      describe('on click', () => {
+        describe('when user confirms action', () => {
+          it('emits eraseJob event', () => {
+            spyOn(window, 'confirm').and.returnValue(true);
+            spyOn(vm, '$emit');
+
+            vm.$el.querySelector('.js-erase-link').click();
+
+            expect(vm.$emit).toHaveBeenCalledWith('eraseJob');
+          });
+        });
+
+        describe('when user does not confirm action', () => {
+          it('does not emit eraseJob event', () => {
+            spyOn(window, 'confirm').and.returnValue(false);
+            spyOn(vm, '$emit');
+
+            vm.$el.querySelector('.js-erase-link').click();
+
+            expect(vm.$emit).not.toHaveBeenCalledWith('eraseJob');
+          });
+        });
+      });
+    });
+
+    describe('when it is not erasable', () => {
+      it('does not render erase button', () => {
+        vm = mountComponent(Component, {
+          rawTracePath: '/raw',
+          canEraseJob: false,
+          size: 511952,
+          canScrollToTop: true,
+          canScrollToBottom: true,
+        });
+
+        expect(vm.$el.querySelector('.js-erase-link')).toBeNull();
+      });
+    });
+  });
+
+  describe('scroll buttons', () => {
+    describe('scroll top button', () => {
+      describe('when user can scroll top', () => {
+        beforeEach(() => {
+          vm = mountComponent(Component, {
+            rawTracePath: '/raw',
+            canEraseJob: true,
+            size: 511952,
+            canScrollToTop: true,
+            canScrollToBottom: true,
+          });
+        });
+
+        it('renders enabled scroll top button', () => {
+          expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toBeNull();
+        });
+
+        it('emits scrollJobLogTop event on click', () => {
+          spyOn(vm, '$emit');
+          vm.$el.querySelector('.js-scroll-top').click();
+
+          expect(vm.$emit).toHaveBeenCalledWith('scrollJobLogTop');
+        });
+      });
+
+      describe('when user can not scroll top', () => {
+        beforeEach(() => {
+          vm = mountComponent(Component, {
+            rawTracePath: '/raw',
+            canEraseJob: true,
+            size: 511952,
+            canScrollToTop: false,
+            canScrollToBottom: true,
+          });
+        });
+
+        it('renders disabled scroll top button', () => {
+          expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toEqual('disabled');
+        });
+
+        it('does not emit scrollJobLogTop event on click', () => {
+          spyOn(vm, '$emit');
+          vm.$el.querySelector('.js-scroll-top').click();
+
+          expect(vm.$emit).not.toHaveBeenCalledWith('scrollJobLogTop');
+        });
+      });
+    });
+
+    describe('scroll bottom button', () => {
+      describe('when user can scroll bottom', () => {
+        beforeEach(() => {
+          vm = mountComponent(Component, {
+            rawTracePath: '/raw',
+            canEraseJob: true,
+            size: 511952,
+            canScrollToTop: true,
+            canScrollToBottom: true,
+          });
+        });
+
+        it('renders enabled scroll bottom button', () => {
+          expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toBeNull();
+        });
+
+        it('emits scrollJobLogBottom event on click', () => {
+          spyOn(vm, '$emit');
+          vm.$el.querySelector('.js-scroll-bottom').click();
+
+          expect(vm.$emit).toHaveBeenCalledWith('scrollJobLogBottom');
+        });
+      });
+
+      describe('when user can not scroll bottom', () => {
+        beforeEach(() => {
+          vm = mountComponent(Component, {
+            rawTracePath: '/raw',
+            canEraseJob: true,
+            size: 511952,
+            canScrollToTop: true,
+            canScrollToBottom: false,
+          });
+        });
+
+        it('renders disabled scroll bottom button', () => {
+          expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toEqual('disabled');
+
+        });
+
+        it('does not emit scrollJobLogBottom event on click', () => {
+          spyOn(vm, '$emit');
+          vm.$el.querySelector('.js-scroll-bottom').click();
+
+          expect(vm.$emit).not.toHaveBeenCalledWith('scrollJobLogBottom');
+        });
+      });
+    });
+  });
+});
+
diff --git a/spec/javascripts/jobs/empty_state_spec.js b/spec/javascripts/jobs/empty_state_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8feb069fe01c655a6871b4f0bf4812e65ab5d00
--- /dev/null
+++ b/spec/javascripts/jobs/empty_state_spec.js
@@ -0,0 +1,90 @@
+import Vue from 'vue';
+import component from '~/jobs/components/empty_state.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Empty State', () => {
+  const Component = Vue.extend(component);
+  let vm;
+
+  const props = {
+    illustrationPath: 'illustrations/pending_job_empty.svg',
+    illustrationSizeClass: 'svg-430',
+    title: 'This job has not started yet',
+  };
+
+  const content = 'This job is in pending state and is waiting to be picked by a runner';
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('renders image and title', () => {
+    beforeEach(() => {
+      vm = mountComponent(Component, {
+        ...props,
+        content,
+      });
+    });
+
+    it('renders img with provided path and size', () => {
+      expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(props.illustrationPath);
+      expect(vm.$el.querySelector('.svg-content').classList).toContain(props.illustrationSizeClass);
+    });
+
+    it('renders provided title', () => {
+      expect(vm.$el.querySelector('.js-job-empty-state-title').textContent.trim()).toEqual(
+        props.title,
+      );
+    });
+  });
+
+  describe('with content', () => {
+    it('renders content', () => {
+      vm = mountComponent(Component, {
+        ...props,
+        content,
+      });
+
+      expect(vm.$el.querySelector('.js-job-empty-state-content').textContent.trim()).toEqual(
+        content,
+      );
+    });
+  });
+
+  describe('without content', () => {
+    it('does not render content', () => {
+      vm = mountComponent(Component, {
+        ...props,
+      });
+      expect(vm.$el.querySelector('.js-job-empty-state-content')).toBeNull();
+    });
+  });
+
+  describe('with action', () => {
+    it('renders action', () => {
+      vm = mountComponent(Component, {
+        ...props,
+        content,
+        action: {
+          link: 'runner',
+          title: 'Check runner',
+          method: 'post',
+        },
+      });
+
+      expect(vm.$el.querySelector('.js-job-empty-state-action').getAttribute('href')).toEqual(
+        'runner',
+      );
+    });
+  });
+
+  describe('without action', () => {
+    it('does not render action', () => {
+      vm = mountComponent(Component, {
+        ...props,
+        content,
+      });
+      expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/jobs/trigger_value_spec.js b/spec/javascripts/jobs/trigger_value_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..acf91510ed28a8fc3aa876d50253942df239a259
--- /dev/null
+++ b/spec/javascripts/jobs/trigger_value_spec.js
@@ -0,0 +1,66 @@
+import Vue from 'vue';
+import component from '~/jobs/components/trigger_block.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Trigger block', () => {
+  const Component = Vue.extend(component);
+  let vm;
+
+  afterEach(() => {
+    vm.$destroy();
+  });
+
+  describe('with short token', () => {
+    it('renders short token', () => {
+      vm = mountComponent(Component, {
+        shortToken: '0a666b2',
+      });
+
+      expect(vm.$el.querySelector('.js-short-token').textContent).toContain('0a666b2');
+    });
+  });
+
+  describe('without short token', () => {
+    it('does not render short token', () => {
+      vm = mountComponent(Component, {});
+
+      expect(vm.$el.querySelector('.js-short-token')).toBeNull();
+    });
+  });
+
+  describe('with variables', () => {
+    describe('reveal variables', () => {
+      it('reveals variables on click', done => {
+        vm = mountComponent(Component, {
+          variables: {
+            key: 'value',
+            variable: 'foo',
+          },
+        });
+
+        vm.$el.querySelector('.js-reveal-variables').click();
+
+        vm
+          .$nextTick()
+          .then(() => {
+            expect(vm.$el.querySelector('.js-build-variables')).not.toBeNull();
+            expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('key');
+            expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('value');
+            expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('variable');
+            expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('foo');
+          })
+          .then(done)
+          .catch(done.fail);
+      });
+    });
+  });
+
+  describe('without variables', () => {
+    it('does not render variables', () => {
+      vm = mountComponent(Component);
+
+      expect(vm.$el.querySelector('.js-reveal-variables')).toBeNull();
+      expect(vm.$el.querySelector('.js-build-variables')).toBeNull();
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/translate_spec.js b/spec/javascripts/vue_shared/translate_spec.js
index 911c09e5b238db6d1e7743ebe427953ae9666d2b..adb5ff682f05e938ec2aa9f87324b787879d72da 100644
--- a/spec/javascripts/vue_shared/translate_spec.js
+++ b/spec/javascripts/vue_shared/translate_spec.js
@@ -202,11 +202,11 @@ describe('Vue translate filter', () => {
         <span>
           {{ n__(
           \`
-          multiline 
+          multiline
           string
           \`,
           \`
-          multiline 
+          multiline
           strings
           \`,
           2
@@ -234,7 +234,7 @@ describe('Vue translate filter', () => {
           {{ s__(
           \`
           Context|
-          multiline 
+          multiline
           string
           \`
           ) }}
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 749b20947878941a14287473a19920c98c8bb19f..797d767465a6980b5ba67b693d0f393e6ce3353e 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -100,7 +100,7 @@ def domain_variable
     end
   end
 
-  describe '#set_gitlab_deploy_token' do
+  describe '#create_gitlab_deploy_token' do
     let(:auto_devops) { build(:project_auto_devops, project: project) }
 
     context 'when the project is public' do
@@ -144,9 +144,9 @@ def domain_variable
       end
     end
 
-    context 'when autodevops is enabled at instancel level' do
+    context 'when autodevops is enabled at instance level' do
       let(:project) { create(:project, :repository, :internal) }
-      let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) }
+      let(:auto_devops) { build(:project_auto_devops, enabled: nil, project: project) }
 
       it 'should create a deploy token' do
         allow(Gitlab::CurrentSettings).to receive(:auto_devops_enabled?).and_return(true)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index fabe52e0b6dac3aaf3327144e848ab68cfba5c02..8a91c0f10eb2bcd9b95fd78cd69719aa6294f405 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -3596,6 +3596,11 @@ def enable_lfs
   end
 
   describe '#auto_devops_enabled?' do
+    before do
+      allow(Feature).to receive(:enabled?).and_call_original
+      Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0)
+    end
+
     set(:project) { create(:project) }
 
     subject { project.auto_devops_enabled? }
@@ -3605,19 +3610,14 @@ def enable_lfs
         stub_application_setting(auto_devops_enabled: true)
       end
 
-      it 'auto devops is implicitly enabled' do
-        expect(project.auto_devops).to be_nil
-        expect(project).to be_auto_devops_enabled
-      end
+      it { is_expected.to be_truthy }
 
       context 'when explicitly enabled' do
         before do
           create(:project_auto_devops, project: project)
         end
 
-        it "auto devops is enabled" do
-          expect(project).to be_auto_devops_enabled
-        end
+        it { is_expected.to be_truthy }
       end
 
       context 'when explicitly disabled' do
@@ -3625,9 +3625,7 @@ def enable_lfs
           create(:project_auto_devops, project: project, enabled: false)
         end
 
-        it "auto devops is disabled" do
-          expect(project).not_to be_auto_devops_enabled
-        end
+        it { is_expected.to be_falsey }
       end
     end
 
@@ -3636,19 +3634,22 @@ def enable_lfs
         stub_application_setting(auto_devops_enabled: false)
       end
 
-      it 'auto devops is implicitly disabled' do
-        expect(project.auto_devops).to be_nil
-        expect(project).not_to be_auto_devops_enabled
-      end
+      it { is_expected.to be_falsey }
 
       context 'when explicitly enabled' do
         before do
           create(:project_auto_devops, project: project)
         end
 
-        it "auto devops is enabled" do
-          expect(project).to be_auto_devops_enabled
+        it { is_expected.to be_truthy }
+      end
+
+      context 'when force_autodevops_on_by_default is enabled for the project' do
+        before do
+          Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
         end
+
+        it { is_expected.to be_truthy }
       end
     end
   end
@@ -3698,6 +3699,11 @@ def enable_lfs
   end
 
   describe '#has_auto_devops_implicitly_disabled?' do
+    before do
+      allow(Feature).to receive(:enabled?).and_call_original
+      Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0)
+    end
+
     set(:project) { create(:project) }
 
     context 'when enabled in settings' do
@@ -3719,6 +3725,16 @@ def enable_lfs
         expect(project).to have_auto_devops_implicitly_disabled
       end
 
+      context 'when force_autodevops_on_by_default is enabled for the project' do
+        before do
+          Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
+        end
+
+        it 'does not have auto devops implicitly disabled' do
+          expect(project).not_to have_auto_devops_implicitly_disabled
+        end
+      end
+
       context 'when explicitly disabled' do
         before do
           create(:project_auto_devops, project: project, enabled: false)
diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb
index f90d558938f0d8afae52ceec48465520e8bb547d..deea1189cdf3dfd8d4afcf7d52e6164e898d7529 100644
--- a/spec/services/projects/detect_repository_languages_service_spec.rb
+++ b/spec/services/projects/detect_repository_languages_service_spec.rb
@@ -5,10 +5,6 @@
 
   subject { described_class.new(project, project.owner) }
 
-  before do
-    allow(Feature).to receive(:disabled?).and_return(false)
-  end
-
   describe '#execute' do
     context 'without previous detection' do
       it 'inserts new programming languages in the database' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 198499237594ff88f2cef57fc0289f5ab69e7252..a819f26a0c2a52ba4b3101d07048117d3e4e81a4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -114,6 +114,13 @@
   config.before(:example) do
     # Enable all features by default for testing
     allow(Feature).to receive(:enabled?) { true }
+
+    # The following can be removed when we remove the staged rollout strategy
+    # and we can just enable it using instance wide settings
+    # (ie. ApplicationSetting#auto_devops_enabled)
+    allow(Feature).to receive(:enabled?)
+      .with(:force_autodevops_on_by_default, anything)
+      .and_return(false)
   end
 
   config.before(:example, :request_store) do
diff --git a/spec/tasks/gitlab/traces_rake_spec.rb b/spec/tasks/gitlab/traces_rake_spec.rb
index bd18e8ffc1e1b1b017867ed51a7e9e34e3b6eadf..aaf0d7242dd50c97ff1ffa51d92ce320bdd49056 100644
--- a/spec/tasks/gitlab/traces_rake_spec.rb
+++ b/spec/tasks/gitlab/traces_rake_spec.rb
@@ -5,51 +5,109 @@
     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]])
+  describe 'gitlab:traces:archive' do
+    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')
+        run_rake_task('gitlab:traces:archive')
+      end
     end
-  end
 
-  shared_examples 'does not pass the job id to worker' do
-    it do
-      expect(ArchiveTraceWorker).not_to receive(:bulk_perform_async)
+    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')
+        run_rake_task('gitlab:traces:archive')
+      end
     end
-  end
 
-  context 'when trace file stored in default path' do
-    let!(:job) { create(:ci_build, :success, :trace_live) }
+    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
+      it_behaves_like 'passes the job id to worker'
+    end
 
-  context 'when trace is stored in database' do
-    let!(:job) { create(:ci_build, :success) }
+    context 'when trace is stored in database' do
+      let!(:job) { create(:ci_build, :success) }
 
-    before do
-      job.update_column(:trace, 'trace in db')
+      before do
+        job.update_column(:trace, 'trace in db')
+      end
+
+      it_behaves_like 'passes the job id to worker'
     end
 
-    it_behaves_like 'passes the job id to worker'
+    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
 
-  context 'when job has trace artifact' do
-    let!(:job) { create(:ci_build, :success) }
+  describe 'gitlab:traces:migrate' do
+    let(:object_storage_enabled) { false }
 
     before do
-      create(:ci_job_artifact, :trace, job: job)
+      stub_artifacts_object_storage(enabled: object_storage_enabled)
     end
 
-    it_behaves_like 'does not pass the job id to worker'
-  end
+    subject { run_rake_task('gitlab:traces:migrate') }
 
-  context 'when job is not finished yet' do
-    let!(:build) { create(:ci_build, :running, :trace_live) }
+    let!(:job_trace) { create(:ci_job_artifact, :trace, file_store: store) }
 
-    it_behaves_like 'does not pass the job id to worker'
+    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(job_trace.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(job_trace.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(job_trace.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(job_trace.reload.file_store).to eq(ObjectStorage::Store::REMOTE)
+      end
+    end
   end
 end